Skip to navigation

Multi-Tenant Isolation

View as Markdown

Reselling browser automation to your own customers? Give each one a subaccount: an isolated, separately-metered environment under your organization. A key scoped to it can’t see anything else, and limits stop one customer from eating your capacity.

import { Bctrl } from "@bctrl/sdk";
const bctrl = new Bctrl({ apiKey: process.env.BCTRL_API_KEY! }); // parent org key
// 1. One subaccount per customer, keyed by your own id.
const sub = await bctrl.subaccounts.create({
name: "Acme Corp",
externalId: "acme", // your tenant id - look it up later with list({ externalId })
limits: {
monthlyCredits: 50_000,
maxConcurrentRuntimes: 5,
},
});
// 2. Mint a key confined to that subaccount.
const key = await bctrl.apiKeys.create({ name: "acme-key", subaccountId: sub.id });
console.log(key.secret); // shown once - store it now
// 3. A client on the scoped key lives entirely inside Acme's world.
const acme = new Bctrl({ apiKey: key.secret });
const runtime = await acme.runtimes.create({ type: "browser", name: "acme-task" });
// runtimes, runs, vault secrets, files - all isolated to the subaccount.
// 4. Meter every customer in one call.
const usage = await bctrl.subaccounts.usage.list();
for (const row of usage.data) {
console.log(row);
}

The scoped client is the whole tenancy model: run it server-side per tenant and every workflow in this cookbook - live Views, replays, and Vault tools - is automatically confined to that customer. View URLs minted from Acme’s client can only ever show Acme’s Runs, which is exactly what you want when the iframe lands in Acme’s dashboard.

A parent-org key can still reach into any subaccount for support and admin; a subaccount key can never reach out.

Lifecycle

When a customer churns, archive rather than delete - the usage history stays readable for billing:

await bctrl.subaccounts.archive(sub.id);

Next