Embed a Live Browser View

View as Markdown

Show your users the browser while the automation runs. One API call mints a tokenized, expiring URL for the run; an iframe does the rest. No SDK on the frontend, no auth wiring - the URL itself is the credential.

1import { Bctrl } from "@bctrl/sdk";
2
3const bctrl = new Bctrl({ apiKey: process.env.BCTRL_API_KEY! });
4
5const runtime = await bctrl.runtimes.create({ type: "browser", name: "live-recipe" });
6const { runId } = await bctrl.runtimes.start(runtime.id);
7
8// Kick off your automation here - your own CDP code or a hosted agent.
9
10const live = await bctrl.runs.live(runId, {
11 control: "none", // viewers watch; they can't drive
12 expiresInSeconds: 3600, // the URL stops working after an hour
13});
14
15console.log(live.url);

Hand live.url to your frontend and embed it:

1<iframe
2 src="https://...the minted url..."
3 width="1280"
4 height="800"
5 style="border: 0"
6 allow="fullscreen"
7></iframe>

📸 Content TODO: screenshot or short GIF of the live view embedded inside a third-party-looking dashboard (e.g. a fake “Acme Ops” page with the iframe next to an order table) - this is the money shot for the recipe.

Hand control to a human

Mint with control: "input" and the viewer can drive the browser through the iframe - type a 2FA code, fix a stuck form, then hand back:

1const takeover = await bctrl.runs.live(runId, {
2 control: "input",
3 expiresInSeconds: 600, // keep takeover leases short
4});

While a human controls the runtime, hosted invocations on it report runtime.controller_busy - your automation should wait, not fight.

🎬 Content TODO: record a takeover clip - agent fills a login form, hits a 2FA prompt, a human types the code through the embed, agent continues. ~20 seconds, no narration needed.

Treat the URL as a secret

Anyone holding the URL can watch (and with control: "input", drive) until it expires. Mint one per viewer, keep expiresInSeconds as short as the use case allows, and mint a fresh one when it lapses - they’re free.

Next