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

# API keys

> Create a personal API key with explicit permissions and expiry, and manage keys and MCP connections.

Vitalog has one primary environment `AUTH_KEY`, optional manually generated API keys and scoped OAuth connection tokens. Manually generated keys carry explicit permission ceilings for the single health ledger; OAuth tokens authorize only their requested MCP scopes. The primary key administers all authentication records. The signed-in UI can manage generated keys and MCP connections after a separate root-credential verification. Key-management operations are REST-only and do not add MCP tools.

An [MCP OAuth client](/oauth) signs in with the same root credentials and creates a scoped 30-day connection token through authorization-code exchange. There are no additional accounts, JWTs, refresh tokens or health-ledger owners.

## Configuration and generation

Configure `ROOT_EMAIL` and `ROOT_PASSWORD` through your deployment's secret manager. Use at least 15 non-padding characters for the password, at most 256 UTF-8 bytes, and a different value from `AUTH_KEY`. Both values must be supplied together. Leave both empty to disable generation; existing primary and generated Bearer keys still work. Changing root credentials requires an API restart and affects new issuance only.

Open `https://vitalog.example.com/api-keys` on your installation, or use Account Settings → API Keys. Choose a Name, Permissions and Expires after, then enter the root email and password. Permissions are Read-only, Edit or Administrative permissions. Expiry choices are 30 days, 90 days, 1 year or Never. The choices start blank and are required. The form sends a JSON POST and returns one token. Copy it immediately; it cannot be retrieved later. The password is cleared after submission, and neither credentials nor tokens are stored in browser storage. The page has no third-party assets, cannot be framed and uses a restrictive CSP and `Cache-Control: no-store`.

The page runs in the separate Next.js app, using React 19 and HeroUI v3 with Tailwind CSS v4, Inter and a red theme. It posts to `https://vitalog-api.example.com/auth/api-keys`. The API accepts browser requests only from its configured `UI_BASE_URL` on this endpoint, with JSON-only preflight. The UI uses a fresh script nonce for each uncached response, allows network calls only to itself and the configured API, blocks native form submissions and framing, and serves its font/logo locally. HeroUI requires inline component styles; scripts require a nonce. The UI image and runtime environment contain no API credentials. Invalid login shows the HeroUI “Invalid credentials” toast and clears the password.

Direct generation uses `POST /auth/api-keys` with `Content-Type: application/json`. This route authenticates using the JSON credentials and requires no Bearer header:

```json theme={"system"}
{
  "email": "<configured root email>",
  "password": "<configured root password>",
  "name": "Personal automation",
  "access": "read",
  "includeAdmin": false,
  "expiresAt": null
}
```

All six fields are required. Names are trimmed and contain 1–120 characters. `access` is `read` or `edit`; `includeAdmin` is a boolean. Administrative permissions require `access: "edit"`. `expiresAt` is a future UTC timestamp or explicit `null` for Never. Omitting any setting fails validation. An optional UUID `Idempotency-Key` header makes creation safe to retry with the same body. A replay returns the original metadata with `api_key: null`; the complete token appears only in the original response. Reusing the identifier with changed settings returns 409. The browser retains the same timestamp and identifier through uncertain retries. Requests are limited to 4 KiB. Invalid email/password pairs return the same 401 response. Issuance permits five attempts per socket/trusted client address per minute, thirty total per process per minute and at most two concurrent password verifications. Limits include successful issuance. Untrusted forwarded IP headers do not change the bucket. A 429 includes `Retry-After: 60`; limits reset after process restart.

Email matching is case insensitive and ignores surrounding whitespace; password matching is exact. The API derives a salted scrypt verifier at startup (`N=32768`, `r=8`, `p=3`) and performs asynchronous verification. Root credentials are not stored in PostgreSQL. The token contains 32 random bytes under the `vlk_` prefix and is stored only as a SHA-256 hash. Its chosen expiry is stored as an absolute UTC timestamp; Never is stored as null. Browser sessions still expire after 720 hours, management sessions after 30 minutes, and OAuth connection tokens after at most 720 hours.

A successful 201 response contains `api_key`, `id`, `name`, `access`, `includeAdmin`, `token_hint`, `created_at`, `expires_at`, `revoked_at` and `status`. Timestamps are UTC ISO strings. The `api_key` field occurs only in this creation response. Metadata status is `active`, `expired` or `revoked`; revocation takes precedence over expiration. Migration `0015_personal_api_key_policy` adds the manual-key name and explicit permissions. Existing manual keys receive a stable name and Edit access without administration, preserving their health read/write ceiling. Token hashes, IDs, expiry, revocation, ledger data and OAuth/session bindings are preserved. Only manual keys permit null expiry.

The generation endpoint accepts browser submissions from the exact configured UI origin, or loopback HTTP during development. Other origins and form-encoded requests are rejected. If UI\_BASE\_URL is absent, direct same-origin API clients retain their previous behavior. Do not put root credentials in URLs or HTTP Basic authentication. Keep reverse-proxy request/response bodies and Authorization headers out of logging and tracing.

## Use and management

