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?
Every non-2xx response is one RFC 9457 problem document, Content-Type: application/problem+json, whatever refused the call - a route, the scope check, request validation, the rate limiter or an unknown path:
{
"type": "https://precisiondocs.ai/developers/errors#bootstrap_not_ready",
"title": "Conflict",
"status": 409,
"code": "bootstrap_not_ready",
"detail": "Project knowledge base is still building.",
"request_id": "5d0c1c9e-7f1b-4f5e-9a57-2f6d3c1b8e44",
"retry_after_seconds": 15,
"poll": "/v1/projects/{project_id}"
}Branch on code. detail is human copy, request_id matches the X-Request-ID response header (quote it to support), and recovery.action (with recovery.url where there is one) says what to do next when the error has a known recovery. The context of each error rides beside them: required_scope, required_credits and available_credits, period and credit_cap, gaps, and for a request the schema refused, errors[] with each bad field, its code and a message.
A 429 from the per-route rate limiter is the same shape with code: "rate_limited", retry_after_seconds and a Retry-After header. It is always transient - wait the named seconds and retry the same call.
Common error codes:
| Code | 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 |
goal_version_conflict | 409 | The project goal moved since the user_goal_revision / user_goal_hash you read - re-read GET /v1/projects/{id}, confirm the wording with the user, and send again |
goal_version_required | 428 | PATCH /v1/projects/{id} was sent without expected_goal_revision or expected_goal_hash; a goal is replaced whole, never overwritten blind |
invalid_interview_answer | 400 | One or more interview answers were rejected; nothing was recorded, and errors names each bad field |
site_plan_studio_not_enabled | 403 | Site Plan Studio is not enabled for the organization (it comes with every paid plan); the compile-lane writes are refused while every read still works |
planning_session_conflict | 409 | The planning_session_id is no longer the active session, or the state_version is stale - re-read and retry |
program_incomplete | 409 | The program still has required gaps; gaps names them per zone |
invalid_program | 422 | The program cannot be approved as it stands - a staged parking draft the studio could not confirm |
program_not_approved | 409 | A concept was proposed before the program was approved |
concept_proposal_rejected | 409 | The proposal could not be built as asked - an unknown zone_id, a zone with no sketched boundary, or no existing concept for a zone_id regeneration to start from |
typology_not_supported_via_api | 409 | Single-family and subdivision plans compile only in the in-app Site Plan Studio |
proposal_override_reason_required | 422 | The proposal carries conflicts; approving it needs the user's written reason |
proposal_expired | 409 | The program or plan changed since the proposal was made - propose again |
proposal_blocked | 409 | A prerequisite the compile needs is missing (block says which); the proposal stays retryable |
proposal_failed | 409 | The compile was refused on a domain rule (repair gate, conflict); the proposal is marked failed |
proposal_decided | 409 | The proposal was already approved, rejected or expired |
proposal_execution_failed | 500 | The compile faulted rather than refusing; the proposal is left retryable, so retry the same approve |
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 |
house_paid_daily_limit | 429 | This key or connection added 50 URL documents and GIS services today, the daily limit for sources PrecisionDocs processes at its own cost - Retry-After names the wait until 00:00 UTC; supplemental ordinance sources and duplicates are not counted |
insufficient_credits | 402 | Organization wallet is empty |
api_key_credit_cap_exceeded | 402 | This key hit its daily or monthly credit cap |
connection_credit_cap_exceeded | 402 | This chat-agent connection hit a daily or monthly credit cap chosen when it was connected - reconnect with a higher cap |
free_tier_limit | 402 | A free-plan limit: the feature (own documents, sources, GIS registration) is not on the free plan, or its allowance is used up - limit names which; add credits |
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 |
PATCH /v1/projects/{id} | 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 .../interview/workflows, GET .../interview/step | 60/min |
POST .../interview/answers | 30/min |
POST .../site-plan/program/approve | 30/min |
POST .../site-plan/proposals, POST .../site-plan/proposals/{id}/approve, POST .../site-plan/proposals/{id}/reject | 10/min |
GET .../site-plan/proposals | 60/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.