Skip to content
Beam Docs

Account Management API

Create, freeze, rotate and revoke API keys, and read credits, usage and auto top-up settings, without a browser.

Everything the console does to an organization's API keys and billing can also be done over HTTP, at api.b1m.ai. Mint a key from CI, freeze a leaked one from a script, or pull usage into your own billing system.

Two APIs, two credentials

This is the part worth reading twice. Beam has two HTTP APIs and they do not share a credential.

Transfer APIManagement API
Hostbeamcore.b1m.aiapi.b1m.ai
DoesRuns transfersAdministers keys and billing
CredentialAn API key, b1m_…A service account credential, bm_sa_…
HeaderX-Api-KeyAuthorization: Bearer

A transfer key cannot manage keys, deliberately. A transfer key is handed to whatever runs a job — CI, a container, a colleague's laptop. If it could also mint keys, one leaked credential could issue successors that outlive revoking the original, and revoking it would not end the compromise. So the two capabilities are held by different credentials, and a service account is created and revoked from the console, separately from the keys it administers.

A service account credential also cannot run transfers. Each does one job.


Getting a credential

In the console, go to Organization → Service accounts and create one. A service account is a machine identity: it has a name, a role, and optionally a project, and it holds one or more credentials.

Creating a credential shows the bm_sa_… secret once. It is stored hashed and cannot be shown again — if you lose it, issue another.

You need the Manage service accounts permission to create one. If you do not have it, an organization owner or admin does.

What a credential is allowed to do

A credential's permissions come from its service account's role, plus any permissions granted to that service account directly:

RoleCan read keys and billingCan create and rotate keysCan revoke keysCan change billing
Owner, AdminYesYesYesYes
DeveloperYesYesNoNo
BillingYesYesNoYes
ViewerYesNoNoNo
CustomOnly what is granted directly

Give a service account the least that its job needs. A deployment pipeline that only rotates keys does not need billing access.

Disabling the service account disables every credential it holds, at once. That is the fastest way to cut off an integration.


Authentication

Send the credential as a bearer token:

curl https://api.b1m.ai/v1/keys \
  -H "Authorization: Bearer bm_sa_your_credential"

X-Api-Key: bm_sa_… is accepted as well, if that is easier for your client.

Every route answers for exactly one organization — the one its credential belongs to. There is no organization parameter, and a credential cannot read or change another organization.


Managing API keys

MethodPathPermission
GET/v1/keysRead API keys
POST/v1/keysCreate API keys
GET/v1/keys/:idRead API keys
PATCH/v1/keys/:idCreate API keys
POST/v1/keys/:id/rotateCreate API keys
DELETE/v1/keys/:idManage service accounts

These manage transfer keys (b1m_…). Service account credentials are not listed here and are managed from the console.

Create a key

curl -X POST https://api.b1m.ai/v1/keys \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{"name": "production ingest", "creditLimit": 5000}'
Field
nameRequired.
expiresAtISO date or timestamp. A bare date expires at the end of that day, UTC. Omit for a key that does not expire.
creditLimitCredits this key may spend, on top of the organization's balance. Omit for no per-key cap.
projectIdScope the key to a project, so its spend is attributed there.
monthlyBudgetCredits, budgetWarningThresholds, budgetBlockOnExceedBudget controls. Sending any of these also requires Manage billing.

The response carries rawKey — the only time the secret is returned. Store it before you do anything else.

{
  "key": {
    "id": "cmud1y0pl0001qr018c338s2g",
    "name": "production ingest",
    "prefix": "b1m_p6e-1LXE",
    "status": "ACTIVE",
    "creditLimit": 5000,
    "rawKey": "b1m_p6e-1LXE…"
  }
}

Your organization must have passed KYC before it can create keys, and a restricted organization cannot create them at all.

Freeze a key

Freezing stops a key working while keeping it, its budget and its usage history. Use it when you suspect a key is compromised but are not ready to throw it away.

curl -X PATCH https://api.b1m.ai/v1/keys/$KEY_ID \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{"status": "DISABLED"}'

status accepts ACTIVE, DISABLED and REVOKED. Freezing is reversible; revoking is not. The same call changes name, creditLimit, expiresAt, projectId and the budget fields.

A frozen key stops authenticating transfers immediately, including any secret still inside a rotation grace period.

Move a key between projects

A key's scope is not fixed at creation. PATCH it with a different projectId to move it, or with null to return it to organization scope:

# scope it to a project
curl -X PATCH https://api.b1m.ai/v1/keys/$KEY_ID \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "cmuel5roi000zop016h40hpi3"}'

# back to organization-wide
  -d '{"projectId": null}'

Moving a key changes where its future spend is attributed. Usage already recorded stays under the project it was spent in, so past periods do not move with the key.

