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. 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. 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 | 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. 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. | Treat as absent, never as a permissions hint — another owner's resource and one that never existed are deliberately the same answer. |
409 | The project or resource is in a state that refuses this change. | Read the state and resolve it — verify the carrier, re-enable the endpoint, rename the duplicate — then retry. |
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 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. 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.