> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CI and API keys

> Use sandboxes from CI with a scoped, expiring API key.

## Create a key for CI

Create keys in the [dashboard](https://app.compute.anonalabs.com) under **API keys** (owners and admins). Give CI its own key, with the least access it needs and an expiry:

| Preset | Scopes | For |
| - | - | - |
| Read only | `sandboxes:read`, `templates:read`, `usage:read` | Dashboards, monitoring, cost reports |
| Use existing | `sandboxes:read`, `sandboxes:use` | Driving sandboxes someone else created |
| Create and use | `sandboxes:read`, `sandboxes:use`, `sandboxes:create`, `sandboxes:delete`, `templates:read` | CI jobs that create and clean up their own sandboxes |
| Full access | `sandboxes:*`, `templates:*`, `usage:read`, `billing:read` | Trusted automation |
| Custom | Any of `sandboxes:read`, `sandboxes:use`, `sandboxes:create`, `sandboxes:delete`, `templates:read`, `templates:write`, `usage:read`, `billing:read`, `integrations:manage` | `integrations:manage` (webhooks, telemetry export) is only in custom keys |

Options on every key:

* **Expiry**: 7, 30, 90 or 365 days, or never.
* **Sandbox list**: limit the key to specific sandbox IDs. Other sandboxes answer "not found" to it.

The secret is shown once. Revoking a key stops it at once. Keys created before the `usage:read` and `billing:read` scopes existed do not have them. Key management is a dashboard action: API keys cannot create, list or revoke keys (the `/v1/api-keys` routes accept dashboard sessions only, see the [API reference](https://docs.anonalabs.com/compute/api-reference/introduction.md)).

## Use it in a job

Store the key as a CI secret and expose it as `E2B_API_KEY` (or `ANONA_API_KEY`) together with `E2B_DOMAIN`:

```yaml theme={null}
# .github/workflows/sandbox.yml
name: sandbox
on: [push]
jobs:
  test-in-sandbox:
    runs-on: ubuntu-latest
    env:
      E2B_DOMAIN: compute.anonalabs.com
      E2B_API_KEY: ${{ secrets.ANONA_COMPUTE_API_KEY }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install e2b
      - run: python ci/run_in_sandbox.py
```

A job script that always cleans up, even when the test fails:

```python run_in_sandbox.py theme={null}
import sys
from anona import Sandbox
from e2b import CommandExitException

with Sandbox.create("base", timeout=900, metadata={"ci": "true"}) as sbx:  # deleted on exit
    sbx.files.write("/home/user/test.sh", "set -e\necho running tests\ntest 2 -gt 1\n")
    try:
        r = sbx.commands.run("bash /home/user/test.sh", timeout=600)
        print(r.stdout)
    except CommandExitException as e:
        print(e.stdout, e.stderr, file=sys.stderr)
        sys.exit(e.exit_code)
```

```bash run_in_sandbox.sh theme={null}
# anona-compute run creates, runs, and deletes the sandbox (also on failure or Ctrl-C),
# and exits with the command's exit code.
export ANONA_COMPUTE_API_KEY="$E2B_API_KEY"
anona-compute run -t base --timeout 15m --metadata ci=true -- 'echo running tests && test 2 -gt 1'
```

Set a timeout on every CI sandbox, so a cancelled job cannot leave one running. Tag CI sandboxes with metadata, then sweep leftovers:

```bash sweep.sh theme={null}
anona-compute list --metadata ci=true --json | jq -r .sandboxID | while read -r id; do
  anona-compute delete "$id"
done
```

## The CLI with a key

The CLI takes `--api-key`, else `ANONA_COMPUTE_API_KEY` or `E2B_API_KEY`, else the profile; a key wins over a browser sign-in. To keep one in a profile:

```bash key_profile.sh theme={null}
printenv E2B_API_KEY | anona-compute --profile ci login --with-api-key
anona-compute --profile ci whoami
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.