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

# Extractions API: Typed Fields from Text, Images, and PDFs

> Pull typed fields (text, numbers, dates, lists, estimates, forecasts) out of text, images, and PDFs with the alpha /api/alpha/extractions route. Request shape, field types, assets, provider options, and per-model limits.

`POST /api/alpha/extractions` asks an extraction model for typed fields: the invoice number in a document, the total as a number, a due date, a list of names, or an estimate with its distribution. You describe each field once in a `fields` map and get one typed answer per field back, with the probability that the text contains nothing for it.

The route is alpha and its shape can change. It takes the [Decisions](https://openrouter.ai/docs/api/api-reference/alphadecisions/submit-a-decisions-request.md) envelope (`model`, `state`, `assets`, `provider`, `user`, `session_id`, `trace`) with a `fields` map in place of `questions`. The envelope is shared, but the specs inside it are not: a field spec is one of the types in the table below, not a Decisions question (`noul`, `choice`, `score`, `box`), so moving a request between the routes means rewriting each question as a field as well as renaming the key. A top-level `questions` field on this route fails with a `400` that says so.

## Supported models

| Model | Reads | Field types | Provider options |
| - | - | - | - |
| [`cloudflare/clairic`](https://openrouter.ai/cloudflare/clairic) | Text and up to 4 PNG, JPEG, or WebP images | Every type in the table below (no `null_threshold` on `list`, `estimate`, or `forecast`) | `include_quantiles`, `return_state_text` under `provider.options.cloudflare` |
| [`inception/mercury-decide`](https://openrouter.ai/inception/mercury-decide) | Text only | `text`, and `list` with `items: "text"` | `snap_to_words`, `threshold`, `group_fields` under `provider.options.inception` |
| [`levanto/sage-2`](https://openrouter.ai/levanto/sage-2) | Text only | `text`, `number` (as stated: no `unit`), `date`, `time`, `datetime`, `duration`, and `list` of any item type (no `null_threshold` on lists); no `reference_date` | None |
| [`scaledown/extract`](https://openrouter.ai/scaledown/extract) | Text, or one PDF or image | `text`, and `list` with `items: "text"` | `threshold`, `top_n` under `provider.options.scaledown` |

A request a model cannot serve (a `date` field on Mercury Decide, an image on a text-only model, a fifth image on Clairic) fails with a `400` from OpenRouter before anything is sent to the provider. The response `provider` field names the provider that answered.

To list these models programmatically, filter the Models API by the request path they serve; `output_modalities` cannot select them because Extractions is served by text and decisions models alike:

```bash lines theme={null}
curl "https://openrouter.ai/api/v1/models?endpoints=/api/alpha/extractions"
```

## Quickstart

```bash theme={null}
curl https://openrouter.ai/api/alpha/extractions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "cloudflare/clairic",
    "state": "Invoice INV-2291 issued 2026-09-02, net 30. Contacts: Dana Ruiz, Priya Shah. Total $1080.",
    "fields": {
      "invoice_number": { "type": "text" },
      "due_date": { "type": "date", "instructions": "Issue date plus the net payment days" },
      "total": { "type": "number", "decimals": 2, "unit": "USD" },
      "contacts": { "type": "list", "items": "text" },
      "days_to_pay": { "type": "estimate", "min": 0, "max": 120, "step": 1 }
    },
    "reference_date": "2026-10-28"
  }'
```

The response has one answer per field, keyed by the field name:

```json theme={null}
{
  "id": "gen-dec-1793251006-X5e5eKGQdvR9rblyX250",
  "model": "cloudflare/clairic",
  "provider": "Cloudflare",
  "answers": {
    "invoice_number": { "type": "text", "value": "INV-2291", "span": [8, 16], "guess": "INV-2291", "p_null": 3.8e-10, "confidence": 1 },
    "due_date": { "type": "date", "value": "2026-10-02", "guess": "2026-10-02", "p_null": 1.9e-8 },
    "total": { "type": "number", "value": 1080, "guess": 1080, "p_null": 2.3e-8, "confidence": 0.9999 },
    "contacts": { "type": "list", "value": ["Dana Ruiz", "Priya Shah"], "spans": [[53, 62], [64, 74]], "confidence": 0.9999 },
    "days_to_pay": { "type": "estimate", "value": 30, "mean": 29.5, "median": 30, "p10": 21, "p90": 38 }
  },
  "truncated": false,
  "usage": { "input_tokens": 655, "output_tokens": 0, "cost": 0.0000655, "state_tokens": 61, "image_tokens": 0, "schema_tokens": 472, "passes": 3 }
}
```

## Request

* `model` is a model slug from the table above.
* `state` is the text to read from. It may be empty or omitted only when the request carries assets, which are then read without being referenced.
* `fields` maps each field name to a typed field spec. At least one field is required. Unknown keys at the top level or inside a field are rejected with a `400`, not ignored, so a provider option in the wrong place never silently does nothing.
* `reference_date` (`YYYY-MM-DD`) anchors dates written without a year. Models without date fields reject it.
* `assets`, `provider`, `user`, `session_id`, and `trace` work as on `/api/alpha/decisions`; see [Assets](#assets) and [Provider options](#provider-options).

## Field types

Every field takes `type`, an optional `instructions` string saying what the field should contain, and an optional `null_threshold`: the probability of null above which the answer's `value` is `null` (default `0.5`). Clairic takes no `null_threshold` on a `list`, an `estimate`, or a `forecast`, and requires it above `0` elsewhere. The other keys depend on the type. A key marked `?` is optional.

| Field `type` | Field keys | Answer keys |
| - | - | - |
| `text` | `instructions?`, `unit?`, `null_threshold?` | `value`, `guess?`, `span?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |
| `number` | `instructions?`, `unit?`, `integer?`, `decimals?`, `null_threshold?` | `value`, `guess?`, `span?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |
| `date` | `instructions?`, `unit?`, `null_threshold?` | `value`, `guess?`, `span?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |
| `time` | `instructions?`, `unit?`, `null_threshold?` | `value`, `guess?`, `span?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |
| `datetime` | `instructions?`, `unit?`, `null_threshold?` | `value`, `guess?`, `span?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |
| `duration` | `instructions?`, `unit?`, `integer?`, `decimals?`, `null_threshold?` | `value`, `guess?`, `unit?`, `span?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |
| `list` | `instructions?`, `items`, `unit?`, `integer?`, `decimals?`, `null_threshold?` | `value`, `guess?`, `spans?`, `confidences?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |
| `estimate` | `instructions?`, `unit?`, `min`, `max`, `step?`, `scale?`, `null_threshold?` | `value`, `guess?`, `mean?`, `median?`, `p10?`, `p90?`, `p_below?`, `p_above?`, `quantiles?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |
| `forecast` | `instructions?`, `unit?`, `min`, `max`, `step?`, `scale?`, `horizon`, `interval`, `null_threshold?` | `value`, `guess?`, `mean?`, `median?`, `p10?`, `p90?`, `steps?`, `p_null?`, `confidence?`, `source?`, `spell_confidence?` |

* `number` and `duration` take `unit`, `integer`, and `decimals` (0 to 6 decimal places kept in `value`). A `duration` `unit` is `seconds`, `minutes`, `hours`, `days`, `weeks`, `months`, or `years`. A provider that returns durations in ISO 8601 form (Sage) has them converted to the requested unit: clock units (`seconds` to `weeks`) convert exactly among themselves, as do `months` and `years` (twelve months to the year); across that divide a month is 30 days and a year 365, so `P1Y` in `days` is `365` and `P12M` is `360`. Ask for `months` or `years` when the document states a calendar span and a precise day count matters.
* `text`, `date`, `time`, and `datetime` take an optional `unit` label, which Clairic reads and the other models reject.
* `list` takes `items`: `text`, `number`, or `date`. A list of numbers also takes `integer` and `decimals`. A list also takes an optional `unit` label, which Clairic reads and the other models reject.
* `estimate` and `forecast` take `min` and `max` (numbers, or ISO dates for date-valued estimates), an optional `step` (a positive number, in days for date estimates, or `"month"` or `"year"`), and an optional `scale` of `linear` or `log`. A `forecast` also takes `horizon` (1 to 64 steps) and `interval` (`step`, `minute`, `hour`, `day`, `week`, `month`, `quarter`, or `year`).

## Answers

* `value` is the typed answer, or `null` when `p_null` crossed the field's threshold. `guess` is the most likely value even then, and is absent when the model has none to offer.
* `p_null` is the probability that the state contains nothing for the field; `confidence` is the model's confidence in `value`.
* `span` (`spans` for lists, one per item) is `[start, end)` as Unicode code point offsets into the state text, so an emoji or an accented letter counts once. Both are optional: an answer read from an image or a document can come without one. When `spans` is present, an item with no offsets has `null` at its position, so check each entry before indexing into it. When ScaleDown returns offsets that are inside the state but don't delimit the returned text, the answer keeps its `value` and `confidence` and has no `span` (`null` in `spans`). This happens for an entity that crosses a line break, which ScaleDown returns with its whitespace normalized. Set `return_state_text` on Clairic to get the exact text the offsets index into back as `state_text`.
* `source` is `image` or `document` when the answer was read from an asset rather than the state text; `spell_confidence` then reports how sure the model is of the spelling.
* `estimate` and `forecast` answers carry the distribution: `mean`, `median`, `p10`, `p90`, `p_below` and `p_above` (probability mass outside `min` and `max`), `quantiles` when asked for, and per-step values under `steps` for forecasts.
* `truncated` is `true` when the provider read only a prefix of the state. `usage.passes` counts the upstream calls the fields needed when they did not fit in one.

## Assets

Images and PDFs go in a top-level `assets` map, each sent once and referenced by its key. An asset is `{ "type": "image" | "document", "format": "png" | "jpg" | "jpeg" | "webp" | "pdf", "data_base64": "..." }`; `type` is optional and `data_base64` is the file in standard base64 with no `data:` prefix. When `state` has text, every asset must be referenced in `state` or a field's `instructions`, or the request fails with a `400` naming the unused key. When `state` is empty or omitted, the assets are what is read and need no reference: every asset is sent, and an instruction that mentions one of several inside longer text does not limit the read to it. An `instructions` value that is exactly one asset's key is still refused when there are two or more images (see the Clairic note below).

```json theme={null}
{
  "model": "cloudflare/clairic",
  "state": "Read the totals from the invoice photo: invoice-photo",
  "assets": {
    "invoice-photo": { "format": "jpg", "data_base64": "/9j/4AAQSkZJRg..." }
  },
  "fields": {
    "total": { "type": "number", "decimals": 2 }
  }
}
```

* An empty or omitted `state`, or one that is exactly the sole asset's key, carries no text of its own and is read as file-only.
* Clairic reads up to 4 PNG, JPEG, or WebP images, each at most 4 MiB decoded, 8 MiB decoded in total, in a request body of at most 16 MiB. It does not read PDFs.
* Clairic reads images positionally, in `assets` key order, and has no in-text reference syntax. With one image, either file-only form above works. With two or more images, a key used as the whole `state` or as the whole `instructions` of a field fails with a `400` (the model could not tell which image is meant), so name each image inside longer text instead; when `state` has text, every key still has to appear somewhere, or the request fails with the unused-asset `400` above:

```json theme={null}
{
  "model": "cloudflare/clairic",
  "state": "Receipt photos, front then back: receipt-front, receipt-back",
  "assets": {
    "receipt-front": { "format": "jpg", "data_base64": "/9j/4AAQSkZJRg..." },
    "receipt-back": { "format": "jpg", "data_base64": "/9j/4AAQSkZJRg..." }
  },
  "fields": {
    "total": { "type": "number", "decimals": 2 },
    "return_policy": { "type": "text", "instructions": "The return policy printed on receipt-back" }
  }
}
```

* ScaleDown Extract reads one PDF, PNG, or JPEG per request (a WebP fails with a `400`), and reads either the text or the document, not both: send the file-only form of `state` or omit `state`.
* Mercury Decide and Sage read text only; any asset fails with a `400`.

## Provider options

Provider-specific knobs go under `provider.options.<provider slug>`, and each model accepts only its own keys; anything else fails with a `400` naming the accepted keys.

* `cloudflare`: `include_quantiles` adds 99 percentiles to estimates as `quantiles`; `return_state_text` returns the text that spans index into as `state_text`.
* `inception`: `snap_to_words` and `threshold` pass through to Mercury Decide; `group_fields` sends all fields in one question (up to 26 per upstream pass) instead of one question per field, which costs fewer state copies but lets a token belong to only one field.
* `scaledown`: `threshold` and `top_n` pass through to Extract. Extract reports no null probability, so `null_threshold` on a field fails with a `400`.

## Related

* [Extractions API reference](https://openrouter.ai/docs/api/api-reference/alphaextractions/submit-an-extractions-request.md) has the full request and response schema.
* [Extract invoice fields with ScaleDown Extract](https://openrouter.ai/docs/cookbook/evaluate-and-optimize/extract-invoice-fields-with-scaledown.md) walks through the same task on the Responses API.
* [Multimodal Decisions](https://openrouter.ai/docs/guides/community/multimodal-decisions.md) covers the Decisions route this one shares its envelope with.
