# Errors

## The success envelope

Every successful response uses a `data` and `metadata` envelope.

```json
{
  "data": {
    "id": "trg_01HZX9Q2K8",
    "task_id": "task_01HZX9Q2K9",
    "status": "accepted",
    "created_at": "2026-09-15T09:30:00Z"
  },
  "metadata": {
    "request_id": "my-request-1"
  }
}
```

A list response adds pagination to the metadata.

```json
{
  "data": [],
  "metadata": {
    "request_id": "my-request-1",
    "pagination": {
      "total": 150,
      "limit": 20,
      "offset": 0,
      "has_more": true
    }
  }
}
```

`metadata.request_id` is the echo of the `X-Request-ID` header. Ringtime
includes it only when you send that header. Send one on each request. It makes
support much faster.

## The error shape

An error does not use the envelope. There are two error shapes. Which one you
get depends on where the request failed.

### Schema errors: HTTP 422 with `detail`

A body that does not match the schema is rejected before Ringtime reads it. A
missing required field, a wrong type, a bad enum value and a value that breaks
its own field's rule all land here. A phone number without a country calling
code is the common one. The answer is HTTP 422 and a list under `detail`, one
entry per broken field.

```json
{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "campaign"],
      "msg": "Field required"
    }
  ]
}
```

| Field | Type | What it is |
|---|---|---|
| `loc` | array | Path to the broken field, starting with `body`. |
| `msg` | string | Text for a human. Do not parse it. |
| `type` | string | The rule that failed, for example `missing`. |

There is no `error_code` on this shape. Read `loc` to find the field to fix.
An entry can carry more keys, such as `input`, the value you sent. Read the
three above and ignore the rest.

One exception: a body that matches the schema but asks for something the
campaign cannot do is also answered with HTTP 422, but with the `error_code`
shape below. Today that is `LANGUAGE_NOT_SUPPORTED` (with `details.supported`
listing the languages on offer) and `CHANNEL_MISMATCH`. Tell the two apart by
the presence of `detail` (a list) versus `error_code` (a string).

### Every other error: `error_code`

Once the body parses, Ringtime answers a refusal with this shape.

```json
{
  "error_code": "VALIDATION_ERROR",
  "message": "missing required id types: ['application']",
  "details": null,
  "request_id": "my-request-1"
}
```

| Field | Type | What it is |
|---|---|---|
| `error_code` | string | Machine-readable code in UPPER_SNAKE_CASE. |
| `message` | string | Text for a human. Do not parse it. |
| `details` | object or null | More context. The keys depend on the error. |
| `request_id` | string or null | Echo of `X-Request-ID`, when you sent one. |

Branch on `error_code`, never on `message`. The message text can change at any
time.

Test both shapes. A client that reads `error_code` on every failure will read
`undefined` on a 422.

## Status codes

| Status | Meaning |
|---|---|
| `400` | The body parses, but Ringtime cannot use it. |
| `401` | The API key is missing or invalid. |
| `403` | The key is valid but not allowed to do this. |
| `404` | The resource does not exist. |
| `409` | Conflict. For example a duplicate. |
| `422` | The body does not match the schema (`detail` shape), or names a language or channel the campaign cannot serve (`error_code` shape). |
| `429` | Too many requests. The rate limit is per client address and per route. |
| `500` | Unexpected server error. |
| `502` | A system Ringtime had to read returned an error. |

## Error codes

| Code | Status | When |
|---|---|---|
| `BAD_REQUEST` | 400 | The request is not usable. |
| `VALIDATION_ERROR` | 400 | The body parsed, but a value cannot be used. |
| `INVALID_SOURCE` | 400 | The source you named is not allowed for your organization. |
| `FORBIDDEN` | 403 | The key may not do this. |
| `NOT_FOUND` | 404 | The resource does not exist. |
| `CONFLICT` | 409 | The request clashes with something that already exists. |
| `INTERNAL_ERROR` | 500 | Unexpected server error. |

Some operations add a more exact code when a client must tell two errors of the
same status apart. The [API reference](/api) lists the codes for each operation.

## How to retry

Retry these:

- `429`. Wait, then send the same request again.
- `409` with `error_code` `SOURCE_RECORD_NOT_AVAILABLE`. The record is not
  readable yet. The `Retry-After` header gives the wait in seconds. Send the
  same body again after that wait.
- `409` with `error_code` `CAMPAIGN_NOT_READY`. Your request is correct, but
  the campaign is still being set up on Ringtime's side (it has no agent yet).
  Keep the body and send it again later — for example on your next scheduled
  run. Do not retry immediately: nothing you change will make it pass until
  Ringtime finishes the campaign.
- `5xx`, including `502` with `error_code` `SOURCE_API_ERROR`. Use an
  increasing wait time.

Do not retry any other `4xx`. Nothing changes until you correct the request.
That includes `409` with `DUPLICATE_TASK`: the task already exists, and
`details.existing_task_id` names it.

Keep the `request_id` of a failed request. Give it to Ringtime support.
