# Error Codes

Every error response from the Roam developer API includes a **machine-readable error code**. Branch on the code rather than parsing the human-readable message.

## Response shape (v0 / Alpha API)

On `/v0/...` paths the human-readable sentence stays in `error`, and the catalog code is additive in `code`:

```json
{
  "error": "The bearer token is unknown, malformed, or expired; obtain a new token.",
  "code": "invalid_token"
}
```

Clients that only read `error` continue to work. Prefer branching on `code`.

:::note v1 shape
On `/v1/...` paths the body is `{"ok": false, "error": "<code>"}` — the catalog
code is the value of `error` (there is no separate `code` field). Full details
and success-envelope rules: [Responses and Errors](/docs/guides/responses-and-errors).
The catalog below is shared across v0 and v1.
:::

## HTTP status codes

| Status | Meaning |
|--------|---------|
| `400 Bad Request` | Invalid request parameters or body |
| `401 Unauthorized` | Missing or invalid authentication |
| `403 Forbidden` | Valid auth but insufficient access or scope |
| `404 Not Found` | Resource does not exist (or is invisible to the caller) |
| `405 Method Not Allowed` | HTTP method not supported |
| `408 Request Timeout` | Server timed out reading the request |
| `409 Conflict` | Conflicts with current resource state |
| `410 Gone` | Endpoint has been removed |
| `413 Payload Too Large` | Request body or message text too large |
| `415 Unsupported Media Type` | Content-Type not supported |
| `429 Too Many Requests` | Rate limit exceeded |
| `500 Internal Server Error` | Unexpected server failure; retrying may succeed |
| `501 Not Implemented` | Operation not supported for the given input |
| `504 Gateway Timeout` | Timed out waiting on an upstream service |

Some codes can appear with more than one status (for example a `*_not_found` code on a `400` when a *referenced* resource is missing). Always check both the status and the `code` field.

## Catalog

Codes use Slack-style snake_case spellings where there is a well-known equivalent (`ratelimited`, `not_authed`, `msg_too_long`, …).

### Generic (by status class)

| Code | Typical status | Description |
|------|----------------|-------------|
| `invalid_arguments` | 400 | One or more request arguments are invalid or malformed |
| `invalid_auth` | 401 | Authentication failed (generic) |
| `access_denied` | 403 | Caller is not permitted to perform this action |
| `not_found` | 404 | Resource could not be found (generic) |
| `method_not_allowed` | 405 | HTTP method not supported |
| `request_timeout` | 408 | Server timed out reading the request |
| `conflict` | 409 | Conflicts with current resource state |
| `method_deprecated` | 410 | Endpoint removed; see docs for replacement |
| `request_too_large` | 413 | Request payload exceeds allowed size |
| `invalid_content_type` | 415 | Content-Type not supported; use `application/json` |
| `ratelimited` | 429 | Rate limit exceeded; retry after a delay |
| `internal_error` | 500 | Unexpected server-side failure; retrying may succeed |
| `not_implemented` | 501 | Operation not supported for the given input |

### Request validation

| Code | Typical status | Description |
|------|----------------|-------------|
| `invalid_json` | 400 | Request body is not valid JSON |
| `missing_parameter` | 400 | A required parameter or field is missing |
| `invalid_parameter` | 400 | A parameter or field has an invalid value |
| `invalid_cursor` | 400 | Pagination cursor invalid or expired; restart without a cursor |
| `invalid_expand` | 400 | An `expand=` field is not recognized |
| `cannot_unfurl_url` | 400 | A requested URL is absent from the message or outside the app's registered unfurl domains |

### Authentication and authorization

