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

# Errors

> Read Puntego's error envelope and the full table of HTTP status codes and namespaced reasons the worker returns.

Every Puntego worker endpoint speaks the same error language: a predictable JSON envelope plus a standard HTTP status code. Read the status to decide how to react, and read the reason string to know exactly what happened.

## The error envelope

A failed request returns a small JSON body with a single `error` field. The value is a namespaced string in the form `<namespace>/<reason>`, where the namespace is usually the endpoint family (`boot`, `chat`, `consent`, `voice`, and so on).

```json Error response theme={null}
{
  "error": "boot/origin_not_allowed"
}
```

<ResponseField name="error" type="string">
  A namespaced reason string, `<namespace>/<reason>`. Branch on the namespace for routing and on the full string for precise handling.
</ResponseField>

Some responses add a context field next to `error` (for example a human-readable `message` on plan-limit responses), but `error` is always present and is the field to switch on.

### Rate-limit responses are shaped differently

A `429` response carries an extra `bucket` field that names the limiter that tripped, and the `error` is always `rate_limit/exceeded`. Every `429` also includes a `Retry-After` header in seconds.

```json Rate-limit response theme={null}
{
  "bucket": "chat/visitor-burst",
  "error": "rate_limit/exceeded"
}
```

<Note>
  See [rate limits](/rate-limits) for the buckets, their windows, and how to back off on `Retry-After`.
</Note>

## Status codes

These are the status codes the worker returns, with representative reason strings for each. Branch on the status first; use the reason for detail.

| Status                                        | Meaning                                                                                        | Example reasons                                                                                                                                                                     |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`                             | The request was malformed or failed validation.                                                | `boot/invalid_request`, `chat/invalid_request`, `consent/invalid_request`                                                                                                           |
| `401 Unauthorized`                            | A required `Authorization: Bearer <visitor-jwt>` was missing, invalid, or expired.             | `chat/unauthorized`, `jwt_expired`, `transcribe/unauthorized`, `tts/unauthorized`, `ingest/unauthorized`, `retrieve/unauthorized`                                                   |
| `402 Payment Required`                        | A plan limit was reached.                                                                      | `voice/monthly_cap`, `knowledge-uploads/page_cap_reached`                                                                                                                           |
| `403 Forbidden`                               | The origin is not allowed, the workspace is not enabled, or the visitor identity was rejected. | `boot/origin_not_allowed`, `boot/origin_mismatch`, `boot/tenant_not_enabled`, `boot/bot_filtered`, `boot/invalid_visitor_id`, `chat/tenant_not_enabled`, `voice/tenant_not_enabled` |
| `404 Not Found`                               | The referenced resource does not exist.                                                        | `sandbox/not_found`, `verified-actions/pending_confirmation_not_found`                                                                                                              |
| `409 Conflict`                                | The request conflicts with current state, such as a duplicate or a missing active session.     | `duplicate_result`, `voice-usage/no_active_session`                                                                                                                                 |
| `429 Too Many Requests`                       | A rate limit was exceeded. Includes a `Retry-After` header and a `bucket` field.               | `rate_limit/exceeded`                                                                                                                                                               |
| `500 Internal Server Error`                   | An unexpected error. Telemetry is captured and scrubbed of PII before it leaves the app.       | `internal_error`                                                                                                                                                                    |
| `502 Bad Gateway` / `503 Service Unavailable` | An upstream or storage dependency was unavailable, or the global killswitch is active.         | `boot/killswitch` (503), `knowledge-uploads/storage_unavailable` (503)                                                                                                              |

## Authentication errors: /boot versus /chat

These two endpoints handle a missing or invalid identity differently, and the distinction is intentional.

<Note>
  **`/boot` never returns `401`.** Boot is where a visitor session is minted, so it has no Bearer token to reject. Origin and visitor problems surface as `403` instead (for example `boot/origin_not_allowed` or `boot/invalid_visitor_id`).

  **`/chat` returns `401`** when the `Authorization: Bearer <visitor-jwt>` header is missing or invalid (`chat/unauthorized`), and also when the token has expired (`jwt_expired`). The visitor JWT minted at `/boot` has roughly a 15-minute lifetime, so a long-lived tab can hit an expired token.
</Note>

The fix for a `401` on `/chat` is to call `/boot` again to refresh the visitor session, then retry the chat request with the new token. See [authentication](/authentication) for the full token lifecycle.

## Common situations

<AccordionGroup>
  <Accordion title="403 boot/origin_not_allowed">
    The requesting origin is not on the workspace allowlist. Add the exact origin in **Dashboard → Settings → Domains**, then reload. See [domains](/domains).
  </Accordion>

  <Accordion title="403 boot/tenant_not_enabled or chat/tenant_not_enabled">
    The workspace is not enabled for this surface yet. During a gated rollout, chat is allowed only for workspaces on the production allowlist; confirm the workspace is enabled before sending traffic.
  </Accordion>

  <Accordion title="401 chat/unauthorized or jwt_expired">
    The visitor JWT is missing, malformed, or past its roughly 15-minute lifetime. Re-run `/boot` to mint a fresh session and retry. See [authentication](/authentication).
  </Accordion>

  <Accordion title="429 rate_limit/exceeded">
    A limiter tripped. Read the `bucket` field to see which one, wait for the `Retry-After` interval, then retry. See [rate limits](/rate-limits).
  </Accordion>

  <Accordion title="503 boot/killswitch">
    The global killswitch is active. `/boot` returns `503` and the embed silently no-ops on the page, so visitors see no broken UI. Traffic resumes automatically once the killswitch is cleared.
  </Accordion>
</AccordionGroup>

## Handling errors well

* Switch on the HTTP status for control flow, and on the `error` string for precise messaging.
* Treat `401` on `/chat` as "refresh the session", not "fail" — re-boot and retry once.
* Always honor `Retry-After` on `429`; never retry tighter than the header asks.
* Treat `5xx` as transient and retry with backoff; treat `400` and `403` as terminal until the request or configuration changes.

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/authentication">
    How the visitor JWT is minted at boot and refreshed before it expires.
  </Card>

  <Card title="Rate limits" icon="gauge" href="/rate-limits">
    The buckets, windows, and how to back off cleanly on Retry-After.
  </Card>
</CardGroup>
