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/leadswith 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_leadtool call (one lead-view per folio) - Webhook
watchlist.matchdelivery (one lead-view per parcel in the payload)
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.
| Tier | Lead-views / month | Rate limit / min | Concurrent webhooks |
|---|---|---|---|
| Starter | 500 | 300 | — |
| Operator | 2,000 | 600 | 5 |
| Team | 5,000 | 600 | 10 |
| Custom | 10,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.
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.
# 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.
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.
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-Remainingand pause before it hits zero. - On a
429, sleep forRetry-Afterseconds 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.