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.

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

The scoped client is the whole tenancy model: run it server-side per tenant and every recipe in this cookbook - live embeds, replays, vault logins - is automatically confined to that customer. Embed 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:

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

Next