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

# Errors

Every error has the same body:

```json
{
  "error": {
    "code": "invalid_request",
    "message": "The request is invalid.",
    "details": {"field": "name"},
    "request_id": "req_01j00000000000000000000000",
    "retry": "no"
  }
}
```

| Field        | Meaning                                                                                                           |
| ------------ | ----------------------------------------------------------------------------------------------------------------- |
| `code`       | A stable code from the platform's error registry. Each code has exactly one HTTP status.                          |
| `message`    | A safe description for people. It can change: never branch on it.                                                 |
| `details`    | Safe, code-specific fields. For each code, only the fields listed in [Error codes](/concepts/error-codes) appear. |
| `request_id` | The same value as the `X-Request-Id` response header. Quote it to support.                                        |
| `retry`      | `no`, `yes` or `retry_after`. See below.                                                                          |

Only `code` and `message` are required by the schema. Rasen sends `details`, `request_id` and
`retry` on every error, but parse an error that lacks them rather than failing on it.

`details` never contains a request fingerprint, a hash of a request, secret material or another
workspace's data. Messages never carry a provider's payload or a secret you submitted.

## Refusals at the edge

Refusals made before an operation runs use the same envelope, with `request_id` and `retry`: a
missing or unknown key (`401`), an unavailable key check (`503`), a NUL character in a JSON string
(`400 invalid_request`) and a body over 1 MiB (`413 payload_too_large`). Their `details` are only
`field`, spelled with dots for nesting and list positions (`a.0.b`), or `bytes` and `limit_bytes`.

A request that no operation serves is refused the same way, with `retry: "no"`:

| Answer                   | When                                                                             | Details                                                           |
| ------------------------ | -------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `404 not_found`          | A path under dedicated-host `/v1` that no operation or documentation page serves | none                                                              |
| `405 method_not_allowed` | A declared path with a method it does not take                                   | `allowed` (the methods it takes), also sent as the `Allow` header |

A request with no key still gets the public `401` first. An unexpected fault outside an operation
also answers in the public envelope.

`method_not_allowed` is **edge-only**: it is a registered code, but no operation declares a `405`, so
it is not in the contract's error catalog or on [Error codes](/concepts/error-codes). Treat it as a
bug in the request's method or path, not as something to retry.

## How to handle an error

1. Branch on `error.code`. For `conflict`, branch on `error.details.reason` too.
2. Follow `retry`:

| `retry`       | What to do                                                                                    |
| ------------- | --------------------------------------------------------------------------------------------- |
| `no`          | Fix the request, or read the current state first. Do not resend it unchanged.                 |
| `yes`         | Resend the same request with the same `Idempotency-Key`.                                      |
| `retry_after` | Wait the `Retry-After` seconds, then resend the same request with the same `Idempotency-Key`. |

`retry` is an open vocabulary: preserve a value you do not know, and never read it as success.

For an operation that takes an `Idempotency-Key`, retrying `internal_error`, `provider_unavailable`,
`unavailable`, `at_capacity` or `call_orchestrator_unavailable` with the **same** key is safe: if the
first attempt took effect, the server replays its stored outcome; if it had no effect, the retry
performs the operation once. Never retry with a new key.

## Conflicts

`409` answers are not retryable, with one exception: `idempotency_in_progress` carries
`Retry-After`, after which you resend the identical request with the same key. For every other
`409`, read the current state, fix the request and send a new request.

`409 conflict` names its cause in `details.reason`. Reasons are an open list; each operation
documents the ones it can return in the API reference (`x-conflict-reasons`):

| `details.reason`                                                   | Returned by                                                                       |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| `stale_revision`                                                   | Phone-number assignments, Knowledge source reingest                               |
| `live_version_changed`                                             | `publishAgent`, `rollbackAgent`                                                   |
| `live_version_mismatch`                                            | `createCall` with `expected_version_id`                                           |
| `outbound_number_ambiguous`                                        | `createCall` without `from_number_id` when the agent has several outbound numbers |
| `name_taken`                                                       | Creating or replacing tools, MCP servers, Knowledge sources and secrets           |
| `resource_key_immutable`                                           | `replaceMcpServer` with a different `resource_key`                                |
| `connected_elsewhere`                                              | `setPhoneNumberInboundAgent` when another workspace answers the number            |
| `active_call`                                                      | Removing an agent from a number while a call on that number and agent runs        |
| `inbound_agent_required`, `account_changed`, `credentials_changed` | `provisionPhoneNumber`                                                            |
| `source_not_eligible`, `sources_not_ready`, `not_reingestable`     | Knowledge library, snapshot and source operations                                 |

## Common codes

| Status | Code                     | Typical cause                                                                           |
| ------ | ------------------------ | --------------------------------------------------------------------------------------- |
| 400    | `invalid_request`        | A malformed field (`details.field`), an unknown field, or an invalid or expired cursor. |
| 401    | `unauthenticated`        | A missing, invalid, expired or revoked key.                                             |
| 403    | `forbidden`              | The key lacks a capability; `details.required_capabilities` lists every missing one.    |
| 404    | `not_found`              | The resource does not exist or is not visible to this workspace; the two look the same. |
| 409    | `draft_conflict`         | A stale ETag on an agent, draft, variable, tool, MCP server or publication.             |
| 409    | `idempotency_key_reused` | The key was already used for a different request.                                       |
| 428    | `revision_required`      | An edit without `If-Match`.                                                             |
| 429    | `rate_limited`           | The rate limit was reached; wait `Retry-After`.                                         |

The complete list of the codes this API returns, with their status, retry class and details, is on
[Error codes](/concepts/error-codes).