# Errors

> One error envelope for every failure, its types, and every error code the API answers with.

Source: https://www.ordstage.com/docs/errors/

The API uses normal HTTP status codes: `2xx` succeeded, `4xx` means the request needs to change, `5xx` means
something went wrong on Ordstage's side. Every error has the same JSON body:

```json
{
  "error": {
    "type": "permission",
    "code": "insufficient_scope",
    "message": "This key can't do that; it needs the bookings:write scope.",
    "param": "bookings:write",
    "request_id": "req_4f2a9c0d1e7b3a5c6d8e9f01"
  }
}
```

| Field | Meaning |
|---|---|
| `type` | The broad kind of error (below). Always present. |
| `code` | A stable, machine-readable reason. Always present. New codes may be added; handle unknown ones by `type`. |
| `message` | A plain explanation for the developer. It can change; don't parse it. |
| `param` | The parameter, field or header the error is about, when there is one (`null` otherwise). |
| `request_id` | The same value as the `Request-Id` header. Quote it to support. |

## Types

| Type | Status | What to do |
|---|---|---|
| `invalid_request` | 400, 405, 413, 422 | Fix the request (see `param`). Retrying the same request fails again. |
| `authentication` | 401 | Check the key. See [Authentication](https://www.ordstage.com/docs/authentication/). |
| `permission` | 403 | The key, plan or business doesn't allow this. Don't retry. |
| `not_found` | 404 | The object doesn't exist, or this key can't see it. |
| `conflict` | 409 | The request clashes with another one (for example an `Idempotency-Key` reuse). |
| `rate_limit` | 429 | Wait `Retry-After` seconds, then retry. See [Rate limits](https://www.ordstage.com/docs/rate-limits/). |
| `api_error` | 500, 503 | Retry with backoff. If it continues, contact support with the request id. |

An object that belongs to another business always answers `404`, never `403`, so ids from other businesses can't
be discovered.

## Retrying

Retry `429`, `500`, `503` and network errors with exponential backoff (for example 1, 2, 4, 8 seconds, plus a
random jitter), honouring `Retry-After` when present. Requests that create something must carry an
[Idempotency-Key](https://www.ordstage.com/docs/idempotency/) so a retry can't create it twice. Don't retry other `4xx` answers unchanged.

## Error codes

Generated from the API itself; the [API reference](https://www.ordstage.com/docs/reference/) lists the codes each endpoint can answer with.

| Code | Status | Type | Meaning |
|---|---|---|---|
| `invalid_api_key` | 401 | `authentication` | Use a valid workspace API key: Authorization: Bearer ord_… |
| `api_key_in_url` | 400 | `invalid_request` | API keys go in the Authorization or X-Api-Key header, never in the address. This key has been revoked; create a new one. |
| `sandbox_not_available` | 403 | `permission` | Test keys work with the sandbox, which comes with API access. This business's plan doesn't include it. |
| `insufficient_scope` | 403 | `permission` | This key doesn't have the scope this request needs. |
| `module_not_active` | 403 | `permission` | This business doesn't have the module this request needs. |
| `api_access_not_allowed` | 403 | `permission` | API access for this request isn't included for this business. |
| `plan_upgrade_required` | 403 | `permission` | The API is included on Growth plans and up. |
| `rate_limited` | 429 | `rate_limit` | Too many requests for this key. Wait and try again. |
| `too_many_invalid_keys` | 429 | `rate_limit` | Too many requests with invalid keys from this address. Try again in 15 minutes. |
| `rate_limiter_unavailable` | 503 | `api_error` | The API is briefly unavailable. Try again shortly. |
| `idempotency_key_required` | 400 | `invalid_request` | Send an Idempotency-Key header with this request. |
| `idempotency_conflict` | 409 | `conflict` | This Idempotency-Key was already used with a different request. |
| `idempotency_in_progress` | 409 | `conflict` | A request with this Idempotency-Key is still running. |
| `invalid_cursor` | 400 | `invalid_request` | starting_after must be the id of an object from this list. |
| `validation_error` | 422 | `invalid_request` | The request isn't valid. |
| `not_found` | 404 | `not_found` | Not found. |
| `booking_rejected` | 422 | `invalid_request` | The booking engine refused the request; `code` says why (for example invalid_time). |
| `key_creator_not_allowed` | 403 | `permission` | The person who created this key can no longer do this: changes that need a person are made as them, so they must still belong to the business with a role that may do it. |
| `not_available_in_sandbox` | 409 | `conflict` | This isn't available in the sandbox. Use a live key. |
| `invalid_role` | 400 | `invalid_request` | Choose owner, admin, manager or member. |
| `owner_role_required` | 403 | `permission` | Only an owner can manage owners; this key's creator is an admin. |
| `last_owner` | 400 | `invalid_request` | Assign another active owner before removing or changing the last owner. |
| `cannot_remove_self` | 400 | `invalid_request` | The key's creator can't remove themselves. |
| `seat_limit_reached` | 403 | `permission` | All seats are in use. Add a seat or remove someone first. |
| `already_member` | 409 | `conflict` | This person is already a member of this business. |
| `invitation_exists` | 409 | `conflict` | An invitation for this email is already pending. Resend it instead. |
| `invitation_not_pending` | 409 | `conflict` | This invitation was accepted or revoked. |
| `invitation_superseded` | 409 | `conflict` | This person already has access or a newer invitation. |
| `resend_too_soon` | 429 | `rate_limit` | Wait a minute before resending this invitation. |
| `member_inactive` | 409 | `conflict` | This member's account is turned off. |
| `password_reset_recently_sent` | 429 | `rate_limit` | A reset email was sent less than a minute ago. |
| `webhooks_not_allowed` | 403 | `permission` | Webhooks aren't included for this business. |
| `webhook_limit_reached` | 409 | `conflict` | A business can have 10 webhooks. |
| `invalid_webhook_url` | 422 | `invalid_request` | Use a public https:// address. |
| `unknown_event` | 422 | `invalid_request` | Choose events from the list of webhook events. |
| `unknown_calendar` | 422 | `invalid_request` | Choose calendars from this business. |
| `ping_not_retryable` | 409 | `conflict` | Test deliveries aren't retried; send a new test instead. |
| `assistant_release_not_ready` | 409 | `conflict` | Resolve the assistant's required setup items before publishing; `message` lists them in Studio. |
| `brand_identity_unapproved` | 409 | `conflict` | Approve the business profile in Studio before publishing. |
| `main_flow_missing` | 409 | `conflict` | Create a conversation flow before publishing this assistant. |
| `critical_action_tests_required` | 409 | `conflict` | Run passing tests for each action in the flow editor before publishing. |
| `flow_validation_failed` | 409 | `conflict` | Fix the assistant's conversation flow in the flow editor before publishing. |
| `flow_draft_conflict` | 409 | `conflict` | The flow changed while publishing. Try again. |
| `assistant_not_resumable` | 409 | `conflict` | Publish the current setup and resolve required items before resuming. |
| `test_key_required` | 409 | `conflict` | Test messages run in the sandbox: send them with a test key (ord_test_…). |
| `daily_limit_reached` | 429 | `rate_limit` | The plan's daily limit for test messages is used up. Try again tomorrow. |
| `reply_not_sent` | 409 | `conflict` | The channel didn't accept the reply; `message` says why (for example, its reply window closed). |
| `assignee_not_available` | 409 | `conflict` | This member can't take the conversation now; `message` says why. |
| `api_error` | 500 | `api_error` | Something went wrong on our side. Try again; if it keeps happening, contact support with the request id. |

### Booking errors

When the booking engine refuses a request (status `422`, code `booking_rejected` in the reference), `code` names
the exact reason, for example `invalid_time`, `need_resource`, `need_dates` or `party_too_large`, and `message`
explains it in words you can show to your team.

### Bookings endpoints: the `detail` field

The bookings endpoints existed before this envelope. Their errors also keep the `detail` field they always had, so
older integrations keep working. New code should read `error`.
