spicrawlspicrawlDocs
Browser

Open a Chrome DevTools Protocol WebSocket to a cloud browser Coming soon

A WebSocket upgrade, not a JSON endpoint. Connect with `puppeteer.connect({browserWSEndpoint})` or `chromium.connectOverCDP(url)`.

Requires scope: browser

GET
/v1/browser

Coming soon A WebSocket upgrade, not a JSON endpoint. Connect with puppeteer.connect({browserWSEndpoint}) or chromium.connectOverCDP(url). Authenticate with, in order of preference: Authorization: Bearer <key> on the upgrade; a single-use token from POST /v1/browser/token (60 s, one connection, bound to its options — the URL the CLI and MCP return); or the apikey query parameter (api_key alias), kept for ZenRows-style URLs. When token is present it is the only credential consulted. Requires the browser scope.

Every refusal happens before the upgrade and is an RFC 7807 problem. After the 101 there is no HTTP response left; the session ends by closing the TCP connection, never with a close frame or error message.

Why a session ends (recorded server-side, not sent to you): client_closed (you disconnected, or stopped answering WebSocket pings: the API pings every 15 s and closes after 2 unanswered, so a half-open client frees its slot within about 30 s), ttl_expired (session_ttl elapsed; a hard wall, not idle), idle_timeout (no CDP messages in either direction for the deployment idle timeout, 2 minutes by default, measured from the last message; pings and pongs do not count), byte_limit (the deployment's per-session traffic cap was reached, if configured), upstream_closed (the browser went away).

Pricing: a session costs 8 credits per started minute, the same as one real-Chromium page. At admission the session HOLDS the price of its whole session_ttl against the monthly credit allowance (default session_ttl 180 s = 24 credits; 60–900 s range = 8–120 credits). At close it is charged for the minutes it actually held (rounded up, capped at session_ttl's minutes) and the unused part of the hold is returned immediately. A session the platform ends (upstream closed, or API shutdown) costs 0; a session that never opens costs 0 and returns its hold. While the operator has switched billing off, sessions hold and cost 0.

Admission, before the upgrade, in this order:

  • Rate limit on session opens per key: 429 ERR::LIMIT::RATE_LIMITED, with X-RateLimit-* and Retry-After.
  • Monthly credit allowance cannot cover the whole session_ttl hold: 402 ERR::LIMIT::QUOTA_EXCEEDED, refused before any browser is leased. A shorter session_ttl may fit.
  • Simultaneous sessions per organization (browser_concurrency, default 2): 429 ERR::LIMIT::CONCURRENCY_EXCEEDED, Retry-After: 5, Concurrency-Limit/Concurrency-Remaining: 0. The slot is held until the socket closes, so always close your browser.
  • If the limiter store is unreachable the session is refused (fail closed): 503 ERR::INTERNAL::UNAVAILABLE, Retry-After: 10.
  • No CDP backend on this deployment: 501 ERR::ENGINE::UNAVAILABLE, retryable: false. Do not retry.

Rate and concurrency headers appear only on refusals; the 101 is the browser backend's handshake forwarded verbatim.

Authorization

AuthorizationBearer <token>

Authorization: Bearer <key>. Read the key from the SPICRAWL_API_KEY environment variable; never hard-code or log it. spicrawl_test_… keys can never spend live credits. Scopes: scrape, batch, sessions (granted by default), browser and read (granted deliberately). A missing scope is 403 ERR::AUTH::INSUFFICIENT_SCOPE naming the scope.

In: header

Query Parameters

token?string

A single-use browser token from POST /v1/browser/token. Consumed on arrival, whatever happens next; expires 60 s after issue; revoked with the key that minted it. The session options are the ones bound into the token; an option in the URL that differs from (or was not part of) the binding is refused with 400 ERR::REQUEST::INVALID_PARAMETER. A used, expired or unknown token is 401 ERR::AUTH::INVALID_KEY.

apikey?string

The API key, for WebSocket clients that cannot set headers. api_key is accepted as an alias. Ignored when an Authorization Bearer header is present. Keep the URL out of logs you do not control.

api_key?string

Alias for apikey, consulted only when apikey is absent or blank.

engine?string

Which browser serves the session. Case-insensitive. fetch and camoufox are refused with 400 because neither speaks CDP. Omitted means the deployment default, chromium.

Default"chromium"

Value in

  • "chromium"
  • "obscura"
proxy_region?string

Broad exit region such as eu. Case-insensitive. global means no constraint and is the same as omitting it. Cannot be combined with proxy_country.

Default"global"
proxy_country?string

Exit country as a two-letter ISO 3166-1 alpha-2 code (case-insensitive, normalised to lowercase). Combining it with a non-global proxy_region is refused with 400 ERR::REQUEST::INCOMPATIBLE_FLAGS.

Match^[A-Za-z]{2}$
session_ttl?integer

Hard session lifetime in whole seconds. Out-of-range values are refused, not clamped. When it elapses the socket is closed even mid-command.

Range60 <= value <= 900
Default180
headless?string

true/1 or false/0 (case-insensitive); anything else is 400. Omitted defers to the deployment. false attaches a display. headless=false with engine=obscura is refused with 400 ERR::REQUEST::INCOMPATIBLE_FLAGS.

Value in

  • "true"
  • "false"
  • "1"
  • "0"
sticky_key?string

Pins the exit for the session. 2-64 letters, digits and hyphens; no underscores. Omitted means the exit rotates, the opposite of /v1/sessions.

Match^[A-Za-z0-9-]{2,64}$
Length2 <= length <= 64

Header Parameters

Upgrade*string

Must be websocket (case-insensitive). A plain HTTP GET is refused with 400 ERR::REQUEST::INVALID.

Connection*string

Must contain the token Upgrade.

Sec-WebSocket-Key*string

Forwarded to the browser backend unchanged; the returned Sec-WebSocket-Accept is computed from it. Your WebSocket library sets it.

Sec-WebSocket-Version*string

Forwarded unchanged. Normally 13.

Response Body

application/problem+json

application/problem+json

application/problem+json

application/problem+json

application/problem+json

application/problem+json

application/problem+json

application/problem+json

application/problem+json

curl -X GET "https://example.com/v1/browser" \  -H "Upgrade: websocket" \  -H "Connection: Upgrade" \  -H "Sec-WebSocket-Key: string" \  -H "Sec-WebSocket-Version: 13"
Empty