Sandbox API

Provisioning credentials

Issue team tokens and model provider keys. For operators of a Sandbox deployment.

This page is for whoever operates a Sandbox deployment. If you are calling the API, you need Authentication instead.

Provisioning is a separate privilege from using the API: it needs an admin key in a dedicated header, and an ordinary API key or team token cannot perform it. A consumer credential must not be able to mint credentials for other tenants.

Two credentials are needed, not one. Authentication runs before the admin check, so the admin key alone answers 401. Send an ordinary platform key and the admin key.

export SANDBOX_GATEWAY_URL="${SANDBOX_GATEWAY_URL:-http://localhost:8780}"

Issue a team token

A team token belongs to one tenant:project namespace, and that is what you hand to a consumer.

curl -sS -X POST \
  -H "X-Api-Key: $SANDBOX_API_KEY" \
  -H "X-Sandbox-Admin-Key: $SANDBOX_ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"namespace":"acme:proj1","keys":{"openai":"sk-…"}}' \
  "$SANDBOX_GATEWAY_URL/sandbox/v1/admin/credential-plane/teams"

The response contains the team token — that string is what the consumer sends.

ResponseMeaning
401No platform key or team token on the request at all
403Authenticated, but the admin key is missing, wrong, or admin keys are unset in this environment
400Refused — most often a namespace that is not tenant:project, or a key for a team that was never created
502The credential plane is reachable but failing
503Admin key accepted, but the credential plane is not configured

Admin keys must not overlap the ordinary API keys — the gateway refuses to start if they do. When no admin keys are configured the provisioning routes are closed entirely and every request gets 403, whatever it sends. That closed state is the default for a new environment, so seeding the secret is a prerequisite for onboarding anyone rather than a later hardening step.

Add a model provider key

An agent that calls a model leases a provider key at run time from the namespace the job belongs to. A key is stored under a team, so the team must exist first — adding a key for a namespace with no team answers 400 unknown_namespace.

curl -sS -X POST \
  -H "X-Api-Key: $SANDBOX_API_KEY" \
  -H "X-Sandbox-Admin-Key: $SANDBOX_ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"namespace":"acme:proj1","provider":"openai","value":"sk-…"}' \
  "$SANDBOX_GATEWAY_URL/sandbox/v1/admin/credential-plane/keys"

provider is one of openai, anthropic, google, xai, and is matched against the model on the run: a run with openai/gpt-5 leases the openai key. A model whose provider cannot be determined leases nothing rather than guessing, so a key is never charged to the wrong provider.

The agent receives it as an environment variable:

Provider inferred from modelEnv var the agent receives
anthropic/…, bare claude-…ANTHROPIC_API_KEY
openai/…, bare gpt-…, o3-…OPENAI_API_KEY
google/…, gemini/…GEMINI_API_KEY
openrouter/…OPENROUTER_API_KEY
xai/…, bare grok-…XAI_API_KEY
fireworks/…FIREWORKS_API_KEY

Which namespace a run leases from

Run submitted withLeases from
A team token, or a platform key plus both tenancy headerstenant:project only
A platform key with no tenancy headers, or only one of them(none — no key is leased)

There is no shared fallback namespace. Provision keys under the same tenant:project the job will carry, or the agent fails with model_credential_absent.

curl -sS -X POST \
  -d '{"namespace":"ctp:your-project-id","keys":{"anthropic":"sk-ant-…"}}' \
  "$SANDBOX_GATEWAY_URL/sandbox/v1/admin/credential-plane/teams"

A missing key is not a request error: the run proceeds and the agent fails on its own terms. Check the worker log for model_credential_absent, which names the provider and the namespaces that were tried.

Set project compute quota

Credentials and compute policy use the same tenant:project name but have different owners: KeyHive stores provider keys; the L2 control plane stores concurrency quota.

curl -sS -X PUT \
  -H "X-Api-Key: $SANDBOX_API_KEY" \
  -H "X-Sandbox-Admin-Key: $SANDBOX_ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"max_concurrent":3}' \
  "$SANDBOX_GATEWAY_URL/sandbox/v1/admin/quotas/acme/proj1"

Set a tenant-wide fallback with project _default:

curl -sS -X PUT \
  -d '{"max_concurrent":2}' \
  "$SANDBOX_GATEWAY_URL/sandbox/v1/admin/quotas/acme/_default"

An exact project policy wins over the tenant default, which wins over the environment default. The operator ceiling may reject a value that exceeds safe fleet capacity.

The consumer can inspect its effective quota without admin privilege:

curl -sS -H "X-Api-Key: $TEAM_TOKEN" \
  "$SANDBOX_GATEWAY_URL/sandbox/v1/quota"

Next