`ChatCompletionResponse.choices` was `items: { type: object }`. Every generator
projects that as the language's untyped bag — `Array<object>` in TypeScript,
`List[object]` in Python — so the one line every SDK user writes first,
choices[0].message.content
did not compile without a cast, on the most-called route in the API. The gate
that caught it is the new TypeScript chat example:
examples/chat/index.ts(25,36): error TS2339:
Property 'message' does not exist on type 'object'.
`ChatChoice` is not a guess about the wire. The route's own tag is "OpenAI
Compatible", `index`/`message`/`finish_reason` is that contract, and `message`
is the ChatMessage this file already defines and already accepts on the REQUEST
side — the response was simply never pointed at it.
merge.py: 1132 paths, 1519 operations, 779 schemas (+1), 0 missing responses.
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.