Hanzo KMS
The sealed secret store every Hanzo service reads its credentials from
Hanzo KMS
API reference · Hanzo KMS API → — every endpoint, generated from the OpenAPI spec.
Hanzo KMS is the credential broker for the platform. It holds secrets sealed at rest, hands them to workloads that present a machine identity, and syncs them into Kubernetes through the KMSSecret CRD. That is the whole job — it is a secret store with a login, not a certificate authority, a rotation engine, or a scanner.
| App | https://kms.hanzo.ai — the console you log into |
| API | https://api.hanzo.ai/v1/kms — one API host for every Hanzo service |
| Auth | Machine identity (clientId/clientSecret) → bearer JWT minted by Hanzo IAM |
| K8s sync | KMSSecret CRD, reconciled by the kms-operator |
| Implementation | The luxfi/kms sealed store, embedded in Hanzo Cloud |
The whole surface
Seven operations. There is no eighth.
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET | /v1/kms/health | public | Readiness — 200 only if the store is open and a master key is configured |
GET | /v1/kms/config | public | Console runtime config (brand, OIDC issuer, API base, login path) |
POST | /v1/kms/auth/login | public | Exchange a machine credential for a bearer token |
GET | /v1/kms/secrets | bearer | List your org's secret metadata — never values |
GET | /v1/kms/secrets/{path}/{name} | bearer | Read one secret's value |
POST | /v1/kms/secrets | bearer | Create or replace one secret |
DELETE | /v1/kms/secrets/{path}/{name} | bearer | Delete one secret |
Your org is never in the URL. It is read from the validated token's owner claim and folded into the storage path server-side. There is no tenant path segment and no admin override, so over HTTP another org's namespace is not merely refused — it is unaddressable.
Addressing a secret
Three coordinates travel in the request; the fourth comes from your token.
org ← the token's owner claim (never in the URL)
path /providers a folder beneath your org root
name OPENAI_API_KEY the exact key
env ?env=prod the environmentenv is part of the storage key, not a filter. A value written under env=prod and one written under env=default are two different records.
| Operation | env behaviour |
|---|---|
POST (write) | Required. There is no default — a silently defaulted write lands in a bucket your readers never resolve, so it fails loudly instead |
GET / DELETE one secret | Optional; falls back to default |
GET list | Optional; omitted means every environment |
The list route also accepts the operator's spellings — environment for env, secretPath for path — so one endpoint serves both callers.
Authenticate
A machine identity is a client-credentials application in Hanzo IAM. KMS brokers its clientId/clientSecret to IAM and returns the bearer you carry on every secret call.
ACCESS_TOKEN=$(curl -sS -X POST https://api.hanzo.ai/v1/kms/auth/login \
-H 'Content-Type: application/json' \
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}" \
| jq -r .accessToken){ "accessToken": "<JWT>", "expiresIn": 3600, "tokenType": "Bearer" }This is the one public, unauthenticated route, so it is the one route rate-limited per source IP — 60 requests per minute. Failures collapse to a single status with no upstream detail (400 missing field, 401 bad credential, 502 IAM unreachable, 503 no issuer configured); a richer error would be a validity oracle for guessed credentials.
Read, write, list, delete
# Read one value
curl -sS "https://api.hanzo.ai/v1/kms/secrets/providers/OPENAI_API_KEY?env=prod" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# → { "name": "OPENAI_API_KEY", "env": "prod", "value": "<secret value>" }# Write one value — env is required
curl -sS -X POST https://api.hanzo.ai/v1/kms/secrets \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"path":"/providers","name":"OPENAI_API_KEY","env":"prod","value":"<new value>"}'
# → { "stored": true, "name": "OPENAI_API_KEY", "env": "prod" }# List metadata — no values come back
curl -sS "https://api.hanzo.ai/v1/kms/secrets?path=/providers&env=prod" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# → { "secrets": [ … ], "total": 2, "names": ["OPENAI_API_KEY","ANTHROPIC_API_KEY"] }# Delete one
curl -sS -X DELETE "https://api.hanzo.ai/v1/kms/secrets/providers/OPENAI_API_KEY?env=prod" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# → { "deleted": true, "name": "OPENAI_API_KEY", "env": "prod" }A secret at the org root omits the folder segment: …/secrets/STRIPE_SECRET_KEY?env=prod.
Listing gives you names, not values. GET /v1/kms/secrets returns each secret's name, path, environment and sealing scheme — no value, no ciphertext. Reading a value is a separate per-secret call, and that response body is the only place the value ever appears: it is not logged and never carried in an error.
What it does not do
Saying so is cheaper than a support thread.
- No
PATCH, no version field, no optimistic concurrency. A write is an upsert; last writer wins. - No rotation engine, no PKI or SSH certificate authority, no secret scanning, no dynamic secrets, no third-party secret syncs. None of these are served under
/v1/kms. - No threshold-signing routes on this surface.
GET /v1/kms/healthreports asigningfield saying whether signing keys are configured for the embedded store; distributed key generation and threshold signatures live in Hanzo MPC, not here. - No cross-org read. Cross-org access exists only in-process, inside the cloud binary that holds the master key — never over HTTP.
Failure modes
GET /v1/kms/health answers 200 with {"status":"ok","ready":true,"signing":…,"service":"kms"} only when the store is open and a master key is present. Anything less is 503 with ready:false and the reason — exactly the two states in which the secret routes refuse.
Admission on the secret routes is fail-closed, in this order, all decided before a record is touched:
| Status | Meaning |
|---|---|
403 | No validated member on the request |
400 | Malformed org, env, path, or name |
503 | Store open but no master key configured |
404 | Secret not there — a plain 404 that names nothing about the store |
Next steps
Log in and read your first secret
How a workload authenticates and refreshes its token
Sync secrets into Kubernetes
The pattern every Hanzo service uses
How is this guide?