Devbox agent tokens
Devbox agent tokens let an agent running inside a devbox manage that same devbox without holding a full account-wide MORPH_API_KEY.
Use an agent token when you want the agent to do narrow self-management tasks such as:
- reading the current devbox summary,
- exposing or hiding an HTTP service,
- saving a snapshot,
- reading or writing secrets when the token has the matching scope,
- writing notifications when you mint the explicit notifications scope.
Use a full MORPH_API_KEY instead when you need account-wide or multi-devbox access, or when you need workflows outside the usual self-management surface.
What the token is scoped to
When you mint a devbox agent token through POST /api/devboxes/{devbox_id}/agent-token, the service binds it to:
- the authenticated user,
- a single
devbox_id, - the effective organization context,
- an explicit set of scopes,
- an optional
purposestring for audit/debug context.
The mint endpoint only succeeds for a devbox that is already claimed and owned by the authenticated user. Later requests made with the token are rejected if you try to use that token against a different devbox id.
Tokens minted by this route are currently non-expiring. The request schema still accepts ttl_seconds for backwards compatibility, but the service does not currently encode an expiry for newly minted tokens and returns expires_at: null.
Default and supported scopes
If you omit scopes when minting, the service currently grants this default self-management set:
devbox:summary:readdevbox:http:writedevbox:snapshot:writedevbox:secrets:readdevbox:secrets:write
Additional supported scopes include:
devbox:notifications:writedevbox:metadata:write(reserved; not part of the normal default workflow)
Best practice: mint only the scopes you actually need, and inspect the returned scopes array instead of assuming every agent token has the same capabilities.
Mint a least-privilege token
There is not currently a repository-backed SDK or CLI helper documented for minting this token, so the canonical workflow today is a raw HTTP request to the devbox service.
export MORPH_API_KEY="<your-user-api-key>"
export DEVBOX_ID="<your-devbox-id>"
export DEVBOX_SERVICE_BASE_URL="${DEVBOX_SERVICE_BASE_URL:-https://devbox.svc.cloud.morph.so}"
MINT_RESPONSE="$(curl -sS \
-H "Authorization: Bearer $MORPH_API_KEY" \
-H "Content-Type: application/json" \
-X POST "$DEVBOX_SERVICE_BASE_URL/api/devboxes/$DEVBOX_ID/agent-token" \
-d '{
"scopes": [
"devbox:summary:read",
"devbox:snapshot:write"
],
"purpose": "codex session: docs update"
}')"
export DEVBOX_AGENT_TOKEN="$(echo "$MINT_RESPONSE" | jq -r '.token')"
echo "$MINT_RESPONSE" | jq '{devbox_id, user_id, scopes, expires_at}'
The response includes the final granted scope list. For newly minted tokens, expires_at should currently be null.
Use an already-injected DEVBOX_AGENT_TOKEN
Some agent environments inject a pre-scoped DEVBOX_AGENT_TOKEN for you. In that case, use it directly and assume it may be narrower than the service default scope set.
export DEVBOX_SERVICE_BASE_URL="${DEVBOX_SERVICE_BASE_URL:-https://devbox.svc.cloud.morph.so}"
: "${DEVBOX_ID:?missing DEVBOX_ID}"
: "${DEVBOX_AGENT_TOKEN:?missing DEVBOX_AGENT_TOKEN}"
curl -sS \
-H "Authorization: Bearer $DEVBOX_AGENT_TOKEN" \
"$DEVBOX_SERVICE_BASE_URL/api/devboxes/$DEVBOX_ID/summary" | jq
If the session tooling manages the token for you, prefer refreshing or restarting that session rather than replacing it ad hoc.
Common self-management calls
# 1) Read the lightweight devbox summary
curl -sS \
-H "Authorization: Bearer $DEVBOX_AGENT_TOKEN" \
"$DEVBOX_SERVICE_BASE_URL/api/devboxes/$DEVBOX_ID/summary" | jq
# 2) Expose a local port as an HTTP service
curl -sS \
-X POST \
-H "Authorization: Bearer $DEVBOX_AGENT_TOKEN" \
-H "Content-Type: application/json" \
"$DEVBOX_SERVICE_BASE_URL/api/devboxes/$DEVBOX_ID/http" \
-d '{"name":"app","port":3000}' | jq '.networking.http_services'
# 3) Hide that HTTP service again
curl -sS \
-X DELETE \
-H "Authorization: Bearer $DEVBOX_AGENT_TOKEN" \
"$DEVBOX_SERVICE_BASE_URL/api/devboxes/$DEVBOX_ID/http/app" | jq '.networking.http_services'
# 4) Save a snapshot before a risky change
curl -sS \
-X POST \
-H "Authorization: Bearer $DEVBOX_AGENT_TOKEN" \
-H "Content-Type: application/json" \
"$DEVBOX_SERVICE_BASE_URL/api/devboxes/$DEVBOX_ID/save" \
-d '{"name":"pre-change: dependency upgrade"}' | jq
Using secret scopes safely
If your token includes devbox:secrets:read or devbox:secrets:write, you can use the devbox service secret endpoints with the same bearer token.
Example: list secret metadata without printing secret values:
curl -sS \
-H "Authorization: Bearer $DEVBOX_AGENT_TOKEN" \
"$DEVBOX_SERVICE_BASE_URL/api/user-secrets" | jq '.data[] | {name, metadata}'
Actual secret-value reads and writes use the same auth model, but be careful not to print values into shell history, logs, chat, or CI output.
If a secret-backed call fails with Agent token session not found (mint a new agent token), mint a fresh token with your user API key and retry. The service stores encrypted session data for agent-token secret resolution, and non-secret endpoints can continue to work even when that session state is unavailable.
Best practices
- Mint one token per agent session or workflow, not one broad token that multiple tools share indefinitely.
- Set
purposeevery time you mint. A short string likecodex session: release docsmakes logs and debugging much easier. - Request the smallest scope set that works. If you only need
summaryandsave, do not also grant secrets or notifications. - Keep the token in process memory or short-lived environment state. Do not commit it, paste it into tickets, or write it to shared files.
- Assume injected
DEVBOX_AGENT_TOKENvalues are intentionally constrained. Check your workflow against the actual granted scope set before widening credentials. - There is no dedicated revoke endpoint documented here today, so treat narrow scopes and one-token-per-session hygiene as your primary containment strategy.
Troubleshooting
401 or 403 with Token expired
- Newly minted tokens from
POST /agent-tokenare currently non-expiring. - If you still see
Token expired, you are probably using an environment-managed token provided by a higher-level session tool. Refresh or restart that session and retry.
401 with Agent token session not found (mint a new agent token)
- This usually appears on secret-backed requests.
- Mint a fresh token with your user API key so the service can recreate the encrypted token-session record it uses to resolve your underlying user API key for secret operations.
403 with Insufficient scope
- Remint the token with the exact missing scope instead of switching immediately to a full
MORPH_API_KEY.
403 with Token is scoped to a different devbox
- The token is bound to one devbox id. Use the matching
DEVBOX_ID, or mint a new token for the correct devbox.
When to use MORPH_API_KEY instead
Prefer MORPH_API_KEY when you need:
- account-wide or organization-wide operations,
- workflows that touch multiple devboxes,
- devbox create, delete, branch, or tmux automation flows that are outside the usual agent-token self-management surface,
- a broader control-plane surface than self-management,
- any automation where the agent token does not have the required scope,
- tasks where your environment provides only a narrowed injected
DEVBOX_AGENT_TOKEN.
For general Devboxes setup and API usage, start with Getting started and Development. For low-level API details, see the hosted devbox service docs.