Account and organization

View as Markdown

Account resources are the organization-level controls around your BCTRL project. Use them to identify the authenticated actor, issue API keys, isolate customers, configure notification destinations, and inspect usage.

The SDK exposes these resources both directly and under bctrl.account:

ResourceDirect accessorGrouped accessor
Account settingsbctrl.accountbctrl.account
API keysbctrl.apiKeysbctrl.account.apiKeys
Notification recipientsbctrl.notificationRecipientsbctrl.account.notificationRecipients
Subaccountsbctrl.subaccountsbctrl.account.subaccounts
Organization usagebctrl.usagebctrl.account.usage

Choose whichever style reads best for your application. The methods and response shapes are the same.

Check the authenticated actor

whoami() tells you which API key made the request and which scope the request is using.

1const me = await bctrl.auth.whoami();
2
3console.log(me.scope); // "organization" or "subaccount"
4console.log(me.organizationId);
5console.log(me.subaccountId); // null for an organization-scoped key
6console.log(me.keyId);

The response also includes the account plan, email, defaultSpaceId, and effectiveScope. effectiveScope describes the scope after a request is narrowed with BCTRL-Subaccount-Id or bctrl.withSubaccount().

whoami() fields

FieldTypeAlways presentDescription
emailstring | nullYesEmail associated with the authenticated actor, when available.
scope"organization" | "subaccount"YesScope of the API key.
organizationIdstringYesOrganization that owns the key.
subaccountIdstring | nullYesSubaccount attached to the key, when the key is subaccount-scoped.
plan"free" | "developer" | "business" | "enterprise"YesCurrent organization plan.
defaultSpaceIdstring | nullYesDefault Space for the effective scope, when one is configured.
keyIdstringYesIdentifier of the API key that made the request.
effectiveScopeAuthEffectiveScopeYesScope after any subaccount context has been applied.

AuthEffectiveScope has the same scope identifiers plus the effective default Space:

1type AuthEffectiveScope = {
2 scope: "organization" | "subaccount";
3 organizationId: string;
4 subaccountId: string | null;
5 defaultSpaceId: string | null;
6};

API keys

Create a key for the organization, or pass subaccountId to create a key scoped to a subaccount.

1const created = await bctrl.apiKeys.create({
2 name: "production",
3 subaccountId: "subacct_123", // omit for an organization key
4 expiresAt: "2027-01-01T00:00:00.000Z",
5});
6
7const { data: key, secret } = created;
8
9console.log(key.id, key.keyPrefix);
10console.log(secret); // save it now; it is returned only once

The create response is { data, secret }. BCTRL never returns the plaintext secret again. Store it in your secret manager and rotate a key by creating the replacement before deleting the old key.

1const keys = await bctrl.apiKeys.list({ type: "organization" });
2for await (const key of bctrl.apiKeys.iter({ subaccountId: "subacct_123" })) {
3 console.log(key.id, key.lastUsedAt);
4}
5
6await bctrl.apiKeys.delete(key.id);

API key fields

FieldTypeAlways presentDescription
idstringYesKey identifier used for deletion and actor identification.
type"organization" | "subaccount"YesScope of the key.
subaccountIdstring | nullYesSubaccount attached to the key.
namestring | nullYesHuman-readable label.
keyPrefixstringYesNon-secret prefix for identifying the key.
scopes"*"[]YesCurrent API keys use the full-access scope.
expiresAtstring | nullYesExpiration timestamp, if configured.
lastUsedAtstring | nullYesMost recent use of the key.
usageCountnumberYesNumber of recorded uses.
createdAt, updatedAtstringYesResource timestamps.

Subaccounts

Use a subaccount when one organization needs isolated customers, spaces, API keys, limits, and usage. Create the subaccount once, then use its ID when creating or scoping resources.

1const subaccount = await bctrl.subaccounts.create({
2 name: "Acme Corp",
3 externalId: "customer_42",
4 metadata: { plan: "growth" },
5 limits: {
6 maxSpaces: 10,
7 maxActiveRuns: 25,
8 monthlyCreditLimit: 1000,
9 },
10});
11
12const scoped = bctrl.withSubaccount(subaccount.id);
13const space = await scoped.spaces.create({ name: "Acme production" });

withSubaccount() returns a client whose requests carry that subaccount context. An organization-scoped key can also select a subaccount per request where the API supports it; a subaccount-scoped key cannot escape its own subaccount.

