Skip to content

Error taxonomy

Every error the API and MCP tools can return comes from one closed set of codes. No misleading errors: a missing price says price_required, not “outside mapped tracts”. The canonical list ships as error-taxonomy.json (one of the machine-readable spec contracts) and is mirrored in packages/spec/error-taxonomy.json.

Every /api/* JSON response carries these fields alongside its existing payload (additive — nothing was removed):

{
"…": "existing top-level fields (data / listings / value / …)",
"as_of": "2026-07-10T13:00:00.000Z",
"model_versions": { "model": "p3s-2026.07", "underwrite": "p3s-2026.07", "serving": "p5s8-2026.07" },
"warnings": []
}

On error, HTTP routes add a structured error object (plus flat code/message for convenience):

{ "error": { "code": "invalid_params", "message": "request failed input validation", "field": "strategy", "expected": "one of [flip,brrrr,hold,house_hack]" },
"code": "invalid_params", "message": "request failed input validation", "field": "strategy",
"as_of": "", "model_versions": { "…": "" }, "warnings": [] }

MCP tool results keep the code in a string error field (backward-compatible and still machine-readable), e.g. { "error": "units_required", "message": "…" }. Validation failures are surfaced through the tool result with isError: true.

Code HTTP Retryable Meaning
units_required 400 no Unit count unknown and no matching listing supplied it — pass units. Never a phantom duplex.
price_required 400 no A positive price is required and none was resolvable.
outside_mapped_tracts 400 no The point is outside Cook + Lake County, IL (current coverage).
tract_not_ingested 400 no Sales data for this census tract is not yet ingested — comps unavailable (never a silent tract substitution).
insufficient_renovated_comps 200 no Tier-3 suppression: not enough renovated comps. No ARV number is emitted (INV-5); a reason is attached.
dispersion_exceeds_threshold 200 no Tier-3 suppression: comp dispersion exceeds the confidence guardrail.
luxury_thin_comps 200 no Tier-3 suppression: the luxury stratum has too few comps.
rate_limited 429 yes Anonymous 30/min/IP, or a keyed rpm_limit token bucket was exceeded. Retry after ~60s.
invalid_params 400 no Failed the JSON-Schema-subset validator (type / enum / required / min / max / pattern). Carries field + expected.
not_found 404 no No such listing, tract, or collection.
unauthorized 401 no Authentication required — send Authorization: Bearer pm_<key>.
forbidden_scope 403 no Your key lacks the scope for this call (write tools need write-state).
payload_too_large 413 no Request or response exceeds a size budget; responses truncate + warn instead where possible.
upstream_unavailable 502 yes A county open-data source (Socrata) or the state store was unreachable.

The three *_comps / dispersion_* codes return HTTP 200 with arv: null and a suppression_reason. That is the honest Tier-3 state — “not enough evidence to value this confidently” — not an outage. Agents should render the reason, not retry.