Scrape API
Turns one web page into LLM-ready content: the page as Markdown, plus its metadata, headings, links, images, structured data, language and AI-use signals. We read the raw HTML, which is what AI crawlers such as GPTBot and ClaudeBot see, and render the page as a browser shows it when that HTML is an empty JavaScript app shell.
Only the links? Link Extraction.
https://rankfield.dev/v1/scrape?url={url}Send Authorization: Bearer YOUR_API_KEY. Get a free key; no account needed.
Parameters#
| Name | Type | Description |
|---|---|---|
url | string | A public http or https URL. A missing scheme gets https://. |
scope | string | How much of the page the Markdown covers. page: the whole page in reading order, each section once, collapsed content included, without the site's menus and link lists (navigation, header and footer links, sidebars, category and archive lists, language and currency pickers, share and follow buttons), skip links and cookie banners; contact details such as an e-mail address or a phone number stay. main: the main article only, or the page's content when the article found is very short next to the rest of the page. all: everything, menus and footers included, each section still once. |
include | string | Comma-separated: content, links, images, headings, metadata, structuredData, language, signals, highlights. Parts left out come back null. highlights alone returns just the passages, and needs query. |
query | string | Words to look for, 1–300 characters. Adds highlights: the page's passages that best match them, best first. Close forms count ("price" finds "prices"), synonyms don't; a phrase in double quotes matches as a phrase. |
maxPassages | integer | 1–20: the most passages returned. Only with query. |
passageChars | integer | 200–2,000: about how long each passage is, in characters. Only with query. |
render | string | auto runs the page's JavaScript only when its raw HTML is an empty app shell; never returns the raw HTML only, as AI crawlers see it; always runs it every time. |
screenshot | string | viewport or fullPage: also return a WebP image of the page as a browser shows it, the first screen or the whole page up to 12,000 px tall. |
viewport | string | mobile (412×915, like Google's smartphone crawler) or desktop (1366×768): the screen for rendering and screenshots. |
maxAge | integer | Seconds a stored answer may be old (default 10 minutes), up to 2,592,000. 0 reads the site fresh. |
timeout | integer | Seconds the call may take, 1 to 60. |
Response#
| Field | Type | Description |
|---|---|---|
url finalUrl status redirects | string, integer, array | What you asked, the page that answered, the website's HTTP status (a 404 is data, not an error) and each redirect. |
contentType bytes truncated fetchedAt timing | mixed | The page's type and size, whether it was cut (over 10 MB, or Markdown over 1 MB), when we fetched it, and DNS/connect/TLS/first-byte/total milliseconds. |
content | object | markdown, words, mode (the scope used: page, main or all), extraction (article when main found an article, document for the whole page, text for a plain-text or Markdown body passed through, or pdf for a PDF's text) and truncated. A product page whose text doesn't name the product starts with the product's name, description and price from the page's structured data. |
metadata | object | Title, description, canonical, meta robots, X-Robots-Tag, hreflang, site name, author, published time, favicon, og:image, charset. |
headings | array | {level, text}, H1 to H6 in page order. |
links images | array | Each once, in page order: links with anchor text and rel flags; images with alt, size, srcset and lazy. |
structuredData | object | Parsed JSON-LD, microdata, OpenGraph and Twitter card tags. |
language signals | object | The page's language; noai, noimageai and the TDM reservation. |
highlights | array | null | Only with query: the best-matching passages, best first, each {text, heading, offset, score, matched}. [] when nothing matches. |
blocked | object | null | {by, kind} when a firewall answered instead of the page, such as a "verify you're human" challenge (reported, never rendered or solved); the page parts are then null. |
render | object | Whether the page was rendered and why (rendered, reason), the time taken, and the files robots.txt kept out (robotsBlocked); or unavailable, and the raw-HTML answer. |
screenshot | object | With screenshot: a WebP image, {format, kind, viewport, bytes, data} (base64). |
cached ageSeconds | boolean, integer | Whether the answer came from our store, and its age. |
Errors#
Errors come as {"error":{"code":"…","message":"…","details":{…}}} with the HTTP status.
| Status | Code | When |
|---|---|---|
400 | invalid_input | A parameter is missing, unknown or malformed. |
401 | unauthorized | The key is missing, unknown or revoked. |
403 | robots_disallowed | robots.txt disallows Rankfield-User for the page or a redirect target; details has the rule and line. |
403 | blocked | The website's bot protection refused us. Reported at once and never worked around; details says which protection. Not counted as usage. |
403 | site_excluded | The site is on the exclusion list: Reddit, LinkedIn, Facebook, Instagram, X/Twitter and TikTok (subdomains included), Google search result pages, and sites whose owners asked us. |
415 | unsupported_content_type | A page that answered 2xx isn't HTML, plain text, markdown or a readable PDF (an image, JSON, a scanned or password-protected PDF…). details.reason says why for PDFs. A website's error page in another type comes back as an answer. |
422 | address_not_public | The host resolves to a private or internal address. |
429 | busy | Our fetch capacity is busy: the call was refused at once, waited 4 seconds without a turn, or its time limit ran out while it waited (then details.reason is capacity). A new job also gets it when the shared job queue is full (Retry-After 60). Not counted as usage. Retry after Retry-After seconds. |
500 | internal | Our bug. Retry. |
502 | fetch_failed | The website could not be reached (DNS, connection, TLS, more than 5 redirects or a bad one, a malformed answer); details.reason says which. |
503 | unavailable | The service is restarting or briefly unavailable. Retry after Retry-After. (A full fetch capacity answers 429 busy.) |
504 | timeout | The website did not answer within timeout. With details.reason: "sitePace", the call waited its turn for this website instead, which we fetch gently, shared by all customers. Retry after Retry-After, or allow a longer timeout. |
Price and limits#
- Free; no per-call charge.
- No per-key limits. When the server is busy you get
429busywithRetry-After. - Only answered calls count as usage (plus the website errors listed below); our own errors (
500,503),429busyand a504withdetails.reasonsitePacedon't.
Up to 10,000 URLs in one job#
POST /v1/scrape/jobs with an Idempotency-Key header and {"items":[{"url":"https://example.com/"}, …]}. Each item takes the same parameters as one call, except timeout: items use the default, 20 seconds. Every item is checked first: if any is invalid, the whole request is a 400 that lists the first 20. A new job answers 202; the same Idempotency-Key with a byte-identical body returns the same job (200, Idempotency-Replayed: true), and with any other body is a 409.
Read GET /v1/scrape/jobs/{id}/results?cursor=0&limit=100: finished items as they complete, each {index, ok, result} or {index, ok: false, error}. Pass cursor back until hasMore is false. POST /v1/scrape/jobs/{id}/cancel stops a job. A job runs 16 items at a time; an item that meets busy waits and retries by itself. Results are kept 24 hours.
Send as many jobs as you like. Jobs from all keys share one queue: when 200,000 items are already waiting, a new job answers 429 busy with Retry-After (60 seconds).
Reference: https://rankfield.dev/docs/get-started/batch-jobs
Notes#
- A page that fills itself in with JavaScript is rendered when its raw HTML is an empty app shell;
render=nevershows what crawlers that run no JavaScript get. - Repeat calls to the same website within about a minute wait their turn for that site's polite slot (we fetch each site gently for all customers). If the wait runs past the call's time limit (20 s by default), the call ends with
504timeoutanddetails.reason: "sitePace". It's not charged; retry afterRetry-After, or allow a longertimeout. - We fetch as
Rankfield-User, obey robots.txt on every request and redirect, render a page only when its raw HTML is an empty JavaScript app shell (or every page withrender=always), use no cookies or proxies, and never work around a firewall. We fetch each site gently, shared by all customers. More: rankfield.dev/bot.
See also#
- Full reference: https://rankfield.dev/docs/scrape/scrape-a-page
- MCP tools on
https://rankfield.dev/mcp:scrape_page,start_scrape_job,get_scrape_results - Try it with your key: the API playground
- This page as Markdown: /endpoints/scrape.md