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

# Errors

> The closed error-code vocabulary, and what to retry

Every failure returns the same JSON shape:

```json theme={null}
{
  "code": "insufficient_balance",
  "error": "insufficient balance",
  "details": "optional, usually absent"
}
```

<Warning>
  **Branch on `code`, never on `error`.**

  `code` is a closed vocabulary and part of the contract. `error` is free text for
  a human reading a log — it may be reworded at any time, without notice and
  without a version bump. Any client that pattern-matches the message will break
  on a copy edit.
</Warning>

If you receive a `code` you do not recognise, treat it as a generic failure of its
HTTP status class. New codes get added; that is why matching on the class is safe
and matching on the exact string is not.

## Codes by status

### 400 — the request is malformed or self-contradictory

Never retry unchanged. The message names what would have worked.

| Code | Condition |
| - | - |
| `invalid_request_body` | The JSON body is missing, unparseable, or a field has the wrong type. |
| `invalid_parameter` | A **query string** parameter is malformed or out of range — an unparseable timestamp, an inverted `from`/`to` window, a search term too short to be selective. |
| `invalid_market_identifier` | The market ticker or id is not a well-formed identifier. |
| `invalid_order_id` | The order id is not well-formed. |
| `invalid_price` | Price must be a whole number of cents from 1 to 99. |
| `invalid_qty` | Quantity must be a whole number greater than zero, and below the per-order ceiling. |
| `invalid_budget` | A budget order's shape is wrong: `budget_cents` alongside `qty` (send one denomination), a non-`buy` side, a `gtc` time-in-force, or a budget that is non-positive or above the per-order notional ceiling. The message names the specific fix. |
| `invalid_enum_value` | A string enum got a value outside its set. The message lists every accepted value — read it rather than guessing. |
| `invalid_cursor` | The `cursor` was not one this API issued. Cursors are opaque; do not construct or edit them. |
| `conflicting_identifiers` | You supplied two ways of naming the same thing — a ticker *and* a `market_id`, or two idempotency keys. Send one. |
| `conflicting_credentials` | The request carried both `X-API-KEY` and `Authorization`. Send one credential. |
| `missing_idempotency_key` | A write needs a `client_order_id`. |

<Note>
  `invalid_parameter` is the **query string**; `invalid_request_body` is the
  **JSON body**. The distinction is deliberate: being told your body is bad when
  the fault is in the query sends you looking in the wrong place.
</Note>

### 401 — we do not know who you are

| Code | Condition |
| - | - |
| `unauthenticated` | No credential, or the key or bearer token is unknown, revoked, or expired. |

Retrying with the same credential will not help. A bearer failure also carries a
`WWW-Authenticate` challenge; an API-key failure does not. See
[Authentication](/api-reference/authentication).

### 403 — we know who you are, and you may not do this

| Code | Condition |
| - | - |
| `forbidden_scope` | The credential authenticated but lacks the scope this route requires. **Scopes do not nest** — reissue the key with the scope you need. On a bearer token, the `WWW-Authenticate` challenge names the missing scope. |
| `api_key_misconfigured` | The key's stored user reference is unusable. A client cannot fix this; contact support. |

### 404 — not there, or not yours

| Code | Condition |
| - | - |
| `market_not_found` | No market, event, or series with that ticker or id. |
| `order_not_found` | No such order **or the order is not yours**. |

<Note>
  `order_not_found` deliberately covers "not yours" as well as "does not exist."
  Distinguishing them would let anyone probe which order ids exist on the venue.
</Note>

### 409 — the venue is not accepting this right now

| Code | Condition |
| - | - |
| `venue_halted` | Venue-wide stop. Nothing is accepted, cancels included. |
| `venue_cancel_only` | Venue-wide stop with an exit: cancels work, new orders do not. |

A venue-wide stop is `409`. A **single market** being halted is `422 market_halted`.
Two different statuses because the recoveries differ — one market halting means
trade elsewhere; the venue halting means stop.

### 422 — well-formed, but a pre-trade check rejected it

Retrying the identical request will fail identically.

