---
title: Authentication & API keys
path: deployment/auth-and-keys
status: published
---

# Authentication & API keys

ScaiLog authenticates every request with a built-in API key — no external
service required. This is deliberate: ScaiLog is the foundational logger every
other platform service logs *into*, so a runtime dependency on an auth service
would create an unbootstrappable cycle (design invariant P1). An optional
OIDC/SSO plugin sits on top for human operators and the admin panel, but API
keys always remain the base mechanism and the system stays fully usable without
OIDC.

## API keys are the base mechanism

Keys are bearer tokens of the form `slk_<role-hint>_<secret>`. The server stores
only their SHA-256 hash — the plaintext is shown once at creation and is never
recoverable. A key is bound to one tenant (or `*` for all tenants) and can be
narrowed to specific services. Keys optionally carry an expiry, a per-minute rate
limit, and an IP allowlist.

Send a key as a bearer token on every request:

```http
Authorization: Bearer slk_agent_...
```

### Roles and capabilities

Each key has a role; the role grants a set of capabilities. Endpoints require a
capability, so you scope access by choosing the narrowest role that works.

| Role | Capabilities | Typical use |
| --- | --- | --- |
| `log_writer` | `ingest` | An agent shipping batches, or a service ingesting directly. |
| `log_reader` | `query` | Read-only log search and tail. |
| `log_reader_plus` | `query`, `decrypt_one` | A reader that can also decrypt a single PI field on demand. |
| `log_admin` | `query`, `decrypt_one`, `admin` | Manage keys, policies, and sites within scope. |
| `dpo` | `ingest`, `query`, `decrypt_one`, `admin`, `dpo` | Full operator/data-protection role; runs DSAR and erasure. |

Two containment rules keep an admin from escalating: a tenant-scoped or
service-scoped admin can only mint keys **within** its own scope, and only a
`dpo` key may create another key that carries the `dpo` capability.

## The bootstrap key

The first key is minted automatically when you initialize an empty server. It is
a `dpo` key scoped to all tenants (`*`) — the full-access operator key you use to
create every narrower key.

```bash
scailog-server --init --data-dir ./data
# prints the bootstrap key once — store it now
```

If keys already exist, `--init` mints nothing. Treat the bootstrap key as a
break-glass secret: use it to create per-service and per-operator keys, then keep
it somewhere safe rather than embedding it in services. (The admin panel stores
credentials in `sessionStorage`, never `localStorage`, precisely so a
full-access bootstrap key does not persist across restarts.)

## Minting keys

### From the CLI

```bash
scailog keys create log_writer --tenant-scope default --service billing,checkout
scailog keys list
scailog keys revoke <key_id>
```

`keys create` prints the new key id and the plaintext key **once**.

### Over HTTP

`POST /v1/keys` requires an `admin`-capable caller. The response returns the
plaintext under `key`, shown once:

```http
POST /v1/keys
Authorization: Bearer <admin-or-dpo-key>
Content-Type: application/json

{"role": "log_writer", "tenant_scope": "default", "service_scope": ["billing"]}
```

`GET /v1/keys` lists keys (a tenant-scoped admin sees only its own tenant's
keys), and `DELETE /v1/keys/{key_id}` revokes one.

## Optional: OIDC / SSO for humans

For human operators — the CLI and the admin panel — ScaiLog can additionally
accept an OIDC ID token issued by ScaiKey or any standards-compliant provider.
This is an **optional plugin** that must degrade to built-in keys: if the plugin
isn't configured or the `scaikey-sdk` extra isn't installed, only API keys are
accepted (design invariant P1). OIDC never touches the ingest hot path — agents
authenticate with built-in API keys — so the identity provider being slow or down
cannot block log ingestion.

### Server side

Install the extra and configure the provider:

```bash
pip install 'scailog[oidc]'
```

The server enables the plugin when both `SCAILOG_OIDC_BASE_URL` and
`SCAILOG_OIDC_CLIENT_ID` are set (`SCAILOG_OIDC_CLIENT_SECRET` and
`SCAILOG_OIDC_DEFAULT_TENANT` are optional). It validates the ID token's
signature and claims (JWKS-backed) and maps the `scailog_role` claim to a ScaiLog
role; a token with no recognized role falls back to `log_reader`. Tenant mapping
**fails closed**: a token with no tenant claim is rejected unless
`SCAILOG_OIDC_DEFAULT_TENANT` names the single tenant such logins map into — a
tokenless-tenant login is never granted the `*` all-tenant scope.

### CLI side

The CLI logs in with the OAuth 2.0 device flow (RFC 8628) against the provider's
discovery document — it carries no ScaiKey dependency:

```bash
scailog login --oidc-url https://id.example.com --client-id scailog-cli
scailog whoami
scailog logout
```

The device flow prints a URL and a user code to enter in a browser. On success
the CLI stores the session (`~/.config/scailog/credentials.json`, mode 0600),
separate from any static API key, and auto-refreshes the ID token. Token
precedence when the CLI makes a request: an explicit `SCAILOG_API_KEY`
environment variable wins, then a valid OIDC ID token, then the configured API
key.

### Admin panel

The admin panel offers the same two ways in: paste an API key, or "Sign in with
SSO" (Authorization Code + PKCE) when the panel is built with `VITE_OIDC_ISSUER`
and `VITE_OIDC_CLIENT_ID`. Either way it sends a bearer token and calls
`GET /v1/whoami`, then hides features by capability. A ScaiKey "super-admin" maps
to the `dpo` role via the `scailog_role` claim.
