LelantosLelantos
Browser

RPC Wire Contract

The NDJSON /rpc action API for browser sandboxes - frame shape, auth, response shapes, and the confirmed action catalog.

Alongside /cdp, every browser sandbox exposes a lightweight NDJSON action API over a single WebSocket. It drives the browser with high-level verbs (page.navigate, page.snapshot, page.evaluate, …) and needs no Playwright. The SDK's RpcClient wraps this contract; this page documents the raw wire format.

Endpoint

wss://{sandboxID}.{domain}/ndjson

Open the WebSocket against the bare {sandboxID} subdomain with the literal path /ndjson. The control-plane proxy rewrites that exact path to the in-sandbox daemon's RPC route on the same passthrough listener (port 7879) that serves /cdp:

/ndjson  ->  /session/{sandboxID}/rpc   (port 7879)

You receive the ndjsonEndpoint field on the browser-sandbox record from POST /browser-sandboxes and GET /browser-sandboxes/{sandboxID}. The SDK also derives it from a wsEndpoint by swapping the trailing /cdp for /ndjson.

Authentication

Identical to /cdp: the bearer token is carried as a WebSocket subprotocol on the upgrade.

Sec-WebSocket-Protocol: bearer.<accessToken>

The daemon echoes the subprotocol back on the 101 upgrade. Token-in-URL is rejected; a tokenless upgrade returns 401 when traffic-token enforcement is on.

Frame shape

The transport is NDJSON - exactly one JSON object per WebSocket text frame (not batched). A request looks like:

{ "id": "1", "action": "page.navigate", "params": { "url": "https://example.com" }, "session": "{sandboxID}" }
FieldRequiredNotes
idyesCorrelation id. Must be a string - an integer id is rejected by the daemon.
actionyesThe verb. The wire field is action, not method - a method-keyed frame is rejected with bad request: missing field \action``.
paramsper-actionAction arguments. Omit when the action takes none.
sessionnoThe session to bind to. Defaults to the sole session (the daemon auto-binds it under --concurrent 1); pass the sandbox ID explicitly for safety.

Two easy mistakes the daemon rejects: a numeric id, and using method instead of action.

Response shapes

A success echoes the request id and nests all result fields under data:

{ "id": "1", "success": true, "data": { "page_id": "page-1", "url": "https://example.com/" } }

There are two error shapes:

  • Application error (e.g. an unknown action) - echoes the request id:

    { "id": "1", "success": false, "error": "<message>" }
  • Protocol / parse error (e.g. a frame missing action) - carries an empty id, so it cannot be correlated to a specific request:

    { "id": "", "success": false, "error": "<message>" }

    The SDK's RpcClient fails the oldest in-flight request (FIFO) on an id-less success:false frame, so a malformed request fails promptly instead of hitting the request timeout.

Action catalog

The in-sandbox daemon owns the full action vocabulary (tab.*, session.*, page.network.*, and more). The actions confirmed and wrapped by the SDK are:

ActionparamsSuccess data
page.navigate{ url }{ page_id, url }
page.snapshotnone{ tree_yaml, title, element_count, page_id, url }
page.evaluate{ expression }{ value, page_id }

page.snapshot returns an accessibility-tree YAML (tree_yaml) plus the page title and element count - a compact, token-efficient page representation well suited to agents. page.evaluate runs JavaScript in the page and returns the result under data.value.

For any action not in the table above, send the raw frame and read the whole response envelope - its data carries the result. The SDK's RpcClient.send(action, params) does exactly that.

Minimal raw client

import WebSocket from 'ws';

const ws = new WebSocket(ndjsonEndpoint, [`bearer.${accessToken}`]);
await new Promise((res) => ws.on('open', res));

ws.send(JSON.stringify({
  id: '1',
  action: 'page.navigate',
  params: { url: 'https://example.com' },
  session: sandboxId,
}));

ws.on('message', (data) => {
  const frame = JSON.parse(data.toString());
  // { id: '1', success: true, data: { page_id, url } }
});

Prefer the RpcClient for production use - it handles frame reassembly, id correlation, timeouts, and the error shapes above.

See also

On this page