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
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, withX-RateLimit-*andRetry-After. - Monthly credit allowance cannot cover the whole
session_ttlhold: 402ERR::LIMIT::QUOTA_EXCEEDED, refused before any browser is leased. A shortersession_ttlmay fit. - Simultaneous sessions per organization (
browser_concurrency, default 2): 429ERR::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: 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
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.
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.
Alias for apikey, consulted only when apikey is absent or blank.
Which browser serves the session. Case-insensitive. fetch and camoufox are refused with 400 because neither speaks CDP. Omitted means the deployment default, chromium.
"chromium"Value in
- "chromium"
- "obscura"
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.
"global"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.
^[A-Za-z]{2}$Hard session lifetime in whole seconds. Out-of-range values are refused, not clamped. When it elapses the socket is closed even mid-command.
60 <= value <= 900180true/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"
Pins the exit for the session. 2-64 letters, digits and hyphens; no underscores. Omitted means the exit rotates, the opposite of /v1/sessions.
^[A-Za-z0-9-]{2,64}$2 <= length <= 64Header Parameters
Must be websocket (case-insensitive). A plain HTTP GET is refused with 400 ERR::REQUEST::INVALID.
Must contain the token Upgrade.
Forwarded to the browser backend unchanged; the returned Sec-WebSocket-Accept is computed from it. Your WebSocket library sets it.
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"End a session and purge its context POST
Marks the session `released` and deletes its stored cookies and storage in the same transaction.
Mint a single-use connect URL for /v1/browser Coming Soon POST
Returns a `/v1/browser` URL that carries a short-lived token instead of the API key, for CDP clients that accept only a URL.