# Error codes

> Every stable error code Hatched raises — HTTP status, SDK class, and how to fix.

Source: https://docs.hatched.live/docs/reference/error-codes

{/* AUTO-GENERATED from packages/sdk-js/src/errors.ts by apps/docs/scripts/generate-error-codes.ts. */}

Every error response follows the canonical envelope:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after 60s",
    "requestId": "req_abc_123",
    "details": { "...": "..." }
  }
}
```

The SDK parses this envelope and throws a typed subclass of `HatchedError` with `code`, `statusCode`, `requestId`, and `details`.

## Catalogue

| Code | HTTP | SDK class | Meaning |
|---|---|---|---|
| `unauthorized` | 401 | `UnauthorizedError` | The API key is missing, invalid, or revoked. |
| `forbidden` | 403 | `ForbiddenError` | Key is valid but lacks permission for this endpoint. |
| `publishable_key_scope` | 403 | `PublishableKeyScopeError` | Publishable key cannot mutate — use a secret key server-side. |
| `resource_not_found` | 404 | `NotFoundError` | The referenced id does not exist or was archived. |
| `config_version_mismatch` | 409 | `ConfigVersionMismatchError` | The buddy is pinned to a different config version than expected. |
| `no_published_config` | 409 | `NoPublishedConfigError` | The customer has no published config version yet, so eggs cannot be created. |
| `active_egg_limit` | 409 | `ActiveEggLimitError` | The per-user active-egg cap was reached. `err.details.active` lists the existing eggs (id + status). |
| `conflict` | 409 | `ConflictError` | A competing mutation won; retry is safe if you re-read state first. |
| `validation_failed` | 422 | `ValidationError` | Fields failed schema/business validation. See `err.details.fields`. |
| `insufficient_balance` | 400 | `InsufficientBalanceError` | Buddy does not have enough coins/tokens for the spend. |
| `rate_limited` | 429 | `RateLimitError` | Over the per-minute quota. Honour `err.retryAfter` (seconds). |
| `upstream_image_error` | 502 | `UpstreamImageError` | The art provider failed during hatch/evolve. No ledger writes happened. |

## unauthorized

- **HTTP status:** 401
- **SDK class:** `UnauthorizedError` (from `@hatched/sdk-js`)
- **Meaning:** The API key is missing, invalid, or revoked.
- **Fix:** Double-check `HATCHED_API_KEY`; rotate in Dashboard → Developers if leaked.
- **More:** [Troubleshooting →](/docs/guides/troubleshooting#401-unauthorized)

## forbidden

- **HTTP status:** 403
- **SDK class:** `ForbiddenError` (from `@hatched/sdk-js`)
- **Meaning:** Key is valid but lacks permission for this endpoint.
- **Fix:** Check your plan tier or the key's scope.

## publishable_key_scope

- **HTTP status:** 403
- **SDK class:** `PublishableKeyScopeError` (from `@hatched/sdk-js`)
- **Meaning:** Publishable key cannot mutate — use a secret key server-side.
- **Fix:** Move the call to a server route. See Auth model.
- **More:** [Troubleshooting →](/docs/concepts/auth-model)

## resource_not_found

- **HTTP status:** 404
- **SDK class:** `NotFoundError` (from `@hatched/sdk-js`)
- **Meaning:** The referenced id does not exist or was archived.
- **Fix:** Verify the id from a recent list/create response.

## config_version_mismatch

- **HTTP status:** 409
- **SDK class:** `ConfigVersionMismatchError` (from `@hatched/sdk-js`)
- **Meaning:** The buddy is pinned to a different config version than expected.
- **Fix:** Migrate the buddy or pin your write to its current config.

## no_published_config

- **HTTP status:** 409
- **SDK class:** `NoPublishedConfigError` (from `@hatched/sdk-js`)
- **Meaning:** The customer has no published config version yet, so eggs cannot be created.
- **Fix:** Publish the gamification plan first. `err.details.publish_url` points at the dashboard publish page.
- **More:** [Troubleshooting →](/docs/guides/first-user-bootstrap)

## active_egg_limit

- **HTTP status:** 409
- **SDK class:** `ActiveEggLimitError` (from `@hatched/sdk-js`)
- **Meaning:** The per-user active-egg cap was reached. `err.details.active` lists the existing eggs (id + status).
- **Fix:** Hatch or cancel one of the listed eggs, or retry the create with `?ensure=true` (`eggs.create({ ..., ensure: true })`) to reuse one.
- **More:** [Troubleshooting →](/docs/guides/first-user-bootstrap#common-pitfalls)

## conflict

- **HTTP status:** 409
- **SDK class:** `ConflictError` (from `@hatched/sdk-js`)
- **Meaning:** A competing mutation won; retry is safe if you re-read state first.
- **Fix:** Re-fetch the resource and retry the mutation.

## validation_failed

- **HTTP status:** 422
- **SDK class:** `ValidationError` (from `@hatched/sdk-js`)
- **Meaning:** Fields failed schema/business validation. See `err.details.fields`.
- **Fix:** Log `err.details`; fix the failing field and resend.
- **More:** [Troubleshooting →](/docs/guides/troubleshooting#422-validation-failed)

## insufficient_balance

- **HTTP status:** 400
- **SDK class:** `InsufficientBalanceError` (from `@hatched/sdk-js`)
- **Meaning:** Buddy does not have enough coins/tokens for the spend.
- **Fix:** Check `err.balance` / `err.required`; surface to the user.

## rate_limited

- **HTTP status:** 429
- **SDK class:** `RateLimitError` (from `@hatched/sdk-js`)
- **Meaning:** Over the per-minute quota. Honour `err.retryAfter` (seconds).
- **Fix:** Let the SDK retry (default on) or backoff manually.
- **More:** [Troubleshooting →](/docs/guides/troubleshooting#429-too-many-requests)

## upstream_image_error

- **HTTP status:** 502
- **SDK class:** `UpstreamImageError` (from `@hatched/sdk-js`)
- **Meaning:** The art provider failed during hatch/evolve. No ledger writes happened.
- **Fix:** Re-call the operation; idempotent.
- **More:** [Troubleshooting →](/docs/guides/troubleshooting#502-upstream-image-error)

## Programmatic handling

```ts
import {
  HatchedError,
  ValidationError,
  RateLimitError,
  InsufficientBalanceError,
} from '@hatched/sdk-js';

try {
  await hatched.buddies.spend(buddyId, { amount: 100, reason: "item" });
} catch (err) {
  if (err instanceof InsufficientBalanceError) {
    return showInsufficientFunds(err.balance, err.required);
  }
  if (err instanceof ValidationError) {
    return showFieldErrors(err.details);
  }
  if (err instanceof RateLimitError) {
    return retryLater(err.retryAfter);
  }
  if (err instanceof HatchedError) {
    console.error(err.code, err.requestId, err.message);
  }
  throw err;
}
```
