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

# Error Reference

> All AI API error codes, their HTTP status, and handling patterns including streaming errors.

Every native `/v1` (+ Data API) error shares a single envelope shape. The HTTP status code and `error.code` are the two identifiers to branch on — `error.message` is human-readable and may change without notice. The `/v1/messages` and `/v1/chat/completions` compat endpoints are the exception: they intentionally return Anthropic's and OpenAI's own wire shapes instead (more detail in the closed-set paragraph after the example) — on those, the HASP code is carried as `error.hasp_code`, and `error.code`/`error.type` are the compat SDK's own fields, not this envelope's.

## Error envelope

```json theme={null}
{
  "success": false,
  "error": {
    "code": "PHI_BLOCKED",
    "type": "permission",
    "message": "Message contains PHI that cannot be sent without redaction.",
    "param": "messages[0].content",
    "details": {
      "entity_types": ["US_SSN", "PERSON"]
    },
    "retryable": false,
    "request_id": "01JQREQ7XZQK5N6PZ1VVXHYB8T"
  },
  "meta": {
    "request_id": "01JQREQ7XZQK5N6PZ1VVXHYB8T"
  }
}
```

`request_id` appears in both `error` and `meta` — the duplication is intentional for convenience when parsing either field. It also appears on the `Request-Id` response header, including on SSE streams.

On this native envelope, `error.type` is a closed set: `invalid_request`, `authentication`, `permission`, `not_found`, `conflict`, `rate_limited`, `payment_required`, `api_error`. Branch on `error.code` for anything more specific — `error.type` is the coarse bucket. This closed set is specific to the native envelope — the `/v1/messages` and `/v1/chat/completions` compat endpoints intentionally return Anthropic's and OpenAI's own wire shapes (e.g. `invalid_request_error`, `not_found_error`) instead, so an existing Anthropic or OpenAI SDK keeps working unmodified against them.

## Error codes by HTTP status

Codes marked **(Agent SDK)** are only ever returned to delegated agent-credential (`hasp_agt_*`) callers — see [Agents overview](/ai-api/agents/overview). Every code below except the ones in the "Legacy AI-surface codes" callouts is a case on the closed `App\Support\Api\ErrorCode` enum and is covered by an automated CI test that fails the build if this table drifts from the enum (`tests/Feature/Api/V1/ErrorCatalogConformanceTest.php`).

### 400 — invalid\_request

| Code                    | Description                                                                                                                                                                                                       |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PHI_IN_TOOL_SCHEMA`    | A tool definition contains PHI. Tool schemas are stored in plaintext at the provider — this is always rejected regardless of `phi_mode`.                                                                          |
| `UNSUPPORTED_PARAMETER` | A parameter the resolved provider driver cannot honor was sent (the ADR-45T1K1 feature-variance policy: pass if `supports()`, else this explicit error — never a silent drop). `error.param` names the parameter. |
| `INVALID_API_VERSION`   | The `Hasp-Version` request header names a version this deployment does not support — see [Versioning](#versioning) below.                                                                                         |

### 401 — authentication

| Code                                         | Description                                                                        |
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
| `INVALID_API_KEY`                            | Bearer token is missing, malformed, or revoked.                                    |
| `AGENT_CREDENTIAL_NOT_FOUND` **(Agent SDK)** | The delegated agent credential (`hasp_agt_*`) presented was not found.             |
| `AGENT_CREDENTIAL_REVOKED` **(Agent SDK)**   | The agent credential has been revoked.                                             |
| `AGENT_CREDENTIAL_EXPIRED` **(Agent SDK)**   | The agent credential has expired.                                                  |
| `DEVELOPER_CREDENTIAL_NOT_FOUND`             | The developer OAuth credential (`hasp login`, ADR-YGE00M) presented was not found. |
| `DEVELOPER_CREDENTIAL_REVOKED`               | The developer OAuth credential has been revoked.                                   |

### 402 — payment\_required

| Code                        | Description                                                                                                                                                                                                                                                                                                                       |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BAA_REQUIRED`              | No active Business Associate Agreement on the org. Sign at Settings → Compliance.                                                                                                                                                                                                                                                 |
| `CONSUMPTION_BLOCKED`       | Org has used its full credit or spend allotment for the current billing cycle. `error.details` (surfaced under `error.context`) carries cycle and cap detail. Replaces the legacy `AI_CREDITS_EXHAUSTED` / `CREDITS_EXHAUSTED` / `USER_CREDIT_LIMIT_REACHED` / `EVALUATION_CREDITS_EXHAUSTED` codes, which are no longer emitted. |
| `EVALUATION_ACCESS_BLOCKED` | The org's Free Evaluation window has ended. Upgrade to a paid plan at Settings → Billing to continue. Distinct from `CONSUMPTION_BLOCKED`, which fires on credit-allotment exhaustion while still within the evaluation window.                                                                                                   |

