Skip to content

Error Handling

All error responses share a consistent JSON shape:

json
{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_CODE",
  "details": "Optional additional context"
}
  • error — a human-readable string. May change between releases — never branch on it.
  • code — a stable, machine-readable enum. Branch on this. The value set is a contract and will not change for a given meaning (new codes may be added).
  • details — optional extra context for debugging (not user-facing).

On the swap/send/quote endpoints, error bodies also echo back any relevant context fields (inputMint, outputMint, signature, …) — the shape is a strict superset of the table above, so older clients keep working.

HTTP Status Codes

CodeMeaningWhen
200SuccessRequest completed successfully
202*AcceptedReserved for async submission flows
400Bad RequestInvalid request body, missing fields, malformed/oversized/unsigned tx
401UnauthorizedMissing or invalid x-api-key header
404Not FoundNo route for the pair, unknown mint, or quote ID not found
410GoneQuote expired (30s TTL)
429Too Many RequestsRate limit exceeded — see Retry-After + X-RateLimit-*
500Internal Server ErrorUnhandled server-side failure
502Bad GatewaySubmission failed (SEND_FAILED / SUBMISSION_FAILED)
503Service UnavailableWarming up (blockhash/quote service) — see Retry-After
504Gateway TimeoutUpstream route-validation walk timed out — retryable

* 202 is not currently emitted; successful submits return 200 with "status": "submitted".

Error Codes

Switch on code rather than the status + prose:

codeTypical statusMeaningRetry?
INVALID_REQUEST400Malformed body/params (bad JSON, missing field, bad mint)No — fix the request
TX_INVALID400Signed tx bytes could not be deserializedNo
TX_UNSIGNED400Transaction has no signature in slot 0No
TX_TOO_LARGE400Serialized tx exceeds the 1232-byte Solana packet limitNo — rebuild smaller
QUOTE_NOT_FOUND404quoteId unknown (never issued or already consumed)No — re-quote
QUOTE_EXPIRED410quoteId valid but its 30s TTL elapsedYes — re-quote
NO_ROUTE404No routable path for the pair/amountMaybe — try a different size/pair
TOKEN_NOT_FOUND404The mint isn't a token / doesn't exist (/v1/tokens/:mint, search)No — check the mint
EXCEEDED_SLIPPAGE404Candidates exist but all exceed price-impact toleranceMaybe — raise slippage / smaller size
SIMULATION_FAILED400simulate=true and the on-chain simulation revertedDepends on details
ROUTE_TIMEOUT504Upstream route-validation walk timed outYes — retry shortly
BLOCKHASH_UNAVAILABLE503No recent blockhash yet (warmup)Yes — retry after Retry-After
SERVICE_UNAVAILABLE503Quote/metadata service warming up or unavailableYes — retry after Retry-After
SEND_FAILED502The node rejected the send — the tx never entered the networkYes — safe to retry the same signed tx
SUBMISSION_FAILED502Transport/internal error mid-submit — landing state unknownYes — idempotent; see below
BUNDLE_UNAVAILABLE503Bundle submission not configured on this deploymentNo
BUNDLE_REJECTED502The Jito block engine rejected the bundleMaybe
RATE_LIMITED429Per-key/per-IP rate limit exceededYes — after Retry-After
INTERNAL500Unhandled server errorMaybe

Signature is always echoed on submit failures

On any non-2xx from POST /v1/swap and POST /v1/send (including the 502s above), the response includes the transaction signature. You never need to derive it locally to track a submission whose landing state is unknown:

json
{ "error": "Submission failed", "code": "SUBMISSION_FAILED", "signature": "5xZt…", "details": "socket hang up" }

SEND_FAILED means the tx never entered the network (safe to retry). SUBMISSION_FAILED means the outcome is unknown — poll GET /v1/tx/:sig or retry idempotently (the same signed tx is deduplicated server-side; see Reliability & retries).

Rate Limit Errors

json
{
  "error": "Rate limit exceeded",
  "code": "RATE_LIMITED",
  "limit": 60,
  "tier": "starter",
  "retryAfterSeconds": 12
}

Every metered response — success and 429 — carries:

HeaderMeaning
X-RateLimit-LimitYour per-minute quota for this category
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetEpoch seconds when the window frees up
Retry-After(429/503 only) Seconds to wait before retrying

Read X-RateLimit-Remaining proactively to throttle before you hit 429.

Handling Tips

  • Branch on code, not on the status + prose.
  • On 429, respect Retry-After; use X-RateLimit-Reset to schedule the next call.
  • On 503/504, retry after Retry-After (defaults to 1–2s) — these are warmup/timeout, not hard failures.
  • On 502, the signature is in the body. SEND_FAILED → retry freely. SUBMISSION_FAILED → retry idempotently or poll GET /v1/tx/:sig.
  • See Reliability & retries for the full idempotency + backoff contract.