Render JavaScript pages
Run a page in a real browser with js_render, wait for content with wait and wait_for, and let mode=auto escalate only when it has to.
Use this when the page you fetched is an empty shell: a <div id="root"></div>, a "Loading…" placeholder, or markdown with no article in it. A plain fetch, the default, downloads the HTML and runs no JavaScript. js_render=true loads the page in a browser, lets its scripts build the content, then captures it. Rendering is also required for wait, wait_for, actions, block_resources, network_capture, screenshot and response_format=pdf; setting any of those without rendering is a 400, never silently ignored.
Minimal request
curl https://api.spicrawl.com/v1/scrape \
-H "Authorization: Bearer $SPICRAWL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/products/42",
"js_render": true,
"wait_for": ".price",
"response_format": "markdown"
}'What comes back
The rendered page, in the format you asked for. Check the headers:
HTTP/1.1 200 OK
Content-Type: text/markdown; charset=utf-8
X-Target-Status: 200
X-Credits-Charged: 3X-Credits-Chargedis the price of the rendering tier that served the page, andX-Enginenames it:fetch,js(JavaScript rendering) orbrowser(full-browser rendering).X-Warningappears when something was adjusted.RENDER_DEGRADEDmeanswait_fornever matched and you got the page as it stood.EMPTY_RENDERmeans the page rendered with no visible content: nothing is charged, so retry withwait_foror a longer wait.ENGINE_SUBSTITUTEDmeans this deployment served the request on a different rendering tier than usual (see below).ENGINE_IGNOREDmeans the request sentengineorheadless, which never change anything (see below).
How rendering is chosen
You do not choose how a page is rendered. Spicrawl picks the path from what the request needs, and you are billed for the tier that served it.
| Rendering | Selected by | Credits | Use it for |
|---|---|---|---|
| Plain fetch | default | 1 | Server-rendered HTML. No JavaScript. |
| JavaScript rendering | js_render=true | 3 | Most JavaScript sites without anti-bot protection. The volume path. No screenshot or PDF. |
| Full-browser rendering | a screenshot or PDF, actions, an explicit block_resources, mode=auto, or a challenge climb | 8 | Screenshots and PDF, actions, and pages that block JavaScript rendering. Runs with a real display. |
On a premium_proxy Coming soon exit these cost 10, 25 and 32. stealth=true Coming soon renders with a hardened fingerprint on full-browser rendering and is billed for the tier that served; see Anti-bot.
The engine and headless fields are deprecated and ignored: they never change how a page is rendered, nothing returns a 400, and the response carries X-Warning: ENGINE_IGNORED (in the JSON envelope, under warnings). The CLI flags --engine and --headless map to those fields and are ignored too.
Full-browser rendering always runs with a real display and adds about 1 s of startup.
ENGINE_SUBSTITUTED
js_render=true asks for JavaScript rendering, not for a specific path. If this deployment does not run the usual one, the request is served by the next tier up that it does run, which can cost more (8 or 25 credits), and is flagged with X-Warning: ENGINE_SUBSTITUTED: the requested rendering was unavailable; served by browser rendering. (or JavaScript rendering), and X-Engine names the tier that ran. Set max_cost if you would rather fail with ERR::LIMIT::MAX_COST_EXCEEDED than pay more.
Options that matter
Without wait or wait_for, a render waits a few seconds after load for the page's content to appear, and keeps waiting (up to that limit) while the page still shows a loading placeholder. Use wait_for when you need a specific element.
| Field | Limits | Effect |
|---|---|---|
wait | 0–30000 ms, default 0 | Fixed delay after load, before capture. Use wait_for instead when you can name an element. |
wait_for | CSS selector | Capture as soon as the element appears. If it never does, you get the page as it stood plus X-Warning: RENDER_DEGRADED. |
wait_for_timeout | 0–120000 ms, default 0 (engine default) | How long to wait for wait_for. Requires wait_for (400 ERR::REQUEST::INCOMPATIBLE_FLAGS otherwise). |
block_resources | image, font, media, stylesheet, script, xhr, fetch, websocket, document, other, or none | Subresources the browser does not load. Plural and short aliases (images, css, js) are accepted. |
mode | auto | Escalate from a plain fetch to JavaScript rendering to full-browser rendering, then stealth rendering Coming soon, until a rung returns a usable page. |
solve_captcha | true or false (default false) | Solves an embedded Cloudflare Turnstile widget. Runs on full-browser rendering, 5 credits more when solved. Not with mode=auto. See Cloudflare. |
block_resources defaults
When you omit block_resources, full-browser rendering blocks nothing, so images and fonts load. They are most of a page's bytes, so a full-browser pass transfers more than it needs to if you only want the text: send ["images", "fonts"] to skip them. An explicit list blocks exactly the classes you name; ["none"] blocks nothing. JavaScript rendering cannot block resources, so a request with an explicit list runs on full-browser rendering, and no default block is applied to JavaScript rendering. Block script only when the content is already in the HTML, or the page will not build.
mode=auto
mode=auto tries a plain fetch first, then JavaScript rendering, then full-browser rendering, then stealth rendering Coming soon. It moves up a rung only when the previous one was met by a bot challenge or a target status that is not billable. You pay only for the rung that succeeded, and 0 if all fail. If full-browser rendering is refused by a challenge that names its vendor, the request ends there with ERR::UPSTREAM::CHALLENGE instead of trying stealth rendering Coming soon; see Anti-bot. Two things to know:
max_costand your monthly allowance are checked against the price of the ladder's dearest rung (25 credits for stealth rendering; 32 for full-browser rendering on apremium_proxyComing soon exit), even when the request stops at fetch.- When the request escalated, the response carries one
X-Warning: ESCALATEDper abandoned rung (for exampleESCALATED: fetch (1 credit): blocked by challenge page (Cloudflare …)), and the JSON envelope includesdiagnostics.attemptslisting each rung and why it was abandoned.
mode=auto cannot be combined with js_render, stealth or solve_captcha (400 ERR::REQUEST::INCOMPATIBLE_FLAGS). It does reach full-browser rendering, when JavaScript rendering was met by a challenge.
Failure modes
| Code | HTTP | What to do |
|---|---|---|
ERR::REQUEST::INCOMPATIBLE_FLAGS | 400 | A browser-only flag without js_render (or stealth, solve_captcha or mode=auto), wait_for_timeout without wait_for, or mode=auto with js_render, stealth or solve_captcha. The detail names the flag. |
ERR::REQUEST::CAPABILITY_UNSUPPORTED | 400 | A screenshot or PDF on a deployment with no full-browser rendering. Drop the screenshot or PDF. 0 credits. |
ERR::ENGINE::UNAVAILABLE | 503 | No capacity, or the rendering path the router chose (for example full-browser rendering for a screenshot) is not deployed. Retryable after Retry-After. |
ERR::ENGINE::RENDER_FAILED | 502 | The browser could not render the page. Retryable. |
ERR::UPSTREAM::TIMEOUT | 504 | The render timed out, often while waiting on wait_for. Raise wait_for_timeout or fix the selector. |
ERR::LIMIT::MAX_COST_EXCEEDED | 400 | The rendering tier (or the top mode=auto rung) costs more than max_cost. |
All of these cost 0 credits.
Cost
Per successful request: plain fetch 1, JavaScript rendering 3, full-browser rendering 8, stealth rendering Coming soon 25. On a premium_proxy Coming soon exit: 10, 25, 32 and 25. wait, wait_for and block_resources add nothing. Failures cost 0. Read X-Credits-Charged for the amount billed. See Credits.
Related
- Anti-bot when a render returns a challenge page
- Browser actions to click, scroll and fill before capture
- Network capture to read the page's own JSON API calls
- Screenshots and PDF
Markdown for LLMs
Turn any page or PDF into main-content markdown with response_format=markdown, and cut tokens with include_tags, exclude_tags and main_content_only.
Anti-bot
How Cloudflare-protected pages are handled through your own proxy, what to do on ERR::UPSTREAM::CHALLENGE, the escalation ladder with the credit cost of each rung, the automatic climb to stronger rendering, and how to read blocks in your request logs.