spicrawlspicrawlDocs

Drive a cloud browser over CDP, and hand it to a person

Connect any DevTools Protocol (CDP) client to a real browser hosted by Spicrawl with GET /v1/browser, and open a live view so a person can watch it and take control for a login, a one-time code or a captcha.

GET /v1/browser is a WebSocket that speaks the DevTools Protocol (CDP). Point any CDP client at it and you drive a real browser hosted by Spicrawl: open tabs, click, type, run scripts, intercept requests. When a page needs a person, such as a login, a one-time code or a captcha, ask for a live view link. They open it, take control, finish the step, and give control back. Your connection carries on in the same tab with the same cookies.

It needs a key with the browser scope, which new keys do not get by default; grant it deliberately. A session costs 8 credits per started minute and lasts session_ttl seconds (60-900, default 180, at most 15 minutes) or until you disconnect. For scripted page interaction inside one request, use browser actions.

Connect

Mint a single-use connect URL, then open it with your CDP client as its browser WebSocket endpoint:

curl -X POST https://api.spicrawl.com/v1/browser/token \
  -H "Authorization: Bearer $SPICRAWL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"proxy_country": "de", "session_ttl": 300}'
{
  "url": "wss://api.spicrawl.com/v1/browser?proxy_country=de&session_ttl=300&token=wbt_...",
  "path": "/v1/browser?proxy_country=de&session_ttl=300&token=wbt_...",
  "expires_at": "2026-11-10T10:31:00Z",
  "expires_in": 60,
  "single_use": true,
  "session_id": "bs4f0c2a9e1d7b38c65a0e9f12"
}

The token (wbt_...) works for one connection, within 60 s, and only with the options it was minted with: editing them in the URL is 400 ERR::REQUEST::INVALID_PARAMETER. Mint a new one for every session. Keep session_id: it is how you ask for the live view later.

This Node.js 22+ script needs no library. It opens example.com and reads its title:

const mint = await fetch('https://api.spicrawl.com/v1/browser/token', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.SPICRAWL_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ session_ttl: 300 }),
}).then((r) => r.json());

const ws = new WebSocket(mint.url);
let id = 0;
const send = (method, params = {}, sessionId) => new Promise((resolve) => {
  const me = ++id;
  ws.addEventListener('message', function on(ev) {
    const msg = JSON.parse(ev.data);
    if (msg.id === me) { ws.removeEventListener('message', on); resolve(msg); }
  });
  ws.send(JSON.stringify({ id: me, method, params, ...(sessionId && { sessionId }) }));
});
await new Promise((r) => ws.addEventListener('open', r));

const { result: { targetId } } = await send('Target.createTarget', { url: 'https://example.com' });
const { result: { sessionId } } = await send('Target.attachToTarget', { targetId, flatten: true });
await new Promise((r) => setTimeout(r, 2000));
const title = await send('Runtime.evaluate', { expression: 'document.title', returnByValue: true }, sessionId);
console.log(title.result.result.value, '| session', mint.session_id);
ws.close();

Any CDP client library connects the same way: pass url as its browser WebSocket endpoint. If your client can set headers on the WebSocket upgrade, you can skip the token and connect to wss://api.spicrawl.com/v1/browser with Authorization: Bearer <key>. That session has no session_id you can address, so it has no live view.

Options

Send them in the token body (or as query parameters on a header-authenticated connect).

OptionValuesDefault
enginebrowser (full-browser rendering, the whole protocol) or js (lighter JavaScript rendering, a smaller protocol surface; a live view can return 409 on it)browser
session_ttl60-900 seconds. Out of range is 400, not clamped.180
proxy_countryISO 3166-1 alpha-2, lowercase (de)any Refused with 400 ERR::REQUEST::INVALID_PARAMETER when Spicrawl's pool has no exit in that country (checked at mint and connect); omit it to exit anywhere.
proxy_regiononly global (no constraint) is accepted for now; any other value is 400global
sticky_key2-64 letters, digits or hyphens: pins the exit for the session. Known limit: on our pool it does not yet keep the same exit across sessions, so a later session with the same key may get another IPexit rotates
proxyyour own http://user:pass@host:port; never echoed back in urlour pool

Live view and human takeover

While your connection is open, ask for a live view link and send it to a person:

