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

# Errors

Every error uses one envelope, everywhere:

```json
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded.",
    "request_id": "req_01H..."
  }
}
```

* **`code`** - a stable, machine-readable string. Branch your error handling on this, not on the message.
* **`message`** - a human-readable explanation. Safe to log; never contains stack traces, tenant IDs, or internal
  identifiers.
* **`request_id`** - the same value as the `X-Request-Id` response header. Include it in support requests.

## Correlation

`X-Request-Id` is returned on **every** response - success or error. If you supply your own `X-Request-Id` on the
request, it is echoed back so you can correlate across your logs and ours.

## HTTP status discipline

| Status                     | When                                                                                  |
| -------------------------- | ------------------------------------------------------------------------------------- |
| `200 OK`                   | Read succeeded, or a mutation returned the updated resource.                          |
| `201 Created`              | An order was created.                                                                 |
| `204 No Content`           | An order was deleted.                                                                 |
| `400 Bad Request`          | Malformed JSON, or an **unknown field** was sent (strict input).                      |
| `401 Unauthorized`         | Missing, invalid, expired, or revoked API key.                                        |
| `403 Forbidden`            | Valid key without the required scope, or a blocked source IP.                         |
| `404 Not Found`            | Resource does not exist **or belongs to another tenant** (existence is never leaked). |
| `409 Conflict`             | State conflict (e.g. mutating a non-`DRAFT` order) or an idempotency-key conflict.    |
| `422 Unprocessable Entity` | Body is syntactically valid but semantically invalid.                                 |
| `429 Too Many Requests`    | Rate limit exceeded. See [Rate Limits](/guides/api-conventions/rate-limits).          |
| `402 Payment Required`     | Over the monthly credit limit or insufficient balance.                                |
| `503 Service Unavailable`  | A dependency the launch path needs is unavailable (fail-closed).                      |

## Error codes

Common `code` values and their typical status:

| `code`                 | Status | Meaning                                                 |
| ---------------------- | ------ | ------------------------------------------------------- |
| `INVALID_REQUEST`      | `400`  | Malformed body or an unknown/unexpected field.          |
| `UNAUTHORIZED`         | `401`  | Missing, invalid, expired, or revoked key.              |
| `INSUFFICIENT_SCOPE`   | `403`  | Key lacks the scope for this endpoint.                  |
| `IP_BLOCKED`           | `403`  | Source IP is not on the key's allowlist.                |
| `NOT_FOUND`            | `404`  | Resource missing or outside your tenant.                |
| `CONFLICT`             | `409`  | State conflict - e.g. mutating a non-`DRAFT` order.     |
| `IDEMPOTENCY_CONFLICT` | `409`  | Idempotency key reused with a different request body.   |
| `OVER_CREDIT_LIMIT`    | `402`  | Launch would exceed the monthly credit pool or balance. |
| `RATE_LIMITED`         | `429`  | Too many requests; honor `Retry-After`.                 |
| `SERVICE_UNAVAILABLE`  | `503`  | A launch-path dependency is down (fail-closed).         |

> **Note**
>
> A cross-tenant read returns `404`, not `403`, so the API never reveals whether a resource you can't access exists.

## No tenant IDs, ever

Organization and workspace IDs are derived from your API key and appear in **neither** requests nor responses -
including error responses. Sending an unexpected field such as `organization_id` is rejected as `400 INVALID_REQUEST`.