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.
This page uses DEVBOX_SERVICE_BASE_URL to make it obvious that you are talking directly to the devbox service.
If you already use MORPH_DEVBOX_BASE_URL elsewhere, point it to the same value and keep it consistent across your scripts.
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
In morphcloud-devbox-service, POST /api/devboxes/{devbox_id}/http currently forces auth_mode to none.
That means exposing a port through this route creates a public URL. Only expose services you intentionally want reachable from the public internet.
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.