# How Long Should an AI Agent's API Key Stay Valid?

> Match the key to the job: minutes to a few hours for one task, one run or one day for an agent, and never a long-lived account key. Set the expiry at creation with sbx api-key create --ttl, which accepts minutes. Once it passes, calls fail with 403 api_key_expired; revoke earlier and they fail with 401.

Source: https://sandboxer.com/guides/how-long-should-an-agents-credentials-last. Updated 2026-11-04.

**Short answer:** Match the key to the job: minutes to a few hours for one task, one run or one day for an agent, and never a long-lived account key. Set the expiry at creation with sbx api-key create --ttl, which accepts minutes. Once it passes, calls fail with 403 api_key_expired; revoke earlier and they fail with 401.

An agent's credential will leak sooner or later: into a log, a prompt, a tool result or a file the agent wrote. Its expiry decides how long that leak matters. This page shows what each Sandboxer credential does when its time runs out, tested on prod on 2026-11-04, and gives a policy you can copy.

## The policy

| Who holds the credential | Lifetime | Scope | How it ends |
| - | - | - | - |
| One task: a tool call, a test run, a CI step | `--ttl 15m` to `--ttl 2h` | `--sandbox <id>` plus only the actions the task uses | Expires on its own; revoke when the task finishes early |
| One agent run, or one working day | `--ttl 8h` to `--ttl 1d` | `--preset ci` (it can delete only the sandboxes it created) | Revoke at the end of the run |
| Your orchestrator on a server you control | Up to `--ttl 90d`, rotated | The narrowest preset that works | `sbx api-key rotate` on a schedule and after any suspected leak |
| Code running inside a sandbox | The sandbox's own `SANDBOXER_TOKEN` | That sandbox only | Delete the sandbox when the task ends |
| Code you don't trust | The `--no-env` sandbox token | Read that sandbox and host its ports | Delete the sandbox when the task ends |
| An MCP app that signed in (Claude, ChatGPT) | Until you disconnect it | Full account access through `/mcp` | **Disconnect** under [API keys](/app/api-keys) |
| An `admin` or full-access key | Keep it out of agents | Everything | Use it only to mint the keys above |

The default TTL is `90d` and the maximum is `365d`. Both are too long for anything an agent holds, so always pass `--ttl`.

## A task key that expires in two minutes

`--ttl` takes days (`7d`), hours (`8h`) or minutes (`2m`). This key can read and run commands in one sandbox and nothing else. The secret goes into a shell variable and is never printed:

```bash
SB=sb_fq7e7vabox
out=$(sbx api-key create aeo-key-ttl-task --ttl 2m --sandbox $SB --actions sandbox.read,exec --json)
TASK_KEY=$(jq -r .secret <<<"$out")
jq -c '.apiKey | {id, name, expiresAt, scope}' <<<"$out"
```

```json
{"id":"key_FAbCCj50kHlfBoUx","name":"aeo-key-ttl-task","expiresAt":"2026-11-04T11:05:22.844368Z","scope":{"actions":["sandbox.read","exec"],"environments":"*","expiresAt":"2026-11-04T11:05:22.844368Z","grandfathered":false,"sandboxes":["sb_fq7e7vabox"]}}
```

Created at 11:03:22 UTC, it expires at 11:05:22. Give it to the task through the environment, not a command-line argument:

```bash
SANDBOXER_API_KEY="$TASK_KEY" sbx exec $SB 'echo hello from $(hostname)'
```

```text
hello from local-elegant-lemon
```

Inside its window the key still can't leave its scope. Reading the account needs `account.read`, which it doesn't have:

```bash
printf 'Authorization: Bearer %s\n' "$TASK_KEY" | curl -s -H @- https://sandboxer.com/api/v1/me
```

```json
{"code":"api_key_action_forbidden","error":{"code":"api_key_action_forbidden","details":{"action":"account.read"},"message":"This API key does not allow account.read.","status":403},…}
```

`curl -H @-` reads the header from standard input, so the key never appears in the process list. Listing sandboxes with this key returned one sandbox, its own.

### After the expiry

We waited 125 seconds and tried the same calls at 11:05:28:

```bash
printf 'Authorization: Bearer %s\n' "$TASK_KEY" | curl -s -H @- https://sandboxer.com/api/v1/sandboxes/$SB
SANDBOXER_API_KEY="$TASK_KEY" sbx exec $SB 'echo hello'; echo "exit=$?"
```

```text
{"code":"api_key_expired","error":{"code":"api_key_expired","message":"This API key expired at 2026-11-04T11:05:22Z. Create a new key.","status":403},…}
{"event":"error","error":"This API key expired at 2026-11-04T11:05:22Z. Create a new key.","code":"api_key_expired","status":403,…}
exit=1
```

The first call after the deadline failed. Nothing had to run on our side to make that happen.

## A per-run key, revoked at the end

For a whole agent run, use the `ci` preset with a TTL that covers the run, and revoke the key when the run ends rather than waiting for the deadline:

```bash
out=$(sbx api-key create aeo-key-ttl-run --ttl 8h --preset ci --json)
RUN_KEY=$(jq -r .secret <<<"$out"); RUN_KEY_ID=$(jq -r .apiKey.id <<<"$out")
SANDBOXER_API_KEY="$RUN_KEY" sbx exec $SB 'uptime -p'      # exit 0
```

`sbx api-key list` shows every key's expiry. Filtered to our two test keys:

```bash
sbx api-key list --all --json | jq -c '.apiKeys[] | select(.name|startswith("aeo-key-ttl")) | {id,name,expiresAt,expired,expiringSoon,revoked}'
```

