scrape
Ask one page load for markdown, HTML, raw HTML, plain text, links and metadata at once. Every requested format is derived from the same fetch and returned together under formats, so asking for six costs exactly what asking for one costs. Add a highlights or question format with a query to get only the matching sentences, table rows and code blocks — verbatim page text with an offset into the markdown, no model in the path — for 1 credit more. Pass escalate: true and a page that comes back as a bot wall instead of the page is re-fetched once through the stealth browser inside the same call, for 5 credits more.
Use Cases
One Call Instead of Four
Get markdown, links, and metadata from a page in a single request instead of chaining fetch_url, extract_links, and extract_metadata.
LLM-Ready Markdown
Request markdown with onlyMainContent enabled to feed clean, boilerplate-free page text straight into a RAG pipeline or prompt.
Archival Snapshots
Ask for rawHtml and markdown together to keep the exact source alongside a clean reading copy, from a single fetch.
One Fetch, Many Formats
Six formats cost the same 2 credits as one, so a pipeline that needs markdown, links and metadata should ask for all three in a single call rather than three.
Quote the Page, Don't Summarise It
Ask for highlights with a query, or question with a question, to get only the sentences, table rows and code blocks that match — verbatim page text with an offset into the markdown of the same call, so an agent quotes the source instead of paraphrasing it. Unlike summarising fetch tools, no model is in the path.
Endpoint
/api/v1/tools/scrapeParameters
highlights or question format adds 1 credit, charged once per request whether you include one or both. With escalate: true the projection is the ceiling at 7 credits, 8 alongside a query-scoped format; the escalation stage's 5 credits are charged only when the plain fetch was walled and the stage actually ran.| Name | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | Required | - | The URL to scrape (must include protocol: http:// or https://) Example: https://example.com |
formats | array | Optional | ["markdown"] | Output formats to return. One or more of `markdown`, `html`, `rawHtml`, `text`, `links`, `metadata`, plus two query-scoped object formats: `{ "type": "highlights", "query": "…", "max_highlights": 10, "mode": "extractive" }` returns the sentences and code blocks that best match `query` (`max_highlights` 1–50, default 10) — `kind` is `sentence` or `code_block` on the hosted REST API; `table_row` units come from the MCP server, whose markdown keeps tables as pipe rows, whereas the REST API flattens tables to text — and `{ "type": "question", "question": "…", "mode": "extractive" }` returns an `answer` assembled from the best-matching passages. Both return verbatim page text; each unit carries an `offset` and `length` into the `markdown` format of the same call (same `onlyMainContent` setting), so also ask for `markdown` to quote with a locator. `mode: "model"` is rejected here with 400 — it needs an LLM and is available on the MCP server only. `screenshot` and `json-schema` pass validation but are then rejected with 400 — they need a browser or an LLM, which the hosted REST API does not provide. Example: ["markdown", { "type": "highlights", "query": "Starter plan price" }, { "type": "question", "question": "How much does the Starter plan cost?" }] |
onlyMainContent | boolean | Optional | true | Strip navigation, headers, and footers so only the main article content is returned. Example: true |
escalate | boolean | Optional | false | Opt in to one automatic retry through the stealth browser when the plain fetch comes back as a bot wall instead of the page — a Cloudflare, Amazon, DataDome, PerimeterX, Akamai or Vercel challenge page, an empty shell, or a short error-titled placeholder. The plain fetch always runs first; only when it is walled does the same call render the page with the stealth browser and derive every requested format from what it rendered, so a blocked page costs one call instead of two. Escalation reuses the same stealth path as [stealth_mode](/docs/api-reference/tools/stealth-mode) and the same robots.txt gate, and adds no new handling of a bot defence. A wall the stealth render is refused at too still comes back as a blocked page, carrying `escalated: true` and charged nothing: escalation saves the second round trip, it does not promise the page. It is most reliable where the plain fetch failed only because the page needs JavaScript to render. The response then carries `escalated`, and `stealth` when that is `true`. Adds 5 credits, charged only when the escalation stage actually runs. On the MCP server only, a host that walled a request is remembered for 24 hours, so the next `escalate: true` call to that host skips the doomed plain fetch and says so in a warning. Example: true |
escalate_engine | string | Optional | playwright | Browser engine for the escalation stage: "playwright" (default) or "camoufox" (Firefox-based, stronger anti-detection; available only where installed on the backend). Ignored unless `escalate` is `true` and the plain fetch was walled. Example: camoufox |
respect_robots | boolean | Optional | true | Respect the target site's robots.txt. Left at `true`, a path disallowed for `CrawlForge` is refused with 403 before anything is fetched and no credits are charged. Set it to `false` only for a target you have your own agreement with — the response then carries a `warnings` entry and the override is recorded against your API key. Example: true |
max_inline_chars | number | Optional | 40000 | Largest result to return inline, in characters of its JSON (1,000-10,000,000). Over it the response carries `preview` (the first `max_inline_chars` characters of the markdown), `result_handle`, `total_chars`, `truncated: true` and `expires_at`, and [read_result](/docs/api-reference/tools/read-result) reads the rest for 1 credit per call. Stored results are kept for 1 hour. Example: 40000 |
redact_pii | boolean | object | Optional | false | Remove personal data from the text this call returns, before the result is stored or sent back. `true` is shorthand for `{ mode: "fast" }` — all four regex classes, tagged. Whenever you ask for redaction the response carries `redaction: { entities, count, mode }` inside `data`, even when nothing matched (`count: 0`), so "found nothing" is never mistaken for "the parameter was ignored"; a class with no hits is left out rather than reported as `0`. Redaction runs **before** the result is stored, so an oversized result read back later with [read_result](/docs/api-reference/tools/read-result) is already redacted. Two deliberate limits: addresses (`url`, `link`, `href`, `canonical_url`) are never redacted, and counters derived from the text (`content_length`, `word_count`, `character_count`) describe it as extracted, before redaction. Example: true |
Request Examples
cURL
curl -X POST https://crawlforge.dev/api/v1/tools/scrape \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"formats": [
"markdown", "links", "metadata",
{ "type": "highlights", "query": "professional plan price per month" }
],
"onlyMainContent": true,
"escalate": true
}'TypeScript
// npm install crawlforge-sdk
import { CrawlForge, ToolError } from 'crawlforge-sdk';
const client = new CrawlForge({ apiKey: process.env.CRAWLFORGE_API_KEY });
try {
const result = await client.scrape({
url: 'https://example.com',
formats: [
'markdown', 'links', 'metadata',
// 1 extra credit: the matching sentences, table rows and code blocks, verbatim, with offsets
{ type: 'highlights', query: 'professional plan price per month' },
],
onlyMainContent: true,
// Only when the plain fetch meets a bot wall: one stealth render in this same call (5 extra credits)
escalate: true,
});
// result.data is untyped in crawlforge-sdk 0.1 — its shape is the Response Example below.
const { formats } = result.data as {
formats: { markdown: string; links: string[]; metadata: { title: string }; highlights: { text: string }[] };
};
console.log('Markdown:', formats.markdown);
console.log('Links found:', formats.links.length);
console.log('Title:', formats.metadata.title);
// Each highlight is verbatim page text; offset/length index formats.markdown.
console.log('Best match:', formats.highlights[0]?.text);
// A success with warnings is a PARTIAL result — some formats came back, others didn't.
if (result.warnings.length > 0) {
console.warn('Partial result:', result.warnings);
}
console.log('Credits used:', result.creditsUsed);
console.log('Credits remaining:', result.creditsRemaining);
} catch (err) {
// A bot wall the stealth render could not pass either: not charged, blocked.vendor names it.
if (err instanceof ToolError && err.blocked) {
console.error('Blocked by', err.blocked.vendor, '— escalated:', err.escalated);
} else {
throw err;
}
}Python
# pip install crawlforge
from crawlforge import CrawlForge, ToolError
client = CrawlForge() # reads CRAWLFORGE_API_KEY
try:
result = client.scrape(
url='https://example.com',
formats=[
'markdown', 'links', 'metadata',
# 1 extra credit: the matching sentences, table rows and code blocks, verbatim, with offsets
{'type': 'highlights', 'query': 'professional plan price per month'},
],
onlyMainContent=True,
# Only when the plain fetch meets a bot wall: one stealth render in this same call (5 extra credits)
escalate=True,
)
except ToolError as e:
# A bot wall the stealth render could not pass either: not charged, blocked names the vendor.
print(f"Blocked: {e.blocked} (escalated: {e.escalated})")
raise
# result.data is a plain dict — its shape is the Response Example below.
formats = result.data['formats']
print(f"Markdown: {formats['markdown']}")
print(f"Links found: {len(formats['links'])}")
print(f"Title: {formats['metadata']['title']}")
# Each highlight is verbatim page text; offset/length index formats['markdown'].
print(f"Best match: {formats['highlights'][0]['text']}")
# A success with warnings is a PARTIAL result — some formats came back, others didn't.
if result.warnings:
print(f"Partial result: {result.warnings}")
print(f"Credits used: {result.credits_used}")
print(f"Credits remaining: {result.credits_remaining}")Response Example
{ "success": true, "data": { "url": "https://example.com", "formats": { "markdown": "# Example\n\nMain content scraped from https://example.com. The Starter plan costs $12 per month and includes three seats. Annual billing lowers the Starter plan to $10 per month.", "highlights": [ { "text": "The Starter plan costs $12 per month and includes three seats.", "kind": "sentence", "offset": 58, "length": 62, "score": 6.612 }, { "text": "Annual billing lowers the Starter plan to $10 per month.", "kind": "sentence", "offset": 121, "length": 56, "score": 5.809 } ], "answer": { "text": "The Starter plan costs $12 per month and includes three seats.", "grounded": true, "evidence": [ { "text": "The Starter plan costs $12 per month and includes three seats.", "kind": "sentence", "offset": 58, "length": 62, "score": 6.612 } ] } }, "escalated": true, "stealth": { "engine": "playwright", "vendor_detected": "cloudflare" }, "scraped_at": "2026-08-26T14:30:00.000Z" }, "credits_used": 8, "credits_remaining": 992, "processing_time": 4218}data.urlThe URL that was fetched.data.formatsOne key per requested format — the extracted content lives in here, not at the top level of data.data.formats.markdownMain content converted to markdown (present when `markdown` is requested).data.formats.highlightsThe top-ranked sentences and code blocks matching `query`, verbatim (present when a `highlights` format is requested). Each unit carries `text`, `kind` (`sentence` or `code_block` on the hosted REST API; `table_row` units come from the MCP server, whose markdown keeps tables as pipe rows, whereas the REST API flattens tables to text), `offset`, `length` and `score` — a raw BM25 relevance value, meaningful for ordering only, not a 0–1 confidence.data.formats.highlights.offsetCharacter index into the `markdown` format of the same call (same `onlyMainContent` setting): `markdown.slice(offset, offset + length) === text`.data.formats.answerThe answer to `question` (present when a `question` format is requested): `text` is the best-matching evidence joined, and `evidence` lists up to 5 supporting units.data.formats.answer.groundedAlways `true` on the REST API — the text is verbatim page content, nothing is synthesised.data.escalatedWhether the escalation stage ran (present only when the request passed `escalate: true`). `false` means the plain fetch returned the page and only the base cost was charged.data.stealthHow the page was rendered (present only when `escalated` is `true`): `engine` is the browser that ran, and `vendor_detected` names the bot-defence vendor the plain fetch hit — `cloudflare`, `amazon`, `datadome`, `perimeterx`, `akamai` or `vercel` — or `null` when the wall was an empty shell or an error placeholder rather than a named challenge.data.scraped_atISO 8601 timestamp of the fetch.credits_usedCredits deducted for this request — 2 per scrape, regardless of how many formats you asked for, plus 1 when a `highlights` or `question` format is included, plus 5 when the escalation stage ran. Here: 2 + 1 + 5.credits_remainingYour remaining credit balance.Error Handling
Invalid Input (400 Bad Request)
The URL format is invalid, formats contains a value outside the supported list, or a highlights or question format asks for mode: "model", which needs an LLM and is available on the MCP server only. At least one format is required when the field is supplied.
URL Blocked (403 Forbidden)
The target resolved to a private, internal, or link-local address and was rejected by SSRF protection. Only publicly reachable URLs can be scraped.
Insufficient Credits (402 Payment Required)
Your account doesn't have enough credits. Purchase more credits or upgrade your plan.
Rate Limit Exceeded (429 Too Many Requests)
You've exceeded your plan's rate limit. Wait a moment or upgrade your plan for higher limits.
Escalation Backend Not Configured (503 TOOL_NOT_AVAILABLE)
escalate: true runs its stealth stage on the CrawlForge execution backend. When that backend is not configured the stage returns 503 and no credits are charged for it.
Blocked by robots.txt (403 Forbidden)
The target site's robots.txt disallows this path for CrawlForge. Set respect_robots: false to override if you have your own agreement with the target — the override is recorded against your API key. The override does not reach a host on CrawlForge's permanent opt-out list, which is refused whatever respect_robots is set to.
markdown, links and metadata together costs the same 2 credits as requesting one — every format is derived from a single page load. Ask for everything you might need in one call; a highlights or question format adds 1 credit once, not per format.Credit Cost
highlights or question format adds 1 credit, charged once per request whether you include one or both. A call with escalate: true projects up to 7 credits, 8 alongside a query-scoped format, and pays the 5-credit escalation add-on only when the plain fetch came back as a bot wall and the stealth stage ran — never more than the projection.Free Plan: 1,000 one-time credits = 500 requests
Hobby Plan: 5,000 credits/month = 2,500 requests ($19/mo)
Professional Plan: 50,000 credits/month = 25,000 requests ($99/mo)
Business Plan: 250,000 credits/month = 125,000 requests ($399/mo)