CI Tokens
CI tokens (a.k.a. service-account tokens) let the proxy authenticate from non-interactive environments - CI runners, Docker images, scheduled jobs - without going through the browser-based notdiamond login flow.
Tokens come in two flavours:
- Root tokens are minted by an account owner or admin from the dashboard. They are mint-only: a root token can issue child tokens via the API, but cannot drive proxy traffic.
- Child tokens are minted from a root (typically inside a CI job). Children drive proxy traffic - they call
/jwt, sync runtime configuration, send ingest, etc.
This split means a leaked CI secret can be revoked without affecting other automation, and a leaked child token cannot be used to mint further tokens.
Mint a root token
Section titled “Mint a root token”- Sign in to the dashboard as an owner or admin.
- Go to Settings → CI Tokens.
- Click Mint token, give it a label (e.g.
github-actions), and pick an expiry. - Copy the token value immediately - it is shown once and stored only as a hash. Losing it means minting a new one.
Tokens look like nd_<random> and are ~51 characters long.
Save the value in your CI provider’s secret store (e.g. GitHub Actions secrets) as NOTDIAMOND_ROOT_TOKEN.
Mint a child token in CI
Section titled “Mint a child token in CI”From a CI job, exchange the root for a short-lived child token:
curl -sS -X POST https://code.notdiamond.ai/api/cli/tokens/service-account \ -H "Authorization: Bearer $NOTDIAMOND_ROOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"label": "ci-run-${GITHUB_RUN_ID}", "expires_in_seconds": 3600}' \ | jq -r .tokenThe response includes token, token_id, is_root: false, and expires_at. Export the value as NOTDIAMOND_TOKEN for the proxy:
export NOTDIAMOND_TOKEN="nd_…"export CI=truenotdiamond claude -p "run my task"Expiry
Section titled “Expiry”- Default TTL: 7 days.
- Maximum TTL when minted via the API: 90 days.
- Each token’s
last_seen_atupdates on every request - visible in the dashboard.
For CI, prefer a short expiry (an hour or two) and mint a fresh child per run.
Use the token with the proxy
Section titled “Use the token with the proxy”The proxy reads its credential from one of two places:
| Source | When it’s used |
|---|---|
NOTDIAMOND_TOKEN env var | CI / non-interactive - checked first |
~/.notdiamond/auth.json | Interactive notdiamond login flow |
When NOTDIAMOND_TOKEN is set, the proxy skips the local file entirely. At startup it calls /api/cli/whoami to verify the token is still valid and is not a root.
CI mode for evals
Section titled “CI mode for evals”For CI and eval harnesses, run with CI=true:
export CI=trueCI mode makes notdiamond claude verify dashboard-controlled launch settings before starting Claude Code. If that verification fails, the command exits instead of launching with stale local state. This is recommended for evals because Claude Code settings are fixed for the spawned Claude Code process, so stale settings can change which models are available during a run.
Most hosted CI systems, including GitHub Actions, set CI=true automatically. If your eval runner does not, set it explicitly in the job environment.
If you accidentally hand a root token to the proxy, it exits with:
Root service-account tokens cannot drive proxy traffic; mint a childtoken via POST /api/cli/tokens/service-account and use that in NOTDIAMOND_TOKEN.Example: GitHub Actions
Section titled “Example: GitHub Actions”jobs: claude: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: curl -fsSL https://code.notdiamond.ai/install.sh | bash - name: Mint child token run: | NOTDIAMOND_TOKEN=$(curl -sS -X POST \ https://code.notdiamond.ai/api/cli/tokens/service-account \ -H "Authorization: Bearer ${{ secrets.NOTDIAMOND_ROOT_TOKEN }}" \ -H "Content-Type: application/json" \ -d '{"label":"gha-${{ github.run_id }}","expires_in_seconds":3600}' \ | jq -r .token) echo "NOTDIAMOND_TOKEN=$NOTDIAMOND_TOKEN" >> $GITHUB_ENV - run: notdiamond claude -p "summarise the diff" env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} CI: "true"Manage tokens
Section titled “Manage tokens”- List:
GET /api/cli/tokens/service-account(session-authed; or view in Settings → CI Tokens). - Revoke:
DELETE /api/cli/tokens/service-account/:id- accepts the parent root, the child itself (self-revoke), or a session admin. Revoking a root does not cascade to its children; revoke them individually if needed. - Inspect a token:
GET /api/cli/whoamiwith the bearer returns the user, account, prefix, expiry, andis_root.
Endpoints reference
Section titled “Endpoints reference”All endpoints expect Authorization: Bearer nd_….
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST | /api/cli/tokens/service-account | Session (root mint) or child bearer (child mint) | Mint a token |
GET | /api/cli/tokens/service-account | Session (owner/admin) | List tokens for the account |
DELETE | /api/cli/tokens/service-account/:id | Session, parent bearer, or self | Revoke a token |
GET | /api/cli/whoami | Bearer (either type) | Identify the current token |
POST | /api/cli/jwt | Child bearer only | Mint a short-lived JWT used by the proxy |
Root tokens are explicitly rejected from proxy-traffic endpoints such as /jwt and /ingest.
Troubleshooting
Section titled “Troubleshooting”Your login is no longer valid. Run: notdiamond login - the token was revoked or expired. Mint a new child token from your root and update the secret.
Root service-account tokens cannot drive proxy traffic… - you wired the root token directly into NOTDIAMOND_TOKEN. Exchange it for a child first (see above).
Can’t reach cloud at … - the runner can’t reach code.notdiamond.ai. Check egress rules, or set NOTDIAMOND_CLOUD_URL if you self-host.