Skip to main content

API Overview

The API is plain REST over JSON, authenticated with the krypton_session cookie (an HS256 session JWT minted at OIDC login). Values cross the API boundary as plaintext data for text values and base64 binaryData for non-text values in authorized detail/history responses. Summary binaryKeys contains names only. Create/update data remains plaintext; metadata-only PUTs omit it. See the data contract before round-tripping responses.

Conventions​

  • Auth: Secret/namespace endpoints require a session; configuration and login/callback/logout are public entry points. Protected unauthenticated calls get 401 { "error": "Authentication required" }. (The one exception is GET /api/auth/me, which answers 401 { "user": null }.)
  • Errors: { "error": "<message>" } with the HTTP status. Zod validation failures answer 400 { "error": "Validation failed", "issues": [...] }, with at most 20 issues containing known top-level paths and fixed code/message classifications. Submitted values, arbitrary nested keys, enum values, upstream messages, and raw headers are omitted. Unexpected failures use fixed guidance.
  • Caching: API JSON successes/errors and auth redirects explicitly send Cache-Control: no-store, including authenticated metadata lists and failures.
  • Gates: secrets routes enforce namespace scope. Named-secret routes then enforce live-object visibility (404 for snapshots in both modes, and for unmanaged secrets in managed-only mode) → hybrid authorization (403). See Authorization.
  • Policy unavailable: decisions that need namespace fallback return 503 { "error": "Namespace access policy is unavailable" } on malformed policy or failed ConfigMap reads. This includes non-admin creation and list hints. Admins and explicit live ACLs keep precedence; only a confirmed ConfigMap 404 or valid empty rules mean intentionally open policy.
  • History: snapshots are immutable and UID/source-resourceVersion bound. Unbound legacy/predecessor history is quarantined. Snapshot identity/content conflicts return 409; snapshot creation/list/pruning failures use controlled 503 errors. Cleanup may be partial; see history semantics.
  • Restore/delete revisions: both require the caller's expected live resourceVersion (restore JSON, DELETE query). Missing input is 400; stale input is 409 before history side effects. DELETE returns a typed status: 200 deleted for confirmed live absence, 202 pending while it still exists. Ordered cleanup is retryable but nontransactional; it does not promise atomic erasure of all copies.
  • Consumer rollouts: acknowledged pod-template patches are rollout requests, not verified readiness or credential uptake. The restart result distinguishes 200 requested/no_targets, 207 partial, and 503 failed, with structured patch and discovery failures. Retain those result bodies; failed inventories must not be treated as empty. Gate/preparation errors keep { error }.
  • Other Kubernetes failures retain numeric statuses with Kubernetes API (<status>): <fixed guidance>. A 403 can mean effective human RBAC or ServiceAccount impersonation permission denial in impersonation mode, or ServiceAccount RBAC in hybrid mode. Kubernetes 401s are distinct from expired app sessions. Raw Kubernetes Status messages/bodies/headers are never forwarded.

Request and collection budgets​

Create, update, and restore accept an inclusive 16 MiB UTF-8 JSON envelope (16,777,216 bytes). Oversized Content-Length is rejected before reading; chunked and understated-length bodies are counted as they arrive and cancelled on overflow (413). Malformed JSON/UTF-8 and invalid length headers are 400 before mutation. The envelope accommodates JSON escaping and overlapping create data/stringData; the separate conservative 1 MiB data validation still applies. Individual text maps use ceil(1.4 × UTF-8 key/value bytes) <= 1 MiB (schema failures are 400); oversized merged create data or native restore bytes return 413. Upstream ingress limits may reject requests earlier.

Submitted ACL arrays allow 256 nonempty strings each, at most 256 UTF-8 bytes per entry. Policy rule arrays use the same count/byte ceilings; invalid policy remains unavailable, never absent. These limits do not truncate session groups.

Kubernetes collections use 100-object pages, at most 100 pages per inventory:

InventoryRaw object limitAggregate JSON bytes
Live Secrets / namespace5,00032 MiB
Raw history / live UID1,000160 MiB
History cleanup or pruning / live UID1,0008 MiB projected metadata
Workloads / kind2,00032 MiB
Unrestricted namespaces5,0008 MiB

Byte accounting measures SDK-decoded JSON pages before filtering/deduplication; metadata-only cleanup measures projections and page metadata without visiting Secret values. Raw object/page counts apply in every mode. The SDK buffers each upstream page first; these are application accumulation/work bounds, not streaming response-size or global concurrency limits. Each multi-inventory operation applies the bounds independently; restart reads three kinds concurrently.

Opaque continuation tokens must be printable ASCII and at most 8 KiB. Bad/repeated tokens, inconsistent list revisions/conflicting duplicates, missing items, or budget exhaustion produce a controlled 503 incomplete-inventory error, never partial success. Exact projected duplicates appear once. Failed/expired pages never trigger an automatic continuation restart. History wraps upstream list failures as 503; restart preserves per-kind failures; other lists preserve safe numeric statuses (including 410). Retry a fresh request after repair, or use operator-reviewed cleanup for an over-budget backlog. See Namespaces for the allowlist's zero-discovery-call contract and Secrets for selectors.

