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

# Errors

> Error codes returned by the AtlaSent API and how to recover from each.

All errors use standard HTTP status codes and a consistent JSON body:

```json theme={null}
{
  "error": "not_found",
  "message": "Permit not found",
  "status": 404
}
```

| Field | Type | Description |
| - | - | - |
| `error` | string | Machine-readable error code (e.g. `"rate_limited"`). Branch on this, not on `message`. |
| `message` | string | Human-readable description of the error. May be refined over time. |
| `status` | integer | HTTP status code, mirrored in the body for convenience. |

Some responses add extra top-level fields. For example, a rate-limited
evaluate or verify call carries `retry_after_ms`:

```json theme={null}
{
  "error": "unavailable",
  "message": "Runtime cannot decide right now; retry shortly",
  "status": 429,
  "retry_after_ms": 60000
}
```

<Note>
  **Previous error shape removed.** The `{ error: { code, details } }` nested
  envelope and the bare `{ code, details }` shape are no longer returned by any
  endpoint. All error paths use `{ error, message, status }`.
</Note>

<Note>
  **A policy denial is not an HTTP error.** When `/v1/evaluate` reaches a
  `deny`, `hold`, or `escalate` decision, it returns HTTP `200` with the
  decision, `deny_code`, and `deny_reason` in the body — see
  [Deny codes](/concepts/deny-codes). Likewise, `/v1-verify-permit` reports an
  expired, revoked, already-consumed, or mismatched permit in its response body
  (`valid: false` with a `verify_error_code` such as `PERMIT_EXPIRED` or
  `PERMIT_ALREADY_USED`). In both cases the protected action must not run.
</Note>

## Code reference

| HTTP | `error` | What happened | Recovery |
| - | - | - | - |
| 400 | `bad_request` | Missing or malformed field | Fix the request payload; don't retry as-is. |
| 401 | `unauthorized` | Missing / invalid / expired credential | Refresh token or rotate key. |
| 403 | `forbidden` / `insufficient_scope` | Valid credential but it lacks the scope or role this endpoint requires | Use a key with the right scope. |
| 404 | `not_found` | Unknown policy / permit / approval | Check the ID. |
| 409 | `conflict` | Request conflicts with current state | Re-read the resource state before retrying. |
| 422 | `validation_error` | Schema validation failed | Fix the request per `message`. |
| 429 | `rate_limited` | Quota exhausted (`/v1/evaluate` and `/v1-verify-permit` report this as `unavailable` with status 429) | Back off; honor `retry_after_ms` when present. See [Rate limits](/api-reference/rate-limits). |
| 500 | `internal_error` | AtlaSent-side bug | Retry with backoff; open a support ticket. |
| 503 | `service_unavailable` | Maintenance / partial outage | Retry with backoff; treat sustained 503 as **deny**. |

## Fail-closed rule

If you get 5xx or a network error from `/v1/evaluate`, your agent must treat the call as **deny** — never as "allow by default." This is non-negotiable for regulated deployments.

## Handling in the SDKs

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { AtlaSentDeniedError, AtlaSentError } from "@atlasent/sdk";

  try {
    const r = await client.evaluate({ ... });
  } catch (e) {
    if (e instanceof AtlaSentDeniedError) return halt(e.reason);          // e.decision, e.evaluationId
    if (e instanceof AtlaSentError && e.code === "rate_limited")
      return await retryAfter(e.retryAfterMs);
    if (e instanceof AtlaSentError) return halt("fail-closed: upstream unavailable");
    throw e;
  }
  ```

  ```python Python theme={null}
  from atlasent.exceptions import AtlaSentDeniedError, AtlaSentError, RateLimitError

  try:
      r = client.evaluate(...)
  except AtlaSentDeniedError as e:
      halt(e.reason)                     # e.decision, e.deny_code
  except RateLimitError as e:
      time.sleep(e.retry_after or 1)     # seconds; None when not provided
  except AtlaSentError:
      halt("fail-closed: upstream unavailable")
  ```

  ```go Go theme={null}
  permit, err := client.Protect(ctx, req)
  if atlasent.IsDenied(err) { return halt(err.Error()) }
  var apiErr *atlasent.Error
  if errors.As(err, &apiErr) { return halt("fail-closed: " + apiErr.Code) }
  ```
</CodeGroup>


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