Errors
HTTP status is the truth. The body is a hint.
Shape
{
"error": {
"message": "Agent not found",
"status": 404,
"timestamp": "2026-09-11T10:00:00.000Z"
}
}message is meant for humans. There is no stable machine readable code except one: RATE_LIMITED on 429s. Branch on the HTTP status, not the body.
Some 404 and 500 responses carry "status": 400 in the body. Known, harmless, ignore the body field.
Statuses you will see
| Status | Meaning | Usually because |
|---|---|---|
| 400 | Bad request | Missing field, bad phone format, validation. Message tells you which. |
| 401 | No or bad key | Typo, expired, owner left the org. |
| 403 | Not allowed | Blocked phone number, job doesn't allow manual contacts, role too low. |
| 404 | Not yours or not there | Same response either way, on purpose. |
| 207 | Partial success | Batch endpoints. Read results[]. |
| 429 | Slow down | 1000 req/min per key. |
| 503 | Downstream is out | Chat worker unavailable. Retry with backoff. |
| 500 | Our bug | Retry once, then tell us. |
Validation messages
Zod backed endpoints (chat, knowledge, outbound calls, listings) return one 400 with every failing field joined:
{ "error": { "message": "Validation error: sources.0.title: title is required, sources.1.content: content too long (max 500KB)", "status": 400, "timestamp": "..." } }Older endpoints (agents, schedule, batch calls) check fields by hand and return the first problem only.
Malformed JSON
Depends on the endpoint. Some treat it as an empty body, one returns 500. Send valid JSON and Content-Type: application/json and this never comes up.