| Code | Typical status | Description |
|------|----------------|-------------|
| `not_authed` | 401 | No bearer token was provided |
| `invalid_token` | 401 | Token unknown, malformed, or expired; obtain a new token |
| `token_revoked` | 401 | Token permanently unusable (e.g. owner archived); discard and re-authenticate — **do not retry** with the same token |
| `missing_scope` | 403 | Token lacks a required scope; body includes `needed` (any-of) and `provided` |
| `access_mode_not_supported` | 403 | Token access mode (personal vs organization) cannot use this endpoint |
| `not_in_chat` | 403 | Caller is not a member of the specified chat |
| `not_in_group` | 403 | Caller is not a member of the specified group |
| `user_not_in_chat` | 400 | The targeted user is not a member of the specified chat (e.g. [`/chat.postEphemeral`](/docs/api/chat-post-ephemeral) to a non-member) |

`invalid_token` and `token_revoked` responses also include:

```http
WWW-Authenticate: Bearer error="invalid_token"
```

**`token_revoked` vs `invalid_token`:** both are 401, but `token_revoked` means
the credential is permanently dead (owner archived/deleted, client archived).
Do not retry with the same token — discard it and re-authenticate. See also
[Authentication failures](/docs/api/api#authentication-failures).

### Resource not found

| Code | Typical status | Description |
|------|----------------|-------------|
| `chat_not_found` | 404 | Chat could not be found |
| `user_not_found` | 404 | User could not be found |
| `group_not_found` | 404 | Group could not be found |
| `meeting_not_found` | 404 | Meeting could not be found |
| `meeting_link_not_found` | 404 | Meeting link could not be found |
| `message_not_found` | 404 | Message could not be found |
| `scheduled_message_not_found` | 404 | Scheduled message could not be found (or was scheduled by a different credential) |
| `event_not_found` | 404 | On-Air event could not be found |
| `guest_not_found` | 404 | On-Air guest could not be found |
| `address_not_found` | 404 | Address could not be found |
| `lobby_not_found` | 404 | Lobby could not be found |
| `magicast_not_found` | 404 | Magicast could not be found |
| `transcript_not_found` | 404 | Meeting has no transcript, or it is not yet available |
| `recording_not_found` | 404 | Recording could not be found |
| `webhook_not_found` | 404 | Webhook subscription could not be found |
| `item_not_found` | 404 | Item could not be found |
| `asset_not_found` | 404 | Asset could not be found |
| `handle_not_found` | 404 | Handle could not be found |
| `session_not_found` | 404 | Agent session could not be found (reserved) |
| `connection_not_found` | 404 | Agent connection could not be found (reserved) |
| `file_not_found` | 404 | File could not be found (reserved) |
| `stream_not_found` | 404 | Stream could not be found |

### Message and resource conditions

| Code | Typical status | Description |
|------|----------------|-------------|
| `msg_too_long` | 413 | Message text exceeds maximum length; truncate and retry |
| `name_taken` | 400 | A resource with that name already exists |
| `is_archived` | 400 | Target resource is archived and cannot be modified |
| `scheduled_message_already_sent` | 409 | Scheduled message was already sent (or delivery has begun) and can no longer be canceled |

### Transcripts and search

| Code | Typical status | Description |
|------|----------------|-------------|
| `transcript_pending` | 404 | Transcript not ready yet (meeting in progress or still processing); retry later (may include `Retry-After`) |
| `transcript_unavailable` | 404 | Meeting was not transcribed; **stop retrying** |
| `upstream_timeout` | 504 | Timed out waiting on an upstream service; retry |

## Client guidance

1. **Branch on `code`**, not on free-text `error` strings.
2. Treat `token_revoked` as terminal for that credential — discard it and run your re-auth flow.
3. Treat `transcript_unavailable` as terminal for that meeting; treat `transcript_pending` as retryable.
4. For `ratelimited` (429), honor `Retry-After` and use exponential backoff. See [Rate Limiting](/docs/api/api#rate-limiting).
5. For `missing_scope` (403), read `needed` (any-of array) and `provided`
   (granted scopes, post-alias) to decide what to request on the next
   authorization — do not parse the human-readable message. See
   [Responses and Errors](/docs/guides/responses-and-errors#missing_scope-details).