zeekayandHanzo Dev a2a49658e5 placement: one product, one owning spec — and 31 paths nothing serves
capabilities.yaml states the law and nothing enforced it. Measured against
api.hanzo.ai: 103 authored paths outside their owner's prefix, and four
products (kv, s3, search, vector) described by TWO specs at once.

provisioning was the bucket — it claimed /v1/{sql,vector,datastore,kv,search,
s3,docdb}, seven other products' roots, and its own header says why it could:
"these routes are keyed by RESOURCE KIND at top-level /v1/, NOT under
/v1/provisioning/." There is no /v1/provisioning route. Each kind's spec owns
its own quartet now; sql/datastore/docdb became their own specs under `data`;
provisioning is collapsed.

The inference edge (chat/completions, completions, embeddings, rerank, models,
messages, images/generations, audio/speech) moved from ai/ to gateway/, the
binary that answers it, and lost the vendor-as-standard prose on the way — a
tag named for another company's product is not a description of ours. The
/v1/admin/* operations authored in affiliates/, authors/, referrals/ and
plugin/ moved to admin/; /v1/edge/nodes moved from zt/ to edge/. plan/ ->
plans/ and plugin/ -> plugins/, because the routes are /v1/plans and
/v1/plugins and the route is the part that cannot move.

31 paths deleted, each probed live and absent from the route table: the whole
/v1/gateway/* subtree (23 — a phantom second copy of the inference edge, and
the reason three operationIds needed a _2 suffix to stay distinct in codegen),
/v1/load-balancers x2 (served product is /v1/balancers), /v1/bots/launch|{id}|
{id}/{action} (served at /v1/compute/bots/*), /v1/machines/{id}/bind-agent|
agent-binding (served at /v1/machines/{id}/agent), /v1/audio/transcriptions.
hanzo.yaml 1742 -> 1711 paths: 31 removed, 0 added, nothing served lost.

test_placement.py is the gate. Rule 1 — no two authored specs claim one
product — is absolute. Rule 2 — a spec claims its own prefix — carries
OFF_PREFIX, 48 declared exceptions each measured live, each a binary answering
at a noun that is not its service's name; the fix for those is a route move in
the serving repo, and a stated gap beats a silent one.

flows.yaml's chat journey follows the operation it names: ai_createChatCompletion
-> gateway_createChatCompletion. That gate caught the move, which is what it is
for.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 10:29:43 -07:00
2026-06-01 12:44:10 -07:00

openapi

Hanzo OpenAPI

hanzo.yaml is THE published document. One OpenAPI 3.1 surface for every Hanzo service — the one every SDK, every doc site and every tool generates from.

curl -fsSL -H "Authorization: Bearer $GITHUB_TOKEN" \
  https://raw.githubusercontent.com/hanzoai/openapi/main/hanzo.yaml

This repo is private, so that URL needs a token — unauthenticated it is a 404, not a 403, which reads like a missing file rather than a missing header. Any fetch of it without an Authorization header is broken; curl -f under set -e turns that into a build failure with nothing that names the cause.

Nothing generates from anything else, and nothing is pushed to an SDK. The SDK repos PULL this file and regenerate themselves; a client is never hand-written and never receives a patch from here.

Forward-only. No backwards compatibility, no deprecated paths, no /api/ prefix, no cross-brand references. A single bearer JWT from Hanzo IAM (https://hanzo.id) authenticates every route, and every route is /v1/<service>/<resource> — the same path through the gateway (api.hanzo.ai) and through the service's own hostname.

One exception — AI inference is top-level. hanzoai/ai serves its OpenAI- and Claude-compatible surface at the bare /v1/* paths (/v1/chat/completions, /v1/completions, /v1/embeddings, /v1/models, /v1/rerank, /v1/messages) because every OpenAI/Anthropic SDK hard-codes them: there, the path IS the compatibility contract.

curl -H "Authorization: Bearer ${HANZO_TOKEN}" \
     https://api.hanzo.ai/v1/chat/completions -d '{"model":"zen5","messages":[...]}'

curl -H "Authorization: Bearer ${HANZO_TOKEN}" \
     https://api.hanzo.ai/v1/billing/balance

OIDC discovery: https://hanzo.id/.well-known/openid-configuration. hanzo.yaml is a file in this repo, not a served endpoint — /v1/discovery was probed, it 404s, and no handler for it exists.

The files

File What it is
hanzo.yaml the published document. Generated by merge.py; never edited.
<service>/openapi.yaml one self-contained authored spec per service.
cloud/openapi.yaml not authored here — hanzoai/cloud's own woven document, copied verbatim by sync.py.
capabilities.yaml the ONE registry: every capability, in exactly one domain.
flows.yaml the canonical example set — the same six journeys in every SDK.
CAPABILITIES.md generated index. Never hand-edit.
merge.py sync.py generate.py skills.py audit.py build · resync · clients · skills · measurement.

The capability index is CAPABILITIES.md, generated from capabilities.yaml, so it cannot drift from what is here. There is no second index in this file to fall out of date.

Two commands

python3 sync.py     # pull hanzoai/cloud's woven document, then merge
python3 merge.py    # rebuild hanzo.yaml + CAPABILITIES.md from the specs

sync.py is the whole resync: it fetches origin/main in a hanzoai/cloud checkout (../cloud, or --cloud DIR, or CLOUD_DIR), writes origin/main:openapi.yaml here byte-for-byte, and re-merges. --check reports staleness without writing. Nothing is ever written back to hanzoai/cloud.

That document is the one input here that cannot lie about what is served — cloud weaves it from its own router and gates the weave against source — so merge.py lets it WIN wherever it and an authored spec describe the same route. The authored specs cover what other binaries serve, cloud's covers what cloud serves, and hanzo.yaml is the union with truth on top.

Conventions

  • info.version: 8.0.0 (the V8 generation). The /v1 path is the immutable contract — there is no v2, ever. Breaking changes ship as new resources.
  • operationIds are BARE in each spec (logs_query); merge.py namespaces them <svc>_ in the published document. Never self-prefix.
  • No cross-file $ref. Each spec is self-contained.
  • No deprecated: true. No /api/ prefix. No dates in filenames.
  • Security: every operation uses BearerAuth. No service has its own login.
  • Errors: 4xx client, 5xx server, JSON body with status and msg.
  • A product whose own authored routes answer 404 — probed at api.hanzo.ai AND at its own host — is DELETED, not annotated. skills.py has no liveness filter, so an unserved route ships as a live instruction to call a dead endpoint. See LLM.md, "Who reads this repo".

Validate

python3 -c "import yaml, glob; [yaml.safe_load(open(s)) for s in glob.glob('*/openapi.yaml') + ['hanzo.yaml']]; print('OK')"

