API Keys

Dashboard: /overview/api-keys. Internal API: /api/internal/api-keys.

Endpoints (internal)

Method Purpose
GET List non-revoked keys for session's org
POST Create key — returns full key once
DELETE ?id=key_... Soft revoke via revoked_at

Create

POST /api/internal/api-keys
{
  "name": "Server",
  "environment": "live",
  "expiresInDays": 90,
  "rateLimitPerMinute": 1200
}
Field Notes
name required, user-visible label
environment live or test
expiresInDays optional, 1–3650. If set, expiresAt is stored; validation rejects expired keys.
rateLimitPerMinute optional, 1–100000. Per-key sliding-window limit checked before the org/plan bucket. Omit to inherit the plan default.
allowedCidrs optional array of up to 20 CIDR ranges (IPv4 or IPv6). Requests from outside these ranges fail with 403 IP_NOT_ALLOWED.

Response (201):

{
  "id": "key_...",
  "key": "sok_live_...",
  "name": "Server",
  "environment": "live",
  "rate_limit_per_minute": 1200,
  "expires_at": "2026-07-20T00:00:00.000Z"
}

IP allowlist

When allowedCidrs is set, withApiAuth compares the request's client IP (from X-Forwarded-For or X-Real-IP) against each CIDR. Any match allows the request through; no match returns:

HTTP 403
{ "error": { "type": "authentication_error", "code": "IP_NOT_ALLOWED", "message": "Request IP is not in this key's allowlist" } }

IPv4 and IPv6 are both supported. Bare IPs are treated as /32 (IPv4) or /128 (IPv6). 0.0.0.0/0 effectively disables the check.

Audit: api_key.created logged.

Storage model

  • Full key displayed once to user, never persisted.
  • key_hash = sha256(full key) — lookup index.
  • last_four = last 4 chars of the nanoid portion — shown in UI.
  • key_prefix = sok_live_ or sok_test_.
  • expires_at enforced in validateApiKey() via OR (expires_at IS NULL, expires_at > now()).

Revoke

DELETE /api/internal/api-keys?id=key_... — sets revoked_at = now(). Revoked keys fail the isNull(revoked_at) filter and return 401 on use. Audit: api_key.revoked.

Test vs live

See ../api/authentication.md. Test keys bypass AWS calls and usage limits.