### 403 — permission

| Code                                         | Description                                                                                                                                                           |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MISSING_SCOPE`                              | API key exists but lacks the required scope or capability. `error.details.required_scope` names the missing scope.                                                    |
| `PHI_BLOCKED`                                | Message contains PHI and `phi_mode=block` is configured. `error.details.entity_types` lists what was detected.                                                        |
| `FEATURE_NOT_HIPAA_ELIGIBLE`                 | The requested AI feature is not HIPAA-eligible and is disabled in the allowlist.                                                                                      |
| `MODEL_ACCESS_DENIED`                        | Opus model access is disabled for this org. Enable at Settings → AI Workspace → Models.                                                                               |
| `TOOL_NOT_IN_SCOPE` **(Agent SDK)**          | The agent attempted to invoke a tool outside its delegated scope grant.                                                                                               |
| `SCOPE_CONSTRAINT_VIOLATION` **(Agent SDK)** | The request violates a constraint attached to the agent's scope grant (e.g. a resource-id allowlist).                                                                 |
| `AI_PAUSED`                                  | AI is paused for this organization. An org admin can resume it in Settings → AI Controls.                                                                             |
| `FEATURE_NOT_AVAILABLE`                      | The requested feature is not included in the org's current plan tier. `error.details.required_tier` names the tier that unlocks it.                                   |
| `ORGANIZATION_MISMATCH`                      | The authenticated caller does not belong to the organization that owns the requested resource.                                                                        |
| `RAG_RETRIEVAL_DISABLED`                     | Knowledge retrieval is disabled for this org or persona.                                                                                                              |
| `RETRIEVAL_PHI_BLOCKED`                      | The knowledge-search query or a retrieved chunk was blocked because it contains PHI and the resolved PHI policy forbids returning it.                                 |
| `ORG_ADMIN_REQUIRED`                         | The action requires the delegating user to be an organization admin.                                                                                                  |
| `INTEGRATIONS_DISABLED`                      | The Integrations (BYO-Tool) feature is disabled for this org.                                                                                                         |
| `CONVERSATION_OWNER_REQUIRED`                | Server-owned conversations are owned by the credential that creates them, and this credential type cannot hold that ownership. Use an API key or an agent credential. |

### 404 — not\_found

| Code                 | Description                                                      |
| -------------------- | ---------------------------------------------------------------- |
| `NOT_FOUND`          | The requested route or record does not exist.                    |
| `RESOURCE_NOT_FOUND` | The requested resource does not exist or belongs to another org. |

### 405 — invalid\_request

| Code                 | Description                                      |
| -------------------- | ------------------------------------------------ |
| `METHOD_NOT_ALLOWED` | The HTTP method is not supported for this route. |

### 409 — conflict

| Code                   | Description                                                                                                                   |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `IDEMPOTENCY_CONFLICT` | The `Idempotency-Key` on this request was already used with a different request body — see [Idempotency](#idempotency) below. |

### 410 — gone

| Code            | Description                                                                                                                                                                            |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MODEL_RETIRED` | The specified model has been retired. `error.details` includes `retired_at`, `recommended_replacement`, and `docs_url`. HASP provides at least 90 days notice before retiring a model. |

### 422 — invalid\_request

