Files
python-sdk/pkg/hanzo-kms/README.md
T
zeekayandHanzo Dev bf49810b8f kms.lux.cloud is the brand host; kms.lux.network is retired
The .network host was retired 2026-07-27 and no longer resolves at all (a
request to it now returns nothing, not an error), so every reference to it named
an address that cannot answer. The brand host is .cloud, matching kms.zoo.cloud
and the rest of the white-label convention.

Left alone deliberately, because rewriting them would invert what they say:
the LLM.md line that RECORDS the retirement, the e2e spec that ASSERTS
kms.lux.network must not resolve, and the recorded applies + cluster backups,
which are faithful accounts of what was applied and are not config.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 08:12:54 -07:00

4.2 KiB

Hanzo KMS - Python SDK

Official Python SDK for Hanzo KMS — secret management for your applications.

The server is luxfi/kms; this SDK speaks its canonical /v1/kms surface and nothing else.

Installation

pip install hanzo-kms

Or with uv:

uv add hanzo-kms

Quick Start

A secret is identified by (org, path, name, env). The org scopes both the URL and the JWT; path, name and env identify the value.

from hanzo_kms import ClientSettings, KMSClient

client = KMSClient(ClientSettings(
    org="lux",
    client_id="your-client-id",
    client_secret="your-client-secret",
))

# List the secret names at a path
names = client.list_secrets("providers/lux", env="prod")

# Read one value
mnemonic = client.get_secret("providers/lux", "deploy-mnemonic", env="prod")

# Create or replace — one upsert; KMS holds one value per (path, name, env)
client.put_secret("providers/lux", "deploy-mnemonic", mnemonic, env="prod")

# Delete
client.delete_secret("providers/lux", "deploy-mnemonic", env="prod")

# Load a whole path into os.environ, keyed by secret name
client.inject_env("providers/lux", env="prod")

env defaults to "default".

Async

AsyncKMSClient is the mirror image of KMSClient — same methods, same arguments.

from hanzo_kms import AsyncKMSClient

async with AsyncKMSClient() as client:
    names = await client.list_secrets("providers/lux", env="prod")

Environment Variables

export HANZO_KMS_URL="https://kms.hanzo.ai"   # default
export HANZO_KMS_ORG="hanzo"                  # default
export HANZO_KMS_CLIENT_ID="your-client-id"
export HANZO_KMS_CLIENT_SECRET="your-client-secret"
# ...or a pre-issued IAM bearer token instead of client credentials:
export HANZO_KMS_TOKEN="eyJ..."

Then:

from hanzo_kms import KMSClient

client = KMSClient()  # configures itself from the environment

Authentication

Two ways, because the server has one login route:

Setting Behavior
client_id + client_secret Exchanged at POST /v1/kms/auth/login for a bearer token
access_token A pre-issued IAM bearer token, used as-is

The AWS / Azure / GCP / Kubernetes / SRP methods this SDK used to advertise were Infisical's. luxfi/kms has never served them.

CLI

hanzo-kms list --path providers/lux --env prod
hanzo-kms get providers/lux deploy-mnemonic --env prod
hanzo-kms set providers/lux deploy-mnemonic "word word ..." --env prod
hanzo-kms delete providers/lux deploy-mnemonic --env prod
hanzo-kms export --path providers/lux --env prod --format json

API Reference

KMSClient

Method Description
list_secrets(path="", env="default") Secret names at a path
get_secret(path, name, env="default") The secret's value
put_secret(path, name, value, env="default") Create or replace
delete_secret(path, name, env="default") Delete
inject_env(path="", env="default", overwrite=False) Load a path into os.environ
health() {"service": "kms", "status": "ok"}

Server surface

POST   /v1/kms/auth/login                            {clientId, clientSecret} -> {accessToken, expiresIn}
GET    /v1/kms/orgs/{org}/secrets?path=&env=         -> {"names": [...]}
GET    /v1/kms/orgs/{org}/secrets/{path}/{name}?env= -> {"secret": {"value": "..."}}
POST   /v1/kms/orgs/{org}/secrets                    {path, name, env, value}
DELETE /v1/kms/orgs/{org}/secrets/{path}/{name}?env=
GET    /healthz | /v1/kms/healthz                    -> {"service": "kms", "status": "ok"}

Two things follow from it:

  • The server splits the trailing path at its LAST slash into (path, name). A name may not contain / — this SDK rejects one rather than let a write land under a key the matching read can never find.
  • There is no versioned read. KMS holds exactly one value per (path, name, env), so get_secret(..., version=N) raises VersionUnsupportedError instead of quietly handing back the current value.

Compatibility

License

MIT License - see LICENSE for details.