Code inside a sandbox can call B3OS at http://127.0.0.1:7700 (B3OS_API_URL). The call travels over vsock to the B3OS daemon on the same machine, with no network in between. It needs no key: the node knows which sandbox is calling and runs the call as that sandbox's own principal.

Grant access

A sandbox has no B3OS access until you grant it in api when you create it:

bash
curl -s https://beta-api.b3os.org/v1/sandboxes \ -H "Authorization: Bearer $B3OS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"template": "agent", "api": {"scopes": ["infer:read", "infer:run", "vision:run", "actions:run", "vault:run"], "spend_cap_usd": 5, "connections": ["conn_01m5t6cbgdjs1ketgcqwa2sne8"]}}'
FieldRule
scopesA subset of your key's scopes. * and <product>:* expand to the scopes you hold, except sandboxes:write, the vault:* scopes and the platform:* scopes, which a grant gets only when it names them exactly.
spend_cap_usdStops the sandbox's own API calls at this spend. null (the default) means your key's cap applies. A cap above your key's is refused.
connectionsUp to 16 vault connections the sandbox's action calls may use.

Asking for more than you hold fails with forbidden, reason scope_escalation. The grant ends when the sandbox stops.

Call B3OS from inside

The B3OS SDK and CLI read B3OS_API_URL and use it without a key. Compatible clients only need the base URL; pass any placeholder as the key:

ClientBase URL inside a sandbox
B3OS SDK, CLIAutomatic
OpenAI SDKhttp://127.0.0.1:7700/v1/infer
Firecrawl SDKhttp://127.0.0.1:7700/compat/firecrawl
Plain HTTPhttp://127.0.0.1:7700/v1/...
python
import osfrom openai import OpenAIclient = OpenAI(base_url=os.environ["B3OS_API_URL"] + "/v1/infer", api_key="unused")

The sandbox agent quickstart runs a complete tool-calling loop this way.

Account operations (keys, usage, limits, organizations, projects and the pack registry) are not served on the node. From inside a sandbox they answer 421 edge_only; call https://beta-api.b3os.org with a key for those.

Give the sandbox a credential

Three ways, from safest to least safe:

  1. Let the egress proxy inject it. Grant vault:run and the connection in api.connections, then call a marketplace action. The proxy adds the credential to requests for the hosts the connection is bound to; the sandbox never sees it.

  2. Put a connection's secret in an environment variable. secrets takes a vault connection reference. The value is never returned by the API or logged:

    json
    { "template": "base", "secrets": { "GITHUB_TOKEN": { "connection": "conn_01m5t6cbgdjs1ketgcqwa2sne8" } } }

    field picks which secret field to use; it defaults to the one the connection's type injects (api_key, token or access_token), and basic connections need username or password.

  3. Pass a literal value in secrets, such as {"NPM_TOKEN": "..."}. It is not returned or logged either, but it lives in your request.

Prefer the first: a credential that never enters the sandbox cannot leak from it.

Sandboxes that create sandboxes

Code in a sandbox can create sandboxes only if its grant names sandboxes:write exactly. The child's grant must be a subset of the parent sandbox's, and a child's spend_cap_usd above the parent's grant cap is refused, so caps never grow down a chain.

Limit where it can reach

egress controls outbound traffic: public (the default) allows any public address, allowlist allows only the hosts in allow (exact names or *. wildcards), and deny blocks everything. Private networks, loopback, the node's own addresses and cloud metadata are always refused. The policy covers traffic bound for the internet; calls to http://127.0.0.1:7700 go to B3OS itself, under the grant.

See what it did

Every call a sandbox makes is in the request log with connection: sandbox and its sandbox_id:

bash
curl -s "https://beta-api.b3os.org/v1/requests?sandbox_id=$SBX" -H "Authorization: Bearer $B3OS_API_KEY"

Usage from those calls is recorded with the sandbox's ID and billed to the key that created it.

Ask a question... ⌘I