1const active = await bctrl.subaccounts.list({ status: "active", q: "Acme" });
2const withUsage = await bctrl.subaccounts.get(subaccount.id, { include: "usage" });
3
4await bctrl.subaccounts.update(subaccount.id, {
5 name: "Acme Inc",
6 limits: { maxActiveRuns: 50 },
7});
8
9await bctrl.subaccounts.archive(subaccount.id);

Subaccount fields

FieldTypeAlways presentDescription
idstringYesIdentifier used to scope keys, clients, spaces, and usage.
namestringYesDisplay name.
slugstringYesStable URL-friendly name.
externalIdstring | nullYesYour customer or tenant identifier.
status"active" | "archived"YesCurrent lifecycle state.
defaultSpaceIdstringYesDefault Space for the subaccount.
metadataobject | nullYesPublic application metadata.
limitsSubaccountLimitsYesLimits for spaces, active runs, and monthly credits.
usageSubaccountUsageNoIncluded when include: "usage" is requested.
createdAt, updatedAtstringYesResource timestamps.

SubaccountLimits contains maxSpaces, maxActiveRuns, and monthlyCreditLimit. A null limit means unlimited when updating limits.

Notification recipients

Notification recipients are destinations for human-in-the-loop requests. Create an email, SMS, or WhatsApp destination, then enable or disable it without deleting the resource.

1const recipient = await bctrl.notificationRecipients.create({
2 type: "email",
3 value: "[email protected]",
4 name: "Operations",
5});
6
7await bctrl.notificationRecipients.update(recipient.id, {
8 enabled: false,
9});
10
11const enabled = await bctrl.notificationRecipients.list({ enabled: true });

Use E.164 phone numbers for sms and whatsapp, such as +15551234567.

FieldTypeAlways presentDescription
idstringYesRecipient identifier used for updates and deletion.
type"email" | "sms" | "whatsapp"YesDelivery channel.
valuestringYesEmail address or phone number.
namestring | nullYesHuman-readable label.
enabledbooleanYesWhether the destination can receive requests.
subaccountIdstring | nullYesSubaccount context for the recipient.
createdAt, updatedAtstringYesResource timestamps.

Usage

Read organization usage from bctrl.usage. Read usage for every subaccount from bctrl.subaccounts.usage.

1const organizationUsage = await bctrl.usage.get();
2const subaccountUsage = await bctrl.subaccounts.usage.list();
3
4console.log(organizationUsage.credits.available);
5console.log(organizationUsage.isBlocked);
6
7for (const usage of subaccountUsage.data) {
8 console.log(
9 usage.subaccountId,
10 usage.credits.used,
11 usage.credits.limit,
12 usage.runs.active,
13 usage.runs.limit,
14 usage.spaces.used,
15 usage.spaces.limit,
16 );
17}

Organization usage includes the current credit balance, monthly cycle, blocking state, and a breakdown across browser, proxy, AI, challenge solver, files, and notification credits.

Subaccount usage is a facts-only snapshot: credits.used/limit, runs.active/limit, and spaces.used/limit, plus the billing period and computation timestamp. A null limit means unlimited. Clients can derive remaining credits or a resource-specific limit notice from these values; the response does not include a subaccount-wide blocked state.

Account settings

Use account settings for organization-level branding. get() returns both the values you configured and the resolved values currently used by BCTRL.

1const account = await bctrl.account.get();
2console.log(account.id, account.name);
3console.log(account.branding.config);
4
5const updated = await bctrl.account.update({
6 branding: {
7 productName: "Acme Automation",
8 accent: "#6D5EF5",
9 showPoweredBy: false,
10 },
11});

Branding updates use a merge-patch shape: omitted fields stay unchanged, and null restores a field’s BCTRL default. Set dryRun: true in the second argument to validate and resolve a change without saving it.

ParameterTypeRequiredDescription
branding.productNamestring | nullNoProduct name, 2–40 characters. null restores the default.
branding.logostring | nullNoBase64 PNG or SVG data URI. null restores the default.
branding.accentstring | nullNoSix-digit hex color such as #6d5ef5. null restores the default.
branding.showPoweredByboolean | nullNoShow or hide BCTRL attribution. null restores the default.
1await bctrl.account.update(
2 { branding: { accent: "#6D5EF5" } },
3 { dryRun: true },
4);

Next

  • Spaces — create isolated automation environments
  • Runtimes — run automation inside a durable resource
  • Tools — configure automation capabilities and human input
  • Conversations — connect an agent to a Runtime