# Errors & retries

> The error envelope, what each code means, and how to retry safely.

Source: https://docs.1delta.io/errors/

---

## Branch on `success`, not on the status code

Every failure — validation, upstream, rate limiting — returns the same envelope:

```json
{
  "success": false,
  "error": {
    "code": "MISSING_PARAM",
    "message": "marketUid is required",
    "details": null
  }
}
```

| Field | Use |
| --- | --- |
| `error.code` | Stable and machine-readable. Branch on this. |
| `error.message` | For humans and logs. Wording may change; do not parse it. |
| `error.details` | Optional extra context; shape varies by code. |

The HTTP status is **not** a reliable signal. A missing required parameter
currently comes back as `502` with `ORIGIN_FAILED`, not as `400`:

```bash
curl -i "https://portal.1delta.io/v1/data/lending/pools"   # no chainId
```

```
HTTP/2 502
```
```json
{
  "success": false,
  "error": {
    "code": "ORIGIN_FAILED",
    "message": "All origins failed",
    "details": [
      { "origin": 0, "status": 500, "details": "{\"ok\":false,\"error\":\"chainId is required\"}" }
    ]
  }
}
```

So the only correct check is:

```ts
const body = await res.json();
if (!body.success) {
  throw new Error(`${body.error.code}: ${body.error.message}`);
}
```

Checking `res.ok` first will mislabel a validation mistake as an outage.

## Codes

| Code | Meaning | Retry? |
| --- | --- | --- |
| `MISSING_PARAM` | A required parameter was absent. | No — fix the request. |
| `INVALID_PARAM` | Present but malformed: bad `marketUid`, unknown lender, non-integer amount. | No — fix the request. |
| `NOT_FOUND` | No such market, pool, or position. | No. |
| `VALIDATION_FAILED` | A pre-flight on-chain check could not be confirmed. | Sometimes — often an RPC hiccup. |
| `ACTION_FAILED` | The transaction could not be built: no route, insufficient liquidity, unsupported path. | Sometimes — retry with different amounts or slippage. |
| `ORIGIN_FAILED` | An upstream data source or protocol origin failed. Also what a missing parameter currently produces. | Yes, if the request itself is valid. |

## Statuses you should handle

| Status | Meaning | Handling |
| --- | --- | --- |
| `200` | Success. Still check `success` in the body. | — |
| `400` | Validation error. | Fix the request. |
| `404` | No such resource. | Fix the request. |
| `429` | Rate limited. | Back off exponentially, then retry. |
| `500` | Server error. | Retry with backoff. |
| `502` | Upstream origin failed — **or** a bad parameter. Inspect `error.code`. | Retry only if the request is valid. |

## Rate limits

Requests are limited per IP when unauthenticated. Send an API key to raise the
limit:

```bash
curl -H "x-api-key: YOUR_KEY" "https://portal.1delta.io/v1/data/chains"
```

Get a key at [auth.1delta.io](https://auth.1delta.io/).

An unrecognised key is **ignored, not rejected** — a bad key never causes a
`401`, it just leaves you on the unauthenticated limit. If you are being
throttled while sending a key, verify the key is actually valid rather than
assuming the header is being read.

Treat `429` as retryable with exponential backoff and jitter rather than coding
against a fixed budget.

```ts
async function call(url: string, attempt = 0): Promise<any> {
  const res = await fetch(url, { headers: { "x-api-key": process.env.ONEDELTA_KEY! } });

  if (res.status === 429 || res.status >= 500) {
    if (attempt >= 4) throw new Error(`giving up after ${attempt} retries: ${res.status}`);
    const backoff = 2 ** attempt * 500 + Math.random() * 250;
    await new Promise((r) => setTimeout(r, backoff));
    return call(url, attempt + 1);
  }

  const body = await res.json();
  if (!body.success) throw new Error(`${body.error.code}: ${body.error.message}`);
  return body;
}
```

Note this still retries a `502` caused by a bad parameter. Inspect
`error.code === "ORIGIN_FAILED"` versus a validation code if you want to avoid
burning retries on a request that can never succeed.

## Failures that happen on-chain, not in the API

A `200` with calldata does not guarantee the transaction succeeds. The usual
causes of a revert after a successful build:

- An approval from `actions.permissions` was not mined before the action was sent.
- More than one entry from `actions.alternatives` was executed.
- The quote went stale — rates and routes move every block.
- `slippage` was too tight for the route, or was passed as a percentage instead
  of [basis points](https://docs.1delta.io/market-identifiers/#basis-points).
- The position's health factor would drop below the liquidation threshold.

Simulate first where the endpoint supports it: `POST` the same parameters with
the user's current `balanceData` and `aprData`, or pass `simulate=true` on
lending actions, and inspect the projected health factor before signing.

## Related

- [Quickstart](https://docs.1delta.io/quickstart/)
- [Market identifiers](https://docs.1delta.io/market-identifiers/)
