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

# Error Handling

> Every error the Orwel REST API returns: the response format, each error code with its cause and fix, rate limit headers, and which requests are safe to retry.

When a request to the Orwel REST API fails, the response tells you what happened in two ways: the HTTP status says which kind of problem it is, and the JSON body carries a stable `error` code you can branch on, plus a `message` written for people.

## Response format

```json theme={null}
{
  "error": "insufficient_scope",
  "message": "This API key is missing the required scope: leads:write",
  "details": {}
}
```

| Field | Always present | Use it for |
| - | - | - |
| `error` | Yes | Your code. It is a stable snake\_case identifier; compare against it, not against the message. |
| `message` | Yes | Logs and people. The wording can change between releases. |
| `details` | Only for some `400` errors | Which field failed and why, for example `{ "field": "id", "issue": "Must be a valid UUID" }`. |
| `retry_after` | Only for `429` | Seconds until you can send requests again. |

Successful responses never contain `error`; they wrap their result in `data`.

## Error codes

| Status | `error` | When it happens | How to fix it |
| - | - | - | - |
| `400` | `bad_request` | The body is not valid JSON, a path id is not a UUID, or a parameter has the wrong type. | Read `message` and `details`, fix the request. Retrying the same request will fail again. |
| `401` | `unauthorized` | No `Authorization` header, a mistyped key, or a key that was revoked or has expired. The message says which. | Send `Authorization: Bearer orwel_sk_...` with an active secret key from **Settings, API keys**. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope the endpoint needs. The message names the missing scope. | Create a key with that scope. See the scope list in [API keys and authentication](https://www.orwel.io/docs/authentication.md). |
| `403` | `origin_not_allowed` | The request came from a browser, and the key is server only or the page's origin is not in the key's allowed origins. | Call the API from your backend, or add the origin to the key. |
| `403` | `forbidden` | The request refers to another workspace, for example a `workspaceId` that does not match the key. | Keys only work on their own workspace. Use a key created in the target workspace. |
| `403` | `plan_not_allowed` | The feature is not part of the workspace plan (for example the MCP server without an active subscription). | Activate the subscription in **Settings, Billing**. |
| `404` | `not_found` | The resource does not exist, or it belongs to a different workspace. | Check the id. For security, a resource in another workspace looks exactly like a missing one. |
| `429` | `rate_limit_exceeded` | The key sent more requests than its limit in the current minute. | Wait `retry_after` seconds. See [Rate limiting](#rate-limits) below. |
| `500` | `internal_error` | Something failed on Orwel's side. | Retry with backoff. If it keeps happening, write to [support](https://www.orwel.io/docs/support.md) with the time of the request. |

## Rate limits

Limits are counted per API key in one minute windows: 1,000 requests per minute for each `orwel_sk_` key. Every response carries the current state in headers:

| Header | Meaning |
| - | - |
| `X-RateLimit-Limit` | Requests allowed per minute for this key. |
| `X-RateLimit-Remaining` | Requests left in the current window. |
| `X-RateLimit-Reset` | Unix time (seconds) when the window resets. |
| `Retry-After` | Only on `429`: seconds to wait before the next request. |

A batch job that reads `X-RateLimit-Remaining` and slows down near zero never hits a `429`. More in [Rate Limiting](https://www.orwel.io/docs/concepts/rate-limiting.md).

## What to retry

| Status | Retry? |
| - | - |
| `400`, `401`, `403`, `404` | **No.** The same request will fail the same way. Fix the request, the key or the id first. |
| `429` | **Yes, after waiting.** Use `Retry-After` (or `retry_after` in the body). |
| `500`, network errors, timeouts | **Yes, with backoff.** Wait 1, 2, then 4 seconds, and stop after a few attempts. |

Reads (`GET`) are always safe to retry. For writes, check before retrying a `POST` that may have succeeded: a timeout does not mean the lead was not created. Look it up with [List Leads](https://www.orwel.io/docs/api-reference/leads/list.md) first, or use a value you control (such as the email) to detect the duplicate.

## Handling errors in code

```typescript theme={null}
type OrwelError = { error: string; message: string; details?: unknown; retry_after?: number }

async function orwelFetch(path: string, init: RequestInit = {}, attempt = 1): Promise<unknown> {
  const res = await fetch(`https://api.orwel.io/api/v1${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.ORWEL_API_KEY}`,
      'Content-Type': 'application/json',
      ...init.headers
    }
  })

  if (res.ok) return (await res.json()).data

  const body = (await res.json().catch(() => ({}))) as Partial<OrwelError>

  if (res.status === 429 && attempt <= 3) {
    const wait = Number(res.headers.get('Retry-After') ?? body.retry_after ?? 1)
    await new Promise((r) => setTimeout(r, wait * 1000))
    return orwelFetch(path, init, attempt + 1)
  }

  if (res.status >= 500 && attempt <= 3) {
    await new Promise((r) => setTimeout(r, 2 ** (attempt - 1) * 1000))
    return orwelFetch(path, init, attempt + 1)
  }

  throw new Error(`Orwel ${res.status} ${body.error ?? 'unknown'}: ${body.message ?? res.statusText}`)
}
```

The helper returns `data` on success, waits and retries on `429` and `5xx`, and throws with the error code for everything else, so a missing scope or a wrong id shows up immediately in your logs instead of being retried.

## Errors outside the REST API

* **Browser SDK:** `orwel.conversion()` does not throw on delivery problems; it resolves `{ success: false, reason }`. See [Conversions](https://www.orwel.io/docs/sdk/conversions.md#the-result). Ingest problems sent with the development key are explained in [Dev mode](https://www.orwel.io/docs/guides/dev-mode.md).
* **MCP server:** authentication errors use the same codes (`401 unauthorized`, `403 insufficient_scope`, `403 plan_not_allowed`). Errors inside a tool come back as the tool result so the agent can adjust. See [MCP for agents](https://www.orwel.io/docs/agents/mcp.md#when-something-fails).


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