Skip to content

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.

  1. Sign in to the dashboard as an owner or admin.
  2. Go to Settings → CI Tokens.
  3. Click Mint token, give it a label (e.g. github-actions), and pick an expiry.
  4. 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.

From a CI job, exchange the root for a short-lived child token:

Terminal window
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 .token

The response includes token, token_id, is_root: false, and expires_at. Export the value as NOTDIAMOND_TOKEN for the proxy:

Terminal window
export NOTDIAMOND_TOKEN="nd_…"
export CI=true
notdiamond claude -p "run my task"
  • Default TTL: 7 days.
  • Maximum TTL when minted via the API: 90 days.
  • Each token’s last_seen_at updates on every request - visible in the dashboard.

For CI, prefer a short expiry (an hour or two) and mint a fresh child per run.

The proxy reads its credential from one of two places:

SourceWhen it’s used
NOTDIAMOND_TOKEN env varCI / non-interactive - checked first
~/.notdiamond/auth.jsonInteractive 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.

For CI and eval harnesses, run with CI=true:

Terminal window
export CI=true

CI 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 child
token via POST /api/cli/tokens/service-account and use that in NOTDIAMOND_TOKEN.
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"
  • 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/whoami with the bearer returns the user, account, prefix, expiry, and is_root.

All endpoints expect Authorization: Bearer nd_….

MethodPathAuthPurpose
POST/api/cli/tokens/service-accountSession (root mint) or child bearer (child mint)Mint a token
GET/api/cli/tokens/service-accountSession (owner/admin)List tokens for the account
DELETE/api/cli/tokens/service-account/:idSession, parent bearer, or selfRevoke a token
GET/api/cli/whoamiBearer (either type)Identify the current token
POST/api/cli/jwtChild bearer onlyMint a short-lived JWT used by the proxy

Root tokens are explicitly rejected from proxy-traffic endpoints such as /jwt and /ingest.

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.