curl -X POST https://api.spicrawl.com/v1/browser/sessions/bs4f0c2a9e1d7b38c65a0e9f12/live \
  -H "Authorization: Bearer $SPICRAWL_API_KEY"
{
  "session_id": "bs4f0c2a9e1d7b38c65a0e9f12",
  "live_view_url": "https://api.spicrawl.com/v1/browser/live#wss%3A%2F%2Fapi.spicrawl.com%2Fv1%2Fbrowser%2Fsessions%2Fbs4f0c2a9e1d7b38c65a0e9f12%2Flive%3Ftoken%3Dwbv_...",
  "live_url": "wss://api.spicrawl.com/v1/browser/sessions/bs4f0c2a9e1d7b38c65a0e9f12/live?token=wbv_...",
  "expires_in": 60,
  "single_use": true
}

The person opens live_view_url

They see the page your connection is on, streamed live, and follow it to any new tab you open. The link works once and must be opened within 60 s; ask again for a new one.

They press "Take control"

Their mouse, keyboard and scrolling now drive the browser. Your connection's input (Input.*), navigation (Page.navigate, Page.reload, Page.navigateToHistoryEntry) and tab changes (Target.createTarget, Target.closeTarget, Target.activateTarget) are answered with CDP error -32001 instead of reaching the browser. Reading the page (Runtime.evaluate, DOM.*) keeps working, so a script that polls does not crash.

They press "Give control back to the agent"

Poll until controller is agent, then carry on: same tab, same cookies, now logged in.

curl https://api.spicrawl.com/v1/browser/sessions/bs4f0c2a9e1d7b38c65a0e9f12 \
  -H "Authorization: Bearer $SPICRAWL_API_KEY"
# {"session_id":"bs4f0c2a9e1d7b38c65a0e9f12","status":"running","controller":"agent","expires_at":"2026-11-10T10:35:00Z"}

You can also flip control yourself: POST /v1/browser/sessions/{id}/takeover hands it to the person, and POST /v1/browser/sessions/{id}/release gives it back to your connection. Both answer like the GET.

  • Not billed separately. Watching rides the session's per-minute price. The live view ends when the session does.
  • Who can see it. The routes need your key and the browser scope. Another organization's session, an unknown id and a closed session are all 404 ERR::REQUEST::NOT_FOUND. The viewer token (wbv_ and 64 hex characters) is stored only as a hash. The page keeps it in the URL fragment, which a browser never sends to a server, and removes it from the address bar on load.
  • What the viewer can do. Stream the page and, while holding control, send input. It cannot run scripts, read cookies or navigate by URL.
  • From an AI agent. The MCP tools spicrawl_browser_live_view and spicrawl_browser_control wrap these routes.

What is refused

A few commands would let a session leave its assigned exit or act on the browser as a whole. They are answered with a CDP error and never reach the browser: a per-context proxy (Target.createBrowserContext with proxyServer), Browser.close and Browser.crash, SystemInfo.*, Target.exposeDevToolsProtocol, Target.sendMessageToTarget (use flat sessions, sessionId), and navigation to any scheme other than http, https, data and about:blank. Downloads are always denied. Every request the browser makes goes through Spicrawl's proxy with the same private-network guard as /v1/scrape.

Limits and errors

WhatValue
Session lengthsession_ttl, at most 900 s
Idle timeout120 s with no CDP traffic either way
Concurrent sessions2 per organization by default
Traffic per session2 GiB, both directions together

Errors before the WebSocket upgrade are ordinary problem responses; after it, a session ends by closing the connection.

CodeHTTPDo next
ERR::AUTH::INVALID_KEY401The token was used, expired (60 s) or is unknown. Mint a new one.
ERR::AUTH::INSUFFICIENT_SCOPE403Grant the key the browser scope in the dashboard.
ERR::REQUEST::INVALID_PARAMETER400An option is out of range, or differs from the one the token was minted with.
ERR::LIMIT::CONCURRENCY_EXCEEDED429Your organization already has its maximum of sessions open. Close one, or wait Retry-After seconds; the same token still works.
ERR::LIMIT::RATE_LIMITED429Sessions are being opened too fast. Wait Retry-After seconds; the same token still works.
ERR::LIMIT::QUOTA_EXCEEDED402Not enough credits for the whole session_ttl.
ERR::ENGINE::UNAVAILABLE501This deployment has no cloud browsers. Do not retry.
ERR::REQUEST::NOT_FOUND404(live view) The session is closed or not yours.
ERR::REQUEST::CONFLICT409(live view socket) This session's browser cannot take a viewer, for example with engine: "js".

On this page