> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revdesk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Every error the API can return: a granular code for your integration logic, and a generalized category you can show an end user.

When a request fails, RevDesk returns a consistent error envelope. Call-related errors
carry **two tiers** so you can act on them precisely *and* show something friendly to a
person:

* **`code`**: a granular, machine-readable reason (e.g. `call_id_not_verified`,
  `busy`, `agent_dispatch_failed`). Branch on this in your integration.
* **`category`**: a generalized, user-facing bucket (e.g. `invalid_request`,
  `provider_error`). Map it straight to a message you display to an end user.

```json theme={null}
{
  "error": {
    "code": "call_id_not_verified",
    "category": "invalid_request",
    "message": "The caller ID isn't verified for this account.",
    "doc_url": "https://docs.revdesk.com/api-reference/errors#invalid-request"
  }
}
```

`message` is human-readable but may change; never branch on it. `fields` (when present)
maps a request field to a per-field explanation. `doc_url` deep-links to the relevant
section below.

## Categories

The generalized bucket. If you only surface one thing to an end user, surface this.

| Category           | What it means                                                                                             | What the caller can do                 |
| ------------------ | --------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| `invalid_request`  | The request was malformed: bad/missing destination, caller ID not verified, no number on the account      | Fix the request                        |
| `compliance_block` | Blocked by a calling rule: DNC, opt-out, no consent, calling window/day, frequency cap, emergency number  | Remove from list / wait for the window |
| `account_limit`    | An account-level limit: minutes exhausted, no active subscription, payment required, rate/concurrency cap | Upgrade / add payment / retry later    |
| `unreachable`      | Placed, but never reached a person: busy, no answer, invalid/disconnected number, carrier rejected        | Retry later                            |
| `voicemail`        | Reached an answering machine                                                                              | Informational                          |
| `provider_error`   | A RevDesk / telephony infrastructure failure                                                              | Retry, then contact support            |

## When you receive them

`invalid_request`, `compliance_block`, `account_limit`, and `provider_error` codes come
back **synchronously** on the dial request (with the HTTP status shown below).

`unreachable` and `voicemail` describe how a *placed* call ended; they are **not** returned
on the dial request. They arrive on the call resource (`failure_code` / `failure_category`)
and on the `phone_call.ended` webhook once the call completes.

## Invalid request

`400` / `409` / `422`: returned synchronously.

| Code                   | HTTP | Retryable | Meaning                                                                         |
| ---------------------- | ---- | --------- | ------------------------------------------------------------------------------- |
| `destination_missing`  | 400  | no        | No destination phone number was provided                                        |
| `destination_invalid`  | 400  | no        | The destination isn't a valid, dialable E.164 number                            |
| `no_active_number`     | 409  | no        | No active phone number is available to place the call (e.g. still provisioning) |
| `call_id_not_verified` | 422  | no        | The caller ID isn't verified for this account                                   |

## Compliance block

`403` / `429`: returned synchronously. The call was refused by a calling rule.

| Code                  | HTTP | Retryable | Meaning                                                         |
| --------------------- | ---- | --------- | --------------------------------------------------------------- |
| `destination_blocked` | 403  | no        | Emergency or special-service number that can't be dialed        |
| `dnc_suppressed`      | 403  | no        | The number is on a Do-Not-Call list                             |
| `recipient_opted_out` | 403  | no        | The contact has opted out of calls                              |
| `no_consent`          | 403  | no        | No calling consent is on file for the contact                   |
| `outside_call_window` | 403  | yes       | It's outside the permitted calling hours for the recipient      |
| `day_not_allowed`     | 403  | yes       | Calls aren't permitted to the recipient today                   |
| `frequency_exceeded`  | 429  | yes       | The contact has already been called the maximum number of times |

## Texting not ready

`403`: returned synchronously by `POST /v1/sms/send` when the workspace's A2P/10DLC texting
registration blocks the send. Not retryable until the registration state changes —
subscribe to the `TEXTING_REGISTRATION_STATUS_CHANGED` webhook or check `texting_status`
on `GET /v1/phone-numbers` before sending.

| Code                | HTTP | Retryable | Meaning                                                                                                                                                                                                                                                                                                                 |
| ------------------- | ---- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `texting_not_ready` | 403  | no        | The A2P texting registration is missing, pending carrier review, or rejected. `fields.reason` carries the specific gate reason (`a2p_registration_required` / `a2p_registration_pending` / `a2p_registration_rejected` / `no_sender`) and `fields.registration_status` the coarse `none` / `pending` / `rejected` state |

## Content policy forbidden

`403`: returned synchronously by `GET /v1/calls/{id}/transcript` (and other call-content endpoints)
when the organization's call-content policy pins content to a compliance (HIPAA) boundary. Transcripts,
recordings, and derived analysis are never returned over the public API for these organizations.

| Code                       | HTTP | Retryable | Meaning                                                                                        |
| -------------------------- | ---- | --------- | ---------------------------------------------------------------------------------------------- |
| `content_policy_forbidden` | 403  | no        | The organization's policy forbids returning call content (transcripts/recordings) over the API |

## Account limit

`402` / `429`: returned synchronously.

| Code                     | HTTP | Retryable | Meaning                                            |
| ------------------------ | ---- | --------- | -------------------------------------------------- |
| `voice_usage_exhausted`  | 402  | no        | Your included calling minutes are used up          |
| `no_active_subscription` | 402  | no        | No active subscription is available to place calls |
| `no_number_available`    | 429  | yes       | All of your numbers are busy right now             |
| `rate_limited`           | 429  | yes       | Too many calls are being placed at once            |

