# Errors

Source: https://docs.havilo.ai/reference/ai-engine/errors

Errors use a structured envelope.

```json
{
  "error": {
    "code": "schema_validation_failed",
    "type": "invalid_request",
    "message": "Request body failed schema validation.",
    "doc_url": "https://docs.havilo.ai/reference/ai-engine/errors#schema_validation_failed",
    "request_id": "req_01hxbexample",
    "param": "query",
    "details": {
      "issues": [
        {
          "code": "invalid_type",
          "expected": "string",
          "received": "undefined",
          "path": ["query"],
          "message": "Required"
        }
      ]
    }
  }
}
```

| Field        | Always present | Description                                                                                    |
| ------------ | -------------- | ---------------------------------------------------------------------------------------------- |
| `code`       | Yes            | Machine-readable error code. Stable across API versions.                                       |
| `type`       | Yes            | Broad error category.                                                                          |
| `message`    | Yes            | Human-readable description. Do not parse this string.                                          |
| `doc_url`    | Yes            | This page, at the heading for the code.                                                        |
| `request_id` | Yes            | The same id as the `X-Request-Id` response header: yours if you sent one. Quote it to support. |
| `param`      | No             | The request field that caused the error. For validation errors, the dot-joined path.           |
| `details`    | No             | Code-specific data. Its shape depends on `code`.                                               |

## Retry guidance

Retry only `429`, `500`, `503` and `504`, with exponential backoff, keeping the same `Idempotency-Key`
when retrying `POST /runs`. Anything else needs a changed request first.

## Request errors

### invalid\_json

`400`. The body is not valid JSON, or could not be read.

### unsupported\_input\_type

`400`. `input_type` is not `query`, `plan` or `preset`.

### unsupported\_preset

`400`. The preset does not exist. The only preset is `dossier`.

### payload\_too\_large

`413`. The request body is over 1 MB, counted in bytes.

### schema\_validation\_failed

`422`. The body or a query parameter has the wrong shape. `param` names the first failing field and
`details.issues` lists every failure, each with `path` as an array. A plan step with a param its kind
does not take fails here too, so a misspelled param is never silently ignored.

### missing\_required\_field

`422`. A required field is missing; `param` names it.

### query\_ambiguous

`422`. The query has more than one reasonable reading. `details.reason` says what needs clarifying.
Rephrase it, or submit an explicit `plan`.

### entity\_ambiguous

`422`. The query names a record Havilo cannot pin down. `details.question` says what to confirm and
`details.candidate_ids` lists the records it considered, when it found any. Resolve them with
`GET /entities/resolve`, then send the query again naming the record, or run a `plan` or the
`dossier` preset with `{ "id": "<uuid>" }`.

```json
{
  "error": {
    "code": "entity_ambiguous",
    "type": "invalid_request",
    "message": "Multiple entity matches found — confirmation required",
    "doc_url": "https://docs.havilo.ai/reference/ai-engine/errors#entity_ambiguous",
    "request_id": "req_01hxbexample",
    "details": {
      "question": "Which Acme do you mean: Acme Corp or Acme Holdings?",
      "candidate_ids": [
        "550e8400-e29b-41d4-a716-446655440000",
        "661f9511-f3ac-52e5-b827-557766551111"
      ]
    }
  }
}
```

### entity\_not\_resolvable

`422`. The record reference matches no active record in the workspace. A dossier on such a record is
refused before a run is created, with `param: "params.entity"`.

### plan\_too\_large

`422`. `plan.steps` has more than 50 steps. Split the plan into several runs.

```json
{
  "error": {
    "code": "plan_too_large",
    "type": "plan_error",
    "message": "The plan exceeds the maximum allowed size.",
    "doc_url": "https://docs.havilo.ai/reference/ai-engine/errors#plan_too_large",
    "request_id": "req_01hxbexample",
    "param": "plan.steps",
    "details": { "max_steps": 50, "received": 51 }
  }
}
```

### invalid\_callback\_url

`422`. `config.callback_url` is not HTTPS or points to a disallowed address. `details.reason` says
which.

