Errors & status codes
Errors & status codes
Dial uses standard HTTP status codes. Successful reads return 200; successful creates (a sent message, a placed call, a provisioned number) return 201.
Status codes
Error shape
Errors carry an error field. For most failures it’s a string:
For 400 validation failures it can be an object describing the invalid fields:
Always branch on the HTTP status first, then read error for detail.
Retries and idempotency
Reads are safe to retry. GET requests — list numbers, messages, calls, account, usage — have no side effects, so retry them freely.
Placing a call is safe to retry with an Idempotency-Key. POST /api/v1/calls accepts an optional Idempotency-Key header — a unique client-generated key (e.g. a UUID) identifying the call attempt:
- If the original attempt placed the call, a retry with the same key returns the original call with
200instead of dialing again. (A fresh call returns201, so you can tell them apart.) - If a duplicate arrives while the original is still processing, it waits for the original and then replays its result. If the original is still going when the wait window closes, the duplicate gets
409— retry with the same key shortly. - A non-2xx response guarantees no live call: if the call already fired when the failure happened, Dial cancels it before responding. Retrying with the same key is always safe.
Other writes are not idempotent. Without an Idempotency-Key, every accepted POST is a distinct action and a retry creates a new resource:
- Retrying
POST /api/v1/messagessends a second SMS. - Retrying
POST /api/v1/callswithout the header places a second call. - Retrying
POST /api/v1/numbersprovisions another number.
The SDKs and CLI make a single attempt per call and never retry on your behalf, so each retry is a deliberate choice you make.
When a non-idempotent write fails ambiguously — a network timeout, a dropped connection, or an unexpected 5xx — you can’t tell whether it took effect. Don’t blind-retry. Confirm first: list recent messages or calls (GET /api/v1/messages, GET /api/v1/calls) and re-send only if the action isn’t already there. Retrying is always safe after a 400 or 401, because the request was rejected before any action ran.
Billing: PAYG vs FIXED
Billing is per phone number. A number is either:
- Pay-as-you-go (PAYG) — its calls, SMS, and monthly ownership are billed against your account’s credit wallet. When the wallet balance is at or below zero, new spend on a PAYG number is rejected with
402: placing a call, sending an SMS, and provisioning a number all return402; inbound calls are declined. Add credit to continue. - Subscription (FIXED) — covered by an active subscription; never gated by the wallet.
Check balances and per-number mode with GET /api/v1/billing (or dial billing).
Handling timeouts
A events/wait call returns 408 when no matching event arrives in the window — this is expected, not a failure. Retry the wait, or keep a live stream open instead. The CLI’s dial wait-for exits non-zero on timeout for the same reason.