> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://platform.bctrl.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://platform.bctrl.ai/_mcp/server.

# Account and organization

> Manage authentication, API keys, subaccounts, notifications, usage, and account settings.

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`:

| Resource                | Direct accessor                | Grouped accessor                       |
| ----------------------- | ------------------------------ | -------------------------------------- |
| Account settings        | `bctrl.account`                | `bctrl.account`                        |
| API keys                | `bctrl.apiKeys`                | `bctrl.account.apiKeys`                |
| Notification recipients | `bctrl.notificationRecipients` | `bctrl.account.notificationRecipients` |
| Subaccounts             | `bctrl.subaccounts`            | `bctrl.account.subaccounts`            |
| Organization usage      | `bctrl.usage`                  | `bctrl.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.

```ts
const me = await bctrl.auth.whoami();

console.log(me.scope); // "organization" or "subaccount"
console.log(me.organizationId);
console.log(me.subaccountId); // null for an organization-scoped key
console.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

| Field            | Type                                                  | Always present | Description                                                        |
| ---------------- | ----------------------------------------------------- | -------------- | ------------------------------------------------------------------ |
| `email`          | `string \| null`                                      | Yes            | Email associated with the authenticated actor, when available.     |
| `scope`          | `"organization" \| "subaccount"`                      | Yes            | Scope of the API key.                                              |
| `organizationId` | `string`                                              | Yes            | Organization that owns the key.                                    |
| `subaccountId`   | `string \| null`                                      | Yes            | Subaccount attached to the key, when the key is subaccount-scoped. |
| `plan`           | `"free" \| "developer" \| "business" \| "enterprise"` | Yes            | Current organization plan.                                         |
| `defaultSpaceId` | `string \| null`                                      | Yes            | Default Space for the effective scope, when one is configured.     |
| `keyId`          | `string`                                              | Yes            | Identifier of the API key that made the request.                   |
| `effectiveScope` | `AuthEffectiveScope`                                  | Yes            | Scope after any subaccount context has been applied.               |

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

```ts
type AuthEffectiveScope = {
  scope: "organization" | "subaccount";
  organizationId: string;
  subaccountId: string | null;
  defaultSpaceId: string | null;
};
```

## API keys

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

```ts
const created = await bctrl.apiKeys.create({
  name: "production",
  subaccountId: "subacct_123", // omit for an organization key
  expiresAt: "2027-01-01T00:00:00.000Z",
});

const { data: key, secret } = created;

console.log(key.id, key.keyPrefix);
console.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.

```ts
const keys = await bctrl.apiKeys.list({ type: "organization" });
for await (const key of bctrl.apiKeys.iter({ subaccountId: "subacct_123" })) {
  console.log(key.id, key.lastUsedAt);
}

await bctrl.apiKeys.delete(key.id);
```

### API key fields

| Field                    | Type                             | Always present | Description                                                |
| ------------------------ | -------------------------------- | -------------- | ---------------------------------------------------------- |
| `id`                     | `string`                         | Yes            | Key identifier used for deletion and actor identification. |
| `type`                   | `"organization" \| "subaccount"` | Yes            | Scope of the key.                                          |
| `subaccountId`           | `string \| null`                 | Yes            | Subaccount attached to the key.                            |
| `name`                   | `string \| null`                 | Yes            | Human-readable label.                                      |
| `keyPrefix`              | `string`                         | Yes            | Non-secret prefix for identifying the key.                 |
| `scopes`                 | `"*"[]`                          | Yes            | Current API keys use the full-access scope.                |
| `expiresAt`              | `string \| null`                 | Yes            | Expiration timestamp, if configured.                       |
| `lastUsedAt`             | `string \| null`                 | Yes            | Most recent use of the key.                                |
| `usageCount`             | `number`                         | Yes            | Number of recorded uses.                                   |
| `createdAt`, `updatedAt` | `string`                         | Yes            | Resource 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.

```ts
const subaccount = await bctrl.subaccounts.create({
  name: "Acme Corp",
  externalId: "customer_42",
  metadata: { plan: "growth" },
  limits: {
    maxSpaces: 10,
    maxActiveRuns: 25,
    monthlyCreditLimit: 1000,
  },
});

const scoped = bctrl.withSubaccount(subaccount.id);
const 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.

```ts
const active = await bctrl.subaccounts.list({ status: "active", q: "Acme" });
const withUsage = await bctrl.subaccounts.get(subaccount.id, { include: "usage" });

