# Errors and recovery

> Parse stable error codes, follow recovery instructions, and reconcile writes safely.

The catalogue and trading APIs use one failure contract. Handle failures by
`code`; use the HTTP status only as a broad category. Never branch on `title`,
`detail`, or a validation message because those strings are written for people
and can change without changing the condition.

## HTTP Problem Details [#http-problem-details]

An error returned by the API origin uses `application/problem+json`:

```http
HTTP/2 422 Unprocessable Entity
Content-Type: application/problem+json
X-Request-ID: 8051f907-4eeb-4ca9-bd84-b447ea65a68c
```

```json
{
  "type": "urn:agara:problem:insufficient-balance",
  "title": "Insufficient balance",
  "status": 422,
  "code": "insufficient_balance",
  "detail": "Available balance is lower than the order value.",
  "request_id": "8051f907-4eeb-4ca9-bd84-b447ea65a68c",
  "recovery": {
    "strategy": "none"
  }
}
```

| Field          | How to use it                                                  |
| -------------- | -------------------------------------------------------------- |
| `type`         | Stable URI identifying the condition. Log it; switch on `code` |
| `title`        | Short display text. Do not parse it                            |
| `status`       | HTTP status repeated in the body                               |
| `code`         | Stable machine-readable condition                              |
| `detail`       | Optional safe display text. Do not parse it                    |
| `request_id`   | Correlation ID. Log it and include it when contacting support  |
| `recovery`     | Structured instruction for the next client action              |
| `field_errors` | Optional safe validation errors for individual request fields  |

Clients should accept `field_errors` when present, but an endpoint is not
required to provide it for every validation failure.

The response body and `X-Request-ID` header carry the same server-generated
UUID. A request ID supplied by a client is not echoed back as the server ID.

## Fix invalid requests [#fix-invalid-requests]

Invalid input is split into distinct conditions:

| Status and code              | Meaning                                              |
| ---------------------------- | ---------------------------------------------------- |
| `400 malformed_json`         | The body is not valid JSON                           |
| `400 invalid_path_parameter` | A value embedded in the URL is malformed             |
| `413 request_body_too_large` | The body exceeds the endpoint limit                  |
| `415 unsupported_media_type` | A JSON endpoint did not receive `application/json`   |
| `422 validation_failed`      | JSON or query parameters are well formed but invalid |

