> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.getdial.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getdial.ai/_mcp/server.

# Errors & status codes

> HTTP status codes Dial returns and the shape of error responses.

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

| Code  | Meaning                                                                                                                                                                                                                                                                                                                                            |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | Success.                                                                                                                                                                                                                                                                                                                                           |
| `201` | Resource created (message sent, call placed, number provisioned).                                                                                                                                                                                                                                                                                  |
| `400` | The request body failed validation. On a 10DLC submission (`tendlc_validation_failed`) `error` is an object keyed by field — including `phone` when it's one of your Dial numbers, which carriers won't verify.                                                                                                                                    |
| `401` | Missing or invalid API key.                                                                                                                                                                                                                                                                                                                        |
| `402` | Insufficient Dial credit — a pay-as-you-go number with a credit balance at or below zero, or a balance below a one-time fee such as 10DLC registration. See [Billing](#billing-payg-vs-fixed).                                                                                                                                                     |
| `403` | The action isn't available for this resource, account, or destination — e.g. iMessage on an ineligible account, 10DLC registration on a number it doesn't apply to (`tendlc_not_applicable`, `tendlc_not_available`), or a destination in a region Dial can't reach (`unsupported_region`).                                                        |
| `404` | The resource doesn't exist on your account.                                                                                                                                                                                                                                                                                                        |
| `408` | A `events/wait` request timed out before a matching event arrived.                                                                                                                                                                                                                                                                                 |
| `409` | Conflict — e.g. an [`Idempotency-Key`](#retries-and-idempotency) whose original request is still processing.                                                                                                                                                                                                                                       |
| `429` | A rate or concurrency limit was hit — an [iMessage sending limit](/documentation/reference/usage-and-limits#imessage-sending-limits), either a number at its daily new-conversation cap or a new contact who hasn't replied yet (`rate_limited`), or a [free account](#free-accounts) already at its concurrent-call limit (`call_limit_reached`). |
| `503` | The event stream isn't configured on this deployment.                                                                                                                                                                                                                                                                                              |

## Error shape

Errors carry an `error` field. For most failures it's a string:

```json
{ "error": "Phone number not found" }
```

For `400` validation failures it can be an object describing the invalid fields:

```json
{ "error": { "fieldErrors": { "to": ["Required"] } } }
```

Always branch on the HTTP status first, then read `error` for detail.

## Restricted destinations

A `403` with `unsupported_region` means the destination's region isn't reachable from Dial — either it's embargoed, or calling and messaging permissions for that country aren't enabled on the account placing the request. It's a property of the number you asked for, not of your request, so retrying won't change the outcome:

```json
{
  "error": "Calls to this destination are not available due to regional restrictions. To request access to this region, contact support@getdial.ai or reach out to us on Discord (https://discord.com/invite/tEmpesw6GW).",
  "code": "unsupported_region"
}
```

`POST /api/v1/messages` returns the same code with wording for messages. Where the restriction is a permission rather than an embargo, the message says so and how to ask — some regions can be enabled on request.

## Unexpected failures

Rarely, a request fails in a way Dial can't classify. Those responses carry a generic message with a reference code:

```json
{
  "error": "Please contact support@getdial.ai, or reach out to us on Discord (https://discord.com/invite/tEmpesw6GW). Error reference code: ref_3f9a2c1b7d4e",
  "code": "bad_request"
}
```

The `ref_…` code identifies that exact failure in Dial's logs. Quote it to support or on Discord and we can look up what happened without needing you to reproduce it. The HTTP status still describes the class of failure, so keep branching on status as usual.

## 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 `200` instead of dialing again. (A fresh call returns `201`, 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.

```bash
curl -X POST https://api.getdial.ai/v1/calls \
  -H "Authorization: Bearer $DIAL_API_KEY" \
  -H "Idempotency-Key: 4cf1a3b2-7c4e-4a2f-9d3e-2f1a8b9c0d1e" \
  -H "Content-Type: application/json" \
  -d '{"to": "+14155550123", "fromNumberId": "pn_123", "outboundInstruction": "..."}'
```

**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/messages` sends a **second SMS**.
* Retrying `POST /api/v1/calls` without the header places a **second call**.
* Retrying `POST /api/v1/numbers` provisions **another number**.

The [SDKs](/documentation/sdks/overview) and [CLI](/documentation/cli/commands) 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 return `402`; 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`](/api-reference/billing/get-billing) (or `dial billing`).

## Free accounts

An account that has **never added credit and never subscribed** runs with two guardrails:

* **Call length** — every call is hard-capped at **5 minutes**, regardless of any `maxCallDurationSeconds` configured on the call, the number, or the account.
* **Concurrent calls** — at most **2 calls in progress** at once, inbound and outbound combined. An outbound call over the limit is rejected with `429` (error code `call_limit_reached`); an inbound call over the limit is declined.

Both limits lift permanently the first time you add credit or start a subscription.

Read the exact values from `limits` on [`GET /api/v1/account`](/api-reference/account/get-account) rather than hardcoding them — it's `null` once the account has paid:

```json
{
  "maxCallDurationSeconds": 300,
  "limits": { "maxCallDurationSeconds": 300, "maxConcurrentCalls": 2 }
}
```

### Asking for more than the cap

A `maxCallDurationSeconds` **you send explicitly** above `limits.maxCallDurationSeconds` is **rejected with `400`** — on [`POST /api/v1/calls`](/api-reference/calls/place-call), [`PATCH /api/v1/numbers/{id}`](/api-reference/numbers/update-number), and [`PATCH /api/v1/account`](/api-reference/account/update-account) alike. The request is refused rather than quietly given a shorter call than you asked for.

A cap **inherited** from the number or account chain is not an error: it's clamped down to the free ceiling and the call proceeds. So a number whose cap was set to 1800 before the account hit the free tier still places calls — they just end at 5 minutes.

## 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](/documentation/platform/stream-account-events) open instead. The CLI's [`dial wait-for`](/documentation/cli/commands) exits non-zero on timeout for the same reason.