TypeScript
Four npm packages, and which one to install — hanzoai for the whole /v1 surface, @hanzo/ai for inference and agents, @hanzo/sdk per service, @hanzo/iam for identity.
TypeScript has more than one Hanzo package because it has more than one job to do, and the packages are not versions of each other. Pick by what you are building; installing two is normal.
| Package | Install | What it gives you |
|---|---|---|
hanzoai | npm i hanzoai | The generated client for every /v1 capability. One *Api class per product, typed request and response models. |
@hanzo/ai | npm i @hanzo/ai | The AI client — chat, messages, models, agents, sandboxes, tools, sessions. Hand-written, browser-safe. |
@hanzo/sdk | npm i @hanzo/sdk | One client per service — IAM, KMS, Commerce, Billing, MPC, PaaS, Team — with subpath imports. |
@hanzo/iam | npm i @hanzo/iam | Identity alone: OIDC, JWT validation, browser PKCE, React bindings. |
There is also hanzo on npm, which is the CLI. Its library export
re-exports hanzoai, so npm i -g hanzo gives you the command and npm i hanzoai gives you the client; you do not need both for the client.
Set the key once:
export HANZO_API_KEY="sk-..." # from platform.hanzo.ai@hanzo/ai — inference and agents
The one to reach for when the job is a model call. It runs on Node, Deno, Bun and the browser, and it names the key field after the key type, so a mistake is a type error rather than a leak:
import { Hanzo } from '@hanzo/ai'
// On a server. An `sk-` resolves to you, so it never goes in a bundle.
const hanzo = new Hanzo({ secretKey: process.env.HANZO_API_KEY })
const answer = await hanzo.chat.completions.create({
model: 'zen6',
messages: [{ role: 'user', content: 'Explain quantum computing' }],
})
console.log(answer.choices[0].message.content)In a browser, pass the publishable key instead — it names the tenant and cannot read, which is what makes it safe to ship:
const hanzo = new Hanzo({ publishableKey: 'pk-...' })Authentication is the whole rule for which key goes where.
Streaming
stream: true changes the return type to an async generator of OpenAI-shaped
chunks:
const stream = await hanzo.chat.completions.create({
model: 'zen6',
messages: [{ role: 'user', content: 'Write a haiku about AI' }],
stream: true,
})
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
}The rest of it
The same client carries the surfaces an agent needs — hanzo.models.list(),
hanzo.agents, hanzo.sandboxes, hanzo.tools, hanzo.sessions,
hanzo.threads — plus hanzo.messages.create() for the Anthropic-shaped
request, and hanzo.search() for a source-cited answer.
hanzoai — the whole /v1 surface
Generated from the API's own document, so every capability is present whether or
not anyone wrote a page about it. The shape is one Configuration and one class
per product:
import { AiApi, BillingApi, Configuration } from 'hanzoai'
const config = new Configuration({ accessToken: process.env.HANZO_API_KEY })
const { data: models } = await new AiApi(config).getModels()
const balance = await new BillingApi(config).getBillingBalance()Method names come from the operation, so the reference is the index: each operation page prints the call for this client next to the raw HTTP.
@hanzo/sdk — one client per service
For a backend that talks to a few named services rather than to the API at large. Subpath imports keep a bundle to what it uses:
import { IAMClient } from '@hanzo/sdk/iam'
import { KMSClient } from '@hanzo/sdk/kms'https://api.hanzo.ai/v1 speaks the OpenAI and Anthropic wire formats, so the
openai package with baseURL pointed there and a Hanzo key sends the same
body it sends today. The packages above add the rest of /v1 on top of that —
see Migrate from OpenAI.
Resources
hanzoai·@hanzo/ai·@hanzo/sdk·@hanzo/iam- Source — hanzo-js
- Every
/v1operation →