{"openapi":"3.1.0","info":{"title":"flare-encrypt store API","version":"0.1.0","summary":"The BYOC envelope-encryption secrets store — HTTP API under /v1.","description":"The deployed Worker store: scoped-token secret CRUD over a three-tier AES-256-GCM envelope, an Ed25519/ES256 signing oracle, async KEK/DEK rotation, crypto-shred, and a hash-chained audit log. Hierarchy: project → environment → tenant → secret. All binary values on the wire (`message`, `signature`, `publicKey`) are standard base64 strings. Every error uses the canonical envelope `{ error: { code, message, requestId } }` with a closed `code` set; messages never echo secret values. Authoritative prose: specs/API.md and specs/SIGNING.md.","license":{"name":"MIT","identifier":"MIT"}},"servers":[{"url":"https://flare-encrypt.fractalbox.dev","description":"Reference deployment. flare-encrypt is BYOC — substitute your own store's origin."}],"security":[{"bearerToken":[]}],"tags":[{"name":"secrets","description":"Secret CRUD, list, and the CLI-injector export."},{"name":"signing-keys","description":"Server-side Ed25519/ES256 keys — the sign oracle."},{"name":"rotation","description":"Async KEK/DEK rotation Workflows and their status."},{"name":"tenants","description":"Tenant lifecycle — crypto-shred."},{"name":"audit","description":"The hash-chained audit log."},{"name":"admin","description":"Projects, environments, tokens, and bootstrap."},{"name":"meta","description":"Health and this document."}],"paths":{"/v1/secrets/{project}/{env}/{tenant}/{name}":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."},{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"Secret name (opaque; doubles as the AAD `column`)."}],"put":{"operationId":"putSecret","tags":["secrets"],"summary":"Upsert a secret value","description":"Gets-or-creates the tenant DEK first (atomic), updates the freshness anchor BEFORE writing the row. Optional `version` enables optimistic concurrency (mismatch → 409 `conflict`).","x-capability":"secrets:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretWriteRequest"}}}},"responses":{"200":{"description":"Upserted; returns the new value revision.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretWritten"}}}},"409":{"description":"`conflict` — optimistic-concurrency `version` mismatch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"operationId":"getSecret","tags":["secrets"],"summary":"Read a secret value","description":"Verifies the freshness anchor on read; a rollback-detected mismatch is 409 `stale_ciphertext` (distinct from `conflict`) and raises an audit alarm.","x-capability":"secrets:read","responses":{"200":{"description":"The decrypted value.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretValue"}}}},"409":{"description":"`stale_ciphertext` — freshness-anchor mismatch (rollback detected).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"deleteSecret","tags":["secrets"],"summary":"Soft-delete a secret","description":"Recoverable soft delete — NOT crypto-shred (that is `DELETE /v1/tenants/...`).","x-capability":"secrets:delete","responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/secrets/{project}/{env}/{tenant}":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."}],"get":{"operationId":"listSecrets","tags":["secrets"],"summary":"List secret names and versions (never values)","x-capability":"secrets:list","responses":{"200":{"description":"Names + versions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretList"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/secrets/{project}/{env}/{tenant}:export":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."}],"post":{"operationId":"exportSecrets","tags":["secrets"],"summary":"Export all of a tenant's secrets in one round-trip","description":"`{ NAME: value, ... }` for the CLI injector — a single audited event. A cross-tenant (wildcard) token additionally requires `secrets:export-all` or `admin`.","x-capability":"secrets:read","responses":{"200":{"description":"Name → plaintext value map.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretExport"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/keys/{project}/{env}/{tenant}/{name}":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."},{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"Signing-key name (unique per tenant)."}],"post":{"operationId":"createSigningKey","tags":["signing-keys"],"summary":"Create a named Ed25519 or ES256 signing key","description":"The keypair is generated server-side; the private key is wrapped under the KEK and NEVER returned (wrapped or plaintext). Explicit creation: an existing name is 409 `conflict`, never an upsert.","x-capability":"keys:create","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SigningKeyCreateRequest"}}}},"responses":{"201":{"description":"Created — public key + metadata only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SigningKeyCreated"}}}},"409":{"description":"`conflict` — a signing key with this name already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"operationId":"getSigningKey","tags":["signing-keys"],"summary":"Read public key(s) for verifier distribution","description":"Current version plus non-retired prior versions.","x-capability":"keys:read","responses":{"200":{"description":"Public keys + `{ alg, keyVer, status }`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SigningKeyInfo"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/keys/{project}/{env}/{tenant}/{name}/public":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."},{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"Signing-key name (unique per tenant)."}],"get":{"operationId":"getPublishedSigningKey","tags":["signing-keys"],"summary":"Fetch an explicitly published key without authentication","description":"Returns one uniform 404 for unknown and unpublished keys. Includes current and non-retired versions in standard base64 and bare hex.","security":[],"responses":{"200":{"description":"Published public-key set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishedKeyInfo"}}}},"404":{"description":"Unknown or unpublished key (uniform response).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/keys/{project}/{env}/{tenant}/{name}/public/jwks":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."},{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"Signing-key name (unique per tenant)."}],"get":{"operationId":"getPublishedSigningKeyJwks","tags":["signing-keys"],"summary":"Fetch an explicitly published key as JWKS","description":"Ed25519 keys use OKP/Ed25519; ES256 keys use EC/P-256.","security":[],"responses":{"200":{"description":"Published JWKS.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Jwks"}}}},"404":{"description":"Unknown or unpublished key (uniform response).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/keys/{project}/{env}/{tenant}/{name}:sign":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."},{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"Signing-key name (unique per tenant)."}],"post":{"operationId":"sign","tags":["signing-keys"],"summary":"Sign a message with the active key version","description":"The sign oracle: accepts the FULL message as base64 bytes (never a caller-supplied digest). The private key is rehydrated non-extractable, signs, and never leaves. The audit records SHA-256(message), never the payload.","x-capability":"keys:sign","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignRequest"}}}},"responses":{"200":{"description":"The signature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignResult"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/keys/{project}/{env}/{tenant}/{name}:rotate":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."},{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"Signing-key name (unique per tenant)."}],"post":{"operationId":"rotateSigningKey","tags":["signing-keys"],"summary":"Rotate to a new key version","description":"Mints keyVer+1 (active for signing); the prior version is retained as `retiring` for verification. Synchronous and O(1).","x-capability":"keys:rotate","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SigningKeyRotateRequest"}}}},"responses":{"200":{"description":"The new active version.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SigningKeyCreated"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/rotate/kek/{project}/{env}":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."}],"post":{"operationId":"rotateKek","tags":["rotation"],"summary":"Start async KEK rotation (re-wrap tenant DEKs)","description":"Async Workflow: re-wraps tenant DEKs under the newest KEK version in the map. Add the new version to the KEK map first. Crypto-KEY rotation, not secret-VALUE rotation.","x-capability":"rotate","responses":{"202":{"description":"Rotation started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/rotate/dek/{project}/{env}/{tenant}":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."}],"post":{"operationId":"rotateDek","tags":["rotation"],"summary":"Start async DEK rotation (new DEK, re-encrypt tenant secrets)","x-capability":"rotate","responses":{"202":{"description":"Rotation started.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/rotate/status/{jobId}":{"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string"},"description":"Rotation job id (ULID) returned by a 202 rotate response."}],"get":{"operationId":"rotateStatus","tags":["rotation"],"summary":"Rotation progress + remaining-on-old-version count","description":"Completion gate: pruning a KEK-map entry or dropping an old DEK is forbidden until `remaining` is 0.","x-capability":"rotate","responses":{"200":{"description":"Job status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateStatus"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/rotate/policy":{"put":{"operationId":"upsertRotationPolicy","tags":["rotation"],"summary":"Declare or replace a rotation policy","description":"Application-declared scheduled rotation (ROTATION_SCHEDULER.md §4). Body: `{scope:{project,env,tenant?,name?}, kind, everySeconds, graceSeconds?}`. P1 executes the `signing` kind; cadence is floored per kind.","x-capability":"policies:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scope","kind","everySeconds"],"properties":{"scope":{"type":"object","required":["project","env"],"properties":{"project":{"type":"string"},"env":{"type":"string"},"tenant":{"type":"string"},"name":{"type":"string"}}},"kind":{"type":"string","enum":["signing","dek","kek","value"]},"everySeconds":{"type":"integer"},"graceSeconds":{"type":"integer"}}}}}},"responses":{"201":{"description":"Policy stored.","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/rotate/policy/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Rotation-policy id (ULID)."}],"get":{"operationId":"getRotationPolicy","tags":["rotation"],"summary":"Inspect a rotation policy (status surface)","x-capability":"policies:read","responses":{"200":{"description":"The policy.","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"operationId":"deleteRotationPolicy","tags":["rotation"],"summary":"Remove a rotation policy","x-capability":"policies:write","responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/rotate/policy/{id}:runNow":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Rotation-policy id (ULID)."}],"post":{"operationId":"runRotationPolicyNow","tags":["rotation"],"summary":"Manual kick — run a policy now","description":"Applies the per-kind floor and re-authorizes against the policy's resolved scope (§6.5). Returns 202 with the run id.","x-capability":"rotate","responses":{"202":{"description":"Run accepted.","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/rotate/policy/{id}/history":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Rotation-policy id (ULID)."}],"get":{"operationId":"rotationPolicyHistory","tags":["rotation"],"summary":"Run history for a policy","x-capability":"policies:read","responses":{"200":{"description":"Recent runs.","content":{"application/json":{"schema":{"type":"object"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/tenants/{project}/{env}/{tenant}":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"path","required":true,"schema":{"type":"string"},"description":"Environment slug."},{"name":"tenant","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant slug (single-owner use: the implicit tenant `_root`)."}],"delete":{"operationId":"shredTenant","tags":["tenants"],"summary":"Crypto-shred a tenant","description":"Drops the tenant's wrapped DEKs — ciphertext becomes undecryptable via the normal path in O(1). Erasure is NOT instantaneous: `erasureEta` reports the honest completion timestamp (Time-Travel window, KEK retention, cache TTL).","x-capability":"admin","responses":{"200":{"description":"Shredded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShredResult"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/audit/{project}":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."},{"name":"env","in":"query","required":false,"schema":{"type":"string"},"description":"Narrow to one environment slug (must be within the token's scope)."},{"name":"tenant","in":"query","required":false,"schema":{"type":"string"},"description":"Narrow to one tenant slug (requires `env`)."},{"name":"since","in":"query","required":false,"schema":{"type":"integer"},"description":"Only entries with `ts >=` this Unix epoch (ms)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"default":100},"description":"Max entries (capped at 1000)."}],"get":{"operationId":"readAudit","tags":["audit"],"summary":"Read audit entries + hash-chain verification status","x-capability":"audit:read","responses":{"200":{"description":"Entries (newest first) + chain verification.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditPage"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/projects":{"post":{"operationId":"createProject","tags":["admin"],"summary":"Create a project","x-capability":"admin","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectCreateRequest"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlugCreated"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/projects/{project}/environments":{"parameters":[{"name":"project","in":"path","required":true,"schema":{"type":"string"},"description":"Project slug."}],"post":{"operationId":"createEnvironment","tags":["admin"],"summary":"Create an environment under a project","x-capability":"admin","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvironmentCreateRequest"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlugCreated"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/tokens":{"post":{"operationId":"createToken","tags":["admin"],"summary":"Mint a scoped token (value returned ONCE)","description":"Scope columns narrow project → env → tenant → key; NULL (omitted) = wildcard within the parent. A wildcard-tenant token does NOT get ambient cross-tenant reads — that requires `secrets:export-all` or `admin`. A `key`-pinned token is sign-only for that key. `admin` mints unrestricted; a `tokens:mint` minter is ATTENUATED — the minted token must be a subset of the minter's own authority (scope chain equal-or-narrower, capabilities ⊆ the minter's, never `admin` or `tokens:mint` itself, expiry no later than the minter's own), else 403. Note: a wildcard-tenant minter holding read/sign caps can fan out per-tenant tokens it cannot use itself — deliberately `secrets:export-all`-equivalent authority, but per-tenant, audited, and revocable per mint.","x-capability":"tokens:mint","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenMintRequest"}}}},"responses":{"201":{"description":"Minted. The full token value appears only in this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintedToken"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/tokens/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"API-token id (ULID)."}],"delete":{"operationId":"revokeToken","tags":["admin"],"summary":"Revoke a token (cascades to its delegated mints)","description":"Revoking a token also revokes every live token it minted via `tokens:mint` (delegated authority is derived and dies with its minter). Admin-minted tokens are independent grants and are never cascade-revoked.","x-capability":"admin","responses":{"200":{"description":"Revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Revoked"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/admin/bootstrap":{"post":{"operationId":"bootstrap","tags":["admin"],"summary":"One-shot first-admin mint","description":"No bearer token. Gated on \"zero admin tokens exist\" (irreversible once the first admin is minted) AND the deploy-time `BOOTSTRAP_TOKEN` second factor, presented in the body or the `x-bootstrap-token` header.","security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BootstrapRequest"}}}},"responses":{"201":{"description":"The first admin token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintedToken"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/admin/kek":{"get":{"operationId":"kekIntrospect","tags":["admin"],"summary":"KEK material fingerprints held by the serving isolate","description":"Reports every KEK version in the map THIS isolate resolves against, as 8-hex SHA-256 fingerprints of each version's raw bytes — never the bytes. After a KEK secret swap, warm isolates keep the old map for an unbounded propagation window; poll until every response reports the expected fingerprint before trusting bulk writes (DEPLOYMENT.md §4.4).","x-capability":"admin","responses":{"200":{"description":"The serving isolate's KEK map, fingerprinted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KekIntrospection"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/health":{"get":{"operationId":"health","tags":["meta"],"summary":"Liveness check","security":[],"responses":{"200":{"description":"The store is up.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/openapi.json":{"get":{"operationId":"openapi","tags":["meta"],"summary":"This document","security":[],"responses":{"200":{"description":"The OpenAPI 3.1 document for this store."},"default":{"description":"Error envelope. `code` is the closed set; out-of-scope names return 403 (indistinguishable from denied — no existence oracle), in-scope-but-absent names 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"components":{"securitySchemes":{"bearerToken":{"type":"http","scheme":"bearer","bearerFormat":"fe_<prefix>_<secret>","description":"Scoped API token minted by `POST /v1/tokens` (or `/admin/bootstrap`). The secret is ≥256-bit random, base64url after the display prefix; the store keeps only its hash. Each operation's required capability is its `x-capability`; `admin` satisfies any."}},"schemas":{"ErrorEnvelope":{"type":"object","description":"Canonical error envelope. Messages never echo secret values; GCM tag failures stay unlocalized (never wrong-key vs wrong-AAD vs corruption): data decrypt → 500 `internal`, key unwrap → 500 `crypto_unwrap_failed`, and a recorded-vs-mapped KEK material fingerprint difference → 500 `kek_material_mismatch`.","required":["error"],"properties":{"error":{"type":"object","required":["code","message","requestId"],"properties":{"code":{"type":"string","enum":["malformed_request","unauthenticated","forbidden","not_found","conflict","stale_ciphertext","payload_too_large","context_mismatch","rate_limited","crypto_unwrap_failed","kek_material_mismatch","internal"]},"message":{"type":"string"},"requestId":{"type":"string","description":"ULID for correlating with the audit log."}}}}},"SecretWriteRequest":{"type":"object","required":["value"],"properties":{"value":{"type":"string","description":"The plaintext secret value (UTF-8)."},"version":{"type":"integer","description":"Optional optimistic-concurrency guard: the write fails 409 `conflict` unless the stored revision still equals this."}}},"SecretWritten":{"type":"object","required":["name","version"],"properties":{"name":{"type":"string"},"version":{"type":"integer","description":"The new value revision."}}},"SecretValue":{"type":"object","required":["name","value","version"],"properties":{"name":{"type":"string"},"value":{"type":"string"},"version":{"type":"integer"}}},"SecretList":{"type":"object","required":["names"],"properties":{"names":{"type":"array","items":{"type":"object","required":["name","version"],"properties":{"name":{"type":"string"},"version":{"type":"integer"}}}}}},"SecretExport":{"type":"object","description":"Secret name → plaintext value.","additionalProperties":{"type":"string"}},"Deleted":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean"}}},"SigningKeyCreateRequest":{"type":"object","properties":{"alg":{"type":"string","enum":["Ed25519","ES256"],"default":"Ed25519"},"published":{"type":"boolean","default":false}}},"SigningKeyRotateRequest":{"type":"object","properties":{"published":{"type":"boolean","description":"Change the unauthenticated-publication opt-in; omitted means unchanged."}}},"SigningKeyCreated":{"type":"object","required":["name","keyId","keyVer","alg","jose","publicKey","status","published"],"properties":{"name":{"type":"string"},"keyId":{"type":"string","description":"Immutable key id (ULID)."},"keyVer":{"type":"integer"},"alg":{"type":"string","enum":["Ed25519","ES256"]},"jose":{"type":"string","enum":["EdDSA","ES256"]},"publicKey":{"type":"string","contentEncoding":"base64","description":"Raw 32-byte Ed25519 or 65-byte SEC1 P-256 public key."},"status":{"type":"string","enum":["active","retiring","retired"]},"published":{"type":"boolean"}}},"PublicKeyVersion":{"type":"object","required":["keyVer","publicKey","status","createdAt"],"properties":{"keyVer":{"type":"integer"},"publicKey":{"type":"string","contentEncoding":"base64","description":"Raw public key, standard base64."},"status":{"type":"string","enum":["active","retiring","retired"]},"createdAt":{"type":"integer","description":"Unix epoch (ms)."}}},"SigningKeyInfo":{"type":"object","required":["name","keyId","alg","jose","keyVer","publicKey","status","published","versions"],"properties":{"name":{"type":"string"},"keyId":{"type":"string"},"alg":{"type":"string","enum":["Ed25519","ES256"]},"jose":{"type":"string","enum":["EdDSA","ES256"]},"keyVer":{"type":"integer","description":"The active (signing) version."},"publicKey":{"type":"string","contentEncoding":"base64","description":"The active version's raw public key, standard base64."},"status":{"type":"string"},"published":{"type":"boolean"},"versions":{"type":"array","items":{"$ref":"#/components/schemas/PublicKeyVersion"}}}},"PublishedKeyVersion":{"allOf":[{"$ref":"#/components/schemas/PublicKeyVersion"},{"type":"object","required":["publicKeyHex"],"properties":{"publicKeyHex":{"type":"string","pattern":"^[0-9a-f]+$"}}}]},"PublishedKeyInfo":{"type":"object","required":["name","keyId","alg","jose","keyVer","publicKey","publicKeyHex","status","versions"],"properties":{"name":{"type":"string"},"keyId":{"type":"string"},"alg":{"type":"string","enum":["Ed25519","ES256"]},"jose":{"type":"string","enum":["EdDSA","ES256"]},"keyVer":{"type":"integer"},"publicKey":{"type":"string","contentEncoding":"base64","description":"Active raw public key."},"publicKeyHex":{"type":"string","pattern":"^[0-9a-f]+$"},"status":{"type":"string"},"versions":{"type":"array","items":{"$ref":"#/components/schemas/PublishedKeyVersion"}}}},"Jwk":{"oneOf":[{"type":"object","required":["kty","crv","x","kid","alg","use"],"properties":{"kty":{"const":"OKP"},"crv":{"const":"Ed25519"},"x":{"type":"string"},"kid":{"type":"string"},"alg":{"const":"EdDSA"},"use":{"const":"sig"}}},{"type":"object","required":["kty","crv","x","y","kid","alg","use"],"properties":{"kty":{"const":"EC"},"crv":{"const":"P-256"},"x":{"type":"string"},"y":{"type":"string"},"kid":{"type":"string"},"alg":{"const":"ES256"},"use":{"const":"sig"}}}]},"Jwks":{"type":"object","required":["keys"],"properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/Jwk"}}}},"SignRequest":{"type":"object","required":["message"],"properties":{"message":{"type":"string","contentEncoding":"base64","description":"The FULL message to sign, standard base64; never a caller-supplied digest."}}},"SignResult":{"type":"object","required":["signature","keyId","keyVer","publicKey","alg","jose"],"properties":{"signature":{"type":"string","contentEncoding":"base64","description":"64-byte Ed25519 or raw r||s ES256 signature."},"keyId":{"type":"string"},"keyVer":{"type":"integer","description":"The version that signed — pin it when verifying."},"publicKey":{"type":"string","contentEncoding":"base64","description":"The signing version's raw public key, standard base64."},"alg":{"type":"string","enum":["Ed25519","ES256"]},"jose":{"type":"string","enum":["EdDSA","ES256"]}}},"JobAccepted":{"type":"object","required":["jobId"],"properties":{"jobId":{"type":"string","description":"Poll `/v1/rotate/status/{jobId}`."}}},"RotateStatus":{"type":"object","required":["jobId","kind","status","oldVer","newVer","remaining"],"properties":{"jobId":{"type":"string"},"kind":{"type":"string","enum":["kek","dek"]},"status":{"type":"string","enum":["pending","running","done","failed"]},"oldVer":{"type":"integer"},"newVer":{"type":"integer"},"remaining":{"type":"integer","description":"DEKs still wrapped under the old KEK version (kind=kek) or rows still on the old DEK version (kind=dek). Old versions MUST NOT be pruned until 0."}}},"ShredResult":{"type":"object","required":["shredded","shreddedAt","erasureEta","pendingKekVersions"],"properties":{"shredded":{"type":"boolean"},"shreddedAt":{"type":"integer","description":"Unix epoch (ms)."},"erasureEta":{"type":"integer","description":"Honest erasure completion (Unix epoch, ms) = max(Time-Travel window, KEK-version retention, DEK-cache TTL) — never \"O(1) done\"."},"pendingKekVersions":{"type":"array","items":{"type":"integer"}}}},"AuditEntry":{"type":"object","required":["seq","id","ts","action","ok"],"properties":{"seq":{"type":"integer","description":"Gap-free hash-chain sequence."},"id":{"type":"string"},"ts":{"type":"integer","description":"Unix epoch (ms)."},"actor_prefix":{"type":["string","null"],"description":"Bearer display prefix."},"action":{"type":"string","description":"Closed vocabulary; see specs/DATA_MODEL.md §3."},"environment_id":{"type":["string","null"]},"tenant_id":{"type":["string","null"]},"target":{"type":["string","null"],"description":"A slug or name — never a secret value."},"meta":{"type":["string","null"],"description":"JSON metadata."},"ok":{"type":"integer","enum":[0,1]}}},"AuditPage":{"type":"object","required":["entries","verification"],"properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/AuditEntry"}},"verification":{"type":"object","required":["valid","firstBrokenSeq","checked"],"properties":{"valid":{"type":"boolean"},"firstBrokenSeq":{"type":["integer","null"],"description":"First broken chain link."},"checked":{"type":"integer","description":"Rows verified."}}}}},"ProjectCreateRequest":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string"},"name":{"type":"string"}}},"EnvironmentCreateRequest":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string"}}},"SlugCreated":{"type":"object","required":["id","slug"],"properties":{"id":{"type":"string","description":"Immutable row id (ULID)."},"slug":{"type":"string"}}},"TokenMintRequest":{"type":"object","required":["name","scopes"],"properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["secrets:read","secrets:write","secrets:list","secrets:delete","secrets:export-all","keys:create","keys:read","keys:sign","keys:rotate","rotate","policies:read","policies:write","audit:read","tokens:mint","admin"]},"description":"Capabilities from the closed set."},"project":{"type":"string","description":"Scope: project slug (omitted = wildcard)."},"env":{"type":"string","description":"Scope: environment slug (requires `project`)."},"tenant":{"type":"string","description":"Scope: tenant slug (requires `env`)."},"key":{"type":"string","description":"Scope: signing-key name (requires `tenant`). Pins the token to that one key — sign-only credentials: a key-pinned mint accepts only `keys:*` capabilities."},"expiresAt":{"type":"integer","description":"Unix epoch (ms)."}}},"MintedToken":{"type":"object","required":["id","prefix","token"],"properties":{"id":{"type":"string"},"prefix":{"type":"string","description":"Display prefix `fe_<hex>` (safe to log)."},"token":{"type":"string","description":"The full bearer value `fe_<prefix>_<secret>` — returned ONCE, never again."}}},"Revoked":{"type":"object","required":["revoked"],"properties":{"revoked":{"type":"boolean"}}},"BootstrapRequest":{"type":"object","properties":{"bootstrapToken":{"type":"string","description":"The deploy-time `BOOTSTRAP_TOKEN` second factor (alternatively the `x-bootstrap-token` header)."}}},"KekIntrospection":{"type":"object","description":"The KEK map held by the isolate that served this request, fingerprinted — never key bytes. A fingerprint is the first 8 hex of SHA-256 over a version's raw 32 bytes; the same digest is recorded per wrapped row (`kek_fp`) at wrap time.","required":["shape","versions","environments"],"properties":{"shape":{"type":"string","enum":["flat","per-environment","mixed","empty","invalid"],"description":"Auto-detected map shape; `invalid` means the KEK value failed to parse as JSON (every resolve then fails `KekVersionUnknown`)."},"versions":{"type":"object","description":"Flat-map entries: kek_kv → fingerprint.","additionalProperties":{"type":"string"}},"environments":{"type":"object","description":"Two-level entries: environment id → (kek_kv → fingerprint).","additionalProperties":{"type":"object","additionalProperties":{"type":"string"}}}}},"Health":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}}}}}