跳到正文
OpenRouter Announcements·· 2 天前AI 评分58

OpenRouter 上线 Extractions API 测试版,支持从文本、图片和 PDF 抽取结构化字段

Introducing the Extractions API

AI 导读

OpenRouter 上线 alpha 版 Extractions API,用户描述所需字段即可从文本、图片和 PDF 中抽取结构化值,首发接入 Cloudflare Clairic、ScaleDown Extract 和 Inception Mercury Decide 三个模型。

正文 · 原文

The Extractions API is now live on OpenRouter in alpha. Use it to pull specific details out of text, images, and PDFs by describing the fields you need, like the names in an email or the total on a receipt.

We’re launching with three extraction models from Cloudflare, Inception, and ScaleDown. Get started by reading the docs or trying it out in the Playground.

An extraction is a request that names the fields you want out of a document and gets one typed value per field back. state carries the text (or the key of an image or PDF in assets), and fields maps each name to a spec: a type, and optionally instructions that say what the field means.

There are nine field types, and each comes back as a JSON type you can store without parsing. Six of them were asked of this invoice, sent as state; the examples in the table are what Clairic returned, and the highlighted words are where the values came from:

state · invoice, as text

Invoice INV-4417 from Larkspur Studio, issued 2026-10-14, payment terms net 45. Bill to: Harbor & Pine LLC. Contacts: Dana Ruiz (dana@harborpine.example), Priya Shah. Line items: brand audit 2,400.00; icon set 1,150.00; rush fee 300.00. Total due $3,850.00. Late payments accrue 1.5% per month.

The fields asked for were invoice_number: text, total: number, due_date: date, contacts: list, po_number: text and days_to_pay: estimate.

TypeWhat you getExample
textA string read from the document"INV-4417"
numberA number, with decimals and unit you choose3850
dateAn ISO date, read or computed"2026-11-28"
timeAn ISO time of daynot asked of this invoice
datetimeAn ISO date and timenot asked of this invoice
listAn array of text, number or date items (items picks which)["Dana Ruiz", "Priya Shah"]
durationA number in the unit you choose, from seconds to years45 with unit: "days"
estimateA distribution for a quantity the document implies, between a min and max you setmedian 45, p10 42, p90 47
forecastThe same, for horizon steps into the future at an interval you set; value, mean, median, p10 and p90 are arrays with one entry per stepnot asked of this invoice

Every answer carries its value; what else it carries depends on the field type and the model. On Clairic, a value read from the text comes with a span, [start, end) into the state counted in Unicode code points, so you can slice the document and show where it came from (a list carries spans, one per item). Single values carry p_null, the probability that the document holds nothing for the field; a value becomes null when it passes 0.5 (set null_threshold per field to move that line), and guess is the most likely value even then. They also carry confidence, the model’s confidence in the value, and a string read from an image adds spell_confidence for its spelling. An estimate or forecast carries no confidence; mean, median, p10 and p90 describe its distribution, and the width of p10 to p90 is the uncertainty. Extract returns a confidence per value and no p_null; on text it returns spans too, on a PDF none (section 3).

Those fields are the trust model. Set a confidence floor, send anything under it to a person, and store the rest. A null is an answer, not a low score: the model judges the field absent (its p_null passed the threshold), and guess says what it would have reached for. A value with no confidence, such as an estimate, is judged by its distribution instead.

What goes in is the document and a list of named, typed fields. What comes out is one value per field, a span where a value was read from the text, a confidence and p_null on single values, and a distribution for an estimate.

The request, run on Clairic for $0.00011:

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-4417 from Larkspur Studio, issued 2026-10-14, payment terms net 45. Bill to: Harbor & Pine LLC. Contacts: Dana Ruiz (dana@harborpine.example), Priya Shah. Line items: brand audit 2,400.00; icon set 1,150.00; rush fee 300.00. Total due $3,850.00. Late payments accrue 1.5% per month.",
    "fields": {
      "invoice_number": { "type": "text" },
      "total": { "type": "number", "decimals": 2, "unit": "USD" },
      "due_date": { "type": "date", "instructions": "Issue date plus the net payment days" },
      "contacts": { "type": "list", "items": "text", "instructions": "People named as contacts" },
      "po_number": { "type": "text", "instructions": "Purchase order number, if any" },
      "days_to_pay": { "type": "estimate", "min": 0, "max": 120, "step": 1, "instructions": "How many days after issue this invoice will actually be paid" }
    },
    "reference_date": "2026-11-06"
  }'

Clairic read 1,138 input tokens and answered (the response, less its model line):

