spicrawl scrape
Fetch one URL, or many from stdin, as markdown, html, text, PDF or extracted JSON with spicrawl scrape; every flag maps to a POST /v1/scrape field.
spicrawl scrape <url> calls POST /v1/scrape and prints the result. On a terminal it prints the document itself; piped, it prints one JSON object.
spicrawl scrape https://example.com/blog/launch --format markdownOnly the flags you set are sent. Everything else takes the API's default, or your project's stored default. Each flag below lists the API field it sets; see POST /v1/scrape for the full field rules.
Examples
# A page as clean markdown, for an LLM context window
spicrawl scrape https://example.com/blog/launch --format markdown
# A JS-rendered page, waiting for the element that holds the data
spicrawl scrape https://example.com/pricing --render --wait-for '.plans' --format markdown
# Through your own proxy, with the metadata on stderr
spicrawl scrape https://example.de/produkt/42 --proxy "$MY_PROXY_URL" --meta
# A Cloudflare-protected page, through your own (residential) proxy
spicrawl scrape https://example.com/products/42 --proxy "$MY_PROXY_URL" --format markdown
# Selector extraction; prints only the extracted data
spicrawl scrape https://example.com/products/1 --extract '{"title":"h1","price":".price"}'
# Render JavaScript, retrying transient failures
spicrawl scrape https://news.example.com --render --retry 3
# Many URLs from a file, 8 at a time, one JSON line each; keep the failures
spicrawl scrape - --format markdown --concurrency 8 < urls.txt | jq -c 'select(.error or .target_error)'Flags
Target
| Flag | API field | Meaning |
|---|---|---|
<url> | url | Target URL, http or https. - reads URLs from stdin (see many URLs). |
--method M | method | HTTP method sent to the target, uppercased. Default GET. No request body is sent; only GET results are cached. |
--header 'Name: value' | custom_headers | Header sent to the target. Repeatable. A value without : exits 2. Hop-by-hop headers (Host, Connection, Content-Length, ...) are refused with ERR::REQUEST::INVALID_PARAMETER. |
--body JSON|@file | whole body | Raw request body as inline JSON, @path, or @- for stdin. Flags you also set override its fields. |
Rendering
| Flag | API field | Meaning |
|---|---|---|
--render | js_render | Render JavaScript (3 credits). Screenshots, PDFs, --actions and challenged pages move to full-browser rendering by themselves, at that tier's price. See credits. |
--impersonate | impersonate | Fetch tier only: present a current browser TLS/HTTP2 fingerprint. The API does this by default; --impersonate=false turns it off. No extra cost. |
--engine E | engine | Deprecated and ignored: rendering is chosen automatically from the rest of the request. The response carries X-Warning: ENGINE_IGNORED. Remove it. Hidden from --help since 0.1.4, but still accepted. |
--mode auto | mode | Start with a plain fetch and move to stronger rendering only when the previous step met a challenge. Bills only the step that served. Cannot be combined with --render. |
--headless=false | headless | Deprecated and ignored, like --engine: rendering is chosen automatically. Remove it. Also hidden from --help. |
--session ID | session_id | Reuse a session's cookies and storage. See sessions. |
You do not pick a rendering tier. The request picks it, and the result names the one that ran: fetch (a plain fetch, no JavaScript), js (JavaScript rendering) or browser (full-browser rendering). It is the engine field in JSON and the engine: line of --meta. See response headers.
Proxy
| Flag | API field | Meaning |
|---|---|---|
--proxy URL | proxy | Your own proxy (http, https, socks5, socks5h). |
--proxy-verify | proxy_verify | Probe --proxy before spending engine time. Requires --proxy. |
Two more options are on the way: stealth mode Coming soon (full-browser rendering with a hardened fingerprint), and the managed proxy flags --premium-proxy, --country and --sticky-key Coming soon.
Cloudflare-protected pages, and pages behind similar managed challenges, are handled automatically when you bring your own --proxy (residential works best): a challenged page is retried on stronger rendering on the same exit, and you get the real page. This is not guaranteed on every site. Automatic handling on managed --premium-proxy exits is Coming soon. On the default shared pool a challenged page can return ERR::UPSTREAM::CHALLENGE; retry with your own residential --proxy. If a request uses your own proxy and hits a blocking challenge that Spicrawl solves, your proxy's address and credentials are shared with the third-party solving service for that solve.
Waiting and page control
These need a browser (--render or --mode auto); otherwise the API returns ERR::REQUEST::INCOMPATIBLE_FLAGS.
| Flag | API field | Meaning |
|---|---|---|
--wait MS | wait | Fixed delay after load, in milliseconds. |
--wait-for SEL | wait_for | CSS selector to wait for before capture. If it never appears the page is returned with the warning RENDER_DEGRADED. |
--wait-for-timeout MS | wait_for_timeout | Cap on --wait-for, in milliseconds. Requires --wait-for. |
--block LIST | block_resources | Comma-separated: images, fonts, media, stylesheets, scripts, or none. Full-browser rendering blocks nothing by default. |
--actions JSON|@file | actions | Ordered browser actions (click, fill, scroll, ...). See browser actions. |
--network-capture JSON|@file | network_capture | Record the XHR/fetch responses the page made. Returned under network. See network capture. |
Extraction
Each of these makes the API return its JSON envelope instead of the raw document.
| Flag | API field | Meaning |
|---|---|---|
--format F | response_format | html (API default), markdown, text, json (the envelope) or pdf (rendered by a full browser, chosen for you). |
--extract JSON|@file | extract | Selector map or JSON Schema. Result under data. |
--ai PROMPT | ai_extract.prompt | Coming soon Model extraction from a plain-language prompt. Adds 4 credits. |
--ai-schema JSON|@file | ai_extract.schema | Coming soon Model extraction to a JSON Schema. Cannot be combined with --ai (exit 2): the API takes a prompt or a schema, not both. |
--autoparse | autoparse | The page's own JSON-LD, OpenGraph and embedded state, under data. |
--links | links | The page's absolute, de-duplicated links, under links. |
Cleaning
| Flag | API field | Meaning |
|---|---|---|
--main-content | main_content_only: true | Keep only the main article. |
--no-main-content | main_content_only: false | Keep the whole document. Markdown, text and json default to main content; html does not. Setting both flags exits 2. |
--include SEL | include_tags | Keep only matching subtrees. Repeatable. |
--exclude SEL | exclude_tags | Remove matching elements. Repeatable. |
--no-parse-pdf | parse_pdf: false | Refuse PDF targets instead of parsing them to text (ERR::EXTRACT::FAILED, 0 credits). |
Screenshots
A screenshot is rendered by a full browser, chosen for you. If it cannot be taken, the API returns ERR::REQUEST::CAPABILITY_UNSUPPORTED at 0 credits.
| Flag | API field | Meaning |
|---|---|---|
--screenshot | screenshot | Capture a screenshot. |
--screenshot-full-page | screenshot_fullpage | Full scrollable page. |
--screenshot-selector SEL | screenshot_selector | One element. Mutually exclusive with --screenshot-full-page. |
--screenshot-format F | screenshot_format | png, jpeg or webp. |
--screenshot-quality N | screenshot_quality | jpeg/webp quality, 1-100. |
--screenshot-dir DIR | none | Save screenshots into DIR as <label>.<format>. |
Cache and cost
| Flag | API field | Meaning |
|---|---|---|
--no-cache | cache: false | Force a fresh fetch. A cache hit is billed at the same price as the fetch that stored it, so this changes freshness, not price. |
--cache-ttl S | cache_ttl | Accept a cached copy no older than this many seconds. Default 172800 (48 hours). |
--max-cost N | max_cost | Refuse with ERR::LIMIT::MAX_COST_EXCEEDED (0 credits, exit 5) if the request could cost more. |
--allowed-status 403,451 | allowed_status_codes | Extra target statuses to treat as billable success (200, 404 and 410 always are). The command exits 0 on them instead of 6 (see target status errors). A non-number exits 2. |
--original-status | original_status | Use the target's status as the HTTP status. The CLI reads the target status either way, so exit codes and output do not change. |
Output and retries (CLI only)
| Flag | Default | Meaning |
|---|---|---|
-o, --output FILE | stdout | Write the document to FILE (raw bytes for PDF), atomically. -o - means stdout. See writing to a file. |
--meta | off | Human mode: print the rendering tier, credits, cache state, target status, proxy, final URL and request id to stderr. A no-op under --json (and when stdout is not a terminal): the JSON output already carries all of it. |
--retry N | 1 | Attempts for errors the API marks retryable. 1 means no retry. Waits retry_after_seconds when given, else backs off from 1 s, doubling to at most 30 s. |
--concurrency N | 4 | With -: parallel requests. |
--jsonl | on for - | Print the result as one compact JSON line, even on a terminal. Always on for -. |
Output
Human mode (terminal)
Prints the document itself: markdown, html or text. With --extract, --ai, --ai-schema or --autoparse it prints only the extracted data, indented, with the fields in the order the API returned them (0.1.4 or later; earlier versions sorted them alphabetically). With --links or --network-capture it prints that data after the document, under --- links (N) --- and --- network (N) --- lines. API warnings go to stderr as warning: ...: they print even with -q, and never in JSON mode, where they are in the output's warnings field.
--format pdf is binary and is never written to a terminal: pass -o FILE, or --json to get it base64-encoded. Without either, the command exits 2.
spicrawl scrape https://example.com/pricing --format markdown --metaengine: fetch
credits: 1 (request cost 1)
cache: miss
target status: 200
proxy: direct
final url: https://example.com/pricing
request id: 01J9ZQ4M7R3T8VX2K5N6P0B1CDengine: is a rendering tier (fetch, js or browser) whatever the API sent. --json and --jsonl print the API's values unchanged.
JSON mode (piped, or --json)
When the API answers with its JSON envelope (--format json, or any of --extract, --ai, --ai-schema, --autoparse, --links, --network-capture, --screenshot, or --actions containing a screenshot step or an evaluate with return_value), the envelope is printed as-is, in the API's key order, and large integers are not rounded (0.1.4 or later): url, final_url, status, content, credits, engine, proxy_source, warnings, data, links, screenshots, network.
When the API answers with the raw document (html, markdown, text), the CLI wraps it with the response headers:
{
"cache_state": "miss",
"content": "# Launch\n\nToday we ...",
"content_type": "text/markdown; charset=utf-8",
"credits": 1,
"credits_charged": 1,
"engine": "fetch",
"final_url": "https://example.com/blog/launch",
"proxy_source": "direct",
"request_cost": 1,
"request_id": "01J9ZQ4M7R3T8VX2K5N6P0B1CD",
"status": 200,
"url": "https://example.com/blog/launch",
"warnings": []
}status is the target site's status (X-Target-Status), not the API's. credits repeats credits_charged under the envelope's name. Binary documents (--format pdf) carry content_base64 instead of content. When the target's status is outside 2xx, the object also has target_error (see target status errors).
stderr in JSON mode is empty on success. On a failure it holds only the failure document, so spicrawl scrape ... 2>err.json; jq . err.json always parses: the API's problem document (code, retryable, request_id, ...), or {"error": "...", "exit_code": N} when the failure did not come from the API (a usage error, a network failure). Warnings, wrote N bytes and the deprecation notices are human-mode output only (0.1.4 or later; earlier versions printed them as prose on stderr, sometimes before the problem document).
spicrawl scrape https://example.com/blog/launch --format markdown | jq -r .contentTarget status errors
A site that answers with an error is still a successful API call: the API returns HTTP 200 with the site's status in X-Target-Status (status in the output). The CLI treats a target status outside 2xx as a failure unless --allowed-status lists it. The document is still printed, stderr gets error: target returned 404 (pass --allowed-status 404 to accept it), and the exit code is 6. In JSON mode the printed object also has "target_error": "target returned 404".
spicrawl scrape https://example.com/old-page --allowed-status 404 # exits 0 on a 404 pageWriting to a file with -o
-o FILE writes the document (the extracted data when extraction is on, raw bytes for PDF) to FILE. In human mode it prints wrote N bytes to FILE on stderr (not with -q, never in JSON mode). The write is atomic: a temporary file in the same directory, then a rename, so a failure never truncates an existing FILE. -o - means stdout. In JSON mode stdout still carries the metadata object, without the written field and with "output": "FILE" added.
FILE, and the screenshots next to it, are written only when the target answered 2xx or its status is in --allowed-status (0.1.4 or later). On any other target status the earlier file is left alone, human mode prints warning: not writing FILE: the target failed (--allowed-status accepts it), and the command exits 6. If the write itself fails (no such directory, no permission), the billed result is printed to stdout as JSON instead of being lost, the error goes to stderr, and the command exits 1.
spicrawl scrape https://example.com/report --format pdf -o report.pdfScreenshots in the envelope are saved next to FILE as <FILE stem>.<label>.<format>, or into --screenshot-dir. Their base64 data is replaced by "file": "<path>" in the printed JSON. With neither -o nor --screenshot-dir, human mode leaves screenshots unsaved and says so on stderr; JSON mode keeps them base64 in the output.
spicrawl scrape https://example.com --screenshot --screenshot-full-page -o home.html
# writes home.html and home.<label>.pngLive progress on a terminal
When stderr is a terminal (0.1.4 or later), a wait longer than about 300 ms shows a spinner there with a label and the elapsed time, for example ⠹ Scraping example.com/pricing · 4.2s. scrape and scrape - end with a one-line summary on stderr: ✓ or ✗, the page, the target status, the credits charged and the duration, for example ✓ example.com/pricing 200 · 1 credit · 4.2s. A request that finishes in under 300 ms shows the summary only. scrape - shows Scraping 2/3 done (1 failed) while it runs and ends with ✗ 3 URLs 2 ok · 1 failed · 1.5s. An API error is drawn as a short block that keeps the ERR:: code and the hint, with the target status, retryable, the request id (spicrawl logs get <id>) and a docs link.
stdout is never touched: the same bytes come out with or without it. It is off, and stderr is exactly what the sections above describe, when:
- stderr is not a terminal (a pipe, a file, a CI log or an agent's tool call),
- you pass
--jsonor-q, NO_COLORis set to anything non-empty, orCLICOLOR=0,TERM=dumb, orCIis set to anything but empty,0orfalse.
Piping stdout alone does not turn it off: spicrawl scrape ... | jq in a terminal still shows the spinner. Outside a UTF-8 locale the glyphs fall back to ASCII (+ x and a | / - \ spinner).
Many URLs from stdin
spicrawl scrape - reads URLs from stdin, one per line. Blank lines and lines starting with # are skipped. It runs --concurrency requests at once (default 4) with the same flags for every URL, and prints one compact JSON line per URL in completion order, in every mode.
spicrawl scrape - --format markdown --concurrency 8 < urls.txt > pages.jsonlEach success line is the result object above plus index, the 1-based position of the URL in the input. Each failure line is:
{"index":3,"url":"https://example.com/products/43","error":{"type":"https://docs.spicrawl.com/errors#UPSTREAM_CHALLENGE","title":"Target served a bot challenge","status":502,"code":"ERR::UPSTREAM::CHALLENGE","retryable":true,"doc_url":"https://docs.spicrawl.com/errors#UPSTREAM_CHALLENGE","target_status":403,"request_id":"01J9ZQ5B2C7D9EXAMPLE00000"}}error is the API's problem document, or {"detail": "...", "exit_code": N} for a network or local failure.
- Exit code is
0when every URL succeeded, else the exit code of the first failure. A target status outside 2xx (and not in--allowed-status) is a failure: its line keeps the result and addstarget_error, and the code is6. In human mode aN ok, M failedline (on a live terminal, the summary line above) anderror: M of N URLs failedgo to stderr; in JSON mode stderr holds{"error": "M of N URLs failed", "exit_code": N}. - If stdout cannot be written the run stops at once with exit
1instead of billing the remaining URLs. Ctrl-C stops it too, including while it waits for stdin (exit130). -ois refused with-(exit2); use--screenshot-dirfor screenshots, which are saved as<index>.<label>.<format>.- Empty stdin exits
2withno URLs on stdin.
Errors
A failed scrape exits with the code for its code. In JSON mode stderr holds the API's problem document, or {"error": "...", "exit_code": N} for a failure that did not come from the API. In human mode it holds error:, hint:, target status:, retryable: and request id: lines, which a terminal draws as the block described under live progress. See exit codes.
| You see | Exit | Do next |
|---|---|---|
ERR::UPSTREAM::CHALLENGE | 6 | Retry with --premium-proxy Coming soon or your own residential --proxy; a challenge on those exits is retried on stronger rendering automatically. --mode auto also climbs on any exit. |
ERR::UPSTREAM::TIMEOUT with --wait-for | 6 | Raise --wait-for-timeout or fix the selector. |
ERR::REQUEST::INCOMPATIBLE_FLAGS | 4 | Remove the conflicting flag named in detail (for example --proxy-verify without --proxy). |
ERR::LIMIT::MAX_COST_EXCEEDED | 5 | Raise --max-cost, or drop what makes the request dearer (--render, --screenshot, --format pdf). |
ERR::PROXY::UNREACHABLE | 7 | Your --proxy did not accept a connection. Check the URL. |
ERR::EXTRACT::FAILED | 8 | Check --ai prompt or schema; the call cost 0 credits. |
ERR::ENGINE::RENDER_FAILED | 8 | A browser action failed (no element matched, a script threw); detail names the step. Fix the selector, add a wait_for before it, or mark the step "on_error":"skip". 0 credits. |
target returned 404 (any status outside 2xx) | 6 | The site answered with an error page. The document is printed, but -o writes nothing. Pass --allowed-status 404 to accept it. |
cannot reach the Spicrawl API | 10 | Nothing was sent. Check the network and --base-url, then retry. |
timed out waiting for the Spicrawl API, or lost the connection ... after the request was sent | 12 | The request may have run and been billed. Check spicrawl logs before retrying; raise --timeout for a slow render. |
| Ctrl-C | 130 | Human mode prints interrupted; JSON mode prints nothing. A request already sent may still have run. |