# Chope · agent-native spec

Full specification with sample payloads. Companion to /llms.txt.

## Conventions

- All endpoints under /api/* return JSON. Content-Type: application/json.
- CORS: access-control-allow-origin: *.
- Cache: read endpoints 60s browser / 300s edge by default.
- Pagination: ?limit (1-500) + ?offset; the response carries .next when more pages exist.
- Errors: { "error": "<code>", "...": "..." } with status 4xx or 5xx.

## GET /api/listings.json

Query params: type (hdb|condo|landed|new-launch), district (e.g. D12), verified (1), price_min, price_max, limit, offset.

Sample:

```
{
  "count": 330,
  "returned": 100,
  "limit": 100,
  "offset": 0,
  "verified_pct": 34.24,
  "listings": [
    {
      "id": "lst-0001-tampines-tampines-st-81",
      "type": "hdb",
      "subtype": "5-room",
      "title": "5-Room HDB · Tampines · High Floor",
      "address": { "street": "Tampines St 81", "postal": "521820", "district": "D18", "planning_area": "Tampines", "lat": 1.3496, "lng": 103.9568 },
      "price_sgd": 920000,
      "psf_sgd": 798,
      "floor_area_sqft": 1152,
      "bedrooms": 4,
      "bathrooms": 2,
      "floor_level": "high",
      "lease": { "tenure": "99", "remaining_years": 73, "top_year": 2000 },
      "mop_status": "mop-cleared",
      "verified_owner": true,
      "mrt": [{ "name": "Tampines", "line": "EWL", "walk_min": 8 }],
      "schools_within_1km": ["Junyuan Primary", "St Hilda's Primary"],
      "url": "/listing/lst-0001-tampines-tampines-st-81",
      "json_url": "/api/listing/lst-0001-tampines-tampines-st-81.json"
    }
  ],
  "next": "/api/listings.json?offset=100&limit=100"
}
```

## GET /api/listing/{id}

Returns the full listing record plus the responsible agent, the live demand block for that subtype + district, and three related listings.

## POST /api/wishlists.json

Body:

```json
{
  "looking_for": {
    "types": ["hdb", "condo"],
    "districts": ["D12", "D20"],
    "price_max": 850000,
    "bedrooms_min": 4,
    "must_have": ["high floor", "MOP cleared"],
    "deal_breaker": ["west-facing"]
  },
  "pre_approved": true,
  "monthly_loan_capacity_sgd": 4800
}
```

Returns 201 with the assigned id, the anonymized buyer handle, and the size of the panel that was notified.

## POST /api/calc/hfe-tdsr

Body:

```json
{
  "price_sgd": 780000,
  "monthly_income_sgd": 8500,
  "partner_income_sgd": 3000,
  "other_monthly_debt_sgd": 0,
  "type": "hdb",
  "tenure_years": 25
}
```

Returns the max loan, minimum downpayment, monthly at the 4.0% stress rate, and the TDSR / MSR / LTV utilisation percentages.

## GET /api/heatmap.json

Query: district, subtype. Returns demand cells sorted by 24h search volume.

## GET /api/search.json

Query: q (free text), type, district, verified, sort (relevance|newest|price-asc|price-desc|psf-asc|lease-most), limit.

## MCP server

See /mcp and /.well-known/chope-mcp.json. Tools: search_listings, get_listing, post_wishlist, ping_listing, get_demand_heatmap, compute_hfe_tdsr, compare_launch_vs_resale.

## Rate limits

In this build: none. Production: 60 req/min per IP for read, 10 req/min per token for write.
