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

> ## Agent Instructions
> To integrate AGG, start with Quickstart: REST (https://docs.agg.market/quickstart/rest), then Order lifecycle & statuses (https://docs.agg.market/concepts/order-lifecycle).
> Track every trade until it reaches a terminal status. Before retrying a failed or timed-out call, read Errors, retries & idempotency (https://docs.agg.market/concepts/errors).
> The API reference is generated from https://docs.agg.market/openapi/openapi.json.

# Errors, retries & idempotency

> Error shapes, HTTP statuses, quote and fill error codes, and which calls are safe to retry

## Error shape

Errors return JSON with a readable `message`. Quote and trading errors can add a stable `code`:

```json theme={null}
{ "message": "Quote has expired", "code": "quote_not_found" }
```

Branch on `code` when it is present, and always show or log `message`. Some errors carry only a
`message`. A few `400` messages start with a stable token such as
`app_fee_override_requires_api_key:`; match on that prefix.

## HTTP statuses

| Status | Meaning | Retry? |
| - | - | - |
| `400` | Invalid request, or a quote or fill refusal. Read `code`. | Depends on `code`, see below. Never retry unchanged otherwise. |
| `401` | Missing or invalid `x-app-id`, API key, or access token. | Refresh the access token once, then retry. See [Authentication & sessions](/concepts/authentication). |
| `403` | Not allowed: origin not in allowed origins, wrong-app API key, testing-mode cap reached, or trading not available in the user's region. | No. Fix the cause. |
| `404` | Not found, or not visible to this user or app. | No. |
| `409` | Conflict: a repeated idempotency key, an order that can no longer be cancelled, or a changed withdrawal `requestId` body. | No. Read the existing resource instead. |
| `429` | Rate limited. | Yes, after the `Retry-After` header (seconds). |
| `503` | A dependency is temporarily unavailable. | Yes, with backoff. |
| `5xx` | Server error. | Yes, with backoff, but only for calls that are safe to retry (below). |

## Quote and fill codes

These appear on `400` responses from `GET /orderbook/{venueMarketOutcomeId}/route`,
`POST /execution/fill`, and `POST /execution/orders`.

| `code` | Meaning | What to do |
| - | - | - |
| `quote_not_found` | The quote is not in the cache, usually because it expired. | Re-quote and fill the new quote. Or send `fallbackToLatest` on the fill. |
| `quote_expired` | The quote expired, or an earlier fill of this `quoteId` is still running or failed partway. | Check `GET /execution/status?quoteId=` first. If there are no orders, re-quote. |
| `quote_already_executed` | This `quoteId` was already filled. | Do not re-fill. Poll status for it. |
| `quote_cancelled` | The quote was cancelled. | Re-quote. |
| `quote_user_mismatch` | The quote belongs to another user, or is a preview quote with no user. | Re-quote with the user's access token. |
| `quote_app_blocked` | The route uses a venue or category your app has disabled, or a venue that is no longer available. | Re-quote. The new route avoids it. |
| `quote_unfillable` | The quote cannot be executed as priced, or the fill does not match it (for example mode or signing wallet). | Re-quote. Read `message` for the reason. |
| `quote_min_order_size` | The amount is below a venue minimum. `message` names it. | Re-quote with a larger amount. |
| `quote_insufficient_balance` | The user's balance no longer covers the trade. | Refresh balances and re-quote a smaller amount, or fund the account. |
| `quote_market_inactive` | The market stopped accepting orders. | Re-quote, or pick another market. |
| `quote_stale_status`, `quote_stale_price` | Defined in the schema for stale quotes. | Re-quote. |
| `quote_self_custody_unsupported_venue` | Self-custody fill routes to a venue without self-custody support. | Re-quote without `signingAddress`, or restrict `allowedVenues`. |
| `quote_self_custody_app_fee` | Self-custody fill carries a fee that cannot be collected from this wallet. | See [Self-custody trading](/recipes/self-custody#not-supported-with-the-exact-code). |
| `quote_self_custody_redeem_unsupported` | Self-custody sell that settles by redeeming. | Re-quote without `signingAddress`. |
| `venue_not_executable` | Direct order on a venue that does not support execution. | Terminal for that venue. Pick another. |
| `outcome_ambiguous` | Direct order: several outcomes on the venue match and none is the one you named. `message` lists them. | Send the outcome id that lives on that venue. |

A rejected fill is remembered for the rest of the quote's life. Sending the same `quoteId` again
returns the same error. Get a new quote.

## Idempotency and safe retries

| Call | Key | On repeat | After a timeout |
| - | - | - | - |
| `POST /execution/orders` | `externalId` (required, max 36 chars, unique within your app) | `409`, no second trade | `GET /execution/orders?externalId=` to find the order |
| `POST /execution/limit-orders` | `clientOrderId` (optional, unique per user) | `409`, no second order | `GET /execution/orders` and match `clientOrderId` |
| `POST /execution/fill` | The `quoteId` | `400` with `quote_already_executed` or `quote_expired`; never a second trade | Resend with the same `quoteId`, or poll `GET /execution/status?quoteId=`. Orders there mean the fill was accepted. `404` is not a final answer; see below. |
| `POST /execution/withdraw` | `requestId` (optional UUID, exact-amount only) | Same body returns the original withdrawal; a changed body returns `409` | Retry with the same `requestId` and body |
| `POST /execution/fill/{quoteId}/signatures` | The `stepId` | A step that already succeeded is ignored | Resubmit the batch |

`POST /execution/fill` has no separate idempotency key: one `quoteId` can create at most one set of
orders, so resending the same `quoteId` is always safe. Never get a new quote to retry a fill whose
result you do not know, or you can trade twice.

After a timeout on `POST /execution/fill`:

1. Keep the same `quoteId`. Poll `GET /execution/status?quoteId=`, or resend the fill with that
   `quoteId`.
2. Orders in the status response mean the fill was accepted. Track it to a terminal status.
3. A `404` from status means no orders are recorded yet. The original request may still be
   running, so treat the result as unknown and keep polling.
4. Treat the fill as not executed only when the quote's lifetime has passed, a resend with the same
   `quoteId` returns `quote_expired`, and status for that `quoteId` still returns `404`. Only then
   get a new quote.

Generate one `externalId` per intended trade, store it before you send the request, and reuse it
across retries. A direct order the venue refuses after acceptance still uses up its `externalId`.

Reads (`GET`) are always safe to retry.

## Rate limits

A `429` carries `Retry-After` in seconds. Wait that long before retrying. Server calls with a
validated `x-app-api-key` get their own budget. See
[Base URL, app IDs & API keys](/environments#rate-limits).