| Code | Condition |
| - | - |
| `insufficient_balance` | The reserve this order requires exceeds your available cash. |
| `insufficient_shares` | You tried to sell more than you hold. See below. |
| `market_halted` | This market is halted. |
| `market_resolved` | This market has resolved; it will never accept another order. |
| `duplicate_order_id` | An order derived from this `client_order_id` is already live. |
| `order_not_cancelable` | The order is already filled, canceled, or expired. |
| `budget_not_fillable` | A `fok` budget order: the book cannot absorb your budget within your price bound. Nothing traded. Lower the budget, raise the price bound, or use `ioc` to take what is there. |
| `no_executable_liquidity` | A budget order found nothing resting within your price bound. Nothing traded. |
| `invalid_request` | A rejection with no more specific code. The message carries the detail. |

### 429 — slow down. Nothing was sequenced

| Code | Condition |
| - | - |
| `rate_limited` | Your key's token bucket is empty. |
| `engine_overloaded` | The request was refused **before** being sequenced. |

Both are safe to retry: nothing reached the matching engine, so no order exists.
Back off, then retry.

### 500 / 503 — our side

| Code | Status | Condition | Retry? |
| - | - | - | - |
| `internal_error` | 500 | A failure on our side. The underlying detail is logged, never returned. | Safe for reads. |
| `engine_unavailable` | 503 | The command could not be submitted. **Nothing was sequenced.** | Yes, safe. |
| `result_unknown` | 503 | The command **was** submitted and the outcome is unknown. | **No — read the next section.** |

`engine_unavailable` and `result_unknown` are both `503` and they need opposite
handling. Branch on the code, not the status.

## result\_unknown

This is the one that punishes a naive client.

`result_unknown` means the command was submitted to the matching engine but no
reply arrived before the deadline. **It may have committed.** Your order may be
resting, may be filled, or may never have existed.

It is not a success and it is **not a failure**. Two things you must not do:

* **Do not retry with a new `client_order_id`.** If the first command committed,
  you now hold twice the position you intended. This is the single most expensive
  mistake a trading bot can make against this API.
* **Do not treat it as a rejection.** Your position tracking will drift from
  reality and stay drifted.

Two safe recoveries. Either works; pick one and be consistent.

**Poll the order.** You know the order id from the response body when one was
returned, or from a listing:

```bash theme={null}
curl -s https://api.trysoar.com/v1/orders/{order_id} \
  -H "X-API-KEY: $SOAR_API_KEY"
```

**Or re-send the identical request** — same `client_order_id`, same everything:

```bash theme={null}
curl -s -X POST https://api.trysoar.com/v1/orders \
  -H "X-API-KEY: $SOAR_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "ticker": "ANTHROPIC-VALUATION-26Q3-1.05T",
    "outcome": "yes",
    "side": "buy",
    "price": 42,
    "qty": 10,
    "time_in_force": "gtc",
    "client_order_id": "the-same-one-you-sent-before"
  }'
```

The engine deduplicates on the identity derived from your `client_order_id`, so a
re-send that arrives after the first command committed returns the *existing*
order instead of creating a second one. **This is what makes the retry safe, and it
only works if the id is identical.**

## insufficient\_shares

Worth its own section because it surprises nearly everyone.

There are no short positions. A `sell` for shares you do not hold is
`422 insufficient_shares` — not a short, not a naked position, a rejection.

To express a bearish view you **buy the other outcome**: `buy no` rather than
`sell yes`. See [Binary markets](/concepts/binary-markets), which also explains why
those two are not the same order despite looking like they should be.

The same rule applies per rung of a survival ladder.

## Rate limits

One token bucket per credential across the whole surface: **250 requests/second
sustained, 500 burst.** A key gets its own bucket; a bearer token draws on one keyed
to the granting application and user.

| Header | When | Meaning |
| - | - | - |
| `X-RateLimit-Limit` | Every response to an authenticated request | The burst capacity. |
| `X-RateLimit-Remaining` | Every response to an authenticated request | Tokens left, after this request. |
| `Retry-After` | `429` only | Seconds to wait. |
| `X-RateLimit-Reset` | `429` only | When the bucket refills. |

A `401` carries none of them: the bucket is keyed on the resolved credential, so a
request that fails authentication is rejected before the limiter runs.

<Warning>
  Self-pace from `X-RateLimit-Remaining` instead of discovering the cap by hitting
  it. A throttled **cancel** is the dangerous one — it leaves an order resting that
  you believe you pulled, and your next decision is made against a position you do
  not actually have.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.