> ## Documentation Index
> Fetch the complete documentation index at: https://docs.o1.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> How to interpret every error the API can return and the recommended retry strategy for each.

The API uses HTTP status codes plus a JSON body of the form `{ "error": "<message>" }` for every error. This guide covers what each status code means and the right response.

## Status code reference

| Status | Meaning                                 | Retry?                    |
| ------ | --------------------------------------- | ------------------------- |
| `200`  | Success                                 | n/a                       |
| `400`  | Validation error or no routes available | No (fix the request)      |
| `401`  | Missing or invalid `x-api-key`          | No (fix the key)          |
| `404`  | Quote expired (only on `/submit`)       | Yes (re-quote)            |
| `429`  | Rate limit exceeded                     | Yes (backoff)             |
| `5xx`  | Internal error                          | Yes (backoff with jitter) |

## Common error messages

<Tabs>
  <Tab title="400 examples">
    ```json theme={null} theme={null}
    { "error": "amountIn must be > 0" }
    ```

    ```json theme={null} theme={null}
    { "error": "no routes for pair" }
    ```

    ```json theme={null} theme={null}
    { "error": "slippageBps out of range" }
    ```

    ```json theme={null} theme={null}
    { "error": "invalid token address" }
    ```

    **Treatment:** show the user a clear message ("This pair has no liquidity right now"), do not retry. If `no routes for pair` happens often for a specific token, the token may not have routable on-chain liquidity on Base.
  </Tab>

  <Tab title="401 example">
    ```json theme={null} theme={null}
    { "error": "unauthorized" }
    ```

    **Treatment:** verify the `x-api-key` header is set, the key is current, and your code isn't accidentally trimming whitespace. If you've recently rotated, redeploy with the new key.
  </Tab>

  <Tab title="404 example (only on /submit)">
    ```json theme={null} theme={null}
    { "error": "quote not found or expired" }
    ```

    **Treatment:** call `/quote` with the same parameters and submit again. Don't show a user-facing error unless this happens twice in a row.

    See [Quote freshness](/api/dex-aggregator/guides/quote-freshness) for the full recovery pattern.
  </Tab>

  <Tab title="429 example">
    ```json theme={null} theme={null}
    {
      "error": "rate limit exceeded",
      "limit": 120,
      "windowSec": 60
    }
    ```

    **Treatment:** back off for at least 1 second, ideally with jitter (`1s + random(0..500ms)`). If you sustain this, contact the o1 team to request a higher rate limit.
  </Tab>

  <Tab title="5xx (internal)">
    ```json theme={null} theme={null}
    { "error": "internal error" }
    ```

    **Treatment:** retry once after 1-2 seconds with jitter. If the second retry fails, surface a generic error to the user and emit an internal alert.
  </Tab>
</Tabs>

## On-chain transaction failures

Errors above are HTTP errors. Once you have a `submit` response and broadcast the transaction, a separate class of failures can happen on-chain:

| Revert reason                                      | Likely cause                                                      | Fix                                                                      |
| -------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `Slippage`                                         | Pool state moved between `/quote` and inclusion.                  | Increase `slippageBps`, or re-quote and resend.                          |
| `TRANSFER_FROM_FAILED` / `INSUFFICIENT_ALLOWANCE`  | Allowance for `tokenIn` is below `amountIn`.                      | Approve the router for `amountIn`, or use a `permit` payload.            |
| `TRANSFER_FROM_FAILED` (with sufficient allowance) | The user's balance is below `amountIn`.                           | Validate balance before submitting.                                      |
| `Permit failed`                                    | Permit signature invalid or already consumed.                     | Re-sign with a fresh nonce; verify the EIP-712 domain matches the token. |
| `Out of gas`                                       | Gas estimate was too low (rare; happens during chain congestion). | Re-broadcast with a higher gas limit, e.g. `gasEstimate.gasUnits × 1.5`. |

<Tip>
  The on-chain `Slippage` revert is intentional. It means the safety net worked. If a user complains, the most common fix is "use slightly higher slippage" or "re-quote and retry quickly."
</Tip>

## Recommended retry policy

```ts theme={null} theme={null}
async function callWithRetry<T>(fn: () => Promise<Response>, maxRetries = 2): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await fn();

    if (res.ok) return res.json();

    const body = await res.json().catch(() => ({}));

    // Non-retryable: fix the request and return
    if (res.status === 400 || res.status === 401) {
      throw new Error(body.error ?? `${res.status}`);
    }

    // 404 on /submit: caller must re-quote. Bubble up a typed error.
    if (res.status === 404) {
      throw new Error("QUOTE_EXPIRED");
    }

    // 429 / 5xx: back off and retry
    if (attempt === maxRetries) {
      throw new Error(body.error ?? `${res.status}`);
    }

    const baseDelay = res.status === 429 ? 1000 : 1500;
    const jitter = Math.random() * 500;
    await new Promise((r) => setTimeout(r, baseDelay + jitter));
  }
  throw new Error("unreachable");
}
```

Then in the submit handler:

```ts theme={null} theme={null}
try {
  const submit = await callWithRetry(() =>
    fetch(`${API_URL}/submit`, { /* ... */ }),
  );
  // broadcast as usual
} catch (e) {
  if (e.message === "QUOTE_EXPIRED") {
    const fresh = await refreshQuote(params);
    // retry submit with fresh.quoteId
  } else {
    showError(e.message);
  }
}
```

## What NOT to do

<CardGroup cols={2}>
  <Card title="Don't retry 400 errors" icon="ban">
    `400` means the request itself is malformed or unroutable. Retrying doesn't help; fix the request.
  </Card>

  <Card title="Don't hammer on 429" icon="hand">
    The rate limit window is per API key. Retrying immediately after a `429` makes the situation worse and burns the rest of your minute. Always back off.
  </Card>

  <Card title="Don't surface raw errors to users" icon="user-shield">
    Translate `error` strings into UI-friendly copy. "no routes for pair" → "Couldn't find liquidity for this pair, try a different amount or token."
  </Card>

  <Card title="Don't ignore on-chain reverts" icon="link-slash">
    Always check `receipt.status === "success"` after `waitForTransactionReceipt`. A `0x` tx hash is not the same as a successful swap.
  </Card>
</CardGroup>
