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

# Errors & rate limits

> HTTP status codes, error response shape, business rejections, and rate-limit headers

OCX uses standard HTTP status codes with a consistent JSON error body. Always
branch on the **status code** first, then read `error`/`message` for detail.

## Error shape

```json theme={null}
{
  "error": "INSUFFICIENT_MARGIN",
  "message": "Order requires 512.00 USDC margin; 210.00 available"
}
```

`error` is a stable, machine-readable code you can switch on; `message` is a
human-readable explanation that may include specifics (amounts, the bucket to
transfer from). New codes may be added over time — treat an unrecognized code as
a generic failure of its status class.

## Status codes

| Status        | Meaning                                                                    | What to do                                          |
| ------------- | -------------------------------------------------------------------------- | --------------------------------------------------- |
| `200` / `201` | Success                                                                    | Read the response body.                             |
| `400`         | Bad request — malformed body, invalid enum, or insufficient bucket balance | Fix the request; don't retry unchanged.             |
| `401`         | Not authenticated — missing/expired session or key                         | Re-run the SIWE flow or check your `x-api-key`.     |
| `403`         | Forbidden — wrong scope, IP not allowed, or action not permitted           | Use a `trade`-scoped key; check IP allowlist.       |
| `404`         | Unknown resource — market, order, or record not found                      | Verify the id.                                      |
| `409`         | Conflict — duplicate `clientOrderId` or conflicting state                  | Treat as already-applied; reconcile.                |
| `422`         | Business rejection — the request was valid but the engine declined it      | Handle by code (see below); do not blindly retry.   |
| `429`         | Rate limited                                                               | Back off until the window resets.                   |
| `5xx`         | Server or upstream error                                                   | Retry with backoff; if persistent, contact support. |

## Common business rejections

These come back on trading and wallet endpoints. They are expected outcomes, not
bugs — handle each deliberately.

<AccordionGroup>
  <Accordion title="INSUFFICIENT_MARGIN">
    The order would breach your available margin. Every order passes a
    server-side margin gate before acceptance. Read your headroom from
    `GET /perps/positions` and `GET /perps/balances`, reduce size, or fund the
    venue — do not retry the same order.
  </Accordion>

  <Accordion title="Insufficient {bucket} balance">
    A transfer or withdrawal exceeds the source bucket's available balance.
    Withdrawals draw only from `wallet`; transfer back from `perps`/`options`/`spot`
    first. Returned as `400`.
  </Accordion>

  <Accordion title="POST_ONLY_WOULD_CROSS">
    A `postOnly` order would have taken liquidity, so it was rejected to keep you
    on the maker side. Re-price behind the touch and resubmit.
  </Accordion>

  <Accordion title="REDUCE_ONLY_REJECTED">
    A `reduceOnly` order would have increased your position (e.g. wrong side or
    already flat). Check your current position before retrying.
  </Accordion>

  <Accordion title="Strategy not fully filled">
    A multi-leg combo could not fill every leg immediately. Combos are
    all-or-none (FOK) — the whole strategy is rejected and nothing executes.
    Re-price the legs and resubmit.
  </Accordion>

  <Accordion title="Order would not fill (IOC / FOK)">
    An `ioc` order found no liquidity to take, or a `fok` order could not be
    filled in full. Nothing rests; adjust price or time-in-force.
  </Accordion>
</AccordionGroup>

<Note>
  A `422` business rejection is not a transport failure. Retrying it unchanged
  will fail the same way and can burn your rate budget — fix the cause first.
</Note>

## Rate limits

Requests are rate-limited per client. Authenticated sessions and API keys get a
higher budget than anonymous traffic. Every response carries the current window
state so you can pace yourself.

| Header                  | Meaning                                |
| ----------------------- | -------------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed in the current window |
| `X-RateLimit-Remaining` | Requests left in the window            |
| `X-RateLimit-Reset`     | When the window resets                 |

When you exceed the budget you receive `429 Too Many Requests`. Back off until
`X-RateLimit-Reset`, ideally with exponential backoff and jitter.

<Warning>
  Do not tight-poll market data. Prefer the [streaming](/api-reference/websockets)
  endpoints for live books, marks, fills, and balances — one long-lived SSE
  connection replaces thousands of polling requests and keeps you well under any
  limit.
</Warning>

### Handling 429 in practice

<CodeGroup>
  ```js JavaScript theme={null}
  async function call(url, opts, attempt = 0) {
    const res = await fetch(url, opts);
    if (res.status === 429 && attempt < 5) {
      const reset = Number(res.headers.get("X-RateLimit-Reset")) || 0;
      const waitMs = Math.max(reset - Date.now(), 250 * 2 ** attempt);
      await new Promise(r => setTimeout(r, waitMs));
      return call(url, opts, attempt + 1);
    }
    return res;
  }
  ```

  ```bash cURL theme={null}
  # --retry with backoff on transient failures and 429s
  curl -s --retry 5 --retry-delay 1 --retry-all-errors \
    "https://{API_BASE}/perps/markets"
  ```
</CodeGroup>

## Idempotency

To make retries safe, send a `clientOrderId` on order and quote endpoints. If a
response is lost and you retry, the same `clientOrderId` prevents a duplicate
order — reconcile by reading the resulting order rather than assuming it failed.
Quoting endpoints also accept a `quoteId` to group and atomically replace a
market-maker's quote set.
