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

# Runs

> Start a run, follow it, and read its result.

A run is one call to one tool. `POST /v1/runs` takes a `provider`, a `model` (the tool's id) and the provider's own
`input`, exactly as the tool's input schema names it ([Inspect a model](/docs/api-reference/endpoint/inspect.md)).

> [tab] cURL
>
> ```bash theme={null}
> curl https://api.spiral.ai/v1/runs \
>   -H "Authorization: Bearer $SPIRAL_API_KEY" \
>   -H "Content-Type: application/json" \
>   -H "Idempotency-Key: $(uuidgen)" \
>   -d '{
>     "provider": "fal",
>     "model": "fal-ai/nano-banana-pro",
>     "input": { "prompt": "Monsoon rain over Mumbai at dusk, poster", "num_images": 1 },
>     "webhook": { "url": "https://agent.example.com/hooks/spiral" },
>     "metadata": { "task_id": "t_88" }
>   }'
> ```

* **Async tools** (image, video, audio) answer `202 Accepted` with the queued run, a `Location` header and a
  `Retry-After` poll hint.
* **Sync tools** (web search, scrape) answer `200 OK` with the finished run.

## Statuses

| Status | Meaning |
| - | - |
| `queued` | Accepted and waiting for a worker. Its estimate is held. |
| `running` | Sent to the provider. |
| `completed` | Finished; `output` holds the results and `cost` the charge. |
| `failed` | Ended with an `error` (see [run errors](/docs/api-reference/errors.md#run-errors)). |
| `canceled` | Stopped before it ran. |

A run cannot be canceled after it is submitted.

## The run object

```json theme={null}
{
  "id": "0192f3e7-7c41-7b2a-9e55-3c1d2a7e8f10",
  "object": "run",
  "status": "completed",
  "provider": "fal",
  "model": "fal-ai/nano-banana-pro",
  "output": {
    "items": [
      {
        "type": "image",
        "url": "https://files.spiral.ai/r/0192f3e7…/0.png?sig=…",
        "mime_type": "image/png",
        "size_bytes": 9123456,
        "width": 5504,
        "height": 3072,
        "expires_at": "2026-10-19T12:00:09.300Z"
      }
    ],
    "expired": false
  },
  "usage": { "units": [{ "unit": "image", "quantity": 1 }] },
  "cost": {
    "currency": "USD",
    "state": "settled",
    "estimated": { "amount": "0.39", "amount_micros": 390000 },
    "charged": { "amount": "0.39", "amount_micros": 390000 }
  },
  "timings": {
    "created_at": "2026-10-19T10:00:00.120Z",
    "started_at": "2026-10-19T10:00:00.410Z",
    "completed_at": "2026-10-19T10:00:09.300Z"
  },
  "error": null,
  "metadata": { "task_id": "t_88" },
  "idempotency_key": "9f1c3a52-6a77-4f7e-9d0e-0d1f4b6c2a11",
  "sequence": 4
}
```

| Field | Meaning |
| - | - |
| `output.items[]` | One entry per result: `type` (`image`, `video`, `audio`, `text`, `json` …), a signed `url` and its `expires_at`. Download outputs within 24 hours; after that `output.expired` is `true`. |
| `usage.units[]` | What the run used, in the tool's billing units (`image`, `second`, `character`, `token` …). |
| `cost` | `estimated` is the amount held when the run started; `charged` is what it cost. `state` is `held` until the charge settles, then `settled`. Amounts are decimal strings and integer micro-USD. |
| `error` | On a failed run: `code`, `message`, `retryable`, and for invalid input `errors[]` naming each field. |
| `sequence` | Grows with every change to the run (4 = charged). Use it to drop out-of-order webhooks. |

## Getting the result

> [card] [Webhook (recommended)](/docs/concepts/webhooks.md)
>
> Add `webhook.url` to the run and get its terminal event, signed.

> [card] Polling
>
> `GET /v1/runs/{id}?wait_seconds=30` waits up to 60 seconds for a final status.

Without a wait, poll by the `Retry-After` header. Without one, start at 1 s (search and scrape), 2 s (images) or 5 s
(video and audio), back off by 1.5× up to 30 s, and stop at a final status. Polling has its own rate limit, so it
never uses your create quota.


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