Skip to content
API v1 is deprecated. It stays supported, but new integrations should use the Khalani API. See Migrate to v2.
API v1 · Deprecated

Handling exceptions (v1)

Treat errors as part of the user experience: an expired quote, an unsupported pair and a refund are all ordinary outcomes. Check the HTTP status first, then the body.

Most errors are an exception envelope. Branch on name:

{
"message": "Quote(quoteId: 3f6c2a1e-8b4d-4e7a-9c15-2d0b7e9a4f61, routeId: Hyperstream) not found",
"name": "QuoteNotFoundException",
"details": { "quoteId": "3f6c2a1e-8b4d-4e7a-9c15-2d0b7e9a4f61", "routeId": "Hyperstream", "code": 0 }
}
Field Type Meaning
message string Human-readable. Log it; do not parse it.
name string The exception. Use it for logic.
details object or array Extra context. Optional, and hidden on some exceptions.

A few responses have other shapes:

Source Status Body
Request validation 400 { "message": "Validation failed", "name": "ValidationException", "details": [{ "field", "message", "code" }] }
A body that is not JSON 400 { "message": "Malformed JSON body", "name": "BadRequestException" }
Rate limit (build only) 429 { "errorCode": "RATE_LIMITED", "message": "Too many requests, please retry later" }
Maintenance 503 { "name": "MaintenanceException", "message": "SystemMaintenance" }, with Retry-After: 300
Unknown order in GET /v1/orders/by-id/{orderId} 404 Plain text: Order not found
Unknown path 404 Plain text: 404 Not Found

So read the body as text, then parse it as JSON when you can. Log name ?? errorCode with message, and keep the x-request-id header of every failed response.

  1. A 4xx is not retried as it is. Fix the request, or quote again. The one exception is 429: back off and retry.
  2. A 503 is maintenance. Wait for Retry-After (300 seconds) and retry. Quotes and builds pause together; submits and order reads keep working.
  3. A 500 can be retried with backoff, except a submit that says the deposit was submitted. See the matrix.
  4. An expired or missing quote is not retried. Quote again and build from the new quote.

For the user, map errors to an action: “Price expired, refresh”, “This token is not supported on that chain”, “Connect the wallet you quoted with”.

With POST /v1/quotes?mode=stream:

  • An invalid body returns an ordinary JSON error, not a stream.
  • A route that fails or does not quote is left out. The stream can have fewer lines than there are routes, or none.
  • The stream ends early when the native route quotes. Its line is the only one sent, and the external routes are dropped. With ?routes=, every listed route streams, and the stream ends when all of them have answered or failed.
name Status Retry? Typical cause What to do
ValidationException 400 No A field is missing or malformed. Fix it. details[] names each field.
BadRequestException 400 No Build: the quote expired (Quote has expired. Please request a new quote.), from is missing or differs from the quote’s fromAddress, the quote is bound to another wallet, or recipient or refundTo differs from an earlier build. Submit: the hash format does not match the chain, the buildId is missing or stale, or the quote is funded through a deposit address. Quote again for an expired quote. Otherwise fix the request.
CannotFillException 400 No The route cannot build this deposit. details is hidden. Quote again, or choose another route.
UnexpectedFromAddressException 400 No The address does not fit the source chain’s family, on an external route. Use an address of the source chain’s family.
NotSupportedContractException 400 No A contract the route needs is not configured. Choose another route.
BuildDepositParsingException 400 No The route returned a plan the API could not validate. Quote again and build once more, then try another route.
NotSupportedDepositMethodException 400 No The depositMethod is not one the route offers. details holds depositMethod, fillerId and supportedMethods. Pick from supportedMethods, or leave depositMethod out.
BroadcastException 400 No Server-side broadcast failed: balance, nonce or an expired Solana blockhash. details is hidden. Fix the cause, sign again and resubmit.
NotSupportedTokenException 404 No Build: the token is not supported on that chain. Use a token from GET /v1/tokens.
NotSupportedChainException 404 No Build: the chain is not supported. Submit: the API cannot broadcast on that chain. Use a chain from GET /v1/chains, or broadcast yourself and submit the hash.
NotSupportedAssetReverseContractException 404 No The native route has no deposit contract on that chain. Choose another route.
IntentNotFoundException 404 No The stored quote has no intent to build from. Quote again.
QuoteNotFoundException 404 No Unknown quoteId and routeId pair, a quote past its storage window (validBefore plus 60 seconds), or a quote already used by a successful submit. Quote again. After a submit that may have succeeded, look the order up with GET /v1/orders/{address} before doing anything else.
RATE_LIMITED (errorCode) 429 Yes Too many builds. See Limits and reliability. Back off and retry. There is no Retry-After.
InternalErrorException 500 Yes An unhandled server error. Retry with backoff, then contact Khalani with the x-request-id.
MaintenanceException 503 Yes Quotes and builds are paused. Retry after Retry-After.

A quote that no route can serve is not an error: POST /v1/quotes returns 200 with an empty routes[]. Unsupported tokens and chains only raise exceptions at the build.

DuplicateRecordException (409) exists in the API, but a duplicate submit does not reach it. It returns 404 QuoteNotFoundException instead, as above.