Authentication
Every request is authenticated with your secret API key, sent as a Bearer token in the Authorization header. Create and reveal your key from the dashboard.
Authorization: Bearer $CRAWLFOX_API_KEYKeep it secret
Treat the key like a password — never ship it in client-side code. If it leaks, rotate it from the API Keys page; the old secret is invalidated immediately.
Limits & Credits
There is no per-minute request limit. Credits are the only meter — while your key has credits, you can spend them as fast as you like, so you don't need to build backoff for a rate limit that never fires.
Credits
Successful requests consume credits: 1 credit per page scraped (requesting multiple formats for one page is still 1 credit, and a cached page costs the same as a fresh one), and 1 credit per 10 requested search results (based on num). Failed calls are free.
Status Codes & Errors
Errors return a JSON envelope with a stable machine code, an HTTP status, whether the request is safe to retry, and a human remediation hint.
{
"code": "UPSTREAM_TIMEOUT",
"status": 504,
"retryable": true,
"title": "Request timed out",
"message": "The target site did not respond in time.",
"remediation": "Retry. If a URL consistently times out, try a more specific path."
}Error codes
Switch on the stable `code` — statuses and messages may change, codes won't. Retryable errors are safe to retry with backoff.
| Code | Retryable | When |
|---|---|---|
| MISSING_URL | No | The request body has no url. |
| INVALID_URL | No | The URL isn't fully-qualified (https://…) or is malformed. |
| UPSTREAM_UNREACHABLE | Yes | The target site couldn't be reached. |
| UPSTREAM_NOT_FOUND | No | The target site returned 404 for the URL. |
| UPSTREAM_TIMEOUT | Yes | The target site didn't respond in time. |
| UPSTREAM_RATE_LIMITED | Yes | The target site is rate-limiting requests. |
| UPSTREAM_SERVER_ERROR | Yes | The target site returned a server error. |
| UPSTREAM_GEO_BLOCKED | No | The target site refused the request on regional or legal grounds. |
| BOT_WALL | No | The page could not be retrieved. |
| NO_PUBLIC_CONTENT | No | The page loaded but had no readable content. |
| INTERNAL_ERROR | Yes | An unexpected error on our side — retry, then contact support with the request ID. |
OpenAPI Spec
The whole public API is published as an OpenAPI 3.1 document. Point Swagger UI, Stoplight, an SDK generator, or an agent at it — it describes every live endpoint, request body, response shape and error code.
https://crawlfox.io/openapi.jsonWhat it covers
Only endpoints that are live today. There is no separate extract endpoint — structured extraction is this scrape endpoint with jsonOptions, below — and search is /v1/search, with the engine as a request field. Map and crawl are not shipped yet.
Scrape a URL
https://api-staging.crawlfox.io/v1/scrapeFetch a single URL and return clean, structured data. Blocked and JavaScript-heavy pages are handled for you by our bespoke in-house engine, so you get a result instead of a block. Choose one or more output formats.
Body params
urlstringrequired- The URL to scrape. Must include the scheme (https://).
formatsstring[]- Any of markdown, html, rawHtml, text, json, links, images, emails. Defaults to markdown.
extractMainContentboolean- Strip nav/header/footer/sidebar and return just the article body.
skipCacheboolean- Force a fresh fetch instead of serving a cached result.
timeoutinteger- How long to wait for the page, in milliseconds. Defaults to 30000. Below 1000 the request is rejected; a value above 90000 is capped at 90000 rather than refused.
jsonOptionsobject- Structured extraction. With "json" among formats, maps output keys to CSS selectors and returns the result as data.json — deterministic, no AI. This is how extraction works; there is no separate extract endpoint.
$CRAWLFOX_API_KEYcurl --request POST \
--url https://api-staging.crawlfox.io/v1/scrape \
--header "Authorization: Bearer $CRAWLFOX_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"url":"https://example.com/","formats":["markdown","html","links"]}'{
"success": true,
"data": {
"markdown": "# Example Domain\n...",
"html": "<html>...</html>",
"links": ["https://www.iana.org/domains/example"],
"metadata": {
"title": "Example Domain",
"description": "Example Domain for use in documents.",
"language": "en",
"sourceURL": "https://example.com/",
"statusCode": 200
}
}
}Search the web (SERP)
https://api-staging.crawlfox.io/v1/searchRun a search on Google, Bing, or DuckDuckGo and get ranked organic results as structured JSON. Pick the engine with the engine field in the body — google, bing or duckduckgo; omit it and you get Google. A page is 10 results, so num: 10 or less is a single request; above that the search spans several pages and POST to /v1/search/stream instead returns newline-delimited JSON (NDJSON) — one result page per line, as each lands, rather than waiting for the slowest. At num: 10 or below the stream sends one page and finishes, so it buys you nothing over this endpoint. This endpoint returns the whole set at once, so count data.web to see how many results you got. On the streamed endpoint the closing done event carries partial — true when fewer results came back than you asked for.
Body params
enginestring- Which engine to search: google, bing or duckduckgo. Defaults to google when omitted.
qstringrequired- The search query.
numinteger- Number of results to return (up to 100 on Google; Bing caps ~10).
startinteger- Result offset for pagination — e.g. start: 10 returns the next page after the first 10. Google supports offsets up to 90.
countrystring- Two-letter country code to localize results, e.g. us.
languagestring- Two-letter language code, e.g. en.
$CRAWLFOX_API_KEYcurl --request POST \
--url https://api-staging.crawlfox.io/v1/search \
--header "Authorization: Bearer $CRAWLFOX_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"engine":"google","q":"rust async tutorial","num":20}'{
"success": true,
"data": {
"web": [
{
"position": 1,
"url": "https://rust-lang.github.io/async-book/",
"title": "Asynchronous Programming in Rust",
"description": "An introduction to async programming in Rust..."
}
]
},
"creditsUsed": 2
}Batch scrape
https://api-staging.crawlfox.io/v1/batchScrape many URLs in one call. Each URL is processed independently with the same formats, and results come back in the same order as the input array.
Body params
urlsstring[]required- The list of URLs to scrape — up to 100 per request.
formatsstring[]- Output formats applied to every URL. Defaults to markdown.
extractMainContentboolean- Strip nav/header/footer/sidebar from every page and return just the article body.
skipCacheboolean- Force a fresh fetch for every URL instead of serving cached results.
timeoutinteger- How long to wait for the page, in milliseconds. Defaults to 30000. Below 1000 the request is rejected; a value above 90000 is capped at 90000 rather than refused. Applies to each URL on its own, not to the batch as a whole — a batch of slow pages can take longer than this in total.
$CRAWLFOX_API_KEYcurl --request POST \
--url https://api-staging.crawlfox.io/v1/batch \
--header "Authorization: Bearer $CRAWLFOX_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"urls":["https://example.com/","https://example.org/"],"formats":["markdown"]}'{
"success": true,
"count": 2,
"results": [
{ "success": true, "data": { "markdown": "# Example Domain\n...", "metadata": { "sourceURL": "https://example.com/", "statusCode": 200 } } },
{ "success": true, "data": { "markdown": "# Example Domain\n...", "metadata": { "sourceURL": "https://example.org/", "statusCode": 200 } } }
]
}CrawlFox MCP server
Give any MCP-compatible client — Claude Desktop, Claude Code, Cursor — first-class tools to read the web: scrape a page to clean Markdown, search Google, Bing or DuckDuckGo, batch-fetch, and pull structured fields out of HTML.
It is a faithful adapter over the same public CrawlFox REST API documented here — the same bespoke engine does the work, so a protected page comes back as content rather than as a block. There is nothing new to learn about the underlying behaviour, and tool calls spend credits at the same rate as the equivalent REST call.
What you need
A CrawlFox account. The server requires an OAuth token — the sign-in your client opens is the same one you use for the dashboard. No API key is ever pasted into, or stored by, the client.
Endpoint and authentication
Point your client at this URL:
https://mcp.crawlfox.io/mcp| Value | |
|---|---|
| URL | https://mcp.crawlfox.io/mcp |
| Transport | Streamable HTTP |
| Auth | OAuth 2.1 — your client runs the flow, no key to paste |
OAuth 2.1
Your client registers itself dynamically (DCR) with PKCE and opens a browser for you to sign in and approve access. There is no client ID to request from us and no key to paste.
Discovery
A spec-compliant client discovers everything it needs from these two documents. An unauthenticated call to /mcp returns 401 with a WWW-Authenticate header pointing at them — that response starts the flow.
GET /.well-known/oauth-protected-resource/mcp # RFC 9728
GET /.well-known/oauth-authorization-server # RFC 8414Client setup
These shapes genuinely differ between clients: Claude Desktop adds remote servers through Connectors (its config file only launches local stdio servers), Cursor takes a bare url, and Claude Code requires an explicit type.
Claude Desktop · Connectors
Remote servers are added through Connectors, not claude_desktop_config.json — that file only launches local stdio servers.
https://mcp.crawlfox.io/mcp- Open Settings → Connectors → Add custom connector.
- Paste the URL above, save, then click Connect to run the OAuth sign-in.
Cursor · mcp.json
Add to ~/.cursor/mcp.json for every project, or .cursor/mcp.json for just one.
{
"mcpServers": {
"crawlfox": {
"url": "https://mcp.crawlfox.io/mcp"
}
}
}- Open Settings → MCP and sign in when Cursor prompts.
Claude Code · CLI
Add it from the terminal, or write the entry by hand. The type field is required — an entry with a url and no type is read as stdio and fails.
claude mcp add --transport http crawlfox https://mcp.crawlfox.io/mcp{
"mcpServers": {
"crawlfox": {
"type": "http",
"url": "https://mcp.crawlfox.io/mcp"
}
}
}- Run /mcp, pick crawlfox, then Authenticate.
- A browser opens for sign-in; the session reconnects authenticated.
Any MCP host · Spec
A spec-compliant client discovers everything it needs from these two documents. Registration is dynamic (DCR) with PKCE, so there is no client ID to request from us.
GET /.well-known/oauth-protected-resource/mcp # RFC 9728
GET /.well-known/oauth-authorization-server # RFC 8414- An unauthenticated call to /mcp returns 401 with a WWW-Authenticate header pointing at the metadata above — that response starts the flow.
Tools (13)
Choose a tool
Every tool the server registers, in registration order. Eight of them are single-purpose shortcuts over a scrape restricted to one output format — they exist so a model can ask for exactly what it needs without reasoning about the format list.
| Job | Tool | Use it when |
|---|---|---|
| Fetch one URL as clean, LLM-ready content — Markdown, HTML, text, links, images or emails. Handles JavaScript-rendered and protected pages. | crawlfox_scrape | You know the URL and want the page content. |
| Web search via Google, Bing, or DuckDuckGo, returning ranked organic results. | crawlfox_search | You do not have a URL yet and need to find pages. |
| Scrape several URLs in one call, returning one result per page. | crawlfox_batch_scrape | You have a list of URLs and want them all. |
| Pull named fields out of a page into JSON using CSS selectors — deterministic, no AI in the loop. | crawlfox_extract | You want specific fields, not the whole page. |
| Return only page metadata: title, description, and status. | crawlfox_metadata | You want to know what a page is without reading it. |
| Extract every link on a page. | crawlfox_links | You are mapping where a page points. |
| Extract every image URL on a page. | crawlfox_images | You want a page's images. |
| Extract every email address on a page. | crawlfox_emails | You are collecting contact details from a page. |
| Return a page as plain text. | crawlfox_text | You want prose with no markup. |
| Return a page as clean HTML. | crawlfox_html | You need the markup itself. |
| Lightweight GET scrape of a URL, for quick one-off fetches. | crawlfox_scrape_get | You want the cheapest possible read of one page. |
| Look up the status and timing of a past request by its id. | crawlfox_get_log | You want to know how an earlier call went. Free. |
| Retrieve the full stored result of a past scrape by id. | crawlfox_log_result | You want an earlier result back without paying for it twice. Free. |
Arguments
Every argument each tool accepts. Your client also shows these from the server's own schema; this table is here so you can read them before connecting. Only url-style arguments are required unless marked otherwise.
crawlfox_scrape
Fetch one URL as clean, LLM-ready content — Markdown, HTML, text, links, images or emails. Handles JavaScript-rendered and protected pages. Maps to POST /v1/scrape.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| formats | string[] | — | Any of markdown, html, rawHtml, text, json, links, images, emails. Defaults to markdown. |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
| skipCache | boolean | — | Bypass the cache and force a fresh fetch. |
| timeout | integer | — | Give up on this page after this many milliseconds. Defaults to 30000. Below 1000 the request is rejected; a value above 90000 is capped at 90000 rather than refused. Worth setting for slow or JavaScript-heavy pages. |
crawlfox_search
Web search via Google, Bing, or DuckDuckGo, returning ranked organic results. Maps to POST /v1/search.
| Argument | Type | Required | Description |
|---|---|---|---|
| query | string | yes | The search query. |
| engine | string | — | google, bing or duckduckgo. Defaults to google. |
| count | integer | — | How many results to return, from 1 to 100. |
| offset | integer | — | Skip this many results before returning any — 0 or above, for paging. |
| country | string | — | Two-letter country code, e.g. us. |
| language | string | — | Two-letter language code, e.g. en. |
crawlfox_batch_scrape
Scrape several URLs in one call, returning one result per page. Maps to POST /v1/batch.
| Argument | Type | Required | Description |
|---|---|---|---|
| urls | string[] | yes | Between 1 and 50 absolute URLs. This tool caps lower than the REST endpoint. |
| formats | string[] | — | Any of markdown, html, rawHtml, text, json, links, images, emails. Defaults to markdown. |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
| skipCache | boolean | — | Bypass the cache and force a fresh fetch. |
crawlfox_extract
Pull named fields out of a page into JSON using CSS selectors — deterministic, no AI in the loop. Maps to POST /v1/scrape.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| selectors | object | yes | Field name to CSS selector, e.g. { "title": "h1", "price": ".price" }. |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
| skipCache | boolean | — | Bypass the cache and force a fresh fetch. |
crawlfox_metadata
Return only page metadata: title, description, and status. Maps to POST /v1/scrape.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
crawlfox_links
Extract every link on a page. Maps to POST /v1/scrape.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
crawlfox_images
Extract every image URL on a page. Maps to POST /v1/scrape.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
crawlfox_emails
Extract every email address on a page. Maps to POST /v1/scrape.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
crawlfox_text
Return a page as plain text. Maps to POST /v1/scrape.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
| skipCache | boolean | — | Bypass the cache and force a fresh fetch. |
crawlfox_html
Return a page as clean HTML. Maps to POST /v1/scrape.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| onlyMainContent | boolean | — | Strip nav, header, footer and sidebar and keep the main article only. |
| skipCache | boolean | — | Bypass the cache and force a fresh fetch. |
crawlfox_scrape_get
Lightweight GET scrape of a URL, for quick one-off fetches. Maps to GET /v1/scrape/:url.
| Argument | Type | Required | Description |
|---|---|---|---|
| url | string | yes | Absolute URL to fetch, including the scheme (http:// or https://). |
| formats | string[] | — | Any of markdown, html, rawHtml, text, json, links, images, emails. Defaults to markdown. |
crawlfox_get_log
Look up the status and timing of a past request by its id. Maps to GET /v1/logs/:id.
| Argument | Type | Required | Description |
|---|---|---|---|
| id | string | yes | The id returned by an earlier call. |
crawlfox_log_result
Retrieve the full stored result of a past scrape by id. Maps to GET /v1/logs/:id/result.
| Argument | Type | Required | Description |
|---|---|---|---|
| id | string | yes | The id returned by an earlier call. |
Worked examples
Once the server is connected you do not call these tools by hand — you ask in plain language and the client picks the tool. These are the shapes that work well, with the call each one produces.
Read one page
The most common case. The model picks crawlfox_scrape and gets Markdown back.
You: Summarise https://example.com/pricing for me.
→ crawlfox_scrape { "url": "https://example.com/pricing" }Research a topic across sources
Search first, then read the results. Ask for the search explicitly when you want breadth before depth, otherwise a client may scrape the first URL it can guess.
You: Find three recent write-ups on Rust async runtimes and
summarise what they disagree about.
→ crawlfox_search { "query": "rust async runtime comparison", "count": 10 }
→ crawlfox_scrape { "url": "<first result>" }
→ crawlfox_scrape { "url": "<second result>" }Pull specific fields out of a page
When you want data rather than prose, name the fields. Selectors are CSS, matched against the fetched DOM, and the result comes back as JSON — the same deterministic extraction the REST API does, with no model in the loop guessing values.
You: From https://example.com/product/42 get me the title, price
and stock status.
→ crawlfox_extract {
"url": "https://example.com/product/42",
"selectors": { "title": "h1", "price": ".price", "stock": "[data-stock]" }
}Read many pages at once
Batch when you already have the list. One call, one result per page, in the order you gave them.
You: Scrape these five docs pages and tell me which mention rate limits.
→ crawlfox_batch_scrape { "urls": ["https://…/a", "https://…/b", …] }When a page is slow
A page that renders slowly can exceed the default time budget. Ask for more time and the model passes timeout through. It defaults to 30000 ms; below 1000 the call is rejected, and above 90000 it is capped at 90000 rather than refused.
You: Scrape https://example.com/heavy — give it more time than usual.
→ crawlfox_scrape { "url": "https://example.com/heavy", "timeout": 60000 }Costs
Tool calls spend credits at exactly the same rate as the REST call behind them — 1 credit per page scraped, 1 per 10 requested search results. Looking up a past request or its stored result is free. Failed calls are free.