## Unreachable

Asynchronous: these appear on the call resource and the `phone_call.ended` webhook, not on
the dial response.

| Code                 | Retryable | Meaning                                                   |
| -------------------- | --------- | --------------------------------------------------------- |
| `busy`               | yes       | The line was busy                                         |
| `no_answer`          | yes       | No one answered                                           |
| `number_unreachable` | no        | The number could not be reached (invalid or disconnected) |
| `call_declined`      | no        | The recipient declined the call                           |
| `carrier_rejected`   | no        | The carrier rejected the call                             |

## Voicemail

Asynchronous: appears on the call resource and the `phone_call.ended` webhook.

| Code                | Retryable | Meaning                      |
| ------------------- | --------- | ---------------------------- |
| `voicemail_reached` | yes       | Reached an answering machine |

## Provider error

`500` / `502`: returned synchronously. A transient RevDesk / telephony infrastructure
failure; safe to retry.

| Code                    | HTTP | Retryable | Meaning                                  |
| ----------------------- | ---- | --------- | ---------------------------------------- |
| `room_creation_failed`  | 502  | yes       | We couldn't start the call session       |
| `agent_dispatch_failed` | 502  | yes       | We couldn't connect an agent to the call |
| `sip_gateway_failed`    | 502  | yes       | We couldn't reach the phone network      |
| `provider_api_error`    | 502  | yes       | The telephony provider returned an error |
| `internal_error`        | 500  | yes       | An unexpected server error occurred      |

## Example

Place a call:

```bash theme={null}
curl -X POST https://api.revdesk.com/v1/calls/dial \
  -H "Authorization: Bearer $REVDESK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"from_number": "+14155551234", "to_number": "+14155556789"}'
```

A successful dial returns `202 Accepted` with the `call_id`; subscribe to the webhook for
the final outcome. Representative failures:

```jsonc theme={null}
// 422: the from_number isn't verified on this account
{
  "error": {
    "code": "call_id_not_verified",
    "category": "invalid_request",
    "message": "The caller ID isn't verified for this account.",
    "doc_url": "https://docs.revdesk.com/api-reference/errors#invalid-request"
  }
}

// 409: the number exists but isn't active yet
{
  "error": {
    "code": "no_active_number",
    "category": "invalid_request",
    "message": "from_number is not yet active; wait for provisioning to finish.",
    "doc_url": "https://docs.revdesk.com/api-reference/errors#invalid-request"
  }
}

// 502: couldn't connect an agent to the call (safe to retry)
{
  "error": {
    "code": "agent_dispatch_failed",
    "category": "provider_error",
    "message": "We couldn't connect an agent to the call. Please try again.",
    "doc_url": "https://docs.revdesk.com/api-reference/errors#provider-error"
  }
}
```

An `unreachable` / `voicemail` outcome shows up later on the call resource:

```jsonc theme={null}
{
  "data": {
    "id": "call_a1b2c3",
    "status": "busy",
    "call_outcome": "busy",
    "failure_code": "busy",
    "failure_category": "unreachable",
    "disconnection_reason": "user_busy"
  }
}
```

## HTTP and authentication errors

These apply to every endpoint, not just calls. They use the same envelope (without a
`category`).

| Code                   | HTTP | Description                                                  |
| ---------------------- | ---- | ------------------------------------------------------------ |
| `validation_error`     | 400  | Request body or parameters are invalid (see `fields`)        |
| `unauthorized`         | 401  | Missing or invalid API key                                   |
| `forbidden`            | 403  | API key does not have access to this resource                |
| `not_found`            | 404  | The requested resource does not exist                        |
| `idempotency_conflict` | 409  | An `Idempotency-Key` already succeeded with a different body |
| `rate_limited`         | 429  | Too many requests, slow down                                 |
| `internal_error`       | 500  | An unexpected server error occurred                          |

## With the SDK

[`@revdesk/sdk`](/api-reference/sdks) throws a typed `RevDeskError` for every non-2xx response,
mapping the envelope above onto these fields:

| Property    | From the envelope     | Notes                                                  |
| ----------- | --------------------- | ------------------------------------------------------ |
| `code`      | `error.code`          | The granular, machine-readable reason. Branch on this. |
| `category`  | `error.category`      | The user-facing bucket (call-failure errors only).     |
| `message`   | `error.message`       | Human-readable; may change. Don't branch on it.        |
| `fields`    | `error.fields`        | Per-field validation messages, when present.           |
| `docUrl`    | `error.doc_url`       | Deep link to the relevant docs section.                |
| `status`    | HTTP status           | The numeric status code.                               |
| `requestId` | `x-request-id` header | Echo to support when reporting an issue.               |

`error.isRetryable` is `true` for `429` and `5xx` responses.

```ts theme={null}
import { RevDeskError } from "@revdesk/sdk";

try {
  await revdesk.calls.dial({ from_number, to_number });
} catch (err) {
  if (err instanceof RevDeskError && err.code === "call_id_not_verified") {
    // prompt the user to verify their caller ID
  }
}
```

See [Authentication](/api-reference/authentication) for `scope_missing` and demo-key errors,
and [Conventions](/api-reference/conventions) for idempotency and pagination.
