# Runs

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

Runs are asynchronous units of work. A successful `POST /runs` returns `202 Accepted` immediately, then the run moves through `pending`, `running`, and a terminal state.

> **Query inputs can take up to 45s on create**
>
> For `input_type: "query"`, `POST /runs` first checks the query, which usually takes 5 to 10
> seconds and is cut off at 45. An ambiguous query returns `422 query_ambiguous` (with
> `details.reason`) or `422 entity_ambiguous` instead of `202`; one that runs out of time returns
> `504 upstream_timeout`. Plan and preset inputs return `202` in under a second. Set your client
> timeout to at least 50s on `POST /runs` for query inputs.

## POST /runs

Create a run.

```bash
curl -X POST https://havilo.ai/api/v1/runs \
  -H "Authorization: Bearer $HAVILO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: run-create-001" \
  -d '{
    "input_type": "query",
    "query": "list 3 startups in climate-tech"
  }'
```

### Query Input

Use `query` when you want Havilo to understand the request and choose the steps.

```json
{
  "input_type": "query",
  "query": "Find 10 seed-stage climate startups in Europe",
  "config": {
    "strict_mode": true,
    "global_search": false
  },
  "callback_url": "https://your-server.example/webhooks/network-runs"
}
```

| Field                        | Required | Description                                                                                                                                                    |
| ---------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input_type`                 | Yes      | Must be `"query"`.                                                                                                                                             |
| `query`                      | Yes      | Natural-language request.                                                                                                                                      |
| `config.strict_mode`         | No       | Defaults to `true`. Ambiguous strict queries can fail before work starts.                                                                                      |
| `config.xray`                | No       | Hydrates context from the global workspace alongside the caller's workspace. Requires the `config:xray` scope.                                                 |
| `config.global_search`       | No       | Allows broader search when enabled for your key and workspace. Requires the `config:global_search` scope.                                                      |
| `config.unlimited_transform` | No       | Removes the \~100-entity cap on `transform` steps. Requires the `config:unlimited_transform` scope; runs without it are rejected with `403 forbidden_feature`. |
| `callback_url`               | No       | HTTPS URL for run webhook delivery.                                                                                                                            |

You may send the callback URL as either top-level `callback_url` or `config.callback_url`. If both are present, they must be identical. The resolved value is echoed as `config.callback_url`. The URL must be HTTPS and must not resolve to private IP ranges (RFC1918, loopback, link-local). See [Webhooks](https://docs.havilo.ai/reference/ai-engine/webhooks.md) for signature verification.

### Plan Input

Use `plan` when you already know the exact steps.

```json
{
  "input_type": "plan",
  "plan": {
    "title": "Find and rank climate-tech startups",
    "steps": [
      {
        "index": 0,
        "kind": "fetch",
        "title": "Fetch climate-tech startups",
        "params": {
          "source": "database",
          "query": "climate-tech startups",
          "limit": 50
        }
      },
      {
        "index": 1,
        "kind": "filter",
        "title": "Pick the top 3 by customer fit",
        "instructions": "Keep startups with named paying customers in heavy industry.",
        "params": {
          "steps_to_filter": [0],
          "limit_to": 3
        }
      },
      {
        "index": 2,
        "kind": "reconstitute",
        "title": "Format the result",
        "params": {
          "context_step_ids": [1],
          "expected_artifacts": ["NONE"]
        }
      }
    ]
  }
}
```

`fetch.params.limit` is a retrieval cap: the fetch step returns at most that many rows or entities. For semantic ranking or top-N selection, fetch a broader candidate set and use `filter.limit_to`.

Each step's `instructions` say what it is for and are the only text its model reads; `params` say how it is wired; `title` and the optional `description` are for the person watching the run. `filter` steps require `instructions` — that is the only place a filter's criteria can be stated, and one submitted without them is rejected at creation. See [Step Kinds](https://docs.havilo.ai/reference/ai-engine/step-kinds.md#step-fields).

Plan step params are validated when the run is created. Omitted optional params use the defaults listed in [Step Kinds](https://docs.havilo.ai/reference/ai-engine/step-kinds.md); unknown params are rejected so typos fail before work starts.

Supported public step kinds are `fetch`, `vector_search`, `filter`, `transform`, `analyze`, `discover`, `reconstitute`, and `inject_context`. See [Step Kinds](https://docs.havilo.ai/reference/ai-engine/step-kinds.md) for the full parameter reference for each kind.

### Preset Input

Use `preset` for a packaged workflow.

```json
{
  "input_type": "preset",
  "preset": "dossier",
  "params": {
    "entity": {
      "id": "550e8400-e29b-41d4-a716-446655440002"
    },
    "sections": ["background", "funding", "key_people"],
    "max_tokens": 2000
  }
}
```

Only the `dossier` preset is available in this version. See [Dossier Preset](https://docs.havilo.ai/reference/ai-engine/dossier.md).

## GET /runs/{id}

Fetch the current run envelope.

```bash
curl https://havilo.ai/api/v1/runs/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $HAVILO_API_KEY"
```

### Run Envelope

| Field          | Description                                                                                                                                                                                            |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`           | Run UUID.                                                                                                                                                                                              |
| `object`       | Always `"run"`.                                                                                                                                                                                        |
| `status`       | `pending`, `running`, `paused`, `succeeded`, `failed`, or `canceled`. `paused` is a transient state during scheduled maintenance; runs return to `running` automatically and require no client action. |
| `api_version`  | API version used for the run.                                                                                                                                                                          |
| `created_at`   | Unix epoch milliseconds.                                                                                                                                                                               |
| `completed_at` | Unix epoch milliseconds. Present only for terminal runs.                                                                                                                                               |
| `workspace_id` | Workspace UUID.                                                                                                                                                                                        |
| `input`        | Echo of the accepted input.                                                                                                                                                                            |
| `config`       | The config you sent, with `callback_url` resolved. Defaults apply but are not echoed.                                                                                                                  |
| `steps`        | Step summaries with public step kinds.                                                                                                                                                                 |
| `message`      | Narrative answer when available. Records in it are [record links](https://docs.havilo.ai/reference/ai-engine/artifacts.md#record-links).                                                                                   |
| `artifacts`    | Artifact metadata. Always present; empty when the run saved none. Fetch bodies with `GET /artifacts/{id}`.                                                                                             |
| `note_ids`     | IDs of notes produced by the run. Always present; empty when there are none.                                                                                                                           |
| `warnings`     | Non-fatal warnings.                                                                                                                                                                                    |
| `errors`       | Error objects for failed or partially failed runs.                                                                                                                                                     |
| `usage`        | Aggregated metrics for the run. See `usage` fields below.                                                                                                                                              |
| `resumable`    | Whether the run can be resumed.                                                                                                                                                                        |
| `request_id`   | Request correlation ID.                                                                                                                                                                                |

### usage fields

| Field                       | Description                                                                                                                                                                                           |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `usage.calls`               | Successful physical provider calls recorded for this run.                                                                                                                                             |
| `usage.unpriced_calls`      | Successful calls missing provider usage or a configured price.                                                                                                                                        |
| `usage.input_tokens`        | Total input tokens across successful calls.                                                                                                                                                           |
| `usage.cached_input_tokens` | Input tokens served from provider cache.                                                                                                                                                              |
| `usage.output_tokens`       | Total output tokens across successful calls.                                                                                                                                                          |
| `usage.thinking_tokens`     | Provider-reported thinking tokens.                                                                                                                                                                    |
| `usage.cost_usd`            | Exact total of priced successful calls.                                                                                                                                                               |
| `usage.credits_at_most`     | The most the run's plan can cost, in credits. It is held against your credits when the run starts; a run submitted as a query holds 400 until its plan exists. Absent on a run that draws no credits. |
| `usage.credits_used`        | What the run was charged, in credits. `null` until it ends, `0` if it failed. Absent on a run that draws no credits.                                                                                  |

Usage belongs to the run, not to individual step records.

## POST /runs/{id}/cancel

Cancel a run.

```bash
curl -X POST https://havilo.ai/api/v1/runs/550e8400-e29b-41d4-a716-446655440000/cancel \
  -H "Authorization: Bearer $HAVILO_API_KEY"
```

A queued run stops at once. A running run finishes the step it is on, which is billed, then ends
`canceled`; its `run.canceled` event arrives when that step does. If the run is already terminal, the
response returns the current terminal envelope.

## Idempotency

Send an `Idempotency-Key` header on `POST /runs` to make retries safe.

* Same key and same body returns `200` with that run's current envelope. A new create returns `202`.
* Same key and different body returns `409 idempotency_key_conflict`.
* Use a unique key per logical create attempt.