In the signed-in UI, open Settings → API Keys at `/settings/api-keys`. The existing dashboard session lists keys immediately. Verify the root email and password to unlock a 30-minute credential-management session when creating or revoking keys. Create a key with the required name, permissions and expiry, revoke one key or revoke all manually generated keys. Settings → MCP Connections at `/settings/mcp-connections` separately lists OAuth clients, approved permissions and expiry; revoke a connection there to disconnect that client. Revoked rows are filtered before pagination. The browser's read-only health session stays signed in when keys are revoked here. Settings → MCP Guide provides OAuth configurations for Codex, Claude Code, Cursor, VS Code and other MCP clients.

The UI stores the opaque `vlm_` management session in a separate host-only HttpOnly SameSite=Lax cookie. It carries only `keys:manage` for `urn:vitalog:key-management`; it cannot read or write health records, access MCP or use primary-key administration. The server proxies the narrow `/auth/key-management/*` API, checking the dashboard session and exact Origin on mutations. Generated keys and ordinary browser sessions cannot unlock creation or revocation. A browser session can list metadata through `GET /auth/key-management/api-keys`. Logout revokes both browser sessions. Migration `0010_brainy_fat_cobra` fixes management-key expiry at 30 minutes and permits the new scope only on this dedicated resource; OAuth health scopes remain unchanged.

`POST /auth/key-management/session` accepts only the strict JSON `email` and `password` body and shares the sign-in rate limit. `GET` checks the authenticated management session and `DELETE` revokes it. The session authorizes `GET`, `POST` and `DELETE /auth/key-management/api-keys`, plus `DELETE /auth/key-management/api-keys/{id}`. Creation requires a JSON body containing `name`, `access`, `includeAdmin` and `expiresAt`, without root credentials; it accepts the same optional UUID retry header. Optional `kind=api-key` or `kind=mcp` filters management listing before pagination and scopes collection revocation. Revoking manual keys leaves pending OAuth codes and MCP connections intact. Omitting `kind` retains combined administration. Management listing and revocation exclude browser and management sessions. Migration `0014_mcp_connection_metadata` records OAuth client IDs, names and approved scopes without changing existing token hashes or expiry. The primary endpoints below retain their full scope, including those session records.

Supply `Authorization: Bearer <generated key>` privately on every REST or MCP request, including initialization and discovery. Read-only keys read health data through REST and MCP; Edit keys can also write it. Administrative permissions include health read/write and inspection of `/readyz` and `/openapi.json`. Account settings, credential creation/reveal and key management remain protected by browser/root authority. Every request checks the stored ceiling. MCP discovery exposes only permitted tools and tool calls enforce the same ceiling, including when no OAuth issuer is configured. OAuth scope grants are intersected with a manual parent key ceiling where applicable. Expired, revoked, unknown and malformed keys return 401. Generated keys attempting administration return 403. A revoked key is rejected on the next request, even from an already connected MCP client; previously authenticated requests may complete.

Use the primary environment `AUTH_KEY` for these administration APIs:

| Method and path | Result |
| - | - |
| `GET /v1/api-keys?limit=50&offset=0` | `api_keys`, `total`, `limit`, `offset`; metadata only |
| `DELETE /v1/api-keys/{id}` | The key's metadata with `status=revoked` |
| `DELETE /v1/api-keys` | `revoked_count` for all previously unrevoked generated keys |

Listing defaults to 50 rows and accepts a limit from 1 to 100 and offset from 0 to 1000000. It orders newest creation first, breaking ties by ID. Duplicate or unknown query parameters are rejected. Revocation accepts no request body or query. A missing key ID returns 404; repeating revocation preserves the original revocation timestamp. Revoke-all includes expired keys, is safe to repeat, and never changes `AUTH_KEY`. Keys created after revoke-all can be used normally. Metadata is retained for expired and revoked keys.

All state survives process restarts and primary-key rotation. No authentication cache delays revocation. Root credential changes do not implicitly revoke keys. Database backups include token hashes and revocation metadata; restoring an older backup can re-enable a key revoked after that backup. Use the primary key to revoke restored generated keys before exposing a restored instance.

These are bearer API keys. MCP clients connect through the [OAuth flow](/oauth) by signing in with the configured root email and password. Code exchange creates the 30-day MCP token and an API-key management record with a `vlo_…` hint. It appears on the MCP Connections page; revoking it disconnects that client on the next request. Unfiltered administration or MCP-scoped revoke-all also cancels pending authorization codes. Authentication attempts and password-verification concurrency are shared across manual key creation and OAuth sign-in.

## Verification

Run `npm run test:web` for the sidebar, Settings and credential-management boundaries, `npm run test:auth` for database/API verification and `npm run test:container` for the independent production UI. The auth check creates and removes a disposable PostgreSQL database. The auth check verifies the absence of UI assets in the API, a populated-database forward migration, one-time issuance and hash-only storage, both transports, administration boundaries, exact expiry, revocation, restart/rotation durability, login limits, origin protection, configuration fallback and log privacy. Reports are written to ignored `.test-artifacts/auth.json` and uploaded by CI. No production database is used.


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