Scraping API
Help center

Scrape a single page

Fetch one URL with POST /task, choose the output format, extract only what you need, and know when to switch on the browser.

POST /task with type: unlocker fetches one page and returns it in the response. It is the right call for one URL at a time, and the building block behind jobs and crawls.

Minimal request

Shell
curl https://scraping-api.datafuel.ai/api/v1/task \
  --request POST \
  --header "Content-Type: application/json" \
  --header "X-API-Key: df_key_your_key_here" \
  --header "Idempotency-Key: product-123-2026-09-18" \
  --data '{
    "type": "unlocker",
    "proxy_type": "Basic",
    "attributes": {
      "url": "https://example.com/product/123",
      "result_format": "markdown"
    }
  }'

The call blocks until the page is fetched. Set your client timeout to at least 120 seconds; browser and AI workloads can take longer.

Choose the output

result_format controls what result.data contains:

Valueresult.data is
markdownClean Markdown of the page. Best for LLM input and search indexes.
htmlThe raw HTML.
jsonSchema.org / JSON-LD data and embedded JSON objects found on the page, as an object.
png, jpegA screenshot, base64-encoded. Needs js_rendering: true.

Two flags shrink Markdown output a lot: main_content_only: true drops navigation, footers and sidebars, and include_images: false drops image references.

Extract only what you need

extract_selector maps names to CSS selectors and returns an object instead of the whole page. The value is a string: either one bare selector, or the name → selector map encoded as JSON. Sending a JSON object instead of a string is rejected with 400 INVALID_ATTRIBUTES.

JSON
"attributes": {
  "url": "https://example.com/product/123",
  "extract_selector": "{\"title\": \"h1\", \"price\": \".price\", \"links\": \"a @href\"}"
}

Append @attr to a selector to read an attribute instead of the text. extract_regex does the same with regular expressions and follows the same string rule. Both return result.data as an object. (The MCP tools take these as plain objects and encode them for you.)

When to switch on the browser

js_rendering: true renders the page in a real browser before extracting. It costs five times more on Basic and twice more on Premium, so use it only when the request engine falls short:

  • The result is empty or contains only a loading shell.
  • redirected is true and you wanted the filtered URL you asked for, not the canonical page.
  • The content appears only after client-side rendering.

With the browser on, wait_for_selector holds the capture until an element exists, and js_instructions can click, scroll or type before the capture. Send js_instructions as an array of single-action objects; they run in the order you list them and an action can repeat:

JSON
"js_instructions": [
  { "fill": ["input[name=q]", "laptops"] },
  { "click": "button[type=submit]" },
  { "wait_ms": 1000 }
]

The older object form keyed by action, such as {"fill": [...], "click": "..."}, still works, but its order is not guaranteed and an action cannot appear twice. GET /api/v1/config/js-instructions lists every action. block_resource takes one resource type or a list, for example ["Image", "Font", "Media"], to skip what you do not need.

Send a request body

Set method to POST, PUT or PATCH and pass the payload in body, with its type in content_type (for example application/json). This works with the browser on or off.

Read the response

result.data is a string for markdown and html, an object for json, extract_selector and AI output. Always check the fields next to it: a status_code of 404 still completes and bills, and blocked: true means the page was refused and the task refunded. Read the result envelope walks through every field.

Same request, later

Every task gets an id. GET /task/{task_id} returns the same result again, so you never have to pay twice for a page you already fetched.

If a task is still running, GET /task/{task_id} answers 202 with {"id": "…", "code": "TASK_STILL_PROCESSING"}. That is not an error: wait and ask again. A POST /task that runs past ten minutes answers the same 202; poll GET /task/{id} with the id from the body, or resend the request with the same Idempotency-Key to pick the running task up.