API + MCP reference

Rate limits & quotas

Two independent ceilings: a per-minute rate limit (defense against bursts) and a per-month lead-view quota (your tier's billable unit). Crossing either returns a specific status code with an upgrade-prompt URL.

Lead-views (quota unit)

A lead-view is the billable unit. One lead-view = one unique (tenant, parcel-folio, calendar-day-UTC) tuple. Re-fetching the same parcel later the same UTC day does not consume a second lead-view.

The full list of operations that consume a lead-view:

  • GET /v1/leads/{folio} (one lead-view per folio)
  • GET /v1/leads with results (one lead-view per parcel returned in the response)
  • POST /v1/leads/batch (one lead-view per folio in the request)
  • Web UI lead-detail expansion (one lead-view per expansion)
  • MCP get_lead tool call (one lead-view per folio)
  • Webhook watchlist.match delivery (one lead-view per parcel in the payload)
Watchlist + pipeline + feedback ops are free. Listing watchlists, updating pipeline state, posting feedback labels — none of these consume lead-views. The quota tracks parcel reads, not metadata writes.

Per-tier caps

Caps reset on your Stripe billing anniversary — not the first of the calendar month. You can see your next reset in X-Quota-Resets on every response.

TierLead-views / monthRate limit / minConcurrent webhooks
Starter500300
Operator2,0006005
Team5,00060010
Custom10,000+1,200+negotiable

No metered overage. No per-fetch billing. Once you hit your monthly cap the next reads return 402 until reset or tier upgrade.

Calendar-day idempotency

The idempotency window is a UTC calendar day, not a rolling 24-hour window. A read at 23:59:59 UTC and another at 00:00:01 UTC the next day count as two lead-views on the same parcel.

We pinned this to UTC (not the tenant's billing-timezone or Florida-local) so the boundary is unambiguous from the client side: convert to UTC and round down to the day. See docs/architecture/fl-property-feed-adr.md ADR-020 for the rationale.

Florida is UTC-4 / UTC-5. A 20:00 ET re-read on the same parcel is the same UTC day as a 10:00 ET first read — free. A 22:00 ET re-read crosses the UTC midnight at 02:00 the next ET morning and starts a new billable day. Plan batch jobs around the UTC boundary if cap headroom matters.

Rate-limit headers

Every response — success or error — carries the same six headers. Use them to back-off proactively instead of waiting for a 429 or 402.

response headers
# Every successful response carries:HTTP/1.1 200 OKX-RateLimit-Limit:           600X-RateLimit-Remaining:       587X-RateLimit-Reset:           1748534400X-Quota-Lead-Views-Limit:    2000X-Quota-Lead-Views-Remaining: 1853X-Quota-Resets:              2026-06-12T00:00:00Z
  • X-RateLimit-Limit — your per-minute ceiling.
  • X-RateLimit-Remaining — requests left in the current rolling minute.
  • X-RateLimit-Reset — Unix-second timestamp when the rolling minute resets.
  • X-Quota-Lead-Views-Limit — your monthly lead-view cap.
  • X-Quota-Lead-Views-Remaining — lead-views left in the current billing month.
  • X-Quota-Resets — RFC3339 timestamp of your next billing anniversary (when the monthly cap resets).

Hitting the cap (402)

When your monthly lead-view cap is exhausted, parcel reads return 402 Payment Required. The body carries the next reset timestamp + an upgrade URL.

402 quota_exceeded
HTTP/1.1 402 Payment RequiredContent-Type: application/json {  "error": "quota_exceeded",  "detail": "Operator tier monthly lead-views exhausted. Upgrade or wait for reset.",  "quota_resets_at": "2026-06-12T00:00:00Z",  "upgrade_url": "https://terminal.house/api-keys?action=upgrade"}

Watchlist polling, pipeline updates, and other free operations continue to work at cap. A subsequent tier upgrade via Stripe is reflected within ~30 seconds.

Rate limit (429)

The per-minute ceiling protects the upstream county sources and our own queue. Bursting past it returns 429 Too Many Requests with a Retry-After header in seconds. Wait the indicated interval before retrying — repeated immediate retries extend the cool-down.

429 rate_limited
HTTP/1.1 429 Too Many RequestsRetry-After: 17Content-Type: application/json {  "error": "rate_limited",  "detail": "Too many requests/minute. Honor Retry-After header.",  "retry_after_seconds": 17}

Recommended client behavior

  • Watch X-RateLimit-Remaining and pause before it hits zero.
  • On a 429, sleep for Retry-After seconds with full jitter, then retry once.
  • Don't bypass with a second key — keys share their tenant's rate limit pool.
  • For bulk back-fill, prefer the CSV export over loops against GET /leads. See CSV & bulk.