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

# Errors

> Errors: Set Aside Money developer API reference.

Every failure returns the same shape. Anything other than a `2xx` has an `error` object:

```json theme={null}
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_parameter",
    "message": "Too big: expected number to be <=200",
    "param": "limit",
    "request_id": "req_43d29b5c-8f16-47a6-b06a-0c243b38acbc"
  }
}
```

| Field | Notes |
| - | - |
| `type` | Broad category. Branch on this. |
| `code` | Specific, stable identifier. Branch on this when you need to handle one case. |
| `message` | Written for a human reading a log. Do not parse it; the wording can change. |
| `param` | Present when one parameter is at fault. |
| `request_id` | Quote this if you contact support. Also returned as the `X-Request-Id` header on every response. |

## Types

| `type` | Status | Meaning |
| - | - | - |
| `invalid_request` | 400 | The request was malformed. Fix it; retrying will not help. |
| `authentication_error` | 401 | The key is missing, malformed, revoked, expired, or no longer entitled. |
| `permission_error` | 402, 403, 410, 423 | The key or the account is not allowed to do this. |
| `not_found` | 404 | No such endpoint, or no such object on this account. |
| `conflict` | 409 | The request conflicts with current state. |
| `rate_limit_error` | 429 | A limit was exceeded. See [rate limits](/api-reference/rate-limits). |
| `api_error` | 5xx | Something failed on our end. Safe to retry with backoff. |

## Codes worth handling

| Code | Type | What to do |
| - | - | - |
| `missing_api_key` | authentication\_error | Send the `Authorization` header. |
| `invalid_api_key` | authentication\_error | The key is dead. Create a new one. |
| `invalid_credential_context` | authentication\_error | Strip the browser session cookie from the request. |
| `insufficient_scope` | permission\_error | The key is read only. Create a key with write access. |
| `plan_upgrade_required` | permission\_error | The account needs Pro or Max. `upgrade_url` is included. |
| `subscription_required` | permission\_error | The subscription has lapsed. |
| `account_read_only` | permission\_error | A trial ended. Reads still work; writes do not. |
| `invalid_parameter` | invalid\_request | Check `param`. |
| `rate_limit_exceeded` | rate\_limit\_error | Wait for `Retry-After`. |
| `provider_quota_exhausted` | rate\_limit\_error | The bank's own limit. Retrying sooner will not help. |
| `internal_error` | api\_error | Retry with backoff. If it persists, send us the `request_id`. |

## What to retry

Retry `429` and `5xx`. Do not retry `4xx` of any other kind, because the request will fail the same
way every time.

```js theme={null}
const RETRYABLE = new Set(["rate_limit_error", "api_error"]);

async function withRetry(request, attempts = 4) {
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    const response = await request();
    if (response.ok) return response;

    const { error } = await response.clone().json().catch(() => ({ error: {} }));
    if (!RETRYABLE.has(error.type) || attempt === attempts) return response;

    const wait = Number(response.headers.get("Retry-After")) || 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, wait * 1000));
  }
}
```

When retrying a write, send an `Idempotency-Key`. A retried request carrying the same key will not
apply the change twice, which matters most for `POST /v1/expense-transfers`, where a duplicate would
move money twice.

## A note on 5xx messages

`api_error` always returns a generic message. Internal failures can carry database or provider
detail, and none of that crosses the public boundary. The `request_id` is how we look up what
actually happened.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.