# Handle errors and retries

Decide what to do from the error code, and retry only the failures that can succeed later.

Every `/v2` error has a JSON body with a `code`. Branch on `code`. The text of `message` is for people and can change.

## Choose what to do

| Kind of failure | Codes | What to do |
|---|---|---|
| Your request is wrong | `400` codes, `401`, `403`, `404`, `413`, `415` | Fix the request. Retrying unchanged can't help. |
| The points don't work | `point_not_snapped`, `out_of_coverage`, `no_route`, `reference_not_found` | Change the input, or tell the user. |
| Too many requests | `rate_limited` | Wait for `Retry-After`, then retry. |
| Monthly data cap reached | `data_cap_exceeded` | Don't retry. Email [contact@sieitz.com](mailto:contact@sieitz.com) to raise it. |
| A temporary failure | `engine_unavailable`, `unavailable`, `engine_timeout` | Retry with backoff, then give up. |
| An unexpected reply | `internal_error` | Retry once, then report it. |
| Not offered here | `capability_unavailable` | Don't retry. Read `capabilities` from `GET /v2/info`. |

Both `rate_limited` and `data_cap_exceeded` are `429`, so check `code` before you retry. `data_cap_exceeded` has no `Retry-After`, and retrying can't help until the cap is raised or the month ends.

[Errors](../reference/errors.md) lists every code.

## Retry with backoff

Wait longer after each attempt, add some randomness, and stop after a few tries. This example retries only the codes that can succeed later.

```javascript
const RETRY = new Set(["rate_limited", "engine_unavailable", "unavailable", "engine_timeout"]);

async function callWithRetry(url, init, attempts = 4) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, init);
    if (res.ok) return res.json();
    const error = await res.json();
    if (!RETRY.has(error.code) || attempt === attempts - 1) throw error;
    const retryAfter = Number(res.headers.get("Retry-After")) || 0;
    const backoff = 2 ** attempt + Math.random();
    await new Promise((resolve) => setTimeout(resolve, Math.max(retryAfter, backoff) * 1000));
  }
}
```

## Find the point that failed

`point_not_snapped` and `out_of_coverage` include `details.point_index`, the zero-based position of the point that failed. For a matrix, `out_of_coverage` counts the sources first and then the destinations: with 3 sources, `point_index: 4` is the second destination.

## Handle a failure that isn't JSON

Check the status before you rely on the body. A network failure or a proxy between you and the API may not return JSON, so treat a body you can't parse as a temporary failure.