java -jar ~/.cache/openapi-generator/openapi-generator-cli-7.14.0.jar validate -i hanzo.yaml

npx -y @redocly/cli preview-docs hanzo.yaml

Clients

generate.py runs openapi-generator (pinned 7.14.0) over hanzo.yaml for every language; sdks.yaml is the per-language data, and each SDK repo carries only a call site. There is no second generator and no unified SDK monorepo.

python3 generate.py --all            # regenerate every client in place
python3 generate.py go               # just one
python3 generate.py --all --check    # fail if a committed client drifted
Lang Canonical repo Generator Ships as
Python hanzoai/python-sdk python hanzoai on PyPI
TypeScript hanzoai/js-sdk typescript-axios hanzoai on npm
Java · Kotlin hanzoai/java-sdk · hanzoai/kotlin-sdk java · kotlin (okhttp) ai.hanzo:hanzo-{java,kotlin}-cloud
Rust hanzo-rs/sdk rust (reqwest) crates/hanzo-client — own scripts/generate.sh (needs a template override)
Go hanzo-go/sdk go package hanzoai at the module root — own scripts/generate.sh (no directory to own)

A language stays in sdks.yaml while its whole invocation is expressible as data, and leaves when it needs something that is not — a template file, or a layout take cannot describe. It then owns the whole invocation in its own repo. A row here AND flags there is two declarations of one contract.

Examples are not a per-SDK decision either: flows.yaml names the six journeys (hello, chat, money, store, agent, tools) and every SDK renders the same six from the same operationIds. python3 test_flows.py is the gate — an id that no longer resolves fails here, once, instead of in six clients separately.

Agent skills

skills.py derives /.well-known/agent-skills/ from the same per-service specs, clustering GET endpoints by resource — read-only by construction, no destructive verb ever becomes a skill — and emits a SKILL.md per cluster plus an index.json carrying a sha256 of every 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
python3 test_skills.py            # golden test

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. dist/ is generated at build time and is not committed.

License

BSD 3-Clause. Copyright Hanzo AI, Inc.

S
Description
Mirror of github.com/hanzoai/openapi
Readme
22 MiB
Languages
Mustache 52.8%
Python 43.7%
Go 0.9%
Shell 0.8%
Java 0.3%
Other 1.5%