{
  "answers": {
    "invoice_number": { "type": "text", "value": "INV-4417", "span": [8, 16], "p_null": 4.9e-6, "confidence": 0.9999 },
    "total": { "type": "number", "value": 3850, "p_null": 1.6e-5, "confidence": 0.9999 },
    "due_date": { "type": "date", "value": "2026-11-28", "p_null": 4.4e-5, "confidence": 0.9994 },
    "contacts": { "type": "list", "value": ["Dana Ruiz", "Priya Shah"], "spans": [[118, 127], [155, 165]] },
    "po_number": { "type": "text", "value": null, "guess": "INV-4417", "p_null": 0.9999 },
    "days_to_pay": { "type": "estimate", "value": 45, "mean": 44.79, "median": 45, "p10": 42, "p90": 47 }
  },
  "truncated": false,
  "usage": { "input_tokens": 1138, "output_tokens": 0, "cost": 0.0001138, "state_tokens": 118, "schema_tokens": 666, "passes": 4 },
  "id": "gen-dec-1794000212-JVtrkaQ2JbqaR59u2bjZ",
  "provider": "Cloudflare"
}

due_date has no date in the text, so the model computed it from the issue date and the terms; po_number came back null with guess showing what it would have reached for; days_to_pay is an estimate, so it returns a distribution rather than a point, with 80% of the mass between 42 and 47 days. Slice the state with each span to show the evidence next to the value. In Python, with r the requests response to the call above:

state = "Invoice INV-4417 from Larkspur Studio, issued 2026-10-14, payment terms net 45. Bill to: Harbor & Pine LLC. Contacts: Dana Ruiz (dana@harborpine.example), Priya Shah. Line items: brand audit 2,400.00; icon set 1,150.00; rush fee 300.00. Total due $3,850.00. Late payments accrue 1.5% per month."
answers = r.json()["answers"]

for name, answer in answers.items():
    spans = answer.get("spans") or ([answer["span"]] if answer.get("span") else [])
    evidence = [state[start:end] for start, end in spans]
    print(f"{name}: {answer['value']!r} <- {evidence!r}")

What goes in is the image, in assets, referenced by its key from state, plus the same kind of field list. What comes out is the same shape of record, with source: "image" on values the model marks as read off the picture (source is optional and paid_on below lacks it, so do not key on it) and a spell_confidence on text. The boxes on the photo in the clip are drawn for the demo; the API returns values, not image coordinates.

The request, run on Clairic for $0.00025:

import base64
import os

import requests

API = "https://openrouter.ai/api/alpha/extractions"
HEADERS = {"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"}

with open("receipt.png", "rb") as f:
    receipt = base64.b64encode(f.read()).decode()

r = requests.post(
    API,
    headers=HEADERS,
    json={
        "model": "cloudflare/clairic",
        "state": "receipt",
        "assets": {"receipt": {"type": "image", "format": "png", "data_base64": receipt}},
        "fields": {
            "merchant": {"type": "text"},
            "total": {"type": "number", "decimals": 2},
            "paid_on": {"type": "date"},
            "tax": {"type": "number", "decimals": 2, "instructions": "Sales tax charged"},
            "tip": {"type": "number", "decimals": 2, "instructions": "Gratuity, if any"},
        },
    },
)
r.raise_for_status()
print(r.json()["answers"])

Clairic read 1,064 image tokens and 2,532 in total, and answered (the response, less its model line):

{
  "merchant": { "type": "text", "value": "NORTH HAROR C", "p_null": 8.0e-5, "confidence": 0.733, "source": "image", "spell_confidence": 0.733 },
  "total": { "type": "number", "value": 29.04, "p_null": 5.3e-8, "confidence": 0.9995, "source": "image" },
  "paid_on": { "type": "date", "value": "2026-10-24", "p_null": 0.0061, "confidence": 0.9999 },
  "tax": { "type": "number", "value": 3.79, "p_null": 2.8e-4, "confidence": 0.9982, "source": "image" },
  "tip": { "type": "number", "value": null, "guess": 0, "p_null": 0.9999, "confidence": 0.8923 }
}

Every non-null numeric and date field came back above 0.99 confidence, and tip is an explicit null rather than a made-up zero, with p_null at 0.9999. The merchant name is wrong (the receipt says “NORTH HARBOR CAFE”), and Clairic says so: spell_confidence is 0.73, a third below everything else in the response. That is the field you route to a person, and the response told you which one.

PDFs go in assets too, with "type": "document" and "format": "pdf". Clairic reads images only, so a PDF goes to ScaleDown Extract, which reads one PDF or image per request and answers text and list fields. The boxes on the page in the clip are drawn for the demo; Extract returns values and confidences, not page coordinates.

The request, run on Extract for $0.00016:

curl https://openrouter.ai/api/alpha/extractions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "model": "scaledown/extract",
  "state": "po",
  "assets": { "po": { "type": "document", "format": "pdf", "data_base64": "$(base64 < purchase-order.pdf | tr -d '\n')" } },
  "fields": {
    "po_number": { "type": "text" },
    "vendor": { "type": "text", "instructions": "The supplier the order is placed with" },
    "total": { "type": "text", "instructions": "Order total including shipping" },
    "delivery_date": { "type": "text", "instructions": "Requested delivery date" },
    "items": { "type": "list", "items": "text", "instructions": "Ordered line items" },
    "approver": { "type": "text", "instructions": "Who approved the order" }
  }
}
EOF

Extract read 3,972 input tokens from the PDF and answered in 2.9 seconds (the response, less its model line):

{
  "answers": {
    "po_number": { "type": "text", "value": "PO-20931", "confidence": 1, "source": "document" },
    "vendor": { "type": "text", "value": "Meridian Office Supply", "confidence": 1, "source": "document" },
    "total": { "type": "text", "value": "$4,200.00", "confidence": 0.814, "source": "document" },
    "delivery_date": { "type": "text", "value": "November 18, 2026", "confidence": 0.979, "source": "document" },
    "items": { "type": "list", "value": ["Standing desk, 60 in, walnut", "Task chair, mesh back", "Monitor arm, dual"], "confidences": [0.994, 0.999, 1], "source": "document" },
    "approver": { "type": "text", "value": "Priya Shah", "confidence": 1, "source": "document" }
  },
  "truncated": false,
  "usage": { "input_tokens": 3972, "output_tokens": 0, "cost": 0.00015888 },
  "id": "gen-dec-1794274074-RzjUzkyrf7WfR9LpFUTO",
  "provider": "ScaleDown"
}

Every value is right, including the three line items out of a table. Two differences from Clairic to plan for: Extract has no number or date types, so the total and the delivery date come back as the strings on the page, and it reports a confidence per value (and per list item) but no p_null or span. The total at 0.81 is the field to glance at; the rest are at or near 1.

Model list

Three models are available at launch, and the same request works with all of them, so you can pick the one that fits the job. Use Clairic for images, like a photo of a receipt, and when you need numbers, dates, or estimates back. Use ScaleDown Extract to pull text out of a PDF, like the parties in a contract. Use Mercury Decide to pull names or phrases out of plain text, like emails or support tickets.

ModelReadsField typesInput priceProvider options
Clairic (cloudflare/clairic)Text and up to 4 PNG or JPEG imagesEvery type$0.10/Minclude_quantiles, return_state_text under provider.options.cloudflare
Extract (scaledown/extract)Text, or one PDF or imagetext, and list of text$0.04/Mthreshold, top_n under provider.options.scaledown
Mercury Decide (inception/mercury-decide)Text onlytext, and list of text$0.02/Msnap_to_words, threshold, group_fields under provider.options.inception

Output is free on all three. Prices are the catalog values on November 6, 2026; the model pages have the current ones.

Cloudflare’s Clairic

Clairic (cloudflare/clairic), Cloudflare’s extraction model on Workers AI, is the one to start with. It reads text and up to four PNG or JPEG images per request, supports every field type, and is the only launch model that returns p_null, spans and distributions for estimate and forecast. Sections 1 and 2 above ran on it. Two provider options extend the response: include_quantiles: true returns 99 percentiles for every estimate and forecast, and return_state_text: true echoes back the exact text the span offsets index into, so you can check a slice against it.

Extract (scaledown/extract) reads text, or one PDF or image per request, and answers text and list fields with a confidence per value. It is the model for PDFs today (section 3). It reports no null probability, so it rejects null_threshold; threshold and top_n under provider.options.scaledown tune how it picks values.

Inception’s Mercury Decide

Mercury Decide (inception/mercury-decide) reads text only and answers text and list fields at the lowest input price of the three. It rejects reference_date; snap_to_words, threshold and group_fields under provider.options.inception are its options.

Text-only fields run anywhere. We sent the text and list fields of the invoice in section 1 to Mercury Decide and Extract: both returned the same values, and on text the same spans as Clairic, for $0.000015 and $0.000014. Extract returns no spans for a PDF (section 3).

Both routes take the same envelope (model, state, assets, provider) and differ in what you hand the model and what it hands back.

Decisions: you write the answers, the model weighs them. A Decisions request carries questions, and every question comes with its answer space: a noul is one proposition that is true or false, a choice lists the options, a score names a scale, a box asks for a region in an image. For a noul, choice or score, the model never produces a value you did not write down; it distributes probability over the answers you wrote (a box returns coordinates for the region you described). That is what makes it the right tool for anything that is a judgment call: is this an invoice, which of these five terms applies, how severe is this, does this image contain a receipt. The output is a calibrated number you can threshold, and a wrong answer is one of the options you already handle.

Extractions: you name the field, the model finds the value. An Extractions request carries fields, and each field is a name and a type: text, number, date, list, duration, estimate, forecast. The answer space is the type, not a list you wrote, so the model has to locate the value in the document (or compute it, for a due date from issue date and terms, or estimate it, for days to payment). Because the value is open, the answer comes with evidence: the span it was read from, p_null for whether the document holds anything for it at all, and confidence. That is the right tool when the answer is the content itself: the number on the receipt, the names on the contract, the date on the form.

The same invoice as section 1, through Decisions on Clef Flash:

{
  "questions": {
    "overdue": { "type": "noul", "instructions": "The invoice is past due as of 2026-11-06." },
    "terms": { "type": "choice", "instructions": "What are the payment terms?", "criteria": { "net 15": null, "net 30": null, "net 45": null, "net 60": null, "due on receipt": null } },
    "has_late_fee": { "type": "noul", "instructions": "The invoice states a penalty for late payment." }
  }
}
{
  "answers": {
    "overdue": { "type": "noul", "noul": 0.2069 },
    "terms": { "type": "choice", "choice": "net 45", "probabilities": { "net 15": 0.0096, "net 30": 0.0131, "net 45": 0.9623, "net 60": 0.0096, "due on receipt": 0.0055 }, "confidence": 0.908 },
    "has_late_fee": { "type": "noul", "noul": 0.9358 }
  },
  "usage": { "input_tokens": 466, "output_tokens": 0, "cost": 0.000008854 }
}

In the same invoice, Decisions answers that the payment terms are net 45, with 0.96 confidence, and that the invoice isn’t overdue. Extractions (section 1) returns the invoice number INV-4417, found at characters 8 to 16, and a total of 3850.

As a rule of thumb, use Decisions when you can list the possible answers before you send the request. Use Extractions when the answer is a value written in the document.

You can also use them together. Decisions can check whether a document is an invoice, and Extractions can then pull the fields out of the ones that are.

The two APIs take nearly the same request. The difference is that Decisions asks questions that have a set of possible answers, like yes or no or a choice between options, while Extractions asks for fields that it fills in from the document. To switch a request from Decisions to Extractions, rename questions to fields and rewrite each one as a field.

Getting started

Grab an API key, open the Playground on the Clairic model page and run the invoice above with your own fields, then read the Extractions guide and the API reference. For the same task on the Responses API with ScaleDown Extract, see the cookbook.

Tell us what you’re extracting in #feedback on Discord.

FAQs

Is the API stable?

It is alpha. The shape can change, and the guide is where changes land first.

What happens when a request is wrong?

Validation happens at OpenRouter before anything reaches a provider: an unknown key, a date field on Mercury Decide, a fifth image on Clairic, a PDF on Clairic, or an asset that nothing references is a 400 whose message names the problem. Sending questions to this route is a 400 that points you at /api/alpha/decisions.

How does a field become null?

On Clairic, when p_null passes 0.5. Set null_threshold per field to move the line, and read guess for the value the model would have reached for. Extract reports no p_null, so a null from it is the model’s plain answer and it rejects null_threshold.

What if the document writes dates without a year?

Pass reference_date and the model anchors them to it. Models without a date type reject it: Mercury Decide and Extract.

What if the document is long?

truncated: true in the response means the provider read only a prefix of the state. usage.passes counts the upstream calls your fields needed when they did not fit in one, and schema_tokens is what your field specs cost on every request.

Can I move a Decisions request over?

The envelope is the same; rename questions to fields and rewrite each spec, since a choice with options is a different ask from a text field.

How are spans counted?

In Unicode code points, [start, end) into state. In a language that indexes strings in UTF-16 units, slice by code point: in JavaScript, Array.from(state).slice(start, end).join(''), not state.slice(start, end). return_state_text: true on Clairic echoes the exact text the offsets index into, so you can check the slice.

Does the Playground support it?

Yes: the Clairic model page has a Playground that takes state and fields and shows the typed answers.

来源:OpenRouter Announcements · openrouter.ai