LCX Liberty. American DeFi. Your keys. Your assets. Your control.
Errors
The response envelope, and the codes worth branching on.
The envelope
Every response has the same outer shape, success or failure. Branch on success and then on code, rather than on the status alone.
{
"success": false,
"code": "no_route",
"message": "No route found for this pair at this size."
}message is written to be read by a person. In most cases it is the best thing you can put in front of a user verbatim. error carries the same string as code and exists only so older tooling keeps working; read code in new integrations.
Some codes carry extra fields beside the envelope. insufficient_liquidity is the richest: when the engine refuses a size it also searches for the sizes that would have worked, and reports them so your user does not have to guess.
{
"success": false,
"code": "insufficient_liquidity",
"message": "This pair is indexed, but our liquidity is too thin at this size: the best route we found loses 96.19% of value. About 1.4 UNI trades at roughly 4.5% impact; the most we can quote at all is about 31.25 UNI (49.7% impact).",
"bestImpactPct": 96.19,
"venues": ["uniswap-v2", "uniswap-v3"],
"maxAmountIn": "31250000000000000000",
"maxAmountInImpactPct": 49.671,
"comfortableAmountIn": "1400000000000000000",
"comfortableImpactPct": 4.478
}maxAmountIn is the largest input the engine verified it can still quote, and comfortableAmountIn is the largest it verified at no more than 5% impact. Both are raw units of tokenIn, both were re-quoted before being reported, and both can be null or absent: a pair can be too thin at any size, in which case only the generic message remains. venues names the venues of the single best route that was refused, not everything the search considered.
Codes
| Code | Status | What to do |
|---|---|---|
INVALID_API_KEY | 401 | Fix the key. Retrying will not help. |
RATE_LIMITED | 429 | Back off and retry with jitter. |
QUOTA_EXCEEDED | 429 | Upgrade or wait for the month to roll over. |
no_route | 400 | The pair cannot be routed. Not retryable at this size. |
insufficient_liquidity | 400 | Routable, but not at this size. The body carries maxAmountIn and comfortableAmountIn when the engine could verify working sizes; offer those instead of asking the user to guess. |
pricing_unreliable | 409 | A pool behind this pair reports prices the engine refuses to trust (a stale or manipulated book). No size is safe; retry after the pool's state refreshes rather than shrinking the amount. |
QUOTE_EXPIRED | 410 | Re-quote and show the new price. Do not retry the build. |
price_moved | 400 | The pool moved past the floor the quote promised. Re-quote. |
slippage_mismatch | 400 | The built floor no longer matches the quote. Re-quote; do not relax it. |
store_unavailable | 503 | Our index for this chain is outside its freshness window, so we will not build against it. The message names which check failed. Retry shortly. |
route_not_executable | 400 | The route contains a step the deployed router cannot run. Re-quote. |
PERMIT_UNSUPPORTED | 400 | Fall back to a plain approve. |
quote_unavailable | 503 | The engine could not price this in time. Ours, not yours. Retry shortly. |
Retrying
Retry 5xx, network failures and RATE_LIMITED with jittered backoff. Do not retry any other 4xx. Resending a request the server has already refused will get the same answer, and it delays the moment you notice the bug.
Jitter is not decoration. Without it, every client that trips the same limit retries in lockstep and trips it again.
A failed request costs nothing, so the quota is not the reason to be careful here. Correctness is.