Agara

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"
  }
}
FieldHow to use it
typeStable URI identifying the condition. Log it; switch on code
titleShort display text. Do not parse it
statusHTTP status repeated in the body
codeStable machine-readable condition
detailOptional safe display text. Do not parse it
request_idCorrelation ID. Log it and include it when contacting support
recoveryStructured instruction for the next client action
field_errorsOptional 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 codeMeaning
400 malformed_jsonThe body is not valid JSON
400 invalid_path_parameterA value embedded in the URL is malformed
413 request_body_too_largeThe body exceeds the endpoint limit
415 unsupported_media_typeA JSON endpoint did not receive application/json
422 validation_failedJSON 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 codeMeaning
422 order_value_below_minimumThe order notional is under the minimum
422 order_value_above_maximumThe order notional is over the maximum
422 order_size_below_minimumThe market sell is under the share minimum
422 order_size_above_maximumThe market sell is over the share maximum
422 order_price_out_of_rangeThe limit price is not inside the open unit interval
422 order_expiration_too_soonThe 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 codeMeaning
422 recipient_token_account_missingThe 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

StrategyWhat your client should do
noneDo not automatically repeat the request. Change input or reconcile state first
retryRetry with exponential backoff. Reconcile a write before repeating it
retry_afterWait after_seconds and the full Retry-After interval, then add jitter
refresh_identity_tokenRefresh the browser identity token, then retry once
check_statusRead 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.

WriteSafe recovery after an uncertain response
Ordinary orderPause, 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 orderRead GET /trade/v1/orders/by-hash/{order_hash}; the signed hash is its identity
Signed-order batchRead every order_hash; resubmit only envelopes that are still absent
Account batchRead 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:

CodeTypical response
post_only_would_crossRefresh the book and calculate a new maker price
insufficient_balanceFor 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_sharesReconcile positions and open SELL commitments; reduce size
order_not_fillableRefresh depth and resize or reprice the order
market_closedStop quoting the market
duplicate_orderRead the existing signed order by order_hash
sequence_conflictFor 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_flightWait for an existing batch to finish
wallet_setup_incompleteComplete wallet setup before retrying
rate_limitedWait for Retry-After and add jitter
dependency_unavailableBack off; reconcile a write before resubmitting
account_operations_pausedRetry in a few minutes: splits, merges, withdrawals and account setup are paused for maintenance. Anything already accepted still completes
dependency_outcome_unknownFollow the supplied check_status resource
internal_errorStop 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 codeTypical response
401 credentials_missingSend an access token; this endpoint requires one
401 wrong_credential_typeSend the credential this endpoint accepts. A browser identity token and a personal access token are not interchangeable
401 identity_token_expiredRefresh the browser identity token once, then repeat the request. This is the only code that asks for refresh_identity_token
401 invalid_identity_tokenSign in again. Refreshing will not repair a token the server rejected outright
403 account_frozenStop 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 codeTypical response
404 route_not_foundCheck 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_allowedUse the method the endpoint documents; the path itself exists
410 endpoint_retiredStop 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_configuredTreat price lookups as unavailable in this deployment
503 price_stream_not_configuredTreat 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 codeTypical response
425 pnl_not_readyRetry 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 codeTypical response
503 price_temporarily_unavailableBack off, then retry
503 price_stream_capacity_exceededBack off, then retry. The symbol budget is shared across everyone streaming, so closing your own stream may not free it
503 pnl_unavailableStop 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.