Connecting
GET /connect creates a browser and answers with its CDP WebSocket URL as plain
text. That is the whole call.
curl -i -G "https://browser.rayobyte.com/connect" \ --data-urlencode "os=windows" \ --data-urlencode "vnc=true" \ -H "x-api-key: rb_live_YOUR_KEY"HTTP/2 200x-session-id: br_3f8a1c9d2e4b5a60x-vnc-url: https://sb-02.browser.rayobyte.com/vnc/vnc.html?path=ws%3Ftoken%3Dbr_5d4d8610784e2a91&autoconnect=true&resize=scalecontent-type: text/plain; charset=utf-8
wss://sb-02.browser.rayobyte.com/cdp/br_5d4d8610784e2a91The body is the URL and nothing else: no JSON wrapper, no guaranteed trailing newline. Trim it before you use it.
The br_ id at the end of the CDP URL is a second id for the same session.
Close and status accept either id. Reconnect takes only the x-session-id
value and answers 404 Session not found to the CDP URL’s id.
cloud.rayobrowse.com is an alias for browser.rayobyte.com and answers
identically, so older code keeps working without a change.
Connect a CDP client
Section titled “Connect a CDP client”Each sample closes the session when it is done. Playwright’s browser.close()
and Puppeteer’s disconnect() only detach the client, so the close call is what
stops the browser.
import httpxfrom playwright.sync_api import sync_playwright
API = "https://browser.rayobyte.com"KEY = "rb_live_YOUR_KEY"
resp = httpx.get( f"{API}/connect", params={ "os": "windows", }, headers={"x-api-key": KEY}, timeout=120,)if resp.status_code != 200: raise SystemExit(f"{resp.status_code}: {resp.text}")cdp_url = resp.text.strip()session_id = resp.headers["x-session-id"]
try: with sync_playwright() as p: browser = p.chromium.connect_over_cdp(cdp_url) context = browser.contexts[0] page = context.pages[0] if context.pages else context.new_page() page.goto("https://quotes.toscrape.com/") print(page.title())finally: httpx.post( f"{API}/api/browser/close", json={"browserId": session_id}, headers={"x-api-key": KEY}, timeout=30, )// Save as connect.mjs and run: node connect.mjsimport { chromium } from 'playwright';
const API = 'https://browser.rayobyte.com';const KEY = 'rb_live_YOUR_KEY';
const url = new URL('/connect', API);url.searchParams.set('os', 'windows');
const resp = await fetch(url, { headers: { 'x-api-key': KEY } });if (!resp.ok) throw new Error(`${resp.status}: ${await resp.text()}`);const cdpUrl = (await resp.text()).trim();const sessionId = resp.headers.get('x-session-id');
try { const browser = await chromium.connectOverCDP(cdpUrl); const context = browser.contexts()[0]; const page = context.pages()[0] ?? (await context.newPage()); await page.goto('https://quotes.toscrape.com/'); console.log(await page.title()); await browser.close();} finally { await fetch(`${API}/api/browser/close`, { method: 'POST', headers: { 'x-api-key': KEY, 'content-type': 'application/json' }, body: JSON.stringify({ browserId: sessionId }), });}// Save as connect.mjs and run: node connect.mjsimport puppeteer from 'puppeteer-core';
const API = 'https://browser.rayobyte.com';const KEY = 'rb_live_YOUR_KEY';
const url = new URL('/connect', API);url.searchParams.set('os', 'windows');
const resp = await fetch(url, { headers: { 'x-api-key': KEY } });if (!resp.ok) throw new Error(`${resp.status}: ${await resp.text()}`);const cdpUrl = (await resp.text()).trim();const sessionId = resp.headers.get('x-session-id');
try { const browser = await puppeteer.connect({ browserWSEndpoint: cdpUrl }); const page = (await browser.pages())[0] ?? (await browser.newPage()); await page.goto('https://quotes.toscrape.com/'); console.log(await page.title()); await browser.disconnect();} finally { await fetch(`${API}/api/browser/close`, { method: 'POST', headers: { 'x-api-key': KEY, 'content-type': 'application/json' }, body: JSON.stringify({ browserId: sessionId }), });}Parameters
Section titled “Parameters”proxy is required. Everything else has a default.
| Parameter | Default | Example | Description |
|---|---|---|---|
proxy (required) | — | proxy=http://user:[email protected]:8000 | Route the browser's traffic through this proxy. Mandatory on every launch. Accepted schemes are http, https, socks5 and socks5h, and the port must be written out. |
os | windows | os=windows | Fingerprint OS: windows, android, linux, macos. |
headless | false | headless=true | Run without a window. The literal string true enables it; any other value is false. Sites can detect it: see Headless and headful. |
vnc | false | vnc=true | Return an x-vnc-url header for the live view. |
maxLifetime | 7200 | maxLifetime=3600 | Session lifetime in seconds, clamped into 120 to 86400. 0 counts as omitted. |
sessionId | none | sessionId=br_3f8a1c9d2e4b5a60 | Reconnect to a running session, using the x-session-id value. Every other parameter is ignored and no browser is created. |
token | none | token=rb_live_... | Your API key as a query parameter, in place of the x-api-key header. |
browser_version_min | current | browser_version_min=N | Lowest Chromium major version the profile may claim. Defaults to the version the browser runs. See Version matching. |
browser_version_max | current | browser_version_max=N | Highest Chromium major version the profile may claim. Defaults to the version the browser runs. |
browser_language | auto | browser_language=en-US | Accept-Language value. |
ui_language | auto | ui_language=en-US | Browser UI locale. |
screen_width_min | auto | screen_width_min=1280 | Lowest screen width the profile may have. |
screen_height_min | auto | screen_height_min=720 | Lowest screen height the profile may have. |
force_visibility | false | force_visibility=true | Keep a background or unfocused tab reporting itself as visible and focused. The literal string true only. |
metadata | none | metadata=%7B%22job%22%3A%22crawl-42%22%7D | URL-encoded JSON object stored on the session. A value that does not parse is dropped without an error. |
Build the query string with a URL library, or curl -G --data-urlencode as the
examples here do. A proxy password containing @, :, /, #, %, & or a
space has to be percent-encoded, and a plain curl "...?proxy=..." will not do
it for you. See Proxy support for the
exact rules and what each malformed form returns.
The proxy travels in the query string, so client errors that print the request
URL print its password too. httpx’s raise_for_status() is one of them. The
examples above print the status and body instead. To keep credentials out of
URLs altogether, send the same fields as a JSON body to POST /api/browser/create.
Response headers
Section titled “Response headers”| Header | Description |
|---|---|
x-session-id |
The session id, br_ plus 16 hex characters. Keep it: reconnecting needs it |
x-vnc-url |
The live view, sent only when vnc=true and the browser came up with one |
x-ratelimit-limit |
Your concurrent browser limit, 5 by default. Despite the name, not the per-minute rate limit |
x-ratelimit-remaining |
Concurrent browsers still free after this one |
X-Error-Code |
On a 400, 422 or 503, the machine-readable code: PROXY_REQUIRED, PROXY_INVALID, PROXY_UNREACHABLE or CAPACITY |
Reconnecting
Section titled “Reconnecting”Pass sessionId and no browser is created. You get the CDP URL of the session
you already have:
curl "https://browser.rayobyte.com/connect?sessionId=br_3f8a1c9d2e4b5a60&vnc=true" \ -H "x-api-key: rb_live_YOUR_KEY"Every other parameter on a reconnect is ignored. Sending os=android alongside
sessionId does not change the fingerprint of a running browser, and nothing
warns you that it was dropped.
A reconnect is not subject to the concurrency or rate limits, so it works when all 5 browsers are running.
A session that has ended answers 410 Session is CLOSED (or DEAD, or
EXPIRED). An id that never existed, an id that belongs to another account, or
the id from a CDP URL answers 404 Session not found. Both are final; retrying
returns the same thing.
Every session survives a CDP disconnect, whichever way it was created. Closing it, its lifetime running out, or the browser dying are the three things that end one. See Session management.
What the gateway does with the call
Section titled “What the gateway does with the call”- Checks your key, your concurrency, your rate and your remaining browser hours.
- Checks the proxy’s form. It does not dial the proxy.
- Picks a backend with a free seat and starts Chromium on it. The browser
dials the proxy at this point, and a proxy it cannot get through answers
422 PROXY_UNREACHABLE. - Rewrites the browser’s internal address into its public
sb-NN.browser.rayobyte.comform and returns that. - Steps out. Your CDP traffic goes to the backend directly and the gateway sees none of it.
Was this page helpful?
Thanks — that helps us fix it.