Docs
Dashboard
Guides / WebBeta

Extract

Turn one or more pages into JSON that matches your schema, or answer a question about them.

POST /v1/extract turns web pages into JSON. It opens up to 10 pages in a real browser, then an AI model fills your JSON Schema, follows your prompt, or both. The model reads each page as Markdown, like fetch gives it: links and images are absolute addresses, so a link or image address it returns works as it is, and heading permalinks are left out.

Request

curl -X POST https://api.boxline.dev/v1/extract \  -H "x-api-key: $BOXLINE_API_KEY" \  -H "content-type: application/json" \  -d '{    "url": "https://example.com/pricing",    "prompt": "The name and monthly price of every plan",    "schema": {      "type": "object",      "properties": {        "plans": {          "type": "array",          "items": {            "type": "object",            "properties": { "name": { "type": "string" }, "price": { "type": "string" } }          }        }      }    }  }'
FieldDefaultDescription
url or urlsrequiredOne page, or a list of 1 to 10. The model sees them all at once. With several, a page that does not load is listed in the answer instead of failing the call: see Several pages.
promptnoneWhat to pull out, in plain words. Up to 5,000 characters. You need a prompt, a schema, or both.
schemanoneA JSON Schema the result must follow. Up to 20,000 characters as JSON.
providerfirst configured"anthropic" or "openai".
modela fast modelclaude-haiku-5-5 (Anthropic) or gpt-6-luna (OpenAI) unless you name another from the model list.

It also takes the options every web API shares:

FieldDefaultDescription
waitUntil"load"When the page counts as loaded: "load", "domcontentloaded", "networkidle" (no network traffic for a moment) or "commit".
delayMs0Extra wait after the page loads, up to 10,000 ms. Useful for pages that draw late.
timeoutMs30000How long to wait for the page, from 1,000 to 120,000 ms.
proxynoneLoad the page through a residential, datacenter or your own proxy, e.g. { "type": "residential", "country": "FR" }. The IP changes with every request unless you ask for "ip": "sticky". See Proxies.
browserstandardThe page’s clock and language, e.g. { "mode": "realistic" } to follow the proxy’s country, or { "locale": "fr-FR", "timezone": "Europe/Paris" }. See Browser mode.
blockAdsfalseRefuse requests to ad and tracker sites while the page loads: faster, with less clutter, and no proxy data spent on them. See Ad and cookie-banner blocking.

Response

Response (shape)
{  "data": { "plans": [{ "name": "Pro", "price": "$20" }] },  "pages": [{ "url": "https://example.com/pricing", "finalUrl": "https://example.com/pricing", "status": 200, "title": "Pricing" }],  "provider": "anthropic",  "model": "claude-haiku-5-5",  "usage": { "inputTokens": 5120, "outputTokens": 96, "costUsd": 0.0056 },  "ms": 4210}
  • data is the result, shaped by your schema. The model uses null (or leaves a field out) when the pages do not say.
  • pages says what each URL loaded as, in the order you gave them: the address you asked for, where it ended up, its HTTP status and its title.
  • usage.costUsd is the model cost of this request.

Several pages

With several urls, one page that does not load does not fail the call. A page that could not be loaded (page_unreachable, page_timeout) or is a PDF (not_a_web_page) is left out of what the model reads, and is listed in pages with status: null, finalUrl: null and an error:

Response (shape), the second page did not load
{  "data": { "plans": [{ "name": "Pro", "price": "$20" }] },  "pages": [    { "url": "https://example.com/pricing", "finalUrl": "https://example.com/pricing", "status": 200, "title": "Pricing" },    {      "url": "https://status.example.invalid/",      "finalUrl": null,      "status": null,      "title": "",      "error": {        "code": "page_unreachable",        "message": "could not load status.example.invalid: ERR_TUNNEL_CONNECTION_FAILED (the name does not resolve (DNS))"      }    }  ],  "provider": "anthropic",  "model": "claude-haiku-5-5",  "usage": { "inputTokens": 2630, "outputTokens": 96, "costUsd": 0.0031 },  "ms": 5300}
  • The call fails only when no page loads. Then you get the first page’s error, as one page would (502, 504 or 422). Any other kind of error still fails the call.
  • With one url, its error is the call’s error: 502 page_unreachable, 504 page_timeout or 422 not_a_web_page.
  • The model only sees the pages that loaded, and you pay for those only. Check pages for error before you trust a result that should cover every page.
const result = await bx.extract({  urls: ["https://example.com/pricing", "https://example.org/pricing"],  prompt: "The name and monthly price of every plan",});for (const page of result.pages) {  if (page.error) console.log("not read:", page.url, page.error.code, page.error.message);}

Page text is data

Pages are treated as data, never as instructions: the model is told to ignore any instructions a page contains. Still, check the result before you act on it, as you would with any text from the web.

Cost and limits

  • Each request is billed for its model use. See Pricing.
  • Model use counts toward your plan’s monthly spend cap. Past it you get 402 spend_limit until the 1st of the month (UTC). Extract on your own model key is not charged by us and is outside that cap. The Free plan runs models on your own keys only. See Your own model keys.
  • 30 requests per minute per project. Your plan must include extract (402 feature_not_in_plan).

Errors

Status and codeWhat it means
502 page_unreachableThe page could not be loaded. The message names the host and Chrome’s reason, e.g. could not load example.com: ERR_NAME_NOT_RESOLVED. When Chrome’s reason is a generic one, the message adds the real one in words, e.g. (the name does not resolve (DNS)). See why a page did not load.
504 page_timeoutThe page did not finish loading within timeoutMs. Try a longer time or another waitUntil.
422 not_a_web_pageThe address is a PDF. Chrome only displays it, so there is no page text. Download it in a session (with the files API, or curl in its shell) and read it there, for example with pdftotext. The SDKs raise NotAWebPageError and never retry it.
402 feature_not_in_planYour plan does not include this API, or the kind of proxy you asked for.
429 rate_limitedToo many requests. Wait for the seconds in Retry-After. See Rate limits.

The model can also fail: 422 model_refused when it declines, 502 extraction_failed when it returns no usable data. To pull data from a page you are already on in a session, use the extract step instead.

These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.