---
summary: Programmatic access for agents and services. Bound to a user OR a group;
  permissions flow through RBAC.
title: API keys
path: concepts/api-keys
status: published
---

# API keys

API keys are the agent-facing equivalent of a user session. Issue one,
hand it to your agent or service, and the agent gains exactly the
permissions of whatever the key is bound to — narrowed by optional
**scope qualifiers**.

## What a key is bound to

A key is bound to **either** a user (acts as them) **or** a group (acts as
the group, no user attribution). Exactly one; never both. Both routes
funnel through the same RBAC engine — there is no parallel permission
system for keys.

## Scope qualifiers

A key's `scopes` array can narrow what the key is allowed to do without
changing the underlying role:

| Scope | Permits |
|---|---|
| (no scopes) | Everything the bound principal has |
| `docs:read` | Maps to `docs.read` permission |
| `docs:write:scaigrid` | Write only within the `scaigrid` namespace |
| `docs:write:scaigrid/v2/**` | Write only within version v2 (or a path prefix) |
| `docs:*` | All `docs.*` perms |
| `*` | Wildcard — same as no scopes |

Scopes only *narrow*; they cannot grant a permission the principal doesn't
already hold. This is enforced both for permissions globally (the
`docs:write` part) and per-resource (the `:scaigrid/...` part — checked at
write-time).

## Auth header

Same `Authorization` header as user JWTs; the backend disambiguates by the
`scai_` prefix.

```bash
curl -H "Authorization: Bearer scai_live_NbZRFjU6…" \
     "$API/api/v1/docs/_namespaces"
```

## Lifecycle

1. **Issue** in admin → `/api-keys` → New API key.
2. The plaintext is shown **once** in a one-time modal. Copy it.
3. The key is identified afterwards by its prefix (`scai_live_NbZRFjU6`).
4. **Revoke** by clicking *Revoke* — the key becomes `is_active=false` and
   subsequent requests get `401 Invalid API key`.

## Audit trail

`api_key_logs` records every authenticated request: endpoint, method, status,
IP, user agent. Group-bound keys log against their `api_key_id`, so even
without user attribution you can answer "who/what touched this".