An organization-wide credential can move a key anywhere in its organization, including back to organization scope. A project-scoped credential can only move keys between the projects it holds — sending a key to a project it cannot reach, or to organization scope, would put that key beyond the caller on the very next request. Either returns 403 project_out_of_scope, and the response names the projects that are allowed:

{
  "error": "project_out_of_scope",
  "message": "This credential can only move keys between the projects it is scoped to",
  "allowed": ["cmuel5roi000zop016h40hpi3"]
}

Omitting projectId entirely leaves the scope alone — it is only read when present.

Delete a key

curl -X DELETE https://api.b1m.ai/v1/keys/$KEY_ID \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL"

The key is removed and every secret it ever had stops working. Its past spend stays in your usage history — deleting a key does not rewrite what it cost you.


Rotating a key

Rotation issues a new secret for an existing key while keeping its identity, project, budget and usage history. The old secret keeps working for a grace period, so a deployment can pick up the new secret at its own pace instead of losing access the moment you rotate.

curl -X POST https://api.b1m.ai/v1/keys/$KEY_ID/rotate \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{"gracePeriodDays": 7}'

gracePeriodDays is one of 0, 1, 3, 7 or 14, and defaults to 7.

{
  "key": { "prefix": "b1m_pQOAb7YF", "rawKey": "b1m_pQOAb7YF…" },
  "rotation": {
    "oldPrefix": "b1m_p1y5El5i",
    "newPrefix": "b1m_pQOAb7YF",
    "gracePeriodDays": 7,
    "oldMaterialExpiresAt": "2026-09-29T19:16:10.000Z"
  }
}

Both secrets authenticate until oldMaterialExpiresAt. Deploy the new one, confirm it works, and let the old one lapse.

Choose 0 when the old secret is compromised: it is revoked on the spot, and anything still using it fails immediately. That is the point — a grace period is for planned rotation, not for a leak.

Rotation recomputes the key's permissions rather than copying them, so a permission changed since the key was issued takes effect when it rotates. Only an ACTIVE key can be rotated.


Projects

A project groups keys so their spend is attributed together and can be capped together. Because a key can be created straight into one, the API can make and remove projects too.

MethodPathPermission
GET/v1/projectsRead transfers
POST/v1/projectsCreate transfers, at organization scope
DELETE/v1/projects/:idCreate transfers, at organization scope
curl -X POST https://api.b1m.ai/v1/projects \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{"name": "Data pipeline", "description": "Nightly ingest"}'
Field
nameRequired, at least 2 characters.
descriptionOptional.
memberIdsOrganization members to add. Ignored for anyone outside the organization.

The slug is derived from the name and made unique within the organization, so a second Data pipeline becomes data-pipeline-2.

Creating and deleting both require the permission at organization scope. A credential confined to one project cannot mint another or delete the boundary it was given — see What a credential is allowed to do.

Delete a project

curl -X DELETE https://api.b1m.ai/v1/projects/$PROJECT_ID \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL"

A project must be empty first. While any API key or service account still belongs to it, the call returns 409 project_not_empty and names what is in the way:

{
  "error": "project_not_empty",
  "message": "This project still has 2 API keys and 1 service account. Move or delete them first."
}

This is deliberate. A project-scoped key or credential is scoped by pointing at the project; delete the project underneath it and that scope falls away, quietly promoting it to organization-wide reach. Move them to another project, or delete them, and the promotion never happens.

Deleting a project removes its members and its budget. Usage already recorded stays in your billing history.


Credits and usage

MethodPathPermission
GET/v1/creditsRead billing
GET/v1/usageRead billing

/v1/credits returns the organization's balance:

{
  "organizationId": "org_…",
  "organizationName": "Acme",
  "credits": 4820,
  "restrictionStatus": "NONE"
}

/v1/usage answers where credits went, broken down by key, by project and by action:

curl "https://api.b1m.ai/v1/usage?dateFrom=2026-09-01&dateTo=2026-09-30" \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL"
Parameter
daysDays to look back. Defaults to 30, capped at 365.
dateFrom, dateToAn explicit window, which takes precedence over days. A bare date covers that whole day, UTC, so a calendar month means that month.
keyIdRestrict to one key.

The response carries totalCreditsUsed, totalRequests, byKey, byProject, byAction, dailyUsage, and the 50 most recent usage transactions. All three breakdowns are derived from the same period, so they always agree with the total.

Each breakdown is a snapshot of what was true when the credits were spent. Moving a key between projects does not rewrite earlier periods.


Auto top-up

Auto top-up buys a credit pack automatically when the balance falls to a threshold.

MethodPathPermission
GET/v1/billing/auto-topupRead billing
PATCH/v1/billing/auto-topupManage billing
GET/v1/billing/packsAny valid credential
curl -X PATCH https://api.b1m.ai/v1/billing/auto-topup \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "thresholdCredits": 500, "packId": "pack_50"}'

thresholdCredits is between 1 and 10,000. packId comes from /v1/billing/packs.

