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.
| Response | Meaning |
|---|---|
401 | No platform key or team token on the request at all |
403 | Authenticated, but the admin key is missing, wrong, or admin keys are unset in this environment |
400 | Refused — most often a namespace that is not tenant:project, or a key for a team that was never created |
502 | The credential plane is reachable but failing |
503 | Admin 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 model | Env 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 with | Leases from |
|---|---|
| A team token, or a platform key plus both tenancy headers | tenant: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
- Credential administration reference — full schemas
- Authentication — what each credential can reach