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.
Error bodies
Section titled “Error bodies”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.
What to retry
Section titled “What to retry”- A
4xxis not retried as it is. Fix the request, or quote again. The one exception is429: back off and retry. - A
503is maintenance. Wait forRetry-After(300 seconds) and retry. Quotes and builds pause together; submits and order reads keep working. - A
500can be retried with backoff, except a submit that says the deposit was submitted. See the matrix. - 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”.
Streaming quotes
Section titled “Streaming quotes”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.
Exception matrix
Section titled “Exception matrix”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.