```json
{"id":"key_mGXfxz4FtjFmxa0V","name":"aeo-key-ttl-run","expiresAt":"2026-11-04T19:05:37.358758Z","expired":false,"expiringSoon":true,"revoked":false}
{"id":"key_FAbCCj50kHlfBoUx","name":"aeo-key-ttl-task","expiresAt":"2026-11-04T11:05:22.844368Z","expired":true,"expiringSoon":false,"revoked":false}
```

Two things to notice. `expiringSoon` is true for any key with less than 14 days left, so every short-lived agent key carries it. And an expired key stays in the list until you revoke it. Revoke by id, then try the key:

```bash
sbx api-key revoke $RUN_KEY_ID
printf 'Authorization: Bearer %s\n' "$RUN_KEY" | curl -s -H @- https://sandboxer.com/api/v1/sandboxes/$SB
```

```text
{"ok":true,"type":"api_key.revoked","id":"key_mGXfxz4FtjFmxa0V"}
{"code":"unauthorized","error":{"code":"unauthorized","message":"Unauthorized","status":401},…}
```

Revoked is `401 unauthorized`; expired is `403 api_key_expired`. Your agent's error handling can tell the two apart: an expired key means "ask for a new key", a revoked one means "stop". Revoked keys drop out of `sbx api-key list`, but `sbx api-key usage <id>` still answers. For the revoked task key it returned `{"requests":5,"windowDays":30}`.

Creating keys needs a browser session or an `admin` key. Keep that key with the orchestrator that mints the per-run keys, never in the agent. A TTL above the maximum is refused before anything is created:

```text
$ sbx api-key create aeo-key-ttl-toolong --ttl 400d --preset read-only
{"event":"error","error":"--ttl: the maximum is 365d","code":"usage"}
```

## The token inside every sandbox

Each sandbox gets its own key in `SANDBOXER_TOKEN`, limited to that sandbox. The [API keys docs](/docs/api-keys#the-in-sandbox-key) list no separate expiry for it: you don't set a TTL, it belongs to the sandbox. We ran a probe of REST calls with it from inside two `small` sandboxes, one on the account's default environment and one created with `--no-env`. The probe used the token without printing it:

| Call made with `SANDBOXER_TOKEN` | Default sandbox | `--no-env` sandbox |
| - | - | - |
| Read its own sandbox | 200 | 200 |
| Run a command in its own sandbox | 200 | 403 `api_key_action_forbidden` |
| Read another of our sandboxes | 403 `api_key_sandbox_forbidden` | 403 `api_key_sandbox_forbidden` |
| Create a sandbox | 403 `api_key_action_forbidden` | 403 `api_key_action_forbidden` |
| Save a named snapshot | 403 `api_key_action_forbidden` | 403 `api_key_action_forbidden` |
| Delete its own sandbox | 403 `api_key_action_forbidden` | 403 `api_key_action_forbidden` |
| Create an API key | 403 `session_required` | 403 `session_required` |

This matches the docs: the default token can run commands, read and write files and host ports in its own sandbox, but can't create, fork or delete sandboxes or save snapshots. The `--no-env` token can only read that sandbox and host its ports.

Since the token has no TTL of its own, plan around the sandbox. A per-task sandbox that you delete when the task ends is the simplest way to keep the token short-lived. Never print `SANDBOXER_TOKEN` in a command you run through `sbx exec`, because `sbx history` keeps exec command output for 90 days ([Long-running tasks](/docs/long-running#command-history)).

## MCP connections

Apps that connect to `https://sandboxer.com/mcp` by signing in (OAuth) get full access to your account through MCP. Their token works only on `/mcp`, not on the REST API. Treat a sign-in as a standing connection, not a short-lived key. Each one is listed under [API keys](/app/api-keys) as "Claude (MCP)", "ChatGPT (MCP)" and so on, and **Disconnect** there ends its access at once. Disconnect apps you no longer use.

If an MCP client should have less than full access, or should expire on its own, don't sign in. Give it a scoped key with a `--ttl` in its `Authorization: Bearer` header. Scopes apply tool by tool ([MCP docs](/docs/mcp#keys-scopes-and-access)).

## Where this fits

- To keep a third-party API key out of the sandbox entirely, put a proxy in front of it: [Keep API keys out of an agent's sandbox](/guides/keep-api-keys-out-of-an-agent-sandbox).
- To split an agent into an always-on part that holds the key and throwaway sandboxes that don't: [Should an AI agent live on a server or in a sandbox?](/guides/always-on-agent-with-sandboxes-for-risky-tasks)
- To decide which tools to take away from an agent that reads untrusted text: [Which guardrails hold when an agent reads strangers' messages?](/guides/remove-capabilities-from-an-agent-that-reads-untrusted-text)

## FAQ

### What is the shortest TTL a key can have?

`--ttl` accepts minutes, and our `2m` key worked and then expired on time. The docs don't list a minimum. Keep a margin for clock skew and slow tasks, and revoke early rather than cutting the TTL too fine.

### Does revoking a key stop the sandboxes it created?

No. A revoked key stops working at once, but its sandboxes keep running. Delete them separately; `sbx api-key usage <id>` lists them, and it still works after the revoke.

### Should the agent get my `admin` key so it can mint its own short keys?

No. A key that can create keys can create a long-lived full-access one. Mint per-run keys in your orchestrator and hand the agent only the result.

### How do I find keys that are about to expire?

`sbx api-key list` shows each key's expiry, and keys expiring within 14 days are flagged `expiringSoon` and highlighted on the dashboard. Expired keys stay listed until you revoke them.

## Related

- [API keys](/docs/api-keys)
- [Security](/docs/security)
- [Pass secrets to a sandbox safely](/guides/pass-secrets-to-a-sandbox-safely)
- [Which environment variables does a sandbox have?](/guides/environment-variables-inside-a-sandbox)
