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

# Errors

> The error envelope, the full list of error codes, and how to handle them.

Every failure returns the same envelope, with the right HTTP status:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "not_found",
    "message": "Resource not found",
    "details": null
  }
}
```

`code` is a stable machine string - branch on it, not on the human `message`. `details`
is present on a few errors with extra structure (see below).

## Codes

| Status | `code`             | When                                                                                                                                                                                                                                                                  |
| ------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `invalid_request`  | Malformed parameters or JSON body (e.g. a bad uuid, an empty triage body).                                                                                                                                                                                            |
| 401    | `unauthorized`     | Missing, invalid, revoked, or expired API key on a protected endpoint.                                                                                                                                                                                                |
| 402    | `payment_required` | A credit-spending action was blocked: the key hit its spend cap (`spend_cap_reached`) or the team is out of credits (`out_of_credits`). Branch on `details.reason`.                                                                                                   |
| 403    | `forbidden`        | A limit or guardrail: the plan's watchlist product cap (`product_limit_reached`), or a credit tool called with a credential that has no spend budget - a key without a spend cap, or an OAuth connection without a consent-time credit budget (`spend_cap_required`). |
| 404    | `not_found`        | Unknown resource id (or a product/opportunity not on your team).                                                                                                                                                                                                      |
| 429    | `rate_limited`     | [Rate limit](/guides/rate-limits) exceeded.                                                                                                                                                                                                                           |
| 500    | `internal_error`   | Something went wrong on our side.                                                                                                                                                                                                                                     |

## `details` payloads

Some errors carry structured `details`:

**`403` product limit reached** (follow a product past your plan cap):

```json theme={null}
{
  "code": "forbidden",
  "message": "Product limit reached for your plan",
  "details": { "reason": "product_limit_reached", "limit": 5, "current": 5, "upgrade_required": true }
}
```

**`403` spend cap required** (a credit tool on a credential with no spend budget - keys: set a cap in Settings → API keys; OAuth connections: reconnect and allow a credit budget):

```json theme={null}
{
  "code": "forbidden",
  "message": "This API key has no spend cap. Set a per-key spend cap in Settings to enable credit-spending tools.",
  "details": { "reason": "spend_cap_required" }
}
```

**`402` spend cap reached** (the credential's rolling 30-day budget would be exceeded):

```json theme={null}
{
  "code": "payment_required",
  "message": "API key spend cap reached",
  "details": { "reason": "spend_cap_reached", "spend_cap": 100, "spend_used": 100, "spend_remaining": 0, "credits_required": 1 }
}
```

**`402` out of credits** (the team's balance is too low):

```json theme={null}
{
  "code": "payment_required",
  "message": "Insufficient credits",
  "details": { "reason": "out_of_credits", "credits_required": 1, "credits_remaining": 0 }
}
```

**`429` rate limited:**

```json theme={null}
{ "code": "rate_limited", "details": { "retry_after_seconds": 12, "limit": 60, "remaining": 0 } }
```

## Handling

Check `success` first, then switch on `error.code`:

```js theme={null}
const res = await fetch(url, { headers });
const body = await res.json();
if (!body.success) {
  switch (body.error.code) {
    case "unauthorized": /* refresh/replace the key */ break;
    case "rate_limited": /* honor Retry-After, then retry */ break;
    case "not_found": /* handle missing resource */ break;
    default: /* surface error.message */
  }
}
```
