Reference
Errors
The error body#
Every failure is FastAPI's own envelope — no second wrapper, no vendor code:
{ "detail": "API key is missing the required scope 'voice:write'" }detail is a sentence for the developer. On a 422 it is instead an array of { loc, msg, type } validation items, where loc names the offending field. The agent-test routes add field (the path into your request, like checks[2].text) and message to each item, and their msg starts with the field; see Test an agent. One retired route returns a structured { code, message, migrate_to } object, which is why the declared type is string | object rather than string.
Every status this API returns#
The cause column is the backend's own declaration — from the OpenAPI responses and devplatform/schemas/api_errors.py — not a generic HTTP table. A status that is not listed here is not one this API raises deliberately.
| Status | What causes it | What to do |
|---|---|---|
400 | A hard limit was reached: too many projects, or too many active keys on this project. A requested scope is not a scope this platform defines. | Fix the request or free a slot — the message names the limit that was hit. |
401 | No account session was presented, or it is invalid or expired. No API key was sent in X-Api-Key, or it is invalid or revoked. Missing API key or Invalid API key — the X-Api-Key header is absent, unknown, or revoked. | Re-authenticate. For a console session refresh once, then send the user to sign in; for a project key, check the header and that the key is not revoked. |
402 | The wallet cannot pay for one AI reply; top it up. Wallet balance cannot cover this session's maximum duration. Wallet balance cannot cover this call's maximum duration. | Top up the wallet, or lower max_duration_seconds so the hold fits the spendable balance. |
403 | The account is suspended or its email is not verified. The API key lacks the scope this route needs, or its account or project is unavailable. API key is not scoped to a project, Project unavailable, or API key is missing the required scope '<scope>'. | Verify the account's email, or mint a key with the scope the message names. Never retry unchanged. |
404 | The project or the addressed resource does not exist for this owner. A resource owned by another developer is deliberately indistinguishable from one that never existed. The addressed resource does not exist in this key's project. One in another project is deliberately indistinguishable from one that never existed. | Treat as absent, never as a permissions hint — another owner's resource and one that never existed are deliberately the same answer. |
409 | A call or a chat reply is in progress (money is held); switch after it ends. The project or resource is in a state that refuses this change. A limit or the resource's state refuses this change; the sentence says which. | Read the state and resolve it — verify the carrier, re-enable the endpoint, rename the duplicate — then retry. |
413 | The request body is larger than this route accepts; the sentence names the limit. | |
415 | This route does not read that Content-Type; the sentence names the ones it does. | |
422 | The request body or query is not valid. detail is an array of {loc, msg, type} and loc names the offending field. | The body failed schema validation. detail is an array of {loc, msg, type}; the loc path names the offending field. |
429 | The project's request limit or one of the chat reply limits is reached (per conversation an hour, per minute, per day); Retry-After says when to try again. The project's requests-per-minute limit is reached; Retry-After says when to try again. The project's api_rpm ceiling was exceeded. Honour Retry-After; X-RateLimit-* headers are on every response. | Back off for Retry-After seconds. X-RateLimit-Limit, -Remaining and -Reset are on every response, so pace before you are refused. |
502 | The payment gateway did not create an order. | An upstream (payment gateway, carrier) did not answer usefully. Safe to retry with backoff. |
503 | The order could not be recorded locally, so no checkout was returned. This server is already writing its share of chat replies; Retry-After (2 s) says when to try again. A bounded worker this request needs is busy; Retry-After says when to try again. BYOK provider accounts are unverified/unavailable, or the service is draining. The carrier connection or BYOK pipeline is unavailable. Webhook secret storage (the credential keyring) is unavailable. | A dependency is unavailable or the service is draining. Retry with backoff; do not treat it as a permanent failure. |
Rate limits#
Machine routes are counted per project against that project's api_rpm, in a fixed one-minute window. Every response — not only a refusal — carries:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | the project's requests per minute |
X-RateLimit-Remaining | what is left in this window |
X-RateLimit-Reset | seconds until the window rolls |
Retry-After | on a 429 only; wait this long |
Pace against X-RateLimit-Remaining rather than waiting to be refused. The limiter fails open: if its store is unreachable your requests are allowed through, because a limiter protects other tenants and is not a security control.
WebSocket close codes#
The media socket refuses with an application close code rather than a status:
| Code | Meaning |
|---|---|
4401 | bad or expired stream token, or a token/session mismatch |
4404 | unknown voice session |
4408 | the session configuration expired before you connected |
4409 | the session is not connectable, or the project changed after admission |
4429 | the project's concurrency limit is full |
4453 | the provider pipeline is unavailable |
1013 | the service is restarting; reconnect with a new session |
Retrying safely#
429,502,503— retry with exponential backoff and jitter. HonourRetry-Afterwhen it is present.402— do not retry until the wallet is funded; the answer will not change.400,403,404,409,422— never retry unchanged. Each one names something you must alter in the request or in the resource's state.- Outbound calls take an
Idempotency-Key. Reuse the same key when you retry so a network timeout cannot dial twice; the repeat returns the first call withduplicate: true.
Truthful states#
The API reports what it actually knows, and the console shows those words verbatim rather than smoothing them over:
- A saved credential is configured until the provider confirms it; only a verified account takes calls. A provider outage during a probe leaves it configured, never optimistically verified.
- A carrier connection the carrier rejected is invalid, carrying the carrier's own message.
- A voice session is billed for connected seconds only, rounded up to whole minutes, and settled exactly once.
- A webhook test returns
202when the delivery is recorded, not when it is delivered. Readdead-lettersfor deliveries that never landed.
Use one project per boundary
Provider secrets stay encrypted and project API keys stay on your server.