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
An error returned by the API origin uses application/problem+json:
HTTP/2 422 Unprocessable Entity
Content-Type: application/problem+json
X-Request-ID: 8051f907-4eeb-4ca9-bd84-b447ea65a68c{
"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
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 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:
{
"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
| 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:
{
"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
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 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
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:
{
"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
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; 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.
Access and credentials
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
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 instead. The redeem endpoints have no replacement: resolved positions in supported markets settle through 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. Treat all of these as
"not offered here" rather than as something to retry.
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
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
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:
{
"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 for budgets and header examples.
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
for the frame and close-code contract.