Errors
One error envelope for every failure, the codes it can carry, what to do about each, and how to use request IDs.
Ody uses conventional HTTP status codes: 2xx succeeded, 4xx means the request needs to change, 5xx means something went wrong on Ody's side. Every error body has the same shape.
#The error envelope
JSON
{
"error": {
"code": "permission_denied",
"message": "This API key is missing the 'messages:send' scope",
"requestId": "req_5f0c2a9b1e7d"
}
}error.codestring — Machine-readable. Branch on this, never onmessage.error.messagestring — Human-readable and safe to log. Wording may change.error.requestIdstring — Same value as theX-Request-Idresponse header.
#Codes
| Status | code | Meaning | What to do |
|---|---|---|---|
400 | invalid_argument | A parameter is missing or malformed, or the body isn't JSON | Fix the request; the message says which field |
401 | unauthenticated | Key missing, malformed, unknown, or revoked | Don't retry. Alert whoever owns the integration |
403 | permission_denied | Key lacks the scope; the message names it | Use a key with that scope |
404 | not_found | No such resource in this workspace (or a malformed ID) | Check the ID and that the key's workspace owns it |
409 | failed_precondition | The workspace can't do this yet: no active number, texting not registered, agent not published, no plan, identity not verified, number already taken | Surface it to a human; retrying won't help |
429 | resource_exhausted | Rate limit exceeded | Wait Retry-After seconds, then retry with backoff |
500 | internal | Unexpected server error | Retry with exponential backoff and jitter; cap retries |
#Request IDs
Every response — success or error — carries an X-Request-Id header. Log it next to your own request logs, and quote it when you contact support: it identifies the exact request on our side.
You can also send your own X-Request-Id (up to 64 characters: letters, digits, _ and -). Ody echoes it back instead of minting one, which makes correlating retries across systems easy.
Shell
curl -i https://api.ody.co/v1/api/contacts \
-H "Authorization: Bearer $ODY_API_KEY" \
-H "X-Request-Id: sync-2026-10-14-0001"#Retrying safely
| Situation | Retry? |
|---|---|
429 | Yes — after Retry-After |
500, network error, timeout | Yes — exponential backoff with jitter. For POST /messages and POST /numbers, always send an Idempotency-Key so a retry can't double-send or double-buy |
400, 401, 403, 404, 409 | No — the same request will fail the same way |
#SDK errors
The SDKs raise a typed error carrying the same fields:
import { OdyError } from "@ody/sdk";
try {
await ody.contacts.get("00000000-0000-0000-0000-000000000000");
} catch (err) {
if (err instanceof OdyError) {
console.error(err.code, err.status, err.requestId, err.retryAfter);
}
}from ody import OdyError
try:
client.contacts.get("00000000-0000-0000-0000-000000000000")
except OdyError as err:
print(err.code, err.status, err.request_id, err.retry_after)Last updated Wed Oct 14 2026 00:00:00 GMT+0000 (Coordinated Universal Time)Something wrong on this page? Tell us