Every Hanzo SDK is supposed to ship the SAME six examples — hello, chat, money,
store, agent, tools. Until now that was six repos independently remembering to
agree, and nothing checked. Nothing could: the flows existed in no manifest, in
no repo, in any language. The TypeScript and Python sets are the first two, and
writing them is what made the gap obvious.
flows.yaml is data only, like sdks.yaml beside it: per flow, the operationIds in
hanzo.yaml in call order, plus why THAT route and not a neighbour. Those choices
are the part worth writing down —
hello ai_getAccount, not bot_authMe (bot-service scoped) and not the IAM OIDC
userinfo endpoint, which is on a different ORIGIN; using it would force
every example set to know two base URLs, and IAM answers 200 text/html
for an unregistered path, so a typo there is silent breakage, not a 404.
tools automations_mcp, not tasks_tasksMcp: only the former has typed
request/response schemas, so `method` is a checked enum in every
generated client instead of a free string.
store the data plane (/v1/kv/keys/*), not the provisioning plane (/v1/kv,
which creates stores) — the two are easy to confuse by name alone.
test_flows.py asserts every id named here still exists in hanzo.yaml. Verified it
fails on a typo, not just passes on the truth:
FAIL test_every_operation_exists_in_the_spec: flows.yaml names operations
hanzo.yaml does not have: [('hello', 'ai_getAccount_TYPO')]
So deleting a route breaks ONE gate here rather than six SDKs' examples quietly
and separately.
Also gives this repo a `test:` block, which it did not have. 25 tests lived here
and not one had ever gated a commit — and that is not academic: hanzo.yaml spent
this entire period unable to generate a client in ANY language, because 35
/v1/platform operations carried no `responses` and openapi-generator refuses the
whole document over it. Nothing here noticed, because nothing here ever tried.
hanzo.yaml is the single input to seven language SDKs; a defect merged here is a
defect merged into all seven at once.
25 passed in 19.99s
Co-authored-by: Hanzo Dev <[email protected]>
Hanzo OpenAPI
OpenAPI 3.1 specifications for every Hanzo service. v1.0.0 — locked in.
Forward-only. No backwards compatibility. No deprecated paths. No /api/
prefixes. No cross-brand references.
Single bearer JWT issued by Hanzo IAM (https://hanzo.id) authenticates
every service. Every route is /v1/<service>/<resource> so the same path
works through the gateway (api.hanzo.ai) and through the service's own
hostname (<service>.hanzo.ai).
One exception — AI inference is top-level. The ai service
(hanzoai/ai) exposes its OpenAI- and Claude-compatible surface at the bare
/v1/* paths (/v1/chat/completions, /v1/completions, /v1/embeddings,
/v1/models, /v1/rerank, /v1/messages) — NOT /v1/ai/* — because every
OpenAI/Anthropic SDK hard-codes those paths, so the path is the compatibility
contract. Everything else stays under /v1/<service>/.
# AI inference (top-level, OpenAI-compatible)
curl -H "Authorization: Bearer ${HANZO_TOKEN}" \
https://api.hanzo.ai/v1/chat/completions -d '{"model":"zen5","messages":[...]}'
# Control-plane (per-service prefix)
curl -H "Authorization: Bearer ${HANZO_TOKEN}" \
https://api.hanzo.ai/v1/cloud/get-providers
OIDC discovery:
https://hanzo.id/.well-known/openid-configuration
Canonical capability index: CAPABILITIES.md — one name per capability, grouped into eight categories. The unified hanzo.yaml is grouped by those categories via x-tagGroups.
Service Index
| Service | Spec | Hostname | Gateway prefix | Status |
|---|---|---|---|---|
| ai | ai/openapi.yaml | api.hanzo.ai |
/v1/{chat,completions,embeddings,models,rerank,messages} (top-level — OpenAI/Claude-compatible) |
|
| analytics | analytics/openapi.yaml | analytics.hanzo.ai |
/v1/analytics |
|
| app | app/openapi.yaml | api.hanzo.ai |
/v1/projects (top-level — hanzo.app projects & deploy) |
|
| base | base/openapi.yaml | base.hanzo.ai |
/v1/collections (top-level — Base records store) |
|
| bot | bot/openapi.yaml | app.hanzo.bot |
/v1/bot |
|
| cloud | cloud/openapi.yaml | api.hanzo.ai |
/v1/cloud |
|
| commerce | commerce/openapi.yaml | commerce.hanzo.ai |
/v1/commerce |
|
| dns | dns/openapi.yaml | dns.hanzo.ai |
/v1/dns |
|
| edge | edge/openapi.yaml | edge.hanzo.ai |
/v1/edge |
|
| gateway | gateway/openapi.yaml | api.hanzo.ai |
/v1/gateway |
|
| iam | iam/openapi.yaml | hanzo.id |
/v1/iam + /oauth/* + /.well-known/* |
|
| kms | kms/openapi.yaml | kms.hanzo.ai |
/v1/kms |
|
| kv | kv/openapi.yaml | kv.hanzo.ai |
/v1/kv |
|
| ml | ml/openapi.yaml | ml.hanzo.ai |
/v1/ml |
|
| o11y | o11y/openapi.yaml | o11y.hanzo.ai |
/v1/o11y |
|
| operative | operative/openapi.yaml | operative.hanzo.ai |
/v1/operative |
|
| platform | platform/openapi.yaml | platform.hanzo.ai |
/v1/platform |
DRIFTED — 33 served routes authored nowhere |
| pricing | pricing/openapi.yaml | pricing.hanzo.ai |
/v1/pricing |
|
| s3 | s3/openapi.yaml | s3.hanzo.ai |
/v1/s3 |
|
| search | search/openapi.yaml | search.hanzo.ai |
/v1/search |
|
| vector | vector/openapi.yaml | vector.hanzo.ai |
/v1/vector |
|
| visor | visor/openapi.yaml | vm.hanzo.ai |
/v1/visor |
|
| zt | zt/openapi.yaml | zt.hanzo.ai |
/v1/zt |
Discovery: hanzo.yaml is the canonical list of services with their
spec URLs. It is a file in this repo, not a served endpoint — /v1/discovery was
PROBED on 2026-07-28 and returns 404, and no handler for it exists in cloud.
Status is blank when the service answers at the hostname above. A product
whose own authored routes answer 404 — probed at api.hanzo.ai AND at its own
host — is DELETED from this repo, not annotated. Annotation was tried and it
does not hold: hanzoai/cli's genspec refutes an authored operation only
where the live route table OWNS its product, so a product the router has never
heard of is refuted by nothing and reaches the CLI, the SDKs and the skills
plane ungated. A hostname in this table is where a service is SUPPOSED to
answer, which is not the same claim as that it does — and the spec IS the
claim, so the spec is what gets removed. A product returns to this table the
day it is actually served.
This matters most for skills.py, which has NO liveness filter: it reads
<svc>/openapi.yaml directly and publishes a SKILL.md per authored operation,
so an unserved route ships as a live instruction to call a dead endpoint. See
LLM.md, "Who reads this repo".
v1.0.0 Conventions
- Versioning:
info.version: 1.0.0on every service. There is nov0. There will be nov2. Breaking changes ship as new resources or new endpoints under/v1/, never as a parallel/v2/tree. - Routing: every route is
/v1/<service>/<resource>. No/api/prefix. IAM additionally exposes/oauth/*(RFC 6749) and/.well-known/*(RFC 5785) for OIDC discovery. - Security: every operation uses
BearerAuth(JWT from Hanzo IAM). No service has its own login flow. - Cross-file
$ref: never. Each service spec is self-contained. - Errors: 4xx for client errors, 5xx for server errors. Every error
response has a JSON body with a
statusandmsg.
Validate
# Parse + schema validation across all specs:
python3 -c "import yaml, glob, sys; \
[yaml.safe_load(open(s)) for s in glob.glob('*/openapi.yaml') + ['hanzo.yaml']]; \
print('OK')"
# Spectral lint per spec:
for spec in */openapi.yaml hanzo.yaml; do
npx -y @stoplight/spectral-cli lint "$spec"
done
# Redoc preview:
npx -y @redocly/cli preview-docs hanzo.yaml
Agent Skills discovery
skills.py derives the /.well-known/agent-skills/ discovery surface from the
SAME per-service specs (reusing merge.py's registry loader, so internal
services are excluded identically). For each public service it clusters GET
endpoints by resource — read-only by construction, no destructive verb ever
becomes a skill — and emits a SKILL.md per cluster (YAML frontmatter + a
worked, injection-guarded body) plus an index.json catalogue carrying a
sha256 of every skill file.
python3 skills.py # dist/agent-skills/<brand>/… (hanzo, lux, zoo)
python3 skills.py --no-services # unified tree only (what the cloud binary embeds)
python3 skills.py --check # drift gate: diff a fresh gen against dist/
python3 test_skills.py # golden test (determinism, digests, white-label, read-only)
Output is white-labeled per brand: a Lux surface reads Lux / api.lux.network
/ lux.id, a Zoo surface Zoo / api.zoo.ngo / zoo.id — never Hanzo (host
and issuer tokens in spec prose are rebranded at render time). dist/ is
generated at build/serve time and is NOT committed (.gitignore); the committed
artefacts are the generator + its golden test. Serving: the cloud unified
binary embeds the unified tree and serves it (host decides the brand) at
GET /.well-known/agent-skills/index.json + …/<skill>/SKILL.md.
Code generation
hanzo.yaml (regenerated by merge.py) is the single source of truth every
official SDK derives from. One way per SDK:
Status (2026-07): the Stainless project
hanzo-aistill points at the legacy LLM-gateway spec, so the published python/go/js SDKs cover the inference + gateway-admin surface only — NOT the cloud products. Repoint it tohanzo.yamland close the ~28 missing product specs first. See the SDK generation reconciliation section inLLM.md.
Regenerate the Stainless SDKs (canonical)
The JS, Python and Go SDKs are generated by the Stainless project hanzo-ai.
Pushing an updated spec regenerates all three and opens a PR on each SDK repo:
export STAINLESS_API_KEY="$(hanzo kms secret get stainless-api-key)" # kms.hanzo.ai
stl builds create --project hanzo-ai --branch main hanzo.yaml # npm i -g @stainless-api/cli
Each SDK repo's .stats.yml records the spec hash it was built from. Tag a
semver release in the SDK repo after the PR merges.
One-off client from any spec (openapi-generator)
# Python client for the cloud service:
npx -y @openapitools/openapi-generator-cli generate \
-i cloud/openapi.yaml -g python -o clients/python/cloud
# TypeScript client for the whole surface:
npx -y @openapitools/openapi-generator-cli generate \
-i hanzo.yaml -g typescript-axios -o clients/ts/hanzo
License
Proprietary. Copyright 2024-2026 Hanzo AI, Inc.