The two surfaces
The control surface is the one you call to manage credentials, start runs, and
check health. The device surface is what a worker uses to observe and act on the
one device its run is bound to.
Driving a device
Most callers never touch either surface directly for device control. An agent drives a device over MCP, atPOST /mcp, and that endpoint speaks JSON-RPC
rather than REST. Its shape is documented in the
tool reference, not here, because the unit of the MCP surface
is a tool rather than an endpoint.
If you are looking for something like POST /v1/devices/{id}/tap, it does not
exist. Tapping is a tool call. Start at the Quickstart.
Authentication on the control surface
Two credential channels resolve to the same authorization context, so nothing downstream needs to know which one you used.- OAuth 2.1 for interactive clients. The control plane is a resource server
only. Discovery follows RFC 9728 at
/.well-known/oauth-protected-resource, which is also served under the/mcpsuffix because clients try both. - API keys for headless callers. A key is scoped to one org, and may be narrowed further to a subset of that org’s devices.
Calling from a browser
Browser access
Browsers are refused by default. Cross origin requests from a web page only work if that page’s origin has been added to the deployment’s allow list, and the allow list is exact: there is no wildcard, and no subdomain matching. If you want to call this API from a web application, ask for your origin to be added. Three things about that grant are worth stating plainly, because each one is a decision rather than an accident.- API keys are refused from browsers. On a deployment that grants browser
access at all, a request carrying both an
Originheader and apbk_key is answered403 api_key_from_browser_origin, and that holds for every origin, allow-listed or not. The refusal ships with the grant, so a deployment with no allow list configured does not apply it: there, such a request is authenticated in the ordinary way, because no browser can reach the API cross origin in the first place. An API key is a long lived, org wide credential, so a page holding one has effectively published it. Authenticate a browser with an OAuth session token instead. This applies to the control surface; if a server side integration of yours forwards a browser’sOriginheader upstream, strip it. - Only the control surface is granted.
POST /mcpand the device surface at/vrx/{runToken}/v1/...refuse every request carrying anOriginheader, and neither is part of the grant, so a page cannot call them withfetchand can never read a response from them. Note the exact shape of that guarantee: a browser omitsOriginon a plain subresource load (animgtag, say), so a page that has somehow obtained a run token could still cause aGETon the device surface to execute, blind. Treat a run token as the device credential it is, and keep it out of pages you do not control. - Credentials are sent in the header, never as cookies. The grant does not
enable
Access-Control-Allow-Credentials, so send your bearer token in theAuthorizationheader as any other client would.
WWW-Authenticate are explicitly exposed to browser
callers, so a page can read the budget it is being held to and can tell an
expired token from a refused one. See Rate limits.
The device surface and its run token
The device surface exists so a worker can observe and act without holding an org credential. The token in the path is the credential: the surface reads noAuthorization header, and sending one changes nothing.
That token carries the org, the device, the lease and the run it belongs to, and
it expires. It is bound to exactly one device, so it cannot be used to reach
anything else in the fleet.
Requesting a frame on this surface is metered before the pixels are returned. If
the metering write fails, the frame is withheld and the request fails. That
ordering is deliberate: it means no frame is ever served without being counted.
See The contract in full for everything a
worker may call on this surface.
Rate limits
In hosted mode both surfaces pass a limiter before anything else looks at the request, and which budget applies is decided by whether that request carries anAuthorization header. Control surface calls do, so they are in the
credentialed class. A worker on the device surface sends no such header, because
its credential is in the URL path, so it is in the bare class. Local mode has no
limiter at all.
Every limited response carries its own budget in RateLimit-* headers, and
those are authoritative over any number written down. See
Rate limits.
Health
GET /healthz is unauthenticated and reports whether the service is up. In
hosted mode it also reports whether the billing pipeline is healthy. It is the
one path the limiter never applies to.