Every request runs as a principal: an identity inside one organization and one project, with a set of scopes. Which principal depends on how the request arrived.

PrincipalHow it authenticatesTypical use
API keyAuthorization: Bearer b3os_sk_...Your servers, agents and CI
CLI tokenAuthorization: Bearer b3os_pat_... from b3os loginThe CLI, acting for one user
Dashboard sessionThe __Host-b3os_session cookie plus an x-b3os-csrf header on writesThe dashboard at beta.b3os.org
SandboxNothing: the node knows which sandbox is callingCode inside a sandbox calling http://127.0.0.1:7700

To see which principal a credential maps to, call GET /v1/keys/current:

bash
curl -s https://beta-api.b3os.org/v1/keys/current -H "Authorization: Bearer $B3OS_API_KEY"

API keys

A key belongs to one project. It has scopes, an optional spend cap in USD and an optional expiry. The secret is shown once, when the key is created; B3OS stores only its SHA-256 hash. Keys look like b3os_sk_ followed by 32 base32 characters and a CRC-32 checksum, and the prefix is registered for secret scanning so a leaked key can be found.

Create keys in the dashboard or with POST /v1/keys. A new key can never have more scopes or a higher spend cap than the key that creates it; asking for more fails with forbidden and reason scope_escalation. Scopes cannot change after creation; create another key instead. DELETE /v1/keys/{key_id} revokes a key at once, and open streams authenticated by it close within a minute.

The first key, created when you redeem your invite, gets these scopes:

vision:read, vision:write, vision:run, render:read, render:write, render:run, sandboxes:read, sandboxes:write, sandboxes:run, routines:read, routines:write, routines:run, actions:read, actions:write, actions:run, infer:read, infer:write, infer:run, vault:read, vault:run, platform:read

It never gets *, vault:write or platform:write. Create a key with those in the dashboard when you need them.

Scopes

Scopes are <product>:read, <product>:write and <product>:run. They never imply each other: write does not include read.

  • read lists and reads objects and their event streams.
  • write creates, changes and deletes persistent objects.
  • run does work that costs compute: a scrape, a command, a render, a run, an action call, a model call.

vault:run means using a stored connection. A request that names a connection needs vault:run on top of the operation's own scope.

ScopeAllows
vision:readList and read browser sessions, profiles, crawls and batch scrapes.
vision:writeCreate and close browser sessions; create, update and delete profiles.
vision:runScrape, map, crawl, batch scrape and extract; drive sessions (act, CDP).
render:readList and read screenshots, videos and artifacts.
render:writeDelete screenshots and videos.
render:runTake screenshots; record, restyle and cancel videos.
sandboxes:readList and read sandbox, snapshot and template metadata.
sandboxes:writeCreate, delete, pause, resume, snapshot and extend sandboxes; delete snapshots.
sandboxes:runExec, run code, read and write files, open PTYs, read logs and manage processes inside sandboxes.
routines:readList and read routines, versions, runs and run events.
routines:writeCreate, publish, roll back and delete routines; validate and import definitions.
routines:runStart, signal and cancel routine runs.
actions:readSearch packs, actions and triggers; read their definitions.
actions:writePublish packs to the registry (first-party publishers only in the beta).
actions:runCall actions.
infer:readList models and voices.
infer:writeReserved; no operation requires it yet.
infer:runChat completions, embeddings and speech.
vault:readList and read connection metadata (secret values are never returned).
vault:writeCreate, update and delete connections.
vault:runUse stored connections in action calls and routine steps.
platform:readRead keys, usage, requests, organizations, projects and webhook deliveries.
platform:writeCreate, update and revoke keys; manage projects; read and rotate webhook secrets.

<product>:* grants every scope of one product and * grants all of them. Each operation in the API reference lists the one scope it needs. A few operations use a pseudo-scope instead:

Pseudo-scopeMeaning
publicNo credentials (device login, inbound webhooks verified by signature, usercontent routes verified by their node signature).
authenticatedAny valid principal, whatever its scopes (for example reading its own limits).
sessionA signed-in dashboard session only; API keys and tokens are rejected (403 forbidden, reason session_required).
nodeA B3OS node's Ed25519 request signature (internal node-to-edge endpoints only; never customers).

Organizations and projects

An organization holds projects; a project holds keys and every object you create (sandboxes, routines, profiles, connections). B3OS checks on every request that the object belongs to the caller's organization and project. A key from one organization can never read another's objects, and an object outside your project answers 404, never 403, so its existence does not leak.

Sandboxes get their own principal

Code inside a sandbox calls http://127.0.0.1:7700 and sends no credential; anything it sends is discarded. Its calls run as a child principal created with the sandbox from the key that created it. The child has no B3OS access until you grant it in the api field when you create the sandbox:

json
{ "template": "agent", "api": { "scopes": ["infer:run", "vision:run"], "spend_cap_usd": 5, "connections": ["conn_01m5t6cbgdjs1ketgcqwa2sne8"] }}
  • scopes must be a subset of the creating key's. * and <product>:* expand to the scopes you hold, except the explicit-only scopes below.
  • spend_cap_usd stops the sandbox's own API calls at that spend. null, the default, means the creating key's cap applies. A cap above the creator's is refused with 403 forbidden, reason scope_escalation, so caps never grow from one sandbox to the next.
  • connections lists the vault connections the sandbox's action calls may use.

A grant gets these scopes only when it names them exactly, never through * or <product>:*:

sandboxes:write, vault:read, vault:write, vault:run, platform:read, platform:write

The grant is revoked when the sandbox stops. Agents in sandboxes has the details.

Routines run as their publisher

A routine version runs as the key that published it, with that key's scopes copied at publish time. If the key is revoked or expires, the routine's triggers pause until a new version is published with a valid key. See routines.

Stream tickets

Browsers cannot set an Authorization header on a WebSocket or an EventSource. For the three streams a browser opens directly (a sandbox terminal, a sandbox's logs and a browser session's CDP socket), get a ticket with your normal credential and open the stream within 30 seconds:

bash
curl -s -X POST https://beta-api.b3os.org/v1/sandboxes/$SBX/ticket \ -H "Authorization: Bearer $B3OS_API_KEY"# {"ticket": "tkt_...", "object": "stream_ticket", "resource": "/v1/sandboxes/sbx_...", "expires_in_s": 30}

Pass it as ?ticket=tkt_... or as the WebSocket subprotocol pair b3os.v1, b3os.ticket.tkt_.... A ticket works once, only for streams under its resource, and runs the stream with the scopes of whoever requested it. B3OS never accepts a bearer token in a URL.

How the edge reaches the node

Your key never leaves the edge at beta-api.b3os.org. For each request it forwards, the edge signs a single-use node token that lives 60 seconds and names your organization, project, key and scopes. The node checks the signature, the expiry and that the token was not used before, then runs the request as that principal.

Ask a question... ⌘I