POST /scrape
Fetches a page and extracts values from it with CSS selectors.
Try it
Actions
Data
POST /scrape
Content-Type: application/jsonRequest body
{
"target_url": "https://en.wikipedia.org/wiki/Web_scraping",
"fields": ["h1", "h2"]
}| Property | Type | Required | Description |
|---|---|---|---|
target_url | string | yes (optional with html) | Address of the page to scrape. http or https only |
html | string | no | Extract from this HTML instead of fetching target_url, 5 MB max. target_url becomes optional and only resolves relative links; only options.ai applies. See From your own HTML |
fields | array or object | no | What to extract, 1 to 50 fields. Left out: { "content": { "selector": "body", "output": "markdown" } }, the whole page as markdown (nothing with options.ai, actions, a screenshot or a PDF). An array gives an array of values; an object names the values |
options | object | no | How to fetch the page. See Options |
steps | object[] | no | Several scrapes in a row, 1 to 10, each { target_url, fields, options } defaulting to the top-level ones. target_url and fields are then only required in each step. See Multi-step requests |
continue_on_error | boolean | no | With steps: run every step even if one fails. Default false |
The body must be a JSON object of at most 6 MB, sent with Content-Type: application/json. Unknown properties are ignored.
Response
200 OK
{
"target_url": "https://en.wikipedia.org/wiki/Web_scraping",
"final_url": "https://en.wikipedia.org/wiki/Web_scraping",
"status": 200,
"data": ["Web scraping", "Contents"],
"warnings": [],
"meta": {
"id": "5f0c2d8e-3b1a-4c6e-9d2f-7a8b9c0d1e2f",
"started_at": "2026-10-24T09:12:03.418Z",
"duration_ms": 412,
"engine_ms": 388,
"cached": false,
"attempts": 1,
"strategy": 0,
"bytes": 184213,
"proxy": null,
"fingerprint": null,
"browser": null
}
}What the page holds comes first (data, then what the options add); how the request went is grouped in meta, last. With several pages (steps, /batch, /crawl), each page has its own meta (without id and started_at), and the response's meta describes the whole request.
| Property | Type | Description |
|---|---|---|
target_url | string | The target_url you sent |
final_url | string | Address the data came from, after redirects |
status | number | HTTP status returned by the target site |
redirect_to | string | Only with "follow_redirects": false and a redirect: where it points to |
data | array or object | One value per field: an array for an array of fields, an object for named fields |
actions | object[] | Only with actions: what each one did. See Actions |
captured | object[] | Only with browser.capture: the JSON responses captured. See Capture the page's API calls |
screenshot | object | Only with screenshot: { "content_type": "image/png", "data": "<base64>" }, plus url, file_id and expires_at when the file is kept in storage (GET /v1/files/{id} with your API key, kept 1 to 90 days by plan). See Screenshots & PDF |
pdf | object | Only with pdf: { "content_type": "application/pdf", "data": "<base64>" } |
session | object | Only with session: its id, expiry, proxy, identity and number of cookies. See Sessions |
warnings | string[] | What the API adapted or couldn't do as asked, in plain words. Empty when everything went as asked |
meta | object | How the request went: see below |
meta
| Property | Type | Description |
|---|---|---|
id | string | The request's id (UUID): give it to support, find it in your logs |
started_at | string | When the request reached the API (ISO 8601) |
duration_ms | integer | Time the request took, waiting in the queue included |
engine_ms | integer | Time spent fetching, rendering and extracting |
cached | boolean | true if the page came from the cache |
cache_age | number | Cached answers only: seconds since the page was fetched |
attempts | number | Requests made to the target site: 1, more with retries or fallbacks, 0 when cached |
strategy | number | 0 if the main request got the page, n if options.fallback[n - 1] did. See Fallback strategies |
bytes | integer | Size of the page received from the site, 0 when cached |
proxy | object or null | The proxy used: { "source": "pool", "country": "GB", "city": "London" } or { "source": "custom", "index": 0, … }; null without proxy |
fingerprint | object or null | The fingerprint used, like { "name": "chrome", "platform": "Windows" }; null without fingerprint |
browser | object or null | The browser used: { "engine": "chromium", "driver": "playwright", "stealth": "plugin" }; null without a browser |
data | array or object | One value per field, in the same order (array) or under the same names (object) |
ai | object | Only with options.ai: what the model returned, repairs, steps, cost. See AI extraction |
config | object | Only with options.ai.config or options.ai.example: { target_url, fields, options? }, a request that scrapes this page again without AI. See Generate a config |
Failed requests return an error object instead of data. See Errors.
Fields
The simplest field is a CSS selector string. It returns the text of the first matching element, or null if nothing matched:
"fields": ["h1", "h2"]When you need options, write the field as an object. "h1" and { "selector": "h1" } are the same field.
| Property | Type | Required | Description |
|---|---|---|---|
selector | string | yes* | Standard CSS selector. *One of selector, xpath or extract |
attribute | string | no | Return this attribute (href, src, content…) instead of the text |
multiple | boolean | no | true to return every match as an array. Default: false |
xpath | string | no | XPath instead of selector. See Selecting values |
output | string | no | text, html, markdown or table |
absolute | boolean | no | URL attributes made absolute. Default true |
transform | array | no | Convert values: number, price, date… See Transforms |
items | fields | no | A list of objects, one per match. See Lists & tables |
extract | string | no | Page-level data instead of a selector: json-ld, microdata, open-graph, meta, markdown, text. See Page data |
Strings and objects can be mixed in the same fields.
Get every match
Without `"multiple": true`, a field returns only the first match
"h2" on a page with ten <h2> returns the first one. Set "multiple": true to get all ten.
Actions
Data
{
"data": [
"Web scraping",
["Contents", "History", "Techniques", "…"]
]
}With "multiple": true the value is always an array, [] if nothing matched, even when the page has a single match.
Name your values
With more than a couple of fields, positions get hard to follow. Pass fields as an object instead: each key is a name of your choice, and data comes back as an object with the same names.
Actions
Data
{
"data": {
"title": "Web scraping",
"canonical": "https://en.wikipedia.org/wiki/Web_scraping",
"sections": ["Contents", "History", "Techniques", "…"]
}
}Names start with a letter or _ and contain only letters, digits, _ or -, 64 characters max.
Comma-separated selectors
A selector such as "h1, h2" always returns an array, with the selectors in the order you wrote them, not in the order they appear in the page:
- without
multiple: the first match of each selector, e.g.["Web scraping", "Contents"]; - with
"multiple": true: every match of the first selector, then every match of the next, e.g.["Web scraping", "Contents", "History", …].
A selector that matches nothing is left out of the array.
To tell the values apart, prefer separate fields (["h1", "h2"]) or named values over "h1, h2".
Value types
The type of each value depends only on your request, never on the page, so your code always receives the type it expects:
| Field | Value |
|---|---|
| One selector | string, or null if nothing matched |
One selector, "multiple": true | string[], [] if nothing matched |
Comma-separated selectors, with or without multiple | string[] |
Values are cleaned: whitespace collapsed and trimmed, control characters removed, 10,000 characters max per value, 1,000 values max per array.
Options
| Property | Type | Default | Description |
|---|---|---|---|
method | string | GET | HTTP method sent to the target: GET, POST, PUT, PATCH or DELETE. See Custom requests |
headers | object | — | Headers sent to the target. A User-Agent here replaces the default one |
cookies | object | — | Cookies sent to the target, as { "name": "value" } |
body | string | — | Request body sent to the target, not with GET |
timeout | integer (ms) | 15000 | Time allowed per attempt, 1000 to 60000. See Timeouts & redirects |
follow_redirects | boolean | true | false returns a redirect as is, with redirect_to |
max_redirects | integer | 5 | Redirects followed before TOO_MANY_REDIRECTS, 0 to 10 |
proxy | boolean or object | — | Fetch through a proxy: true, { "country": "GB" }, { "country": ["GB", "US"], "city": "London" }, or your own { "list": [...] } (HTTP, HTTPS, SOCKS4/5) with country, region and city filters. See Proxies |
browser | boolean, string or object | — | Load the page in a real browser: true, an engine ("camoufox"…) or options. See Browsers |
actions | object[] | — | Steps run in the page before extracting: click, fill, scroll… With or without a browser. See Actions |
screenshot | boolean or object | — | PNG or JPEG of the final page: true or { full_page, selector, type, quality }. See Screenshots & PDF |
pdf | boolean or object | — | PDF of the final page: true or { format, landscape } |
ai | object | — | Extraction from a prompt or JSON Schema, generated selectors, repair, summary, classification. Not in fallback strategies. See AI extraction |
escalate | boolean | true | When a request is blocked by an anti-bot, retry with a stealth browser, then Camoufox. Not in fallback strategies. See Anti-bot protection |
debug | boolean | false | Also return debug: every attempt (strategy, driver, proxy, status, duration), the raw HTML and a screenshot (billed). Not in fallback strategies. See Debug mode |
pagination | object | — | Follow next links, merge lists, serve pages of a list. Not in fallback strategies. See Pagination |
session | string, object or true | — | Share cookies, proxy and identity with other requests: a name, { id, ttl } or true. Not in fallback strategies. See Sessions |
fingerprint | boolean, string or string[] | true without a browser | Look like a real browser on the wire (User-Agent, headers, TLS and HTTP/2): true, "chrome", ["firefox", "safari"]… See Fingerprints |
cache_ttl | integer (s) | 0 | Accept a cached copy of the page up to this old, 0 to 86400. See Caching |
retry | integer | 0 | Extra attempts when the site fails, 0 to 5. See Automatic retries |
retry_on | integer[] | 429 and 5xx | HTTP statuses worth retrying. See Choose which statuses to retry |
backoff | object | — | Waits between retries: initial_delay, max_delay, multiplier, jitter. See Exponential backoff |
fallback | object[] | — | Up to 3 other ways to request the page, tried in order if the main request fails. Each accepts every option above except cache_ttl. See Fallback strategies |
"options": {
"method": "POST",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"body": "q=shoes",
"retry": 3,
"backoff": { "initial_delay": 1000 }
}Turn a feature off
Set an option to false to turn it off explicitly:
| Option | false means |
|---|---|
retry | No retry, same as 0 |
backoff | Retry immediately, without waiting |
cache_ttl | Don't read the cache, and don't store this page in it either |
fallback | No fallback strategy |
proxy | No proxy |
fingerprint | The API's own identity |
browser | A plain HTTP request, no browser |
follow_redirects | Return redirects as is |
In a fallback strategy, false also stops inheriting from the main request: "proxy": false, "fingerprint": false, "browser": false, "retry": false, and "headers": false, "cookies": false or "body": false to send none of the main ones.
timeout and max_redirects can't be false: every request has a timeout, and "follow_redirects": false is the way to stop following redirects.