An order that is well formed but outside a published bound is a separate case, and
each bound answers with its own code rather than `validation_failed`. See
[order size limits](/docs/orders#order-size-limits) for the values.

| Status and code                 | Meaning                                               |
| ------------------------------- | ----------------------------------------------------- |
| `422 order_value_below_minimum` | The order notional is under the minimum               |
| `422 order_value_above_maximum` | The order notional is over the maximum                |
| `422 order_size_below_minimum`  | The market sell is under the share minimum            |
| `422 order_size_above_maximum`  | The market sell is over the share maximum             |
| `422 order_price_out_of_range`  | The limit price is not inside the open unit interval  |
| `422 order_expiration_too_soon` | The GTD expiration is nearer than the minimum horizon |

A withdrawal to a wallet that cannot receive the destination token yet also has
its own code. Nothing moves; change the recipient or prepare the wallet, then
request a new quote.

| Status and code                       | Meaning                                                                                                           |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `422 recipient_token_account_missing` | The recipient wallet has no account for the destination token, for example a Solana wallet without a USDC account |

A validation response can identify safe fields without repeating submitted
values:

```json
{
  "type": "urn:agara:problem:validation-failed",
  "title": "Request validation failed",
  "status": 422,
  "code": "validation_failed",
  "detail": "One or more fields are invalid.",
  "request_id": "8051f907-4eeb-4ca9-bd84-b447ea65a68c",
  "recovery": {
    "strategy": "none"
  },
  "field_errors": [
    {
      "path": ["orders", 1, "price_micro"],
      "code": "invalid_value",
      "message": "Invalid request value."
    }
  ]
}
```

Fix every indicated field and submit a new request. Do not send the same body
again unchanged.

## Follow `recovery.strategy` [#follow-recoverystrategy]

| Strategy                 | What your client should do                                                     |
| ------------------------ | ------------------------------------------------------------------------------ |
| `none`                   | Do not automatically repeat the request. Change input or reconcile state first |
| `retry`                  | Retry with exponential backoff. Reconcile a write before repeating it          |
| `retry_after`            | Wait `after_seconds` and the full `Retry-After` interval, then add jitter      |
| `refresh_identity_token` | Refresh the browser identity token, then retry once                            |
| `check_status`           | Read the supplied `resource`; do not submit another mutation first             |

Validate `code` before following any recovery instruction. If the code is
unknown, do not honor an automatic action even when `strategy` is a familiar
value such as `retry` or `refresh_identity_token`. Log the response and
`request_id`, pause the affected mutation, reconcile its state, and update your
client before continuing.

Treat an unknown strategy as `none` until your client understands it. This
keeps a newly introduced recovery instruction from activating an unsafe retry.

`check_status` names the durable resource to read. For example:

```json
{
  "code": "dependency_outcome_unknown",
  "recovery": {
    "strategy": "check_status",
    "resource": {
      "kind": "batch",
      "batch_hash": "0x51cda67500000000000000000000000000000000000000000000000000000000"
    }
  }
}
```

Read `GET /trade/v1/batches/{batch_hash}` until you know the existing
submission's outcome. Creating another batch can duplicate or conflict with
work that was already accepted.

## Retry writes safely [#retry-writes-safely]

An HTTP `429` means the request was rejected before the operation ran. Wait for
`Retry-After`, then resending the same request is safe.

A timeout, TLS reset, or lost response is different: the server may already
have accepted the write.

| Write              | Safe recovery after an uncertain response                                                                                                                                                                          |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Ordinary order     | Pause, reconcile recent and open orders, and cancel plausible matches. Cancel-all does not fence a placement still completing; repeat it if a late order appears and remain paused while the outcome is unprovable |
| Pre-signed order   | Read `GET /trade/v1/orders/by-hash/{order_hash}`; the signed hash is its identity                                                                                                                                  |
| Signed-order batch | Read every `order_hash`; resubmit only envelopes that are still absent                                                                                                                                             |
| Account batch      | Read the hash computed before submission, or the `batch_hash` named by `recovery.resource`                                                                                                                         |

One clean order read is not proof that an ordinary timed-out placement was
rejected; it can appear later. Follow the
[uncertain-submission recovery guide](/docs/api-traders/recovery#no-placement-response)
before resuming or placing a replacement.

Do not blanket-retry every `5xx`. `502 dependency_invalid_response`, for
example, has `recovery.strategy: "none"`; the service returned data that could
not safely be used. Follow the response's recovery value instead of deriving a
retry policy from the status alone.

## Accepted work can fail later [#accepted-work-can-fail-later]

HTTP Problem Details means the request itself failed. A successful `202` or
`201` can still represent asynchronous work that later reaches a terminal
failure.

Orders, account batches, and wallet setup expose that state as `failure`:

```json
{
  "status": "REJECTED",
  "failure": {
    "code": "post_only_would_cross",
    "title": "Post-only order would cross",
    "detail": "The order would execute immediately instead of resting.",
    "recovery": {
      "strategy": "none"
    }
  }
}
```

This embedded object deliberately has no HTTP status, `type`, or `request_id`:
the read request succeeded, and the object describes the earlier operation.
Use `failure.code` exactly as you use the code in an HTTP Problem Details body.

## Common trading decisions [#common-trading-decisions]

The API reference lists every status an endpoint can return, though not the
individual codes behind each one. These are the codes a trading loop most often
needs to distinguish:

| Code                         | Typical response                                                                                                                                                                                                                                                                         |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `post_only_would_cross`      | Refresh the book and calculate a new maker price                                                                                                                                                                                                                                         |
| `insufficient_balance`       | For an order, reconcile collateral and open BUY commitments; for a batch, reconcile every collateral and token asset its operations demand; then reduce size or restore coverage                                                                                                         |
| `insufficient_shares`        | Reconcile positions and open SELL commitments; reduce size                                                                                                                                                                                                                               |
| `order_not_fillable`         | Refresh depth and resize or reprice the order                                                                                                                                                                                                                                            |
| `market_closed`              | Stop quoting the market                                                                                                                                                                                                                                                                  |
| `duplicate_order`            | Read the existing signed order by `order_hash`                                                                                                                                                                                                                                           |
| `sequence_conflict`          | For an immediate refusal, re-read the wallet or batch sequence. For a terminal batch failure, inspect lower queue rows and follow [stranded-sequence recovery](/docs/api-traders/account-batches#what-you-observe-on-failure); do not sign while divergence or unwind remains unresolved |
| `batch_in_flight`            | Wait for an existing batch to finish                                                                                                                                                                                                                                                     |
| `wallet_setup_incomplete`    | Complete wallet setup before retrying                                                                                                                                                                                                                                                    |
| `rate_limited`               | Wait for `Retry-After` and add jitter                                                                                                                                                                                                                                                    |
| `dependency_unavailable`     | Back off; reconcile a write before resubmitting                                                                                                                                                                                                                                          |
| `account_operations_paused`  | Retry in a few minutes: splits, merges, withdrawals and account setup are paused for maintenance. Anything already accepted still completes                                                                                                                                              |
| `dependency_outcome_unknown` | Follow the supplied `check_status` resource                                                                                                                                                                                                                                              |
| `internal_error`             | Stop automatic retries, retain `request_id`, and reconcile state                                                                                                                                                                                                                         |

For the complete API trading recovery flow, see
[Recovery](/docs/api-traders/recovery).

## Access and credentials [#access-and-credentials]

[Authentication](/docs/authentication) covers `forbidden_scope` and
`invalid_personal_access_token`. These are the rest:

| Status and code              | Typical response                                                                                                                        |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `401 credentials_missing`    | Send an access token; this endpoint requires one                                                                                        |
| `401 wrong_credential_type`  | Send the credential this endpoint accepts. A browser identity token and a personal access token are not interchangeable                 |
| `401 identity_token_expired` | Refresh the browser identity token once, then repeat the request. This is the only code that asks for `refresh_identity_token`          |
| `401 invalid_identity_token` | Sign in again. Refreshing will not repair a token the server rejected outright                                                          |
| `403 account_frozen`         | Stop submitting. Trading and account batches are disabled for the account, and repeating the request will not clear it; contact support |

## Endpoints that may not be available [#endpoints-that-may-not-be-available]

Not every endpoint exists in every deployment, and a few have been withdrawn.

| Status and code                    | Typical response                                                                                                                                                                                                                                     |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `404 route_not_found`              | Check the path, then treat the endpoint as unavailable here. Features are switched on per deployment, and a reference page can describe an endpoint your environment does not serve                                                                  |
| `405 method_not_allowed`           | Use the method the endpoint documents; the path itself exists                                                                                                                                                                                        |
| `410 endpoint_retired`             | Stop calling it. For merge-all, [merge a single market](/docs/on-chain/positions#merge) instead. The redeem endpoints have no replacement: resolved positions in supported markets settle through [automatic redemption](/docs/automatic-redemption) |
| `503 price_service_not_configured` | Treat price lookups as unavailable in this deployment                                                                                                                                                                                                |
| `503 price_stream_not_configured`  | Treat price streaming as unavailable in this deployment                                                                                                                                                                                              |

`503 feature_not_configured` says the same about a whole feature, such as
[account batches](/docs/api-traders/account-batches). Treat all of these as
"not offered here" rather than as something to retry.

## Reads that cannot be answered right now [#reads-that-cannot-be-answered-right-now]

A `425` means the request was understood but the answer does not exist yet. Wait
and repeat it; the request itself is fine.

| Status and code     | Typical response                                                                 |
| ------------------- | -------------------------------------------------------------------------------- |
| `425 pnl_not_ready` | Retry after a short wait. The profit-and-loss figures have not been computed yet |

A `503` on these reads is a refusal, not a delay. Follow the `recovery` value
rather than assuming every `503` is transient.

| Status and code                      | Typical response                                                                                                          |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `503 price_temporarily_unavailable`  | Back off, then retry                                                                                                      |
| `503 price_stream_capacity_exceeded` | Back off, then retry. The symbol budget is shared across everyone streaming, so closing your own stream may not free it   |
| `503 pnl_unavailable`                | Stop retrying. Profit-and-loss reporting is unavailable and will not recover on repeat; retain `request_id` and reconcile |

## Resources that do not exist [#resources-that-do-not-exist]

A `404` ending in `_not_found` means the thing you named is not there, or is not
visible to you: `batch_not_found`, `category_not_found`, `event_not_found`,
`group_not_found`, `market_not_found`, `order_not_found`, `profile_not_found`,
`security_not_found`, `token_not_found`, `trading_day_not_found`, and
`venue_not_found`. Check the identifier before repeating the request.

`422 price_feed_not_found` is not one of these. The identifier can be perfectly
valid; this deployment simply has no price source for it, so repeating the
request will not help.

## Origin and edge rate limits [#origin-and-edge-rate-limits]

Most failures come from the API origin and therefore include
`application/problem+json`, `X-Request-ID`, and `request_id` in the body.

An edge rate limit happens before the request reaches the API. It uses
`application/json`, carries the same stable `rate_limited` code and recovery,
but has no request ID or account-bucket headers:

```json
{
  "type": "urn:agara:problem:rate-limited",
  "title": "Too many requests",
  "status": 429,
  "code": "rate_limited",
  "recovery": {
    "strategy": "retry_after",
    "after_seconds": 60
  }
}
```

See [Rate limits](/docs/rate-limits) for budgets and header examples.

## WebSocket failures [#websocket-failures]

WebSocket error frames use the same embedded `failure` object, plus an
`action` that says whether to keep the connection, resubscribe one subject, or
reconnect the whole socket. See [Market stream errors](/docs/streaming#error)
for the frame and close-code contract.
