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

# Errors

> The error envelope, status codes, retry semantics, and the codes you'll want to handle.

## Success envelope

Every successful response is wrapped in a `data` object:

```json theme={null}
{ "data": { "...": "..." } }
```

The shape inside `data` varies by endpoint (array, keyed object, single resource, or `null`) — see
[Groups & proposals → Response envelopes](/concepts/groups-and-proposals#response-envelopes).

## Error envelope

Every error returns a standard HTTP status code and a single, consistent JSON body:

```json theme={null}
{
  "statusCode": 422,
  "code": "INSUFFICIENT_FUNDS",
  "message": "Your wallet has 12.50 USDC but this order needs 25.00 USDC.",
  "retryable": false,
  "details": { "required": "25.00", "available": "12.50", "currency": "USDC" },
  "errorId": "8f3c2b1e-7a90-4c2d-9b1e-2f6a4c8d0e11"
}
```

| Field        | Always present  | Meaning                                                                                                        |
| ------------ | --------------- | -------------------------------------------------------------------------------------------------------------- |
| `statusCode` | yes             | The HTTP status (mirrors the response status line).                                                            |
| `code`       | yes             | A **stable** machine code — branch on this, never on `message`.                                                |
| `message`    | yes             | Human-readable explanation. Wording may change; don't parse it.                                                |
| `retryable`  | when applicable | `true` only when it is safe to resend the **identical** request (see below). Treat a missing value as `false`. |
| `details`    | when applicable | Machine-readable context for the code (amounts, symbol, region, …). Money values are decimal strings.          |
| `errorId`    | yes             | Unique per error; quote it in support requests.                                                                |

`message` may occasionally be an array of validation strings — write your handler to accept either a
string or a string array.

## Response headers

| Header         | When                      | Use                                                               |
| -------------- | ------------------------- | ----------------------------------------------------------------- |
| `X-Request-Id` | every response            | Correlation id. Echoes a value you send, or is generated. Log it. |
| `Retry-After`  | `429` and retryable `5xx` | Seconds to wait before retrying.                                  |

## Status codes

| Status                  | When                                                                                                | What to do                                               |
| ----------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `400 Bad Request`       | Malformed / missing / invalid input.                                                                | Fix the request. Read `code`/`details`.                  |
| `401 Unauthorized`      | Missing or invalid key.                                                                             | Check the `Authorization: Bearer murmo_...` header.      |
| `403 Forbidden`         | Not a member, not the creator, geo-restricted, or in-app-only.                                      | Don't retry as-is.                                       |
| `404 Not Found`         | Unknown resource id (proposal, position, market, group).                                            | Check the id.                                            |
| `409 Conflict`          | The resource's state forbids the action (market closed, proposal/position closed, already claimed). | Re-read state; some are retryable (see `retryable`).     |
| `422 Unprocessable`     | Input is well-formed but breaks a trading rule (insufficient funds, min size, leverage, slippage).  | Adjust the order.                                        |
| `429 Too Many Requests` | Rate limit (1,200 / 60s) exceeded.                                                                  | Back off using `Retry-After`.                            |
| `5xx`                   | Upstream rejected/unavailable or an unexpected server error.                                        | Retry with backoff if `retryable`; report if persistent. |

## Retry semantics

`retryable` means **"safe to resend the identical request — no funds moved and no order was
submitted."** It is not a synonym for "transient."

* `retryable: true` — failures where nothing stuck: `RATE_LIMITED`, `ROUTE_NOT_FOUND`,
  `NO_MARKET_PRICE`, `NO_LIQUIDITY`, `ORDER_NOT_FILLED`, `FUNDING_REFUNDED`, `REQUEST_IN_PROGRESS`,
  `SLIPPAGE_EXCEEDED`, `UPSTREAM_TIMEOUT`, `UPSTREAM_UNAVAILABLE`. Retry with exponential backoff
  (honor `Retry-After` when present).
* `retryable: false` — terminal. Fix the request; do not loop. In particular, after a write that may
  have been submitted, **reconcile via `/positions` or `/trades` rather than retrying** — a retry can
  double-submit (idempotency keys are coming in a future release).

## Codes worth handling

The full machine-readable catalog (every `code` → status and `retryable`) is published as
[`error-catalog.json`](/error-catalog.json), generated from the API's error registry. The most
common:

| `code`                                      | Status    | Meaning                                                                                                                       |
| ------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `INSUFFICIENT_FUNDS`                        | 422       | Not enough balance. `details` carries `required`/`available`.                                                                 |
| `MARKET_CLOSED`                             | 409       | Market isn't open for this action. `details.nextOpenAt` when known.                                                           |
| `MARKET_NOT_ACTIVE`                         | 400       | Prediction market isn't accepting trades (closed, resolved, or not orderbook-enabled).                                        |
| `AMOUNT_BELOW_MINIMUM`                      | 400       | Prediction buys must be at least \$4.                                                                                         |
| `MIN_ORDER_SIZE`                            | 400       | Buy is below the market's venue minimum (typically 5 contracts); the message carries the market's dollar minimum.             |
| `POSITION_BELOW_MIN`                        | 400       | Position is entirely below the venue's sell minimum; it settles automatically at resolution.                                  |
| `NO_LIQUIDITY`                              | 422       | No liquidity on the book right now. Retryable.                                                                                |
| `ORDER_NOT_FILLED`                          | 422       | The order didn't fill and funds were released. Retryable.                                                                     |
| `FUNDING_REFUNDED`                          | 422       | A prediction buy's funding failed and the money was returned. Retryable.                                                      |
| `ORDER_BELOW_MIN_SIZE`                      | 422       | Order rounds below the market minimum. Increase size.                                                                         |
| `LEVERAGE_EXCEEDS_MAX`                      | 422       | Requested leverage above the market max.                                                                                      |
| `SLIPPAGE_EXCEEDED`                         | 422       | Fill would exceed slippage tolerance. Retryable.                                                                              |
| `ROUTE_NOT_FOUND`                           | 422       | No swap route / no quote. Often transient — retry or adjust size.                                                             |
| `NOTHING_TO_SELL`                           | 422       | No position to sell/close for this proposal.                                                                                  |
| `NOT_CLAIMABLE`                             | 422       | Nothing claimable on this proposal. Note: on an `INVALID` (50/50) resolution BOTH sides are claimable at \$0.50 per contract. |
| `REQUEST_IN_PROGRESS`                       | 409       | An identical write is already in flight. Retryable after it settles.                                                          |
| `NOT_GROUP_MEMBER`                          | 403       | You're not a member of this group.                                                                                            |
| `GEO_RESTRICTED`                            | 403       | Product unavailable in your region.                                                                                           |
| `RATE_LIMITED`                              | 429       | Rate limit exceeded. Honor `Retry-After`.                                                                                     |
| `UPSTREAM_TIMEOUT` / `UPSTREAM_UNAVAILABLE` | 504 / 503 | A dependency timed out / is down. Retryable.                                                                                  |
| `VALIDATION_ERROR`                          | 400       | Invalid input. `details.fields` lists the offending fields.                                                                   |

## Handling pattern

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function call(path, init) {
    const res = await fetch(`${BASE}${path}`, {
      ...init,
      headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json", ...init?.headers },
    });
    const body = await res.json().catch(() => ({}));
    if (!res.ok) {
      const err = new Error(`${body.code ?? res.status}: ${body.message}`);
      Object.assign(err, { code: body.code, retryable: body.retryable, details: body.details });
      throw err;
    }
    return body.data;
  }
  ```

  ```python Python theme={null}
  def call(method, path, **kw):
      res = requests.request(method, f"{BASE}{path}",
          headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}, **kw)
      body = res.json() if res.content else {}
      if not res.ok:
          raise ApiError(code=body.get("code"), message=body.get("message"),
                         retryable=body.get("retryable", False), details=body.get("details"))
      return body["data"]
  ```
</CodeGroup>

<Tip>
  Branch on `code`, back off when `retryable` is `true` (honoring `Retry-After`), and treat
  `retryable: false` as terminal. For writes, reconcile via `/positions` or `/trades` instead of
  blind retries.
</Tip>