await bctrl.subaccounts.update(subaccount.id, {
  name: "Acme Inc",
  limits: { maxActiveRuns: 50 },
});

await bctrl.subaccounts.archive(subaccount.id);
```

### Subaccount fields

| Field                    | Type                     | Always present | Description                                                |
| ------------------------ | ------------------------ | -------------- | ---------------------------------------------------------- |
| `id`                     | `string`                 | Yes            | Identifier used to scope keys, clients, spaces, and usage. |
| `name`                   | `string`                 | Yes            | Display name.                                              |
| `slug`                   | `string`                 | Yes            | Stable URL-friendly name.                                  |
| `externalId`             | `string \| null`         | Yes            | Your customer or tenant identifier.                        |
| `status`                 | `"active" \| "archived"` | Yes            | Current lifecycle state.                                   |
| `defaultSpaceId`         | `string`                 | Yes            | Default Space for the subaccount.                          |
| `metadata`               | `object \| null`         | Yes            | Public application metadata.                               |
| `limits`                 | `SubaccountLimits`       | Yes            | Limits for spaces, active runs, and monthly credits.       |
| `usage`                  | `SubaccountUsage`        | No             | Included when `include: "usage"` is requested.             |
| `createdAt`, `updatedAt` | `string`                 | Yes            | Resource 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.

```ts
const recipient = await bctrl.notificationRecipients.create({
  type: "email",
  value: "ops@example.com",
  name: "Operations",
});

await bctrl.notificationRecipients.update(recipient.id, {
  enabled: false,
});

const enabled = await bctrl.notificationRecipients.list({ enabled: true });
```

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

| Field                    | Type                             | Always present | Description                                         |
| ------------------------ | -------------------------------- | -------------- | --------------------------------------------------- |
| `id`                     | `string`                         | Yes            | Recipient identifier used for updates and deletion. |
| `type`                   | `"email" \| "sms" \| "whatsapp"` | Yes            | Delivery channel.                                   |
| `value`                  | `string`                         | Yes            | Email address or phone number.                      |
| `name`                   | `string \| null`                 | Yes            | Human-readable label.                               |
| `enabled`                | `boolean`                        | Yes            | Whether the destination can receive requests.       |
| `subaccountId`           | `string \| null`                 | Yes            | Subaccount context for the recipient.               |
| `createdAt`, `updatedAt` | `string`                         | Yes            | Resource timestamps.                                |

## Usage

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

```ts
const organizationUsage = await bctrl.usage.get();
const subaccountUsage = await bctrl.subaccounts.usage.list();

console.log(organizationUsage.credits.available);
console.log(organizationUsage.isBlocked);

for (const usage of subaccountUsage.data) {
  console.log(
    usage.subaccountId,
    usage.credits.used,
    usage.credits.limit,
    usage.runs.active,
    usage.runs.limit,
    usage.spaces.used,
    usage.spaces.limit,
  );
}
```

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.

```ts
const account = await bctrl.account.get();
console.log(account.id, account.name);
console.log(account.branding.config);

const updated = await bctrl.account.update({
  branding: {
    productName: "Acme Automation",
    accent: "#6D5EF5",
    showPoweredBy: false,
  },
});
```

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.

| Parameter                | Type              | Required | Description                                                         |
| ------------------------ | ----------------- | -------- | ------------------------------------------------------------------- |
| `branding.productName`   | `string \| null`  | No       | Product name, 2–40 characters. `null` restores the default.         |
| `branding.logo`          | `string \| null`  | No       | Base64 PNG or SVG data URI. `null` restores the default.            |
| `branding.accent`        | `string \| null`  | No       | Six-digit hex color such as `#6d5ef5`. `null` restores the default. |
| `branding.showPoweredBy` | `boolean \| null` | No       | Show or hide BCTRL attribution. `null` restores the default.        |

```ts
await bctrl.account.update(
  { branding: { accent: "#6D5EF5" } },
  { dryRun: true },
);
```

## Next

* [Spaces](/sdk/spaces) — create isolated automation environments
* [Runtimes](/sdk/runtimes) — run automation inside a durable resource
* [Tools](/sdk/tools) — configure automation capabilities and human input
* [Conversations](/sdk/conversations) — connect an agent to a Runtime