Endpoint map​

MethodPathPage
GET/api/namespacesNamespaces
GET/api/namespaces/:ns/secretsSecrets
POST/api/namespaces/:ns/secretsSecrets
GET/api/namespaces/:ns/secrets/:nameSecrets
PUT/api/namespaces/:ns/secrets/:nameSecrets
DELETE/api/namespaces/:ns/secrets/:nameSecrets
GET/api/namespaces/:ns/secrets/:name/versionsVersions
POST/api/namespaces/:ns/secrets/:name/restoreRestore
POST/api/namespaces/:ns/secrets/:name/restart-consumersRestart
GET/api/auth/login, /api/auth/callback, /api/auth/logout, /api/auth/meAuth
GET/api/configConfig

Scripted lifecycle​

The full flow — list, negative authz case, create, read, concurrent-safe update, versions, restore, restart, delete — against the sandbox:

# Krypton REST lifecycle against the sandbox (make up + app/.env from app.env.sandbox).
# Mint session JWTs directly — must match the app's SESSION_SECRET.
ALICE=$(node sandbox/scripts/mint-jwt.mjs --email alice@company.com --groups dev-team)
BOB=$(node sandbox/scripts/mint-jwt.mjs --email bob@company.com --groups other-team)

BASE=localhost:3000
NS=krypton-test

# 1. List namespaces and secrets (alice sees dev-team + open secrets)
curl --cookie "krypton_session=$ALICE" $BASE/api/namespaces
curl --cookie "krypton_session=$ALICE" $BASE/api/namespaces/$NS/secrets

# 2. Negative case: bob is not in dev-team
curl --cookie "krypton_session=$BOB" \
$BASE/api/namespaces/$NS/secrets/database-credentials # 403

# 3. Create a secret (201; stamps krypton.io/managed-by + version=1)
curl --cookie "krypton_session=$ALICE" -X POST \
-H 'Content-Type: application/json' \
$BASE/api/namespaces/$NS/secrets \
-d '{"name":"demo-secret","data":{"API_KEY":"s3cr3t"},"description":"Demo"}'

# 4. Read it back (detail includes decoded data + resourceVersion)
curl --cookie "krypton_session=$ALICE" \
$BASE/api/namespaces/$NS/secrets/demo-secret

# 5. Update with optimistic concurrency (409 on resourceVersion mismatch)
RV=$(curl -s --cookie "krypton_session=$ALICE" \
$BASE/api/namespaces/$NS/secrets/demo-secret | jq -r .secret.resourceVersion)
curl --cookie "krypton_session=$ALICE" -X PUT \
-H 'Content-Type: application/json' \
-d "{\"data\":{\"API_KEY\":\"r0tated\"},\"resourceVersion\":\"$RV\"}" \
$BASE/api/namespaces/$NS/secrets/demo-secret

# 6. History + reversible restore (snapshots current state first)
curl --cookie "krypton_session=$ALICE" \
$BASE/api/namespaces/$NS/secrets/demo-secret/versions
RV=$(curl -fsS --cookie "krypton_session=$ALICE" \
$BASE/api/namespaces/$NS/secrets/demo-secret | jq -er .secret.resourceVersion)
curl --cookie "krypton_session=$ALICE" -X POST \
-H 'Content-Type: application/json' \
-d "$(jq -cn --arg rv "$RV" '{version:1,resourceVersion:$rv}')" \
$BASE/api/namespaces/$NS/secrets/demo-secret/restore

# 7. Restart env-var consumers (needs KRYPTON_RESTART_ENABLED=true)
curl --cookie "krypton_session=$ALICE" -X POST \
$BASE/api/namespaces/$NS/secrets/demo-secret/restart-consumers

# 8. Delete with the reviewed live revision (add keepHistory=true to retain copies).
# 409 requires reload/review, not automatic retry. 202 means still present, e.g.
# pending finalizers. Cleanup is ordered, not an atomic erase across K8s objects.
RV=$(curl -fsS --cookie "krypton_session=$ALICE" \
$BASE/api/namespaces/$NS/secrets/demo-secret | jq -er .secret.resourceVersion)
curl --cookie "krypton_session=$ALICE" -G -X DELETE \
--data-urlencode "resourceVersion=$RV" \
$BASE/api/namespaces/$NS/secrets/demo-secret

Session cookies for scripts come from sandbox/scripts/mint-jwt.mjs (must match the app's SESSION_SECRET):

ALICE=$(node sandbox/scripts/mint-jwt.mjs --email alice@company.com --groups dev-team)
curl --cookie "krypton_session=$ALICE" localhost:3000/api/namespaces