| Code                                                    | Description                                                                                                                                                                              |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VALIDATION_FAILED`                                     | Request body failed validation. `error.param` names the offending field; `error.details` carries the full per-field error map.                                                           |
| `UNSUPPORTED_DOCUMENT_TYPE`                             | The uploaded document format is not supported.                                                                                                                                           |
| `AGENT_CONCURRENT_CREDENTIALS_EXCEEDED` **(Agent SDK)** | The agent has reached its cap on concurrently active credentials. `error.retryable: false` — revoke a credential or wait for one to expire before minting another.                       |
| `AGENT_DELEGATION_CHAIN_DEPTH_EXCEEDED` **(Agent SDK)** | The agent-to-agent delegation chain exceeds the maximum allowed depth.                                                                                                                   |
| `MALWARE_DETECTED`                                      | The uploaded file failed malware scanning.                                                                                                                                               |
| `DELEGATING_USER_REQUIRED`                              | The bearer token has no resolvable delegating user (e.g. its creator left the org) and this action requires one to attribute as the human actor.                                         |
| `PHI_DETECTED`                                          | The request body contains PHI in a context that requires acknowledgement rather than outright rejection — distinct from the 403 `PHI_BLOCKED`, which rejects PHI under `phi_mode=block`. |
| `PERSONA_NO_DRAFT`                                      | The persona has no open draft to act on.                                                                                                                                                 |
| `PERSONA_ALREADY_DRAFT`                                 | The persona already has an open draft.                                                                                                                                                   |
| `PERSONA_NOT_PRIVATE`                                   | The action requires the persona to be private (not yet published).                                                                                                                       |
| `SOLVER_SPEC_INVALID`                                   | [`POST /v1/solve`](/ai-api/reference/solve) rejected the spec against the solver's closed constraint vocabulary. `error.details.errors` carries the full list.                           |

### 429 — rate\_limited

| Code                                             | Description                                                                                                                                                              |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `RATE_LIMITED`                                   | Per-key, per-org, or daily request limit exceeded. Check the `Retry-After` and `X-RateLimit-*` headers and `error.details.retry_after_seconds`. `error.retryable: true`. |
| `BUDGET_EXCEEDED`                                | Org-configured spend cap reached. `error.details` includes `cycle_reset_at`, `current_spend_usd`, `cap_usd`. Retryable after the cap is raised or the cycle resets.      |
| `AGENT_RATE_LIMIT_EXCEEDED` **(Agent SDK)**      | The delegated agent credential exceeded its own rate limit, independent of the parent API key's limit.                                                                   |
| `AGENT_CONCURRENCY_CAP_EXCEEDED` **(Agent SDK)** | Too many concurrent requests are in flight for this agent credential.                                                                                                    |

### 502 — provider\_error

| Code                         | Description                                                             |
| ---------------------------- | ----------------------------------------------------------------------- |
| `INFERENCE_UPSTREAM_FAILURE` | The upstream model provider returned an error. `error.retryable: true`. |

### 503 — service\_unavailable

| Code                        | Description                                                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `INFERENCE_UNAVAILABLE`     | The AI inference gateway is temporarily unavailable. Retry with backoff.                                                 |
| `DOCUMENT_STILL_PROCESSING` | Document ingestion is in progress — use webhooks instead of polling.                                                     |
| `SOLVER_UNAVAILABLE`        | The [`POST /v1/solve`](/ai-api/reference/solve) solver sidecar is unreachable or misconfigured. `error.retryable: true`. |

### 500 — api\_error

| Code             | Description                                                                       |
| ---------------- | --------------------------------------------------------------------------------- |
| `INTERNAL_ERROR` | Unexpected server error. If this persists, contact support with the `request_id`. |

## Idempotency

Send an `Idempotency-Key` header on a JSON POST or mutating PATCH/PUT request to make retries safe. Replaying the same key with the same request body within 24 hours returns the original response with an `Idempotent-Replayed: true` header instead of re-executing the request. Reusing a key with a *different* request body is rejected as a 409 `IDEMPOTENCY_CONFLICT`. The key is optional — omitting it disables idempotency for that request.

Idempotency has two boundaries. It only applies to JSON request bodies — a multipart or form-encoded request (e.g. file uploads) is never deduped, regardless of the header. And the AI inference endpoints (`/v1/messages`, `/v1/messages/count_tokens`, `/v1/chat/completions`, `/v1/ai/chat`) are excluded entirely, since their response content may carry re-identified PHI that this primitive must not persist — an `Idempotency-Key` sent to those endpoints is silently ignored.

Full treatment (retry semantics, key generation guidance, worked examples): [Idempotency](/ai-api/concepts/idempotency).

## Versioning

Every successful response is versioned under a dated `Hasp-Version` (`YYYY-MM-DD`), independent of the `/v1` URL. Send a `Hasp-Version` header to pin a request to a specific version; omit it to use your API key's configured default, or the deployment default if the key has none. The resolved version is echoed back on the `Hasp-Version` response header for any request that reaches version resolution — errors raised before that point (e.g. 404 for an unrecognized route, 405 for an unsupported method) do not carry the header. A version, once published, never changes shape underneath a pinned caller.

Full treatment (per-key pinning, the `/v1` major boundary, deprecation-header policy): [Versioning](/ai-api/concepts/versioning).

## Streaming errors

On SSE streams, errors after the stream has opened are delivered as events rather than HTTP status codes:

```
event: error
data: {"type":"error","data":{"error":{"code":"RATE_LIMITED","message":"...","retryable":true,"retry_after_seconds":12}}}
```

Streaming failures emit a standalone `error` event and the stream ends — there is no `run.failed` event and no failure envelope. See [Streaming events](/ai-api/reference/chat#error-events) for the payload shape.

## Handling retryable errors

```javascript theme={null}
async function chatWithRetry(message, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch('https://api.usehasp.com/v1/ai/chat', {
      method: 'POST',
      headers: { 'Authorization': 'Bearer hasp_api_live_...', 'Content-Type': 'application/json' },
      body: JSON.stringify({ message, stream: false }),
    });

    const body = await response.json();

    if (body.success) return body;

    if (!body.error.retryable || attempt === maxRetries) throw new Error(body.error.message);

    const delay = body.error.details?.retry_after_seconds
      ? body.error.details.retry_after_seconds * 1000
      : Math.min(1000 * 2 ** attempt, 30000);

    await new Promise(resolve => setTimeout(resolve, delay));
  }
}
```

## Complete code list

Every error code the API can return, with its HTTP status. This table is generated from the platform's `ErrorCode` enum, so it is exhaustive by construction — the curated sections above explain the ones you are most likely to handle.

| Code                                    | HTTP |
| --------------------------------------- | ---- |
| `AGENT_CREDENTIAL_NOT_FOUND`            | 401  |
| `AGENT_CREDENTIAL_REVOKED`              | 401  |
| `AGENT_CREDENTIAL_EXPIRED`              | 401  |
| `TOOL_NOT_IN_SCOPE`                     | 403  |
| `SCOPE_CONSTRAINT_VIOLATION`            | 403  |
| `AGENT_RATE_LIMIT_EXCEEDED`             | 429  |
| `AGENT_CONCURRENCY_CAP_EXCEEDED`        | 429  |
| `AGENT_CONCURRENT_CREDENTIALS_EXCEEDED` | 422  |
| `AGENT_DELEGATION_CHAIN_DEPTH_EXCEEDED` | 422  |
| `DEVELOPER_CREDENTIAL_NOT_FOUND`        | 401  |
| `DEVELOPER_CREDENTIAL_REVOKED`          | 401  |
| `INVALID_API_KEY`                       | 401  |
| `MODEL_ACCESS_DENIED`                   | 403  |
| `FEATURE_NOT_HIPAA_ELIGIBLE`            | 403  |
| `CONSUMPTION_BLOCKED`                   | 402  |
| `BUDGET_EXCEEDED`                       | 429  |
| `BAA_REQUIRED`                          | 402  |
| `AI_PAUSED`                             | 403  |
| `EVALUATION_ACCESS_BLOCKED`             | 402  |
| `FEATURE_NOT_AVAILABLE`                 | 403  |
| `UNSUPPORTED_PARAMETER`                 | 400  |
| `VALIDATION_FAILED`                     | 422  |
| `NOT_FOUND`                             | 404  |
| `METHOD_NOT_ALLOWED`                    | 405  |
| `INTERNAL_ERROR`                        | 500  |
| `IDEMPOTENCY_CONFLICT`                  | 409  |
| `INVALID_API_VERSION`                   | 400  |
| `MALWARE_DETECTED`                      | 422  |
| `ORGANIZATION_MISMATCH`                 | 403  |
| `RATE_LIMITED`                          | 429  |
| `DELEGATING_USER_REQUIRED`              | 422  |
| `RAG_RETRIEVAL_DISABLED`                | 403  |
| `RETRIEVAL_PHI_BLOCKED`                 | 403  |
| `ORG_ADMIN_REQUIRED`                    | 403  |
| `PHI_DETECTED`                          | 422  |
| `PERSONA_NO_DRAFT`                      | 422  |
| `PERSONA_ALREADY_DRAFT`                 | 422  |
| `PERSONA_NOT_PRIVATE`                   | 422  |
| `INTEGRATIONS_DISABLED`                 | 403  |
| `SOLVER_SPEC_INVALID`                   | 422  |
| `SOLVER_UNAVAILABLE`                    | 503  |
| `CONVERSATION_OWNER_REQUIRED`           | 403  |