### invalid\_rerun\_step

`422`. The step index passed to rerun is out of range for the run.

## Authentication and access

### auth\_missing

`401`. The `Authorization` header is missing or is not `Bearer <key>`.

### auth\_invalid

`401`. The API key is unknown or revoked, or an event token is malformed or belongs to another run.

### auth\_expired

`401`. The API key has expired, or an event token has. For a stream, request a new token with
`POST /runs/{id}/events/token`.

### forbidden\_feature

`403`. The key lacks a scope, or the workspace lacks a feature. `details.required_scope` or
`details.required_feature` names it.

### forbidden\_workspace

`403`. The key cannot act in this workspace.

## Not found

### run\_not\_found

`404`. The run does not exist or is not visible to this key.

### artifact\_not\_found

`404`. The artifact does not exist or is not visible to this key.

### checkpoint\_not\_found

`404`. No saved data exists for that step of the run.

### workspace\_not\_found

`404`. The workspace does not exist or is not visible to this key.

## Conflicts

### idempotency\_key\_conflict

`409`. The same `Idempotency-Key` was used with a different body. Retry with the original body, or
use a new key.

```json
{
  "error": {
    "code": "idempotency_key_conflict",
    "type": "conflict",
    "message": "Idempotency key reused with a different request body.",
    "doc_url": "https://docs.havilo.ai/reference/ai-engine/errors#idempotency_key_conflict",
    "request_id": "req_01hxbexample",
    "param": "Idempotency-Key",
    "details": {
      "original_run_id": "550e8400-e29b-41d4-a716-446655440000"
    }
  }
}
```

### canceled\_by\_user

Not an HTTP error. A canceled run carries it in the run's `errors[]`, with `details.step_index` set
to the step it stopped at. A run canceled mid-step finishes that step first; until it has, the code
carries no `details`.

```json
{ "code": "canceled_by_user", "type": "conflict", "details": { "step_index": 2 } }
```

### run\_not\_resumable

`409`. The run cannot be resumed in its current state. Only failed runs resume; a canceled run does
not. `rerun-from` also returns it, with `details.reason: "no_saved_plan"`, for a run started from a
query: it has no saved plan to rerun from.

### plan\_fingerprint\_mismatch

`409`. The plan changed since the run was created, so it cannot resume.

## Limits

### concurrent\_runs\_exceeded

`429`. Too many runs are in flight for the workspace. Retry when one finishes.

### workspace\_cost\_cap\_exceeded

`429`. The workspace's daily cost cap is reached. Retry after the day resets (UTC).

### cost\_threshold\_exceeded

`429`. The key's daily cost limit is reached. Retry after the day resets (UTC).

## Server errors

### internal\_error

`500`. Unexpected server error. Retry, and quote `request_id` if it persists.

### model\_unavailable

`503`. The AI model is temporarily unavailable. Retry.

### usage\_accounting\_unavailable

`503`. Havilo could not price AI usage for the workspace, so no run was started. Retry later.

### upstream\_timeout

`504`. A service Havilo depends on timed out, including understanding a query past 45 seconds.
Retry, or submit an explicit `plan`.

## Other Havilo APIs

Havilo's other APIs use the same envelope. These codes come from them; the AI Engine API never
sends them.

### field\_memo\_not\_found

`404`. The field memo does not exist.

### calendar\_event\_not\_found

`404`. The calendar event does not exist.

### meeting\_bot\_session\_not\_found

`404`. The meeting bot session does not exist.

### prepared\_email\_not\_found

`404`. The prepared email is not waiting in the mailbox.

### calendar\_event\_not\_toggleable

`409`. Meeting bots cannot be switched on or off for this calendar event.

### meeting\_bot\_session\_not\_stoppable

`409`. This meeting bot session cannot be stopped.

### meeting\_bot\_session\_not\_retryable

`409`. This meeting bot session's post-meeting processing cannot be retried.

### rate\_limit\_exceeded

`429`. Too many requests. Back off and retry.
