Errors & Rate Limits — Developer Docs
The error envelope, common error types, per-route limits, and the 202-then-poll pattern.
What does an error response look like?
Errors return a structured envelope in detail:
{
"detail": {
"type": "bootstrap_not_ready",
"message": "Project knowledge base is still building.",
"recovery_action": "Poll GET /v1/projects/{project_id} until chat_ready is true, then retry."
}
}recovery_action is optional. For backward compatibility, 402 and 503 responses also carry a legacy top-level error key — it is deprecated; parse detail.
The per-route rate limiter is the one exception to the detail envelope: its 429 carries type: "rate_limited", message, and retry_after_seconds at the top level, plus a Retry-After header. It is always transient - wait the named seconds and retry the same call.
Common error types:
| Type | Status | Meaning |
|---|---|---|
not_found | 404 | Unknown resource, or a project outside your organization |
insufficient_scope | 403 | Key lacks the scope for this endpoint |
bootstrap_not_ready | 409 | Knowledge base still building — poll and retry |
zoning_verification_required | 409 | Parcels need zoning confirmation before agent work |
idempotency_key_reused | 409 | Same Idempotency-Key sent with a different payload |
idempotency_in_progress | 409 | A request with this Idempotency-Key is still running — retry |
concurrency_limit_exceeded | 429 | Too many concurrent agent turns for this key (limit 2) - retry_after_seconds in the body and a Retry-After header |
insufficient_credits | 402 | Organization wallet is empty |
api_key_credit_cap_exceeded | 402 | This key hit its daily or monthly credit cap |
async_turns_not_available | 501 | wait: false is not supported yet — use sync turns |
search_unavailable | 503 | Search backend temporarily unavailable — retry with backoff |
What are the rate limits?
Limits are per API key. Exceeding one returns 429 with a Retry-After header and a typed body naming the wait (type: "rate_limited" plus retry_after_seconds) - wait and retry the same call. The MCP client puts the wait in the error text as "Retry after Ns".
| Route | Limit |
|---|---|
POST /v1/projects | 8/min |
POST /v1/plan-check | 20/min |
POST .../sources, POST .../gis-sources | 10/min |
POST /v1/uploads/initiate, POST /v1/uploads/{job_id}/finalize | 30/min |
GET /v1/projects, GET /v1/projects/{id} | 60/min |
PATCH .../parcels/{i}/zoning | 30/min |
GET .../site-knowledge | 60/min |
POST .../messages | 2/min (concurrency 2) |
GET .../messages, GET .../messages/{turn_id} | 60/min |
POST .../search | 30/min |
POST .../summaries | 5/min |
POST .../gis-exports | 10/min |
POST .../maps | 10/min |
POST /v1/parcels/resolve | 20/min |
POST /v1/parcels/preview | 10/min |
GET .../site-plan, GET .../design-criteria, GET .../yield-scenario | 60/min |
POST .../site-plan-exports | 10/min |
GET .../gis-exports/{artifact_id} | 30/min |
POST .../reports | 5/min |
GET .../reports, GET .../reports/{job_id}, GET .../artifacts | 60/min |
GET .../artifacts/{artifact_id} | 30/min |
GET /v1/wallet | 30/min |
The 202-then-poll pattern: long-running work (project creation, report generation) returns 202 immediately. Poll the corresponding read endpoint every 5–15 seconds until the resource is terminal — never spin faster than the read limits.