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

# Errors

> The ScripX API error contract: the flat opaque error body, the stable slug to branch on, the request_id correlation id on every response, every HTTP status the API returns, and how rate-limit and body-size errors behave.

Successful responses return the resource JSON directly, no envelope. Errors are equally plain: one flat, opaque body, every time. It never carries a traceback, a stack frame, an internal field name, or anything that maps out the private request model. What it always carries is a **stable slug you branch on**, a human message, and a **correlation id**.

## Error body

Every error is a flat JSON object. The minimum, always-present shape:

```json theme={"dark"}
{
  "error": "validation_error",
  "message": "the request could not be validated",
  "request_id": "b3f1c2a4e5d67890abcd1234ef567890"
}
```

| Field        |                            Present                           | Meaning                                                                           |
| ------------ | :----------------------------------------------------------: | --------------------------------------------------------------------------------- |
| `error`      |                            Always                            | A stable string slug. Branch on this, never on `message`.                         |
| `message`    |                            Always                            | Human-readable explanation. Safe and generic by design; wording may change.       |
| `request_id` | On transport errors, and always on the `X-Request-ID` header | Correlation id for one request. Quote it in support.                              |
| `code`       |       On business-rule errors from the trading surface       | A stable machine slug in the same vocabulary as `error`. When present, prefer it. |
| `detail`     |                           Sometimes                          | Structured context, redacted so no secret-shaped value can appear.                |

### Branch on the slug, not the message

Two internal layers can produce an error, and they populate the slug field differently. A **business-rule error** from a `/v1` endpoint (a bad amount, a missing scope, a firm that is not connected) sets `error` to the constant `"request_error"` and puts the discriminating slug in `code`. A **transport or framework error** (an oversized body, an unmatched route, an internal fault) puts the slug directly in `error` and has no `code`.

One line reads the right slug in both cases:

<CodeGroup>
  ```python Python theme={"dark"}
  slug = body.get("code") or body.get("error")
  ```

  ```typescript TypeScript theme={"dark"}
  const slug = body.code ?? body.error;
  ```
</CodeGroup>

Never key logic on `message`. It is generic and its wording can change without notice.

## The slug vocabulary

Every slug maps to exactly one HTTP status. Handle the status class; treat the slug set as open and default anything you do not recognise.

| HTTP | Slug                                 | When                                                               |
| :--: | ------------------------------------ | ------------------------------------------------------------------ |
|  400 | `bad_request`                        | Malformed body, unknown field values, a batch over its limit       |
|  401 | `unauthorized`                       | Missing, unknown, revoked, or wrong-environment key                |
|  403 | `forbidden`                          | Key valid but lacks the endpoint's scope, or acts outside its role |
|  404 | `not_found`                          | No such resource on your desk, or an unmatched route               |
|  409 | `conflict`                           | State does not allow the action (e.g. releasing an unfunded cross) |
|  413 | `request_too_large`                  | Request body exceeds the 1 MiB limit                               |
|  415 | `unsupported_media_type`             | Body sent without a JSON content type                              |
|  422 | `unprocessable` / `validation_error` | Well-formed but invalid values                                     |
|  429 | `rate_limited`                       | Per-minute rate or monthly quota exceeded; honour `Retry-After`    |
|  500 | `internal` / `internal_error`        | Our fault. Safe to retry with backoff; quote the `request_id`      |

<Note>
  Two slugs appear for 422 and 500 because either layer can raise them: the trading surface emits `unprocessable` / `internal`, the transport layer emits `validation_error` / `internal_error`. Reading `code ?? error` and switching on the shared HTTP status keeps a client correct regardless of which layer answered.
</Note>

## The request id is on every response

Every response, success or error, carries an `X-Request-ID` header: the id inbound from your gateway if you sent one, otherwise a fresh one ScripX generates. It stamps the matching server-side log line, so one id maps a client-observed error to its server trace.

```http theme={"dark"}
X-Request-ID: b3f1c2a4e5d67890abcd1234ef567890
```

On transport and framework errors the same id is also echoed in the body as `request_id`. Read it from the header when you want it unconditionally, log it on every non-2xx, and quote it in any support request. It identifies exactly one request and contains nothing sensitive.

## Body-size limit

A request body over **1 MiB** is rejected up front, before any parsing, with `413 request_too_large`:

```json theme={"dark"}
{
  "error": "request_too_large",
  "message": "request body exceeds 1048576 bytes",
  "request_id": "…"
}
```

The one place this bites in normal use is [`POST /v1/orders:batch`](/api-reference/trading/orders-batch): keep a batch within the 500-order cap and its body stays well under the limit.

## Rate-limit errors

A `429` carries `Retry-After: 60` plus the full `X-RateLimit-*` header set, so a correct client never guesses. The header table and back-off guidance are in [Rate limits](/rate-limits). The [public surface](/rate-limits#the-public-surface) is metered separately per IP and returns a `retry_after_s` hint in its `detail`.

## Partial success on batches

[`POST /v1/orders:batch`](/api-reference/trading/orders-batch) does not fail wholesale: valid orders submit, invalid ones return per-item `{index, ok, status, error}` results in the same `200` response. Inspect each result rather than the top-level status.

## Handling errors well

1. Read `code ?? error` for the stable slug; switch on it and the HTTP status, never on `message`.
2. Log `X-Request-ID` on every non-2xx, and quote it when you contact support.
3. Retry only `429` (after `Retry-After`) and `500` (with backoff). Treat `4xx` other than `429` as a bug to fix, not to retry.
4. Make retries safe: send an [`Idempotency-Key`](/idempotency) on every order so a retried write can never double-place.

## Related

* [Authentication](/authentication#authentication-errors), the 401/403/429 auth table.
* [Rate limits](/rate-limits), 429, quotas and the header set.
* [Idempotency](/idempotency), safe retries on writes.
* [Webhooks](/webhooks), delivery failures retry and dead-letter rather than error.
