Errors¶
Telbox errors are machine-readable codes, not just human prose. Always branch on the code, not the HTTP status alone (several distinct conditions share a status).
Response shape¶
The error body always contains a detail field carrying the error code:
Some platform errors return a richer object:
Treat detail (or code) as the stable, switchable identifier; treat
message as display text that may change.
Always capture X-Request-ID
Every response — success or error — includes an X-Request-ID header. Log
it. When you report a problem, that id lets us find the exact request in our
structured logs.
Status codes¶
| Status | Meaning | Typical codes |
|---|---|---|
400 |
Malformed request / provider rejection | invalid_request, provider-specific |
401 |
Missing / invalid / expired token | missing_bearer_token, token_expired, token_invalid, device_not_registered |
402 |
Payment required (AI budget exhausted) | ai_budget_exceeded |
403 |
Forbidden / consent required | ai_processing_consent_required |
404 |
Not found (or admin route hidden in prod) | — |
409 |
Conflict (idempotent replay mismatch, state) | — |
422 |
Request body failed validation | validation_error (FastAPI shape) |
429 |
Rate limited or quota exhausted | otp_rate_limited, ai_quota_exceeded_* |
5xx |
Server error | — |
Validation errors (422)¶
Body-shape failures use FastAPI's validation envelope:
{
"detail": [
{ "loc": ["body", "phone"], "msg": "field required", "type": "value_error.missing" }
]
}
AI errors¶
AI endpoints enforce three gates in order — consent, then per-feature quota, then a dollar backstop. Each maps to a distinct status so you can respond appropriately (e.g. show an upgrade prompt vs. a retry timer):
| Condition | Status | detail code |
Retry-After |
|---|---|---|---|
| AI processing not consented | 403 |
ai_processing_consent_required |
— |
| Free-tier feature quota exhausted | 429 |
ai_quota_exceeded_<feature> |
✅ yes |
| Free-tier $ budget exhausted | 402 |
ai_budget_exceeded |
— |
<feature> is one of voice_note, ask, thread_assistant,
insights_rerun, brief — e.g. ai_quota_exceeded_ask. See
Rate Limits & Quotas for the quota model and
AI & Privacy for consent.
Handle 402/403 from every AI call site
POST /v1/ask, POST /v1/messages/{id}/insights,
POST /v1/ai/thread-assistant, and the voice variants all enforce these
gates. A robust client catches 403 (route the user to consent), 429
(back off using Retry-After, or prompt to upgrade), and 402 (prompt to
upgrade).
Rate-limit errors¶
Per-IP and per-actor limits return 429. The OTP per-phone limiter uses the
code otp_rate_limited and sets Retry-After. See
Rate Limits & Quotas.
Recommended client handling¶
import time, httpx
def call(client, method, url, **kw):
for attempt in range(5):
r = client.request(method, url, **kw)
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
if r.status_code == 401 and r.json().get("detail") == "token_expired":
refresh_access_token() # then retry with new bearer
continue
return r
r.raise_for_status()
- 401
token_expired→ refresh and retry once. - 429 → honour
Retry-After; exponential backoff otherwise. - 402 / 403 (AI) → surface an upgrade / consent path; do not retry blindly.
- 5xx → retry idempotent requests with backoff; include
X-Request-IDif you escalate.