Hanzo
PlatformHanzo KMSGetting Started

Quickstart

Log in with a machine identity, write a secret, read it back

KMS quickstart

Four calls end to end. Everything here runs against https://api.hanzo.ai, the one host for every Hanzo service; https://kms.hanzo.ai serves the identical routes.

Get a machine identity

A machine identity is a client-credentials application in Hanzo IAM — a clientId and clientSecret that belong to your org, not to a person. Register one there, then keep the pair out of source control: in a cluster it lives in a Kubernetes Secret, on a laptop in your shell environment.

export KMS_CLIENT_ID=
export KMS_CLIENT_SECRET=

Exchange it for a token

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" }

The token's owner claim is your org. That claim — not anything in the URL — decides whose secrets the following calls reach.

A 401 here means the credential did not authenticate; 502 means IAM was unreachable; 503 means this deployment has no issuer configured. The response deliberately carries no upstream detail, so do not read a 401 as "this clientId exists but the secret is wrong."

Write a secret

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":"sk-…"}'
{ "stored": true, "name": "OPENAI_API_KEY", "env": "prod" }

env is required on a write and has no default. It is part of the storage key, so a defaulted write would land in env=default while every reader resolving env=prod keeps serving the stale value. The write refuses rather than split the record.

The value is sealed before it is written — a fresh per-secret data key, itself wrapped by the master key — so plaintext never reaches disk. The receipt confirms what was written and does not echo the value.

Read it back

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": "sk-…" }

A secret at your org root omits the folder: …/secrets/STRIPE_SECRET_KEY?env=prod.

See what you hold

curl -sS "https://api.hanzo.ai/v1/kms/secrets?path=/providers&env=prod" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
{
  "secrets": [ { "name": "OPENAI_API_KEY", "path": "/providers", "env": "prod" } ],
  "total": 1,
  "names": ["OPENAI_API_KEY"]
}

This is an enumeration, not a bulk read — names, paths, environments and sealing schemes come back, values never do. Omit env to see every environment; omit path to see the whole org.

Delete

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" }

Deleting a secret that is not there is a 404, not a silent success, so you can tell a real deletion from a typo.

Check the service

curl -sS https://api.hanzo.ai/v1/kms/health
{ "status": "ok", "ready": true, "signing": false, "service": "kms" }

200 only when the store is open and a master key is configured. A 503 with ready:false names which of the two is missing — and those are exactly the states in which the secret calls above will refuse.

Next steps

All seven routes, addressing rules, failure modes

Token lifetime, scoping, and rotation

Stop calling the API — declare the sync instead

How is this guide?

On this page