Adding a card is the one thing this API cannot do. Card details go to Stripe through the console, never through Beam. Enabling auto top-up without a saved payment method answers 409 payment_method_required; add one in Billing first, then enable it here. Once a card is saved, everything else about auto top-up is configurable programmatically.

A failed charge disables further attempts and records the reason, so a dead card does not get retried indefinitely. Writing to this endpoint clears that state, which is how you resume after fixing the card.


Budget alerts

A monthly budget is only useful if something tells you when it is running out. Set a budget and its warning thresholds on a key — monthlyBudgetCredits, budgetWarningThresholds, budgetBlockOnExceed — and Beam records an alert the first time each threshold is crossed in a month.

MethodPathPermission
GET/v1/alertsRead billing
curl https://api.b1m.ai/v1/alerts \
  -H "Authorization: Bearer $BEAM_MANAGEMENT_CREDENTIAL"
Query
sinceISO timestamp. Only alerts raised after it — poll with the last value you saw.
unacknowledgedtrue to drop alerts already dismissed in the console.
limit1–200, default 50.

The response also carries organizationId — the organization the credential speaks for. A client holding one credential on behalf of several viewers must check it against whoever is looking, rather than assuming the alerts belong to them.

{
  "alerts": [
    {
      "id": "cmuf…",
      "targetType": "api_key",
      "threshold": 80,
      "usageCredits": 812,
      "budgetCredits": 1000,
      "percentUsed": 81,
      "month": "2026-09-01T00:00:00.000Z",
      "createdAt": "2026-09-23T14:22:04.000Z",
      "acknowledgedAt": null,
      "apiKey": { "id": "cmue…", "name": "production ingest", "prefix": "b1m_p6e-1LXE" },
      "project": null
    }
  ]
}

Each threshold raises one alert per target per month. Crossing 80% does not alert again on the next transfer, so polling this endpoint gives you a list of crossings rather than a stream that repeats.

Spending the budget always raises a threshold: 100 alert, whatever warning thresholds are configured. Without it, a key that crossed 95% earlier in the month and then ran out produced nothing new — exhaustion, the one state worth acting on, was the only silent one.

A refused charge still raises its alert. With budgetBlockOnExceed set, usage beyond the budget is rejected — and the crossing is recorded anyway, so a blocked key explains itself here rather than only through a 403 at the call site. The thresholds recorded are the ones the key has actually reached: a key at 3 of 4 credits that is refused a 2-credit charge reports 75%, not 125%.

beam-cli signs in as a person rather than as a service account, so the same alerts are also served at GET /api/alerts for a device-flow session, taking an X-Organization-Id header and the same query parameters. A member needs billing:read, which every role carries.

Where a budget stands right now

Alerts say what already happened. Every key returned by /v1/keys also carries a budget object saying where it stands:

{
  "budget": {
    "budgetCredits": 1000,
    "usageCredits": 812,
    "percentUsed": 81,
    "thresholds": [50, 80, 95],
    "thresholdsReached": [50, 80],
    "blockOnExceed": true,
    "state": "warning"
  }
}
state
noneNo budget set. Nothing to report.
okUnder every threshold.
warningAt or past a threshold, still under budget.
exceededAt or past budget, and usage continues.
blockedAt or past budget, and further usage is refused.

percentUsed is clamped to 100, so a progress bar built on it never overflows.

blocked is enforced, not advisory. With budgetBlockOnExceed set, usage beyond the budget is rejected with 403. That check runs when usage is recorded, so it stops the next charge rather than one already in flight — bytes that have already moved are still billed.


Errors

Every error returns a JSON body with a stable error code and a human-readable message. Match on the code.

StatusCode
401unauthorizedMissing, unknown, or not a service account credential.
401credential_inactiveThe credential or its service account is disabled, revoked or expired.
403insufficient_scopeValid credential, but it does not hold the required permission. The response names it.
403kyc_requiredThe organization must pass KYC before creating keys.
403organization_blockedThe organization is restricted.
403project_out_of_scopeA project-scoped credential tried to move a key outside its projects.
403monthly budget exhaustedA budget with budgetBlockOnExceed is spent. Raise it, or clear the block.
404not_foundNo such key or project in this organization.
400name_required, invalid_credit_limit, invalid_expirationBad field on create or update.
400invalid_nameA project name must be at least 2 characters.
400invalid_statusstatus must be ACTIVE, DISABLED or REVOKED.
400key_not_activeOnly an active key can be rotated.
400invalid_grace_periodMust be 0, 1, 3, 7 or 14.
400invalid_threshold, invalid_packBad auto top-up setting.
409payment_method_requiredEnable auto top-up only after a card is saved.
409project_not_emptyKeys or service accounts still belong to the project.

An unknown credential and a transfer key presented to this API both return the same 401 unauthorized. Telling a transfer key that it is merely the wrong kind of credential would confirm it is a valid one.


Decentralized distributed bandwidth infrastructure.

On this page