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

# Errors

> One envelope, stable codes, and what to do about each.

Every error has the same shape:

```json theme={null}
{
  "error": {
    "code": "insufficient_balance",
    "num": 257,
    "origin": "core",
    "message": "…",
    "details": null
  }
}
```

**Branch on `code`, never on `message`.** Codes are stable; messages are not.

## `origin` tells you whether it happened

This is the field worth understanding, because it answers "did my order exist?"

| `origin`  | Meaning                                                                                       |
| --------- | --------------------------------------------------------------------------------------------- |
| `gateway` | Rejected at the edge, before sequencing. Never happened.                                      |
| `port`    | Rejected at the order-entry edge. Never happened — no record exists.                          |
| `core`    | The exchange sequenced your request and rejected it. It happened, and it is in the audit log. |

For a retry decision: `gateway` and `port` rejects are safe to fix and resend.
A `core` reject is a real economic answer — resending unchanged gets the same
result.

## Common codes

### Authentication

| Code            | HTTP | What to do                                                                                                                                          |
| --------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unauthorized`  | 401  | Bad signature, unknown key, or clock skew — all identical by design. Work through the [checklist](/quickstart/authentication#why-a-request-failed). |
| `missing_scope` | 403  | Your key lacks the scope. On `/v1/withdrawals` this is expected — bot keys cannot withdraw.                                                         |
| `rate_limited`  | 429  | Back off by `details.retry_after_ms`.                                                                                                               |

### Your request was malformed

| Code               | HTTP | What to do                                                   |
| ------------------ | ---- | ------------------------------------------------------------ |
| `unknown_field`    | 400  | An unrecognised field. Schemas are closed — check for typos. |
| `schema_violation` | 400  | Missing or wrong-typed field.                                |
| `malformed_json`   | 400  | Body did not parse.                                          |
| `bad_price_tick`   | 400  | `tick` must be an integer 1–99.                              |
| `bad_qty`          | 400  | Quantity must be a positive whole number.                    |

### The exchange said no

| Code                                                                  | HTTP | What to do                                                                                        |
| --------------------------------------------------------------------- | ---- | ------------------------------------------------------------------------------------------------- |
| `insufficient_balance`                                                | 422  | Not enough available balance.                                                                     |
| `sell_exceeds_position`                                               | 422  | Selling more than you hold.                                                                       |
| `max_cost_exceeded`                                                   | 422  | A market buy would exceed `max_cost`.                                                             |
| `duplicate_client_order_id`                                           | 409  | Already used. If this follows a timeout, the original order exists — do not resend with a new id. |
| `unknown_order`                                                       | 404  | Already filled, cancelled, or never existed.                                                      |
| `market_not_found`                                                    | 404  | Wrong `market_id` — it is recycled, so re-read the round.                                         |
| `market_frozen`, `market_closed`, `trading_paused`, `exchange_paused` | 409  | The round stopped accepting orders. Move to the next round.                                       |
| `user_suspended`                                                      | 403  | Risk action against the account. Not retryable.                                                   |
| `open_order_cap`, `per_market_limit`                                  | 403  | You hit a position or open-order limit.                                                           |

## Timeouts are not rejections

| Code                 | HTTP | What to do                                                                                                                |
| -------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------- |
| `sequencing_timeout` | 504  | **Your order may still exist.** Retry with the *same* `client_order_id`, or `GET /v1/portfolio/orders?client_order_id=…`. |
| `shed`               | 503  | The write path is saturated. Back off and retry.                                                                          |

<Warning>
  A `504` is the one case where "it failed" is the wrong assumption. The request
  timed out waiting for sequencing; it may well have been sequenced. Always
  resolve it with the same `client_order_id` — that is what the idempotency key is
  for. Retrying with a fresh id is how you end up with two orders.
</Warning>

## Cancellations are not errors

Post-only orders that would cross, unfillable FOKs, IOC remainders and
self-trade prevention all come back as a **successful** `201` with
`status: "canceled"` and a `reason`. They are execution outcomes, not failures,
so check `status` on success responses rather than assuming `201` means resting.
