zeekay 7a5968f9dc a pk- key authenticates nobody, so stop telling readers it reads
The README offered the two key shapes as a permission split — pk- to read,
sk- to write. cloud's own identity boundary says otherwise: middleware_identity
returns nil for any pk- before it consults the key store, because a key meant
for a browser bundle must not read. A pk- resolves to an org so a beacon can be
attributed to a tenant, and that is all it does.

Following the old sentence, a reader exports a pk- and every one of the five
flows answers 403 — /v1/tools spells out why, "a validated principal is
required" — and that refusal reads like a revoked key rather than the wrong
shape of key. examples/client.py now refuses a pk- before the request goes out,
the same argument that already rejects an unsendable access_token.

Also: three status codes in example docstrings had drifted from what the API
answers now (/v1/kv/keys/* 404s on every verb, /v1/automations/mcp 404s), and
two dead files went with them — examples/.keep, whose directory has held real
flows for a while, and scripts/mock, which reads a .stats.yml this repo does
not have and which scripts/test already says is unnecessary.
2026-08-14 14:45:57 -07:00
2025-03-15 03:31:14 +00:00
2025-03-15 03:31:14 +00:00
2026-06-26 11:57:39 -07:00
2025-03-15 03:56:11 +00:00

Hanzo Python SDK

hanzoai is the Python client for the Hanzo API, generated from the API's own OpenAPI document — 1814 paths, 2479 operations, 192 API classes over 2460 models. Every /v1 route is in it, and the names it exposes are the document's operation ids rather than a hand-picked subset. .spec-lock names the commit and sha256 of the document this tree was cut from.

PyPI Python

Install

uv pip install "hanzoai @ git+https://github.com/hanzoai/python-sdk"

Install from source, not from PyPI, until this version reaches it. PyPI serves 3.2.1; this tree is 3.2.11, and in between the API document dropped the default version from its operation ids. 3.2.1's methods are get_v1_keys and get_v1_tools; the current ones are get_keys and get_tools, so pip install hanzoai gets you a client that works and a set of names that matches nothing below.

Check the install without a key — GET /v1/models is public:

import json
from hanzoai.cloud import ApiClient, Configuration, ModelsApi

body = ModelsApi(ApiClient(Configuration())).get_models_without_preload_content().read()
print(len(json.loads(body)["data"]), "models")     # e.g. 112 models

Quickstart

import os
from hanzoai.cloud import ApiClient, Configuration, KeysApi

client = ApiClient(
    Configuration(host="https://api.hanzo.ai"),
    "Authorization", f"Bearer {os.environ['HANZO_API_KEY']}",
)

with client as api:
    for key in KeysApi(api).get_keys().keys or []:
        print(key.prefix, key.type, key.created_at)

Every route follows that shape: one *Api class per tag, one method per operation, typed models in and out. Keys come from cloud.hanzo.ai or hanzo login in two shapes, and only one of them works here. Use an sk-: it carries a principal, which every call above needs. A pk- is publishable — it is safe in a browser bundle precisely because it names an org and authenticates nobody, so cloud refuses it at the identity boundary and it reads nothing.

Auth

The header is Authorization: Bearer <key>, and you set it on the ApiClient as above — not through Configuration(access_token=…). The document declares no security scheme, so the generator wrote an empty auth_settings into every operation and nothing ever reads access_token. A client built that way sends no Authorization header at all: every call goes out anonymous, and the refusal that comes back reads like a bad key rather than an unsent header.

Nothing in the client reads the environment. examples/client.py is where HANZO_API_KEY and HANZO_BASE_URL get resolved — one place, for all five flows.

Examples

examples/ carries one directory per flow. Each is a whole path through one part of the API. On every push CI imports all five and resolves every method name they call against the client, which is what keeps them from rotting into pseudocode.

flow what it does routes
hello prove the key works GET /v1/keys
money balance + usage GET /v1/billing/balance, GET /v1/billing/usage
store KV round-trip POST /v1/kv, GET/DELETE /v1/kv/{name}
agent create, run, read the runs POST /v1/agents, POST /v1/agents/{ref}/run, GET /v1/agents/{ref}/runs
tools the tool catalog GET /v1/tools
export HANZO_API_KEY=sk-...
python -m examples.hello

There is no chat flow because there is nothing to generate one from: POST /v1/chat/completions is declared with no requestBody and no responses, so the method takes no arguments and returns None. Hand-rolling the request inside a generated client is the drift these SDKs exist to prevent. It comes back the day the document describes the body.

The same gap shows up in money, which reads its two payloads through the generated *_without_preload_content variant: of 2479 operations, 716 declare no responses and another 118 declare no response content, so 834 of them model no body to deserialize. Those become ordinary typed calls when the schemas land, and nothing else about them changes.

Reference for the routes themselves: api.hanzo.ai/docs, served from the same document — api.hanzo.ai/v1/openapi.json.

The rest of the repo

This is a uv workspace. pkg/hanzoai is the client above; the other 64 packages are hand-written, ship separately, and mostly carry their own README:

Package Install Purpose
pkg/hanzoai hanzoai the client above
pkg/hanzo-mcp hanzo-mcp Model Context Protocol server
pkg/hanzo-agent hanzo-agent agent framework (import path agents)
pkg/hanzo-agents hanzo-agents agent networks and swarms
pkg/hanzo-memory hanzo-memory persistent memory + RAG over SQLite
pkg/hanzo-network hanzo-network distributed compute nodes
pkg/hanzo-tools-* one each 37 single-concern tool packages, each registering a TOOLS list under the hanzo.tools entry point, which is how hanzo-mcp finds them

The hanzo command is a native binary, not a Python package: curl -fsSL https://hanzo.sh | sh. pip install hanzo ships the older Python CLI under the name hanzo-py, so the two never fight over one name on a PATH.

Development

git clone https://github.com/hanzoai/python-sdk && cd python-sdk
uv sync --all-packages
uv run pytest tests/ -v

pkg/hanzoai/cloud/ is generated and is never edited by hand — a regeneration does rmtree then copytree, so an edit there is gone on the next run. It comes from hanzoai/openapi:

cd ../openapi && uv run --with pyyaml python3 generate.py python \
  --repo ../python-sdk --spec ../cloud/openapi.yaml

A defect found in generated code is fixed in the document, where every other language gets the fix too. See LLM.md for how the lane works.

License

Apache 2.0 — see LICENSE. Report vulnerabilities to [email protected] (SECURITY.md).

Hanzo — the Open AI Cloud

hanzo.ai · docs.hanzo.ai · same client in other languages: TypeScript · Go · Java · Kotlin · umbrella

S
Description
No description provided
Readme Apache-2.0
76 MiB
Languages
Python 99.9%