{
"openapi": "3.1.0",
"info": {
"title": "CRW REST API",
"version": "0.26.0",
"description": "Firecrawl-compatible web scraping, crawling, mapping, search, and extraction API. Hosted at https://api.fastcrw.com; self-host with `docker run -p 3000:3000 ghcr.io/us/crw:latest`.",
"license": {
"name": "AGPL-3.0",
"url": "https://opensource.org/licenses/AGPL-3.0"
},
"contact": {
"url": "https://github.com/us/crw"
}
},
"servers": [
{
"url": "https://api.fastcrw.com",
"description": "Hosted (requires Bearer token)"
},
{
"url": "http://localhost:3000",
"description": "Self-hosted (no auth by default)"
}
],
"security": [
{
"bearerAuth": []
},
{}
],
"tags": [
{
"name": "search",
"description": "Web search via our own search backend (self-hosted) or managed (hosted)"
},
{
"name": "scrape",
"description": "Single-URL content extraction"
},
{
"name": "crawl",
"description": "Async multi-page crawl jobs"
},
{
"name": "map",
"description": "URL discovery without full scraping"
},
{
"name": "extract",
"description": "Structured JSON extraction via scrape + jsonOptions"
},
{
"name": "change-tracking",
"description": "Stateless diff of a scrape against a caller-supplied previous snapshot (monitor primitive)"
}
],
"paths": {
"/v1/search": {
"post": {
"tags": [
"search"
],
"summary": "Search the web and optionally scrape results",
"operationId": "search",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SearchRequest"
},
"examples": {
"minimal": {
"summary": "Top 3 results, no scraping",
"value": {
"query": "renewable energy trends 2024",
"limit": 3
}
},
"with_scrape": {
"summary": "Scrape each result as markdown",
"value": {
"query": "rust async runtime",
"limit": 5,
"scrapeOptions": {
"formats": [
"markdown"
],
"onlyMainContent": true
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "Search results",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SearchResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"429": {
"$ref": "#/components/responses/RateLimited"
},
"503": {
"$ref": "#/components/responses/SearchDisabled"
}
}
}
},
"/v1/scrape": {
"post": {
"tags": [
"scrape"
],
"summary": "Scrape one URL",
"operationId": "scrape",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScrapeRequest"
}
}
}
},
"responses": {
"200": {
"description": "Scrape result",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ScrapeResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
}
}
}
},
"/v1/map": {
"post": {
"tags": [
"map"
],
"summary": "Discover URLs on a site",
"operationId": "map",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MapRequest"
}
}
}
},
"responses": {
"200": {
"description": "List of URLs",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MapResponse"
}
}
}
}
}
}
},
"/v1/crawl": {
"post": {
"tags": [
"crawl"
],
"summary": "Start an async crawl job",
"operationId": "crawlStart",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CrawlRequest"
}
}
}
},
"responses": {
"200": {
"description": "Crawl job accepted; poll with /v1/crawl/{id}",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CrawlAccepted"
}
}
}
}
}
}
},
"/v1/crawl/{id}": {
"get": {
"tags": [
"crawl"
],
"summary": "Get crawl job status and results",
"operationId": "crawlStatus",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Crawl job id returned by POST /v1/crawl"
}
],
"responses": {
"200": {
"description": "Job status + collected pages",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CrawlStatus"
}
}
}
},
"404": {
"$ref": "#/components/responses/NotFound"
}
}
}
},
"/v1/batch/scrape": {
"post": {
"tags": [
"batch"
],
"summary": "Start an async batch scrape over an explicit URL list",
"operationId": "batchScrapeStart",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BatchScrapeRequest"
}
}
}
},
"responses": {
"200": {
"description": "Batch job accepted; poll with /v1/batch/scrape/{id}",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BatchScrapeAccepted"
}
}
}
}
}
}
},
"/v1/batch/scrape/{id}": {
"get": {
"tags": [
"batch"
],
"summary": "Get batch job status and results",
"operationId": "batchScrapeStatus",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Batch job id returned by POST /v1/batch/scrape"
}
],
"responses": {
"200": {
"description": "Job status + scraped pages",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CrawlStatus"
}
}
}
}
}
},
"delete": {
"tags": [
"batch"
],
"summary": "Cancel an active batch job",
"operationId": "batchScrapeCancel",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Batch job id returned by POST /v1/batch/scrape"
}
],
"responses": {
"200": {
"description": "Job cancelled; status becomes \"cancelled\""
}
}
}
},
"/v1/change-tracking/diff": {
"post": {
"tags": [
"change-tracking"
],
"summary": "Diff current scrape content against a previous snapshot",
"description": "Stateless: opencore stores nothing — the caller supplies the `previous` snapshot. Accepts a single body or a `batch` array (presence of `batch` selects batch mode). The crawl-path workhorse for monitors.",
"operationId": "changeTrackingDiff",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChangeTrackingDiffRequest"
},
"examples": {
"single_gitdiff": {
"summary": "Single page, markdown git-diff",
"value": {
"modes": [
"gitDiff"
],
"previous": {
"markdown": "Starter $19",
"contentHash": "abc"
},
"current": {
"markdown": "Starter $24"
}
}
},
"batch_json": {
"summary": "Batch, json per-field mode",
"value": {
"modes": [
"json"
],
"batch": [
{
"url": "https://example.com/pricing",
"previous": {
"json": {
"price": "$19"
},
"contentHash": "abc"
},
"current": {
"json": {
"price": "$24"
}
}
}
]
}
}
}
}
}
},
"responses": {
"200": {
"description": "Diff result (object for single, array for batch) wrapped in the standard envelope",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"success",
"data"
],
"properties": {
"success": {
"type": "boolean"
},
"data": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeTrackingResult"
},
{
"type": "array",
"items": {
"$ref": "#/components/schemas/ChangeTrackingResult"
}
}
]
}
}
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
}
}
}
},
"/v1/capabilities": {
"get": {
"tags": [
"capabilities"
],
"summary": "Server capability discovery",
"description": "Returns what this opencore instance actually supports: LLM providers, output formats, search features, screenshot/renderer availability, document parsers and enforced limits. Every boolean is derived from the real build features and effective config — a capability is true only when the instance can perform it for a request that supplies no extra credentials. SaaS frontends call this on boot to validate a minimum capability set before sending traffic.",
"operationId": "capabilities",
"security": [
{
"bearerAuth": []
},
{}
],
"responses": {
"200": {
"description": "Capability report for this opencore instance",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"version",
"llm",
"formats",
"search",
"screenshot",
"renderers",
"extract",
"documents",
"limits"
],
"properties": {
"version": {
"type": "string",
"description": "opencore semver string"
},
"llm": {
"type": "object",
"required": [
"providers",
"supportsBaseUrl",
"serverKeyConfigured",
"maxConcurrency"
],
"properties": {
"providers": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"anthropic",
"openai",
"deepseek",
"openai-compatible",
"azure"
]
},
"supportsBaseUrl": {
"type": "boolean",
"description": "The LLM dispatcher accepts a custom baseUrl, but only as part of a BYOK request: the per-request LLM config that carries it is only built when the request also supplies llmApiKey. Sending baseUrl without a key leaves the server's own endpoint in force (the server never points its own key at a caller-chosen host). POST /v1/extract rejects baseUrl outright — configure [extraction.llm.base_url] server-side for that route. Build-invariant: no config turns this off."
},
"serverKeyConfigured": {
"type": "boolean",
"description": "True when a server-side LLM API key is present. False on BYOK-only deploys (CRW_DISABLE_SERVER_LLM_KEY=1), where a self-hosted instance still accepts a per-request llmApiKey. The managed cloud does not take a caller-supplied key."
},
"maxConcurrency": {
"type": "integer",
"description": "Server-configured LLM fan-out cap; 0 when no server LLM config is present"
},
"requireByokHeader": {
"type": "string",
"description": "Header name required on LLM-touching requests; omitted when no header guard is configured"
}
}
},
"formats": {
"type": "object",
"required": [
"supported",
"llmRequired",
"changeTrackingModes",
"changeTrackingModesLlmRequired"
],
"properties": {
"supported": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"markdown",
"html",
"rawHtml",
"plainText",
"links",
"json",
"summary",
"changeTracking",
"screenshot"
],
"description": "Output formats this build/config can actually produce. `screenshot` is listed only when a screenshot-capable renderer (chrome / chrome_proxy / playwright) is compiled in and configured."
},
"changeTrackingModes": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"gitDiff",
"json"
]
},
"llmRequired": {
"type": "array",
"items": {
"type": "string"
},
"description": "Formats that additionally need an LLM: a server key (llm.serverKeyConfigured) or a per-request llmApiKey. Requesting them without one is a hard error, never a silent downgrade. When llm.requireByokHeader is set the server key is NOT sufficient: the scrape and extract paths reject these formats unless the request carries llmApiKey.",
"example": [
"json",
"summary"
]
},
"changeTrackingModesLlmRequired": {
"type": "array",
"items": {
"type": "string"
},
"description": "Change-tracking modes that need an LLM. gitDiff is deterministic; json mode calls an LLM.",
"example": [
"json"
]
}
}
},
"search": {
"type": "object",
"required": [
"supported",
"answer",
"summarizeResults"
],
"properties": {
"supported": {
"type": "boolean",
"description": "POST /v1/search is usable: [search].enabled and search.searxng_url are set. Configured, not health-probed."
},
"answer": {
"type": "boolean",
"description": "Answer synthesis works WITHOUT a caller-supplied LLM key (search configured AND a server LLM key present). When false but supported is true, a self-hosted instance still answers a request that carries llmApiKey; the managed cloud does not take a caller-supplied key."
},
"summarizeResults": {
"type": "boolean",
"description": "Same gate as answer."
}
}
},
"screenshot": {
"type": "object",
"required": [
"supported",
"fullPage"
],
"properties": {
"supported": {
"type": "boolean",
"description": "A screenshot-capable renderer (chrome / chrome_proxy / playwright) is compiled in AND configured. LightPanda and Camoufox cannot capture, so an instance with only those reports false and the scrape path fails closed on a screenshot request."
},
"fullPage": {
"type": "boolean",
"description": "Full-page capture (screenshot@fullPage / screenshotFullPage). Same gate as supported."
}
}
},
"renderers": {
"type": "object",
"required": [
"available",
"mode"
],
"properties": {
"available": {
"type": "array",
"items": {
"type": "string"
},
"description": "JS renderer tiers this instance constructed, in fallback order — reflects both the build features and the config. These are the values the per-request `renderer` pin accepts.",
"example": [
"lightpanda",
"chrome"
]
},
"mode": {
"type": "string",
"enum": [
"auto",
"none",
"lightpanda",
"chrome",
"playwright",
"camoufox"
],
"description": "Effective [renderer].mode. Note chrome_proxy is a renderer TIER (it can appear in renderers.available) but not a mode.",
"example": "auto"
},
"renderJsDefault": {
"type": "boolean",
"description": "Effective [renderer].render_js_default; omitted when unset (auto-detect)."
}
}
},
"extract": {
"type": "object",
"required": [
"supported",
"maxUrls",
"perFieldAttribution"
],
"properties": {
"supported": {
"type": "boolean",
"description": "POST /v1/extract works for a request that carries no credentials of its own. The route is ALWAYS mounted, but it needs an LLM: false means a server LLM key is absent, or llm.requireByokHeader is set (which makes the server key insufficient). false does NOT mean 'not implemented' — a request carrying llmApiKey still extracts on a self-hosted instance."
},
"maxUrls": {
"type": "integer",
"description": "Max URLs accepted per extract request (crawler.max_extract_urls)."
},
"perFieldAttribution": {
"type": "boolean",
"description": "Per-field `basis` attribution. This build ships `basis`, so it reports true; the flag reflects build capability, not whether an LLM is configured."
}
}
},
"documents": {
"type": "object",
"required": [
"parsers",
"fileUpload"
],
"properties": {
"parsers": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"pdf"
]
},
"fileUpload": {
"type": "object",
"required": [
"supported",
"endpoint",
"maxBytes",
"types",
"ocr"
],
"properties": {
"supported": {
"type": "boolean"
},
"endpoint": {
"type": "string",
"description": "Upload path to join onto your API base. /v2/parse is the one path served by every surface: the engine mounts it at the root and under /firecrawl, and the hosted API proxies /v1/* and /v2/* only.",
"example": "/v2/parse"
},
"maxBytes": {
"type": "integer",
"description": "The ENFORCED body cap ([document].max_upload_bytes, clamped by the hard ceiling) — the same value the body-limit layer applies."
},
"types": {
"type": "array",
"items": {
"type": "string"
},
"example": [
"application/pdf"
]
},
"ocr": {
"type": "boolean"
}
}
}
}
},
"limits": {
"type": "object",
"required": [
"maxBatchUrls",
"maxExtractUrls",
"searchDefaultLimit",
"searchMaxLimit",
"maxUploadBytes"
],
"properties": {
"maxBatchUrls": {
"type": "integer",
"description": "Max URLs per batch-scrape submission (crawler.max_batch_urls)."
},
"maxExtractUrls": {
"type": "integer",
"description": "Max URLs per /v1/extract request (crawler.max_extract_urls)."
},
"searchDefaultLimit": {
"type": "integer",
"description": "Default /v1/search limit when the request omits it."
},
"searchMaxLimit": {
"type": "integer",
"description": "Hard cap on the /v1/search limit."
},
"maxUploadBytes": {
"type": "integer",
"description": "Enforced upload cap on the parse endpoint, in bytes.",
"example": 52428800
}
}
}
}
}
}
}
}
}
}
},
"/v1/extract": {
"post": {
"tags": [
"extract"
],
"summary": "Start an async multi-URL structured extraction job",
"description": "Native extraction. Returns a fixed-cardinality per-URL array via GET /v1/extract/{id}. At least one of `prompt` or `schema` is required. SDK async starters send `Prefer: respond-async` for managed/self-hosted parity.",
"operationId": "extractStart",
"parameters": [
{
"name": "Prefer",
"in": "header",
"required": false,
"schema": {
"type": "string",
"enum": [
"respond-async"
]
},
"description": "Request the canonical async lifecycle across managed and self-hosted deployments."
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExtractRequest"
}
}
}
},
"responses": {
"200": {
"description": "Extract job accepted; poll with /v1/extract/{id}",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExtractAccepted"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
}
}
}
},
"/v1/extract/{id}": {
"get": {
"tags": [
"extract"
],
"summary": "Get extract job status and per-URL results",
"operationId": "extractStatus",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Extract job id returned by POST /v1/extract"
}
],
"responses": {
"200": {
"description": "Job status + per-URL extraction results",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExtractStatus"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"404": {
"$ref": "#/components/responses/NotFound"
}
}
},
"delete": {
"tags": [
"extract"
],
"summary": "Cancel an extract job",
"description": "Idempotent. Processing becomes cancelling while a claimed URL settles, then cancelled. Completed, failed, and cancelled states are immutable.",
"operationId": "extractCancel",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Extract job id returned by POST /v1/extract"
}
],
"responses": {
"200": {
"description": "Canonical job status + ordered per-URL results",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExtractStatus"
}
}
}
},
"400": {
"$ref": "#/components/responses/BadRequest"
},
"404": {
"$ref": "#/components/responses/NotFound"
}
}
}
}
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "API key",
"description": "Hosted only. Get a key at https://fastcrw.com/register."
}
},
"schemas": {
"SearchRequest": {
"type": "object",
"required": [
"query"
],
"properties": {
"query": {
"type": "string",
"minLength": 1,
"maxLength": 2000
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 20,
"default": 5
},
"lang": {
"type": "string",
"example": "en"
},
"tbs": {
"type": "string",
"enum": [
"qdr:h",
"qdr:d",
"qdr:w",
"qdr:m",
"qdr:y"
]
},
"sources": {
"type": "array",
"items": {
"type": "string",
"enum": [
"web",
"news",
"images"
]
}
},
"categories": {
"type": "array",
"items": {
"type": "string"
}
},
"scrapeOptions": {
"$ref": "#/components/schemas/ScrapeOptions"
}
}
},
"SearchResult": {
"type": "object",
"required": [
"url",
"title"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
},
"snippet": {
"type": "string",
"description": "Result blurb (LLM-ready summary line)"
},
"description": {
"type": "string",
"description": "Alias of snippet (same value, kept for Firecrawl response-shape parity)"
},
"position": {
"type": "integer"
},
"score": {
"type": "number"
},
"category": {
"type": "string",
"description": "Search backend result category (e.g. 'general')"
},
"markdown": {
"type": "string",
"description": "Present when scrapeOptions.formats includes 'markdown'"
}
}
},
"SearchResponse": {
"type": "object",
"required": [
"success",
"data"
],
"properties": {
"success": {
"type": "boolean"
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SearchResult"
}
}
}
},
"ScrapeOptions": {
"type": "object",
"properties": {
"formats": {
"type": "array",
"items": {
"type": "string",
"enum": [
"markdown",
"html",
"rawHtml",
"links"
]
},
"default": [
"markdown"
]
},
"onlyMainContent": {
"type": "boolean",
"default": true
}
}
},
"FieldStatus": {
"type": "string",
"enum": [
"supported",
"unverified",
"unsupported",
"notFound"
],
"description": "How well an extracted field is attributed to its source. Honest: a field whose attribution could not be verified is 'unverified'/'unsupported' rather than given a fabricated citation."
},
"EvidenceCitation": {
"type": "object",
"required": [
"url",
"sourceHash",
"sourceTextKind"
],
"description": "A citation backing an extracted value. Every field is server-produced; never a model-supplied url, hash or excerpt.",
"properties": {
"url": {
"type": "string",
"description": "The document the engine fetched."
},
"title": {
"type": "string"
},
"excerpt": {
"type": "string",
"description": "Verbatim span from the canonical source text. Absent when the span could not be established (status 'unverified')."
},
"sourceHash": {
"type": "string",
"description": "'sha256:<hex>' of the exact text sent to the model."
},
"sourceTextKind": {
"type": "string",
"description": "Which text sourceHash covers. 'llmInput' for extraction basis (the truncated markdown sent to the model)."
}
}
},
"Basis": {
"type": "object",
"required": [
"basisVersion",
"field",
"value",
"status",
"citations"
],
"description": "Per-field extraction evidence. Emitted only for top-level scalar schema properties, and only when the request set basis:true. citations is empty exactly when status is 'unsupported' or 'notFound'; value is null exactly when status is 'notFound'.",
"properties": {
"basisVersion": {
"type": "integer"
},
"field": {
"type": "string"
},
"value": {
"description": "The schema-validated value; null when status is 'notFound'. Never rewritten to the model's claim."
},
"status": {
"$ref": "#/components/schemas/FieldStatus"
},
"confidence": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
},
"citations": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EvidenceCitation"
}
}
}
},
"BasisWarning": {
"type": "object",
"required": [
"field",
"code"
],
"description": "A coded reason a field's attribution was downgraded. Closed, crw-owned code set; never upstream text.",
"properties": {
"field": {
"type": "string"
},
"code": {
"type": "string"
}
}
},
"ScrapeRequest": {
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"formats": {
"type": "array",
"items": {
"type": "string",
"enum": [
"markdown",
"html",
"rawHtml",
"plainText",
"links",
"json",
"summary",
"changeTracking"
]
},
"default": [
"markdown"
]
},
"onlyMainContent": {
"type": "boolean",
"default": true,
"description": "Strip navigation, ads, and boilerplate; keep main article content only"
},
"renderJs": {
"type": "boolean",
"nullable": true,
"description": "null = auto-detect, true = force JS rendering, false = skip JS rendering"
},
"waitFor": {
"type": "integer",
"minimum": 0,
"description": "Milliseconds to wait after JS rendering before extracting content"
},
"includeTags": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "CSS selectors / HTML tags to include in extraction"
},
"excludeTags": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "CSS selectors / HTML tags to remove before extraction"
},
"jsonSchema": {
"type": "object",
"additionalProperties": true,
"description": "JSON Schema describing the structured output shape for the 'json' format"
},
"basis": {
"type": "boolean",
"default": false,
"description": "Return per-field evidence alongside the 'json' format: each top-level scalar schema property comes back as a Basis with an honest status. Requires a JSON schema; off by default, and when off the request sent to the model is unchanged."
},
"headers": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Custom HTTP request headers forwarded to the target URL"
},
"cssSelector": {
"type": "string",
"description": "CSS selector to narrow content extraction to a specific element"
},
"xpath": {
"type": "string",
"description": "XPath expression to narrow content extraction to a specific element"
},
"chunkStrategy": {
"$ref": "#/components/schemas/ChunkStrategy",
"description": "Strategy for chunking the extracted markdown"
},
"query": {
"type": "string",
"description": "Query string for BM25/cosine chunk filtering (used with chunkStrategy)"
},
"filterMode": {
"type": "string",
"enum": [
"bm25",
"cosine"
],
"description": "Filtering algorithm to rank chunks against query"
},
"topK": {
"type": "integer",
"minimum": 1,
"description": "Number of top chunks to return after filtering (default: 5)"
},
"proxy": {
"type": "string",
"description": "Per-request proxy URL (http/https/socks5). Overrides global config."
},
"proxyList": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "Per-request proxy pool to rotate among (BYOP). Takes precedence over proxy and server config."
},
"proxyRotation": {
"type": "string",
"enum": [
"round_robin",
"random",
"sticky_per_host"
],
"description": "Rotation strategy for proxyList (default: sticky_per_host)"
},
"country": {
"type": "string",
"description": "2-letter ISO 3166-1 alpha-2 country code for residential proxy egress (e.g. 'us', 'gb')"
},
"stealth": {
"type": "boolean",
"description": "Override stealth mode for this request (null = use global config)"
},
"actions": {
"type": "object",
"additionalProperties": true,
"description": "Unsupported Firecrawl parameter — returns a clear error if supplied"
},
"extract": {
"$ref": "#/components/schemas/ExtractOptions",
"description": "Firecrawl-compatible extract object (e.g. { schema: {...} })"
},
"llmApiKey": {
"type": "string",
"description": "BYOK: LLM provider API key for structured extraction (overrides server key)"
},
"llmProvider": {
"type": "string",
"enum": [
"anthropic",
"openai",
"deepseek",
"azure",
"openai-compatible"
],
"description": "Per-request LLM provider override"
},
"llmModel": {
"type": "string",
"description": "Per-request LLM model identifier override"
},
"baseUrl": {
"type": "string",
"description": "Per-request LLM base URL override for OpenAI-compatible providers (e.g. https://api.deepseek.com/v1)"
},
"summaryPrompt": {
"type": "string",
"maxLength": 500,
"description": "User-supplied instructions appended to the summary system prompt (capped at 500 chars)"
},
"maxContentChars": {
"type": "integer",
"minimum": 1,
"description": "Maximum bytes of scraped content sent to LLM for 'summary' format (capped at 200 KB)"
},
"renderer": {
"type": "string",
"enum": [
"auto",
"lightpanda",
"chrome",
"chrome_proxy",
"playwright"
],
"description": "Pin this request to a specific renderer. Non-auto implies renderJs:true unless renderJs:false is set."
},
"deadlineMs": {
"type": "integer",
"minimum": 1,
"maximum": 60000,
"description": "End-to-end deadline budget in milliseconds (default: 8000, max: 60000)"
},
"debug": {
"type": "boolean",
"description": "When true, response includes debugExtraction trace with every candidate the extractor considered"
},
"changeTracking": {
"$ref": "#/components/schemas/ChangeTrackingOptions",
"description": "Activated when formats includes 'changeTracking'. The format entry is the plain string; options ride on this sibling field."
},
"goal": {
"type": "string",
"maxLength": 2048,
"description": "Plain-language monitor goal for the meaningful-change judge"
},
"judgeEnabled": {
"type": "boolean",
"description": "Run the LLM meaningful-change judge on a changed page (requires goal)"
},
"parsers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ParserSpec"
},
"description": "Document parser directives. Omit = auto-parse PDFs. Empty array = disable parsing."
}
}
},
"ScrapeResponse": {
"type": "object",
"required": [
"success",
"data"
],
"properties": {
"success": {
"type": "boolean"
},
"warning": {
"type": "string",
"description": "Top-level advisory (e.g. fallback render notice)"
},
"data": {
"type": "object",
"properties": {
"markdown": {
"type": "string"
},
"html": {
"type": "string"
},
"rawHtml": {
"type": "string"
},
"links": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
}
},
"json": {
"type": "object",
"additionalProperties": true
},
"metadata": {
"$ref": "#/components/schemas/PageMetadata"
},
"warning": {
"type": "string"
},
"warnings": {
"type": "array",
"items": {
"type": "string"
}
},
"renderDecision": {
"type": "string",
"description": "Which renderer ran (e.g. 'http', 'browser')"
},
"creditCost": {
"type": "number"
},
"basis": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Basis"
},
"description": "Per-field evidence for the 'json' format; present only when the request set basis:true."
},
"basisWarnings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/BasisWarning"
}
},
"llmInputHash": {
"type": "string",
"description": "'sha256:'-prefixed hash of the canonical text sent to the extraction LLM. The independent record a consumer checks a citation's sourceHash against, so the check is not circular."
}
}
}
}
},
"JsonOptions": {
"type": "object",
"properties": {
"schema": {
"type": "object",
"description": "JSON schema for the extracted shape",
"additionalProperties": true
},
"prompt": {
"type": "string"
},
"systemPrompt": {
"type": "string"
}
}
},
"MapRequest": {
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"maxDepth": {
"type": "integer",
"minimum": 0,
"description": "Maximum link-follow depth from the start URL"
},
"useSitemap": {
"type": "boolean",
"default": true,
"description": "Fetch and parse sitemap.xml first"
},
"crawlFallback": {
"type": "boolean",
"default": true,
"description": "Fall back to BFS crawl when sitemap is missing or sparse"
},
"timeout": {
"type": "integer",
"description": "Custom timeout in seconds (default: 120)"
}
}
},
"MapResponse": {
"type": "object",
"required": [
"success",
"data"
],
"properties": {
"success": {
"type": "boolean"
},
"data": {
"type": "object",
"required": [
"links"
],
"properties": {
"links": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
}
},
"droppedActionCount": {
"type": "integer"
},
"strippedTrackingCount": {
"type": "integer"
}
}
}
}
},
"CrawlRequest": {
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"maxPages": {
"type": "integer",
"minimum": 1,
"default": 100
},
"maxDepth": {
"type": "integer",
"minimum": 0,
"default": 2
},
"scrapeOptions": {
"$ref": "#/components/schemas/ScrapeOptions"
}
}
},
"CrawlAccepted": {
"type": "object",
"required": [
"success",
"id"
],
"properties": {
"success": {
"type": "boolean"
},
"id": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri",
"description": "URL to poll with GET /v1/crawl/{id}"
}
}
},
"CrawlStatus": {
"type": "object",
"required": [
"success",
"status",
"total",
"completed",
"data"
],
"properties": {
"success": {
"type": "boolean"
},
"status": {
"type": "string",
"enum": [
"scraping",
"completed",
"failed",
"cancelled"
]
},
"completed": {
"type": "integer"
},
"total": {
"type": "integer"
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScrapeResponse"
}
},
"error": {
"type": "string"
}
}
},
"ExtractRequest": {
"type": "object",
"required": [
"urls"
],
"description": "At least one of `prompt` or `schema` must be present.",
"properties": {
"urls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"description": "URLs to extract from (1..crawler.max_extract_urls)."
},
"prompt": {
"type": "string",
"description": "Free-text extraction objective."
},
"schema": {
"type": "object",
"additionalProperties": true,
"description": "JSON Schema constraining the extracted output."
},
"llmApiKey": {
"type": "string",
"description": "BYOK LLM API key."
},
"llmProvider": {
"type": "string"
},
"llmModel": {
"type": "string"
},
"basis": {
"type": "boolean",
"description": "Return per-field evidence: each result carries a 'basis' array, one entry per top-level scalar schema property. Requires 'schema'. Reported by GET /v1/capabilities (extract.perFieldAttribution)."
}
}
},
"ExtractResponse": {
"type": "object",
"required": [
"success",
"data"
],
"properties": {
"success": {
"type": "boolean"
},
"data": {
"type": "object",
"additionalProperties": true
}
}
},
"PageMetadata": {
"type": "object",
"additionalProperties": true,
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"sourceURL": {
"type": "string",
"format": "uri"
},
"statusCode": {
"type": "integer"
}
}
},
"ChangeTrackingOptions": {
"type": "object",
"properties": {
"modes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"gitDiff",
"json"
]
},
"description": "Diff surfaces. gitDiff = markdown unified diff + AST; json = per-field diff; both = mixed."
},
"schema": {
"type": "object",
"additionalProperties": true,
"description": "JSON schema of tracked fields (json/mixed mode)"
},
"prompt": {
"type": "string"
},
"previous": {
"$ref": "#/components/schemas/ChangeTrackingSnapshot"
},
"tag": {
"type": "string",
"description": "Opaque caller tag echoed on the result"
},
"contentType": {
"type": "string",
"description": "MIME type; non-text content is hashed, not diffed"
}
}
},
"ChangeTrackingSnapshot": {
"type": "object",
"properties": {
"markdown": {
"type": "string",
"description": "Present for gitDiff/mixed mode"
},
"json": {
"type": "object",
"additionalProperties": true,
"description": "Present for json/mixed mode"
},
"contentHash": {
"type": "string",
"description": "Mode-aware hash; persist + supply on the next check"
},
"capturedAt": {
"type": "string",
"description": "Caller-stamped capture time, echoed untouched"
}
}
},
"ChangeDiff": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "Unified markdown diff (gitDiff/mixed)"
},
"json": {
"type": "object",
"additionalProperties": true,
"description": "Parse-diff AST (gitDiff-only) OR per-field path map {previous,current} (json/mixed)"
}
}
},
"ChangeJudgment": {
"type": "object",
"required": [
"meaningful",
"confidence",
"reason"
],
"properties": {
"meaningful": {
"type": "boolean"
},
"confidence": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
},
"reason": {
"type": "string"
},
"meaningfulChanges": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"added",
"removed",
"changed"
]
},
"before": {
"type": "string"
},
"after": {
"type": "string"
},
"reason": {
"type": "string"
}
}
}
}
}
},
"ChangeTrackingResult": {
"type": "object",
"required": [
"status",
"contentHash"
],
"properties": {
"status": {
"type": "string",
"enum": [
"same",
"changed"
],
"description": "Per-page status. Set-level new/removed are computed by the caller's reconciler."
},
"firstObservation": {
"type": "boolean",
"description": "True when no previous was supplied — caller maps to 'new'"
},
"contentHash": {
"type": "string"
},
"snapshot": {
"$ref": "#/components/schemas/ChangeTrackingSnapshot"
},
"diff": {
"$ref": "#/components/schemas/ChangeDiff"
},
"judgment": {
"$ref": "#/components/schemas/ChangeJudgment"
},
"tag": {
"type": "string"
},
"truncated": {
"type": "boolean"
}
}
},
"ChangeTrackingDiffItem": {
"type": "object",
"required": [
"current"
],
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"current": {
"type": "object",
"properties": {
"markdown": {
"type": "string"
},
"json": {
"type": "object",
"additionalProperties": true
}
}
},
"previous": {
"$ref": "#/components/schemas/ChangeTrackingSnapshot"
},
"modes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"gitDiff",
"json"
]
}
},
"schema": {
"type": "object",
"additionalProperties": true
},
"prompt": {
"type": "string"
},
"contentType": {
"type": "string"
},
"tag": {
"type": "string"
}
}
},
"ChangeTrackingDiffRequest": {
"type": "object",
"description": "Single body (provide 'current') OR batch (provide 'batch'). Top-level modes/schema/prompt/contentType act as shared defaults in batch mode.",
"properties": {
"batch": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ChangeTrackingDiffItem"
}
},
"current": {
"type": "object",
"properties": {
"markdown": {
"type": "string"
},
"json": {
"type": "object",
"additionalProperties": true
}
}
},
"previous": {
"$ref": "#/components/schemas/ChangeTrackingSnapshot"
},
"modes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"gitDiff",
"json"
]
}
},
"schema": {
"type": "object",
"additionalProperties": true
},
"prompt": {
"type": "string"
},
"contentType": {
"type": "string"
}
}
},
"ChunkStrategy": {
"type": "object",
"description": "Strategy for chunking extracted markdown before query-based filtering",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"sentence",
"regex",
"topic"
],
"description": "Chunking algorithm: sentence = split on .!?, regex = split on pattern, topic = split on markdown headings"
},
"maxChars": {
"type": "integer",
"minimum": 1,
"description": "Maximum characters per chunk (merges short chunks up to this limit)"
},
"overlapChars": {
"type": "integer",
"minimum": 0,
"description": "Character overlap between adjacent chunks"
},
"dedupe": {
"type": "boolean",
"description": "Remove duplicate chunks"
},
"pattern": {
"type": "string",
"description": "Regex pattern to split on (required when type = 'regex')"
}
}
},
"ExtractOptions": {
"type": "object",
"description": "Firecrawl-compatible extraction options sub-object",
"properties": {
"schema": {
"type": "object",
"additionalProperties": true,
"description": "JSON Schema for structured extraction. Used as jsonSchema when jsonSchema is not set directly."
}
}
},
"ParserSpec": {
"type": "object",
"description": "Document parser directive. Accepts string form ('pdf') or object form.",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"pdf"
],
"description": "Parser type (only 'pdf' is supported)"
},
"mode": {
"type": "string",
"enum": [
"auto",
"fast",
"ocr"
],
"description": "Parsing strategy (auto = text-first; ocr degrades to text extraction with a warning)"
},
"maxPages": {
"type": "integer",
"minimum": 1,
"description": "Optional cap on the number of pages to parse"
}
}
},
"Error": {
"type": "object",
"required": [
"success",
"error"
],
"properties": {
"success": {
"type": "boolean",
"const": false
},
"error": {
"type": "string",
"description": "Human-readable error message"
},
"errorCode": {
"type": "string",
"description": "Machine-readable error code (see docs/error-codes.md)"
}
}
},
"ExtractAccepted": {
"type": "object",
"required": [
"success",
"id",
"status",
"urls"
],
"properties": {
"success": {
"type": "boolean"
},
"id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"processing"
]
},
"urls": {
"type": "integer",
"description": "Count of URLs enqueued for fetch (preflight-failed URLs appear in results, not here)."
}
}
},
"ExtractUrlResult": {
"type": "object",
"required": [
"url",
"status"
],
"properties": {
"url": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"processing",
"completed",
"failed",
"cancelled"
]
},
"data": {
"type": "object",
"additionalProperties": true,
"description": "Extracted JSON object (present when status is completed). Untouched cancelled slots omit data, error, usage, and basis."
},
"error": {
"type": "string"
},
"llmUsage": {
"type": "object",
"additionalProperties": true,
"description": "Per-URL LLM token usage (provider/model/input/output tokens)."
},
"basis": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Basis"
},
"description": "Per-field evidence for this URL; present only when the request set basis:true."
},
"basisWarnings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/BasisWarning"
}
},
"llmInputHash": {
"type": "string",
"description": "'sha256:'-prefixed hash of the canonical text sent to the extraction LLM."
}
}
},
"ExtractStatus": {
"type": "object",
"required": [
"success",
"id",
"status",
"results",
"expiresAt",
"creditsUsed",
"tokensUsed"
],
"properties": {
"success": {
"type": "boolean"
},
"id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"processing",
"cancelling",
"completed",
"failed",
"cancelled"
]
},
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ExtractUrlResult"
},
"description": "Fixed-cardinality per-URL slots in original request order. Pending slots are processing; untouched terminal slots are cancelled."
},
"error": {
"type": "string",
"description": "Job-level error, set only when every URL failed."
},
"expiresAt": {
"type": "string",
"format": "date-time"
},
"creditsUsed": {
"type": "integer"
},
"tokensUsed": {
"type": "integer"
}
}
},
"BatchScrapeRequest": {
"allOf": [
{
"$ref": "#/components/schemas/ScrapeOptions"
},
{
"type": "object",
"required": [
"urls"
],
"properties": {
"urls": {
"type": "array",
"items": {
"type": "string",
"format": "uri"
},
"minItems": 1,
"description": "URLs to scrape. Each is SSRF-validated; invalid entries are reported in invalidUrls."
},
"ignoreInvalidUrls": {
"type": "boolean",
"default": true,
"description": "When false, a single invalid URL rejects the whole submit with 400."
},
"maxConcurrency": {
"type": "integer",
"minimum": 1,
"description": "How many URLs the job scrapes concurrently. Clamped server-side to crawler.max_batch_concurrency."
}
}
}
]
},
"BatchScrapeAccepted": {
"type": "object",
"required": [
"success",
"id",
"invalidUrls"
],
"properties": {
"success": {
"type": "boolean"
},
"id": {
"type": "string",
"description": "Batch job id; poll with GET /v1/batch/scrape/{id}"
},
"invalidUrls": {
"type": "array",
"items": {
"type": "string"
},
"description": "Submitted URLs rejected by parsing or the SSRF guard, verbatim."
}
}
}
},
"responses": {
"BadRequest": {
"description": "Invalid request body",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"Unauthorized": {
"description": "Missing or invalid bearer token (hosted only)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"NotFound": {
"description": "Resource not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"RateLimited": {
"description": "Rate limit exceeded",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"SearchDisabled": {
"description": "Search is disabled in self-hosted config (error: search_disabled)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}