Hanzo

API Keys

The two Hanzo key types, which one may ship in a browser, and how to create, rotate and revoke them.

API Keys

Every request to the Hanzo platform is authenticated with a bearer token. There are two kinds of key, they are not interchangeable, and the difference is what each one resolves to when the door reads it.

The two types

PrefixTypeWhat it is
sk-secretsession-equivalent, belongs on a server
pk-publishableorg-identifying, safe in a browser bundle

sk- belongs on a server and pk- may ship in a browser bundle. A key spelled any other way is refused at the door — never guessed at, never defaulted to the safer-sounding one.

What each one resolves to

A secret key resolves to you. The door looks it up and gets your user row, which makes it session-equivalent: anything you can do in the console, a holder of your sk- can do over HTTP. That is why it belongs on a server and nowhere else.

A publishable key resolves to your organization and stops there — it never yields a principal, so it cannot read anything. It identifies; it does not authenticate. That is the property that makes it safe to ship in a browser bundle, and it is enforced at the door rather than promised: a pk- presented as a bearer credential is refused outright, no matter which endpoint it arrives at. One pk- covers analytics, product insights and error capture, which is why you do not need a separate key or a separate DSN for each.

A publishable key minted alongside a project names that project rather than the organization, so beacons from two sites attribute separately. It is the same spelling and the same rules.

They are two rows, not two halves of one key

A user holds at most one key of each type, and IAM stores them as separate rows. So rotating the key in your browser bundle does not sign your servers out, and rotating your server key does not blank the analytics on your site. They fail independently, which is the point of having two.

Asking for a type that does not exist is an error, not a default. The API will not hand you a secret key because you asked for something it did not recognize — handing an sk- to someone who wanted a browser key is exactly the accident the refusal exists to prevent.

Creating keys

In the console

  1. Go to console.hanzo.ai
  2. Open API Keys
  3. Create a key and choose its type
  4. Copy a secret key immediately — it is shown once and never again

A publishable key is the exception: you can read it back at any time, because it is public by construction and useless to you if you cannot.

Over the API

Keys are one resource, /v1/keys, and the type is a field on it. There is no separate mint endpoint and no separate revoke endpoint — the HTTP method says which you mean.

# Mint (or rotate) a key. The response carries it once.
curl -X POST https://api.hanzo.ai/v1/keys \
  -H "Authorization: Bearer sk-your-key" \
  -H "Content-Type: application/json" \
  -d '{"type":"publishable"}'

# List what you hold. No secret material comes back — a secret key is
# represented by its prefix; a publishable key by its full value.
curl https://api.hanzo.ai/v1/keys -H "Authorization: Bearer sk-your-key"

# Revoke one type without touching the other.
curl -X DELETE "https://api.hanzo.ai/v1/keys?type=publishable" \
  -H "Authorization: Bearer sk-your-key"

Creating is how you rotate: you hold one key per type, so minting the same type again replaces it and the old credential stops working. Omitting type means secret, which is what every caller meant before publishable keys existed.

Two things worth knowing before you automate this. A browser session must echo an anti-CSRF token — GET /v1/csrf, then send it back as X-CSRF-Token — while a bearer caller does not, because a bearer request cannot be forged by a cross-site page. And revocation reaches IAM immediately but the gateway caches key lookups for a few minutes, so a request that beat the cache may still be served.

The full request and response shapes are in the Keys reference, generated from the same source as this page.

Using keys

All Hanzo APIs take the key in the Authorization header with the Bearer scheme:

Authorization: Bearer sk-your-key

Every service answers on one host, api.hanzo.ai, under /v1/<service> — the same key works across all of them:

ServicePathExample
AI/v1/chat/completions, /v1/models, /v1/embeddingsapi.hanzo.ai/v1/chat/completions
Search/v1/searchapi.hanzo.ai/v1/search
Secrets/v1/kmsapi.hanzo.ai/v1/kms/secrets
Deploys/v1/platformapi.hanzo.ai/v1/platform/projects
Analytics/v1/analyticsapi.hanzo.ai/v1/analytics/overview
Observability/v1/o11yapi.hanzo.ai/v1/o11y/status?product=cloud
Tasks/v1/tasksapi.hanzo.ai/v1/tasks/namespaces

SDK configuration

All official SDKs accept the key as a constructor parameter:

# Python
from hanzoai import Hanzo
client = Hanzo(api_key="sk-your-key")
// TypeScript
import Hanzo from '@hanzo/ai'
const client = new Hanzo({ apiKey: 'sk-your-key' })
// Go
import "github.com/hanzoai/go-sdk"
client := hanzo.NewClient(hanzo.WithAPIKey("sk-your-key"))

Each client defaults to https://api.hanzo.ai/v1. A third-party HTTP client written against the chat-completions shape works too — point its base URL there and give it the same key.

Spend and usage

Spend for the calling credential comes from the billing and analytics APIs — same host, same key:

# Current balance
curl https://api.hanzo.ai/v1/billing/balance \
  -H "Authorization: Bearer sk-your-key"

# Itemized metered usage
curl https://api.hanzo.ai/v1/billing/usage \
  -H "Authorization: Bearer sk-your-key"
{ "balance": 0, "holds": 0, "available": 0 }
{
  "count": 2000,
  "usage": [
    {
      "amount": 24,
      "createdAt": "2026-07-25T21:59:35Z",
      "metadata": { "model": "zen-image" },
      "transactionId": "use_daff3c10f561635114c7a78e8d0eea5d"
    }
  ]
}

For rolled-up request, token and spend counts across a range, use GET https://api.hanzo.ai/v1/analytics/overview?range=30d — see Analytics.

Rotation

  1. Rotate on a schedule. Ninety days is a reasonable default for a secret key. A publishable key is public anyway; rotate it when you want to cut off a site, not on a calendar.
  2. Keep production keys in KMS. Never in source, never in a committed .env.
  3. Mint before you revoke. Mint the replacement, deploy it, watch it work, then revoke. Note that minting the same type again IS the revoke — so for zero-downtime you cut over between two identities, not two keys of one.
  4. One identity per service. Blast radius, and audit logs you can read.
  5. Watch spend. /v1/billing/usage or the console. An unfamiliar spend pattern is what a leaked key looks like from the inside.

KMS integration

A KMSSecret syncs secrets out of Hanzo KMS into a Kubernetes Secret your pods already read. It authenticates as a machine identity, names a scope in KMS, and names the Secret it manages:

apiVersion: secrets.lux.network/v1alpha1
kind: KMSSecret
metadata:
  name: api-kms-sync
  namespace: acme
spec:
  hostAPI: https://kms.hanzo.ai
  resyncInterval: 60
  authentication:
    universalAuth:
      credentialsRef:
        secretName: acme-iam-creds
        secretNamespace: acme
      secretsScope:
        projectSlug: acme
        envSlug: prod
        secretsPath: /api
        keys:
          - HANZO_API_KEY
  managedSecretReference:
    creationPolicy: Orphan
    secretName: api-secrets
    secretNamespace: acme
    secretType: Opaque

creationPolicy: Orphan means deleting the CR leaves the Secret in place, so removing the sync does not take a running workload down with it.

Revoking

From the console: API Keys → Revoke. Over the API: DELETE /v1/keys?type=secret. IAM drops the credential immediately and calls made with it fail 401 once the gateway's cache lapses.

Rules

  • Never commit a key. Environment variables or KMS.
  • Never put a secret key in a browser. It resolves to you. Use a publishable key, which resolves to your org and cannot read.
  • Always use HTTPS. Every Hanzo endpoint enforces TLS; plain HTTP is rejected.
  • Ask for the type you need. An unrecognized type is refused, so a typo fails loudly instead of quietly handing you the more powerful key.

How is this guide?

On this page