API + MCP reference

Errors

Every error carries a JSON body with a stable error code (machine-readable) and a human-readable detail sentence. Some codes add extra fields — the per-code reference below documents them.

Errors are stable contracts. We treat the error error slugs as a public API surface. New slugs ship additively; the ones below won't change shape without a v2.

Error body shape

All non-2xx responses use the same envelope:

error envelope
{  "error": "<machine-readable error code>",  "detail": "<one-sentence human-readable explanation>",  // Optional fields per error code — see the reference below.  "field": "filter.signals[2]",  "reason": "unknown signal name",  "upgrade_url": "https://terminal.house/api-keys?action=upgrade",  "retry_after_seconds": 17,  "quota_resets_at": "2026-06-12T00:00:00Z"}

The full set of optional fields varies by error code. The common ones — upgrade_url, retry_after_seconds, quota_resets_at — are documented per code below.

Status code reference

CodeSlugWhen
400invalid_filterFilter DSL failed validation. Body includes field and reason.
400invalid_requestRequired field missing, malformed JSON, or schema mismatch.
401unauthorizedAPI key missing, malformed, expired, or revoked.
402quota_exceededMonthly lead-views exhausted. Upgrade tier or wait for reset. Free operations (watchlists, pipeline, feedback) still work.
402trial_card_requiredTrial card missing or declined. Re-enter a card via Stripe Checkout to resume access.
403forbidden_tierEndpoint requires a higher tier. Starter keys hitting MCP / webhooks / CSV return this. Body carries an upgrade-prompt URL.
403forbidden_seatMember role lacks permission for this operation (e.g. a member trying to issue an API key on a Team tenant — admin-only).
404not_foundFolio, watchlist, member, or API-key ID does not exist.
409conflictWatchlist name already in use; duplicate key label; pipeline state already at the requested value.
422unprocessableRequest well-formed but semantically invalid (e.g. inline filter omits both recipe and filter).
423account_pausedSubscription canceled; tenant in 90-day data-preservation window. Reactivate to resume.
429rate_limitedToo many requests per minute. Honor Retry-After header. See Rate limits.
500internal_errorUnexpected server-side fault. Logged + Sentry-paged. Safe to retry with exponential back-off.
503upstream_unavailableA county source is degraded. Cached data is still served when possible (cache_freshness_hours in the body indicates how stale).

402 — quota_exceeded

You've hit your monthly lead-view cap. Reads return 402 until your billing-anniversary reset or a tier upgrade. Free operations (watchlists, pipeline, feedback) keep working.

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"}

423 — account_paused

Your subscription was canceled (during-trial cancel or post-trial cancel). The tenant is paused, all surfaces are frozen, and data is preserved for 90 days. After that, an automated cleanup deletes account data per the GDPR-aligned retention policy.

423 account_paused
HTTP/1.1 423 LockedContent-Type: application/json {  "error": "account_paused",  "detail": "Subscription canceled. Reactivate to resume API access. Data preserved 90 days.",  "paused_at": "2026-04-29T14:02:31Z",  "data_purge_at": "2026-07-28T14:02:31Z",  "reactivate_url": "https://terminal.house/api-keys?action=reactivate"}

Re-subscribing within 90 days restores access at the chosen tier — same data, same watchlists, no second trial.

429 — rate_limited

You crossed the per-minute rate ceiling. The Retry-After header is the seconds to wait. Repeated immediate retries extend the cool-down — use full jitter.

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

5xx — upstream_unavailable

A county data source (Miami-Dade Property Appraiser, Broward, Palm Beach, Sunbiz, etc.) is degraded. We serve cached data when we can — cache_freshness_hours in the body tells you how stale.

503 upstream_unavailable
HTTP/1.1 503 Service UnavailableContent-Type: application/json {  "error": "upstream_unavailable",  "detail": "Miami-Dade Property Appraiser is degraded. Cached data served when available.",  "source": "fl-mdc-property-appraiser",  "cache_freshness_hours": 18}

Track upstream source health at status.terminal.house.