Extraction schemas
The small language for describing what to take off a page, including the list type that keeps a row's fields together instead of returning parallel arrays.
Scalars
{ "title": "string", "price": "number", "in_stock": "boolean" }Types are string, number, boolean, string[], number[]. A bare type lets the structured-data tier find the field by name. To pin it down, give a selector:
{ "price": { "type": "number", "selector": ".price", "attribute": "content" } }Lists
A listing page is rows, not columns. Ask for it that way:
{
"listings": {
"type": "list",
"selector": ".product-card",
"limit": 100,
"item": {
"name": { "type": "string", "selector": "h2 a" },
"price": { "type": "number", "selector": ".price" },
"url": { "type": "string", "selector": "h2 a", "attribute": "href" }
}
}
}Why this exists. The alternative — one array per column — is correct exactly while every card carries every field. One card missing a price shortens that array alone, every later pair is off by one, and the result is not an error: it is a table of real names against real prices belonging to different products. Nothing downstream can detect it.
With list, a missing field is a null inside its own record and the rows either side stay aligned.
selector is required for the CSS tier and ignored by the structured-data tier, which finds Product nodes by type — including nested inside ItemList → itemListElement → item, which is how most commerce pages publish a category.
limit defaults to 100 (maximum 500). Items beyond it are not returned — and the response says so: metadata.lists.<field> carries how many items the page had (seen), how many came back (returned), the limit, and truncated. The entry is there for each list that came back with items. When the LLM tier produced the list it cannot count the page, so seen and truncated are null rather than a guess — unless it returned more than the limit, which is cut and reported as truncated: true.
Numbers
Prices arrive as text. number handles currency symbols, spaces, and both separator conventions: ¥1,234,500, $1,299.00, 1.299,00 €, 9 800 円.
A separator that repeats is grouping, in every locale — no notation has two decimal points. So 1,234,500, and every price over a million, parses correctly as a number.
The ladder
| Tier | What it does | Cost |
|---|---|---|
css | Reads selectors you supply | none |
structured_data | Reads JSON-LD and OpenGraph the site already publishes | none |
llm | Describes the page to a model | tokens |
Tiers run in the order you list them and results merge: a later tier fills only what its predecessors left empty. Pass ["css", "structured_data"] to guarantee no model is called.
An empty array counts as *not found*, not as a value — otherwise a stale selector returning no rows would stop the ladder and the structured data on the same page would never get a turn.
Last updated