> ## 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.",
    "resolution_hint": "Verify the caller ID or use a number already owned by this workspace.",
    "doc_url": "https://docs.revdesk.com/api-reference/errors#invalid-request"
  }
}
```

`message` is human-readable but may change; never branch on it. `resolution_hint` gives a
short recovery step that an agent or developer can act on. `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 |
| `contact_automation_paused` | 403 | no | Automated outreach is paused for this contact after a clear decline |
| `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. |
| `resolutionHint` | `error.resolution_hint` | Short, actionable recovery guidance, when present. |
| `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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.