docs(services): correct S3/IAM/KMS/MPC/Base/PaaS to real /v1 surfaces

Audited each service's current source and rewrote the docs to match:
- S3: SeaweedFS-based (was documented as MinIO) - S3 SigV4, real clients
  (mc/aws/boto3), correct ports; dropped fabricated SDKs + cross-brand buckets
- IAM: Casdoor fork - canonical /v1/iam/* OIDC, <org>-<app> app convention,
  JWKS token validation, per-host white-label
- KMS: Go /v1/kms surface - machine-identity login, org/path/name?env reads
  with CAS, KMSSecret keys[]; removed fabricated /api/v1 + SDK examples
- MPC: /v1/mpc custody API with session-based signing; CGGMP21/FROST/LSS/BLS
- Base: PocketBase fork on /v1 - IAM-native auth (local passwords removed),
  real endpoints incl /v1/sql, /v1/tasks, /v1/private
- PaaS: Dokploy fork (SQLite/Base) - /v1 container redeploy + /api
  application.* build pipeline; replaced fabricated Mongo/Redis architecture

House style: /v1 not /api, api.<svc>.hanzo.ai hosts, KMS paths for secrets,
Hanzo-only (stripped Lux/Zoo leaks, prod IPs, nginx, internal slugs).
This commit is contained in:
2026-06-26 11:55:49 -07:00
parent 92bf9543ee
commit a4e7105161
22 changed files with 1355 additions and 3412 deletions
+202 -302
View File
@@ -1,71 +1,217 @@
---
title: Hanzo Base
description: Open-source backend-as-a-service with REST API, auth, realtime, file storage, cloud functions, and plugin system
description: Backend-as-a-service with an auto-generated REST API, IAM-native auth, realtime, file storage, durable tasks, and per-tenant encrypted storage
---
# Hanzo Base
Hanzo Base is an open-source backend-as-a-service (BaaS) that ships as a single Go binary with zero dependencies. It provides a full REST API auto-generated from your schema, authentication, realtime subscriptions, file storage, cloud functions, and an extensible plugin system.
Hanzo Base is a backend-as-a-service that ships as a single Go binary. Define a collection schema and get a full REST API automatically — records CRUD, realtime subscriptions, file storage, a durable task queue, and a per-user key-value store — with authentication delegated natively to [Hanzo IAM](/docs/services/iam).
**Production:** `https://base.hanzo.ai`
**Admin UI:** `https://base.hanzo.ai/_/`
**Source:** [github.com/hanzoai/base](https://github.com/hanzoai/base)
| | |
|---|---|
| **Production** | `https://base.hanzo.ai` |
| **API prefix** | `/v1` |
| **Admin UI** | `https://base.hanzo.ai/_/` |
| **Auth** | Hanzo IAM (OIDC / JWKS) — no local passwords |
| **Source** | [github.com/hanzoai/base](https://github.com/hanzoai/base) |
<Callout>
Hanzo Base serves its API under `/v1`, not `/api`. Authentication is **IAM-native**: the built-in local password/OAuth flows are removed, and Hanzo IAM is the only token authority. Clients run the OIDC PKCE flow against IAM and present the resulting JWT as a bearer token.
</Callout>
## Features
- **Database**: SQLite for local dev, PostgreSQL for production. Full REST API auto-generated from schema.
- **Authentication**: Email/password, OAuth2, and Hanzo IAM integration with fine-grained access control.
- **Realtime**: Subscribe to database changes over SSE with optimistic update support.
- **File Storage**: Upload files directly to collections. Local filesystem or S3-compatible storage (Hanzo S3).
- **Cloud Functions**: Server-side JavaScript hooks, custom routes, and scheduled tasks.
- **Admin UI**: Built-in dashboard at `/_/` for managing collections, users, settings, and logs.
- **Plugin System**: Extend Base with custom Go plugins for routes, hooks, middleware, and data providers.
- **Auto-generated REST API**: Full CRUD for every collection under `/v1/collections/{name}/records`
- **IAM-native auth**: Tokens issued by [Hanzo IAM](/docs/services/iam) and validated against its JWKS — users are not stored in Base
- **Realtime**: Subscribe to record changes over SSE (`/v1/realtime`) or WebSocket (`/v1/realtime/ws`)
- **File storage**: Upload files to records; stored on local disk or [Hanzo S3](/docs/services/s3)
- **Durable tasks & workflows**: A built-in task queue at `/v1/tasks`
- **Per-user private KV**: A scoped key-value store at `/v1/private`
- **Raw SQL over HTTP**: `/v1/sql` for superusers
- **Per-tenant encrypted storage**: One encrypted SQLite database per organization, with WAL replication to object storage
- **Admin dashboard**: A React UI at `/_/` for collections, settings, logs, and backups
## Architecture
## Storage model
Base uses **SQLite by default** — pure-Go, embedded, zero external dependencies — which makes local development instant. Each organization gets its own database file, encrypted at rest with a key derived from [Hanzo KMS](/docs/services/kms); the write-ahead log is streamed (age-encrypted) to S3/GCS for durability.
For production multi-instance deployments that need a shared writer (e.g. leader-elected cron), Base runs on **PostgreSQL** via `pgx`. SQLite remains the default everywhere else.
```
┌──────────────────────────────────────────────────────────────┐
│ HANZO BASE │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ REST API │ │ Auth │ │ Realtime │ │ Admin UI │ │
│ │ (CRUD) │ │ (OIDC) │ │ (SSE) │ │ (/_/) │ │
│ └─────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬─────┘ │
│ │ │ │ │ │
│ ┌─────▼──────────────▼──────────────▼───────────────▼─────┐ │
│ │ Core Engine │ │
│ │ Collections · Hooks · Middleware · Plugin Registry │ │
│ └─────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────▼─────────────────────────────────┐ │
│ │ Storage Layer │ │
│ │ SQLite (dev) │ PostgreSQL (prod) │ S3 (files) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Plugin System │ │
│ │ Routes · Hooks · Middleware · Migrations · Cron Jobs │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Local dev / single node → SQLite (encrypted, per-org)
Production multi-instance → PostgreSQL (pgx)
```
## Quick Start
## Authentication
### Local Development
Base does not authenticate users itself — it trusts [Hanzo IAM](/docs/services/iam). The flow:
<Steps>
### Discover the auth provider
```bash
# Download the binary
curl -fsSL https://base.hanzo.ai/install.sh | sh
# Start in development mode (SQLite)
base serve --dev
curl https://base.hanzo.ai/v1/collections/users/auth-methods
```
The admin UI is available at `http://localhost:8090/_/`.
Returns the single configured `iam` OAuth2/PKCE provider with its authorize URL and PKCE challenge.
### Docker
### Run the OIDC PKCE flow against IAM
Redirect the user through `https://hanzo.id/v1/iam/oauth/authorize`, then exchange the code at IAM's token endpoint. See [Hanzo IAM](/docs/services/iam) for the full flow.
### Call Base with the IAM-issued JWT
```bash
curl https://base.hanzo.ai/v1/collections/posts/records \
-H "Authorization: Bearer $IAM_JWT"
```
Base validates the bearer against IAM's JWKS and evaluates your collection access rules using the token's claims (`sub`, `email`, `owner`, `isAdmin`).
</Steps>
A token can be rotated without re-running the full flow:
```bash
curl -X POST https://base.hanzo.ai/v1/collections/users/auth-refresh \
-H "Authorization: Bearer $IAM_JWT"
```
## Collections & the REST API
Collections are Base's core data model (`base`, `auth`, or `view` type). Define a schema once and every record operation is exposed automatically.
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/v1/collections/{name}/records` | List/search (filter, sort, page, expand, fields) |
| `GET` | `/v1/collections/{name}/records/{id}` | Get one record |
| `POST` | `/v1/collections/{name}/records` | Create a record |
| `PATCH` | `/v1/collections/{name}/records/{id}` | Update a record |
| `DELETE` | `/v1/collections/{name}/records/{id}` | Delete a record |
| `POST` | `/v1/batch` | Batched transactional writes |
### Create a record
```bash
curl -X POST https://base.hanzo.ai/v1/collections/posts/records \
-H "Authorization: Bearer $IAM_JWT" \
-H "Content-Type: application/json" \
-d '{ "title": "Hello", "body": "World", "published": true }'
```
### Filter, sort, paginate
```bash
curl "https://base.hanzo.ai/v1/collections/posts/records?filter=(published=true%26%26author.name~'jin')&sort=-created&page=1&perPage=20" \
-H "Authorization: Bearer $IAM_JWT"
```
### Manage collections (superuser)
```bash
# Create a collection
curl -X POST https://base.hanzo.ai/v1/collections \
-H "Authorization: Bearer $IAM_ADMIN_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "posts",
"type": "base",
"fields": [
{ "name": "title", "type": "text", "required": true },
{ "name": "body", "type": "editor" },
{ "name": "published", "type": "bool" }
]
}'
```
## Realtime
Subscribe to record changes over Server-Sent Events. Base also offers a WebSocket transport at `/v1/realtime/ws`.
```typescript
import PocketBase from '@hanzoai/js-sdk' // wire-compatible client
const base = new PocketBase('https://base.hanzo.ai') // targets /v1 automatically
base.authStore.save(iamJwt, null) // attach the IAM-issued JWT
base.collection('posts').subscribe('*', (e) => {
console.log(e.action, e.record) // 'create' | 'update' | 'delete'
})
```
Under the hood: open `GET /v1/realtime` (the server returns a `clientId`), then `POST /v1/realtime` with `{ clientId, subscriptions: ["posts", "posts/RECORD_ID"] }`. Subscriptions are gated by each collection's list/view rules.
## File Storage
Upload files directly to record fields. Files are served from local disk in development and from [Hanzo S3](/docs/services/s3) in production.
```bash
# Upload (multipart)
curl -X POST https://base.hanzo.ai/v1/collections/posts/records \
-H "Authorization: Bearer $IAM_JWT" \
-F "title=My Post" \
-F "[email protected]"
# Download
curl "https://base.hanzo.ai/v1/files/posts/RECORD_ID/image.jpg?thumb=100x100"
```
Protected files use a short-lived token from `POST /v1/files/token`.
## Beyond CRUD
Hanzo Base adds first-class endpoints on top of the record API:
| Endpoint | Purpose |
|----------|---------|
| `POST /v1/sql` | Run a parameterized SQL query (superuser) |
| `/v1/tasks` | Durable task & workflow queue (claim, start, complete, fail, signal) |
| `/v1/private` | Per-user private key-value store |
| `/v1/backups` | Create, download, and restore database backups (superuser) |
| `/v1/crons` | List and trigger scheduled jobs (superuser) |
| `/v1/logs` | Request logs (superuser) |
## Access Control
Collection access is governed by filter-expression rules evaluated against the IAM token claims:
```json
{
"listRule": "@request.auth.id != ''",
"viewRule": "@request.auth.id != '' || published = true",
"createRule": "@request.auth.id != ''",
"updateRule": "@request.auth.id = author",
"deleteRule": "@request.auth.id = author || @request.auth.isAdmin = true"
}
```
A request is treated as **superuser** when its IAM token carries an admin claim (`isAdmin`/`isGlobalAdmin`, or an `owner` of `built-in`/`superuser`). The platform superuser (`[email protected]`) is seeded in [Hanzo IAM](/docs/services/iam), not in Base — Base simply honors the admin claim on the token.
## Extending Base
Base embeds multiple in-process runtimes for custom logic — JavaScript (goja), WASM (wazero), Python, and Starlark — plus Go extension via `core.App`:
```go
// Go: register a custom route
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.GET("/v1/custom/stats", func(e *core.RequestEvent) error {
return e.JSON(200, map[string]string{"status": "ok"})
})
return se.Next()
})
```
## SDKs
| Language | Package |
|----------|---------|
| JavaScript / TypeScript | `@hanzoai/js-sdk` |
| Dart / Flutter | `@hanzoai/dart-sdk` |
The wire protocol is PocketBase-compatible for record CRUD and realtime, so existing PocketBase clients work for data operations — only the auth step differs (IAM PKCE instead of local password).
## Self-Hosting
```yaml
# compose.yml
@@ -74,6 +220,8 @@ services:
image: ghcr.io/hanzoai/base:latest
ports:
- "8090:8090"
environment:
IAM_ENDPOINT: https://hanzo.id
volumes:
- base-data:/app/data
restart: unless-stopped
@@ -82,269 +230,21 @@ volumes:
base-data:
```
### Production (Kubernetes)
```bash
kubectl apply -k infra/k8s/base/
```
Production deployments use PostgreSQL via Hanzo SQL and S3 storage via Hanzo S3.
## Collections
Collections are Base's core data model. Define a schema and get a full REST API automatically.
### Create a Collection
```bash
curl -X POST https://base.hanzo.ai/api/collections \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "posts",
"type": "base",
"schema": [
{"name": "title", "type": "text", "required": true},
{"name": "body", "type": "editor"},
{"name": "published", "type": "bool", "default": false},
{"name": "author", "type": "relation", "options": {"collectionId": "users"}}
]
}'
```
### Auto-Generated REST API
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/collections/{name}/records` | List records with filtering, sorting, pagination |
| `GET` | `/api/collections/{name}/records/{id}` | Get single record |
| `POST` | `/api/collections/{name}/records` | Create record |
| `PATCH` | `/api/collections/{name}/records/{id}` | Update record |
| `DELETE` | `/api/collections/{name}/records/{id}` | Delete record |
### Filtering
```bash
# Filter with expressions
curl "https://base.hanzo.ai/api/collections/posts/records?filter=(published=true&&author.name~'john')"
```
## Authentication
Base supports multiple auth methods:
| Method | Description |
|--------|-------------|
| **Email/Password** | Built-in with verification emails |
| **OAuth2** | Google, GitHub, Apple, Discord, and more |
| **Hanzo IAM** | Enterprise SSO via `hanzo.id` OIDC integration |
| **API Keys** | For service-to-service communication |
### Hanzo IAM Integration
```bash
# Configure IAM as OAuth2 provider
curl -X PATCH https://base.hanzo.ai/api/settings \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{
"meta": {
"appName": "My App"
},
"iam": {
"enabled": true,
"clientId": "app-myapp",
"clientSecret": "...",
"authUrl": "https://hanzo.id/oauth/authorize",
"tokenUrl": "https://hanzo.id/oauth/token",
"userInfoUrl": "https://hanzo.id/oauth/userinfo"
}
}'
```
## Realtime
Subscribe to collection changes via Server-Sent Events:
```typescript
import Base from '@hanzo/base-sdk'
const base = new Base('https://base.hanzo.ai')
// Subscribe to changes
base.collection('posts').subscribe('*', (e) => {
console.log(e.action) // 'create' | 'update' | 'delete'
console.log(e.record)
})
// Subscribe to specific record
base.collection('posts').subscribe('RECORD_ID', (e) => {
console.log('Record updated:', e.record)
})
```
## File Storage
Upload files directly to collection records:
```bash
curl -X POST https://base.hanzo.ai/api/collections/posts/records \
-H "Authorization: Bearer $TOKEN" \
-F "title=My Post" \
-F "[email protected]"
```
Files are stored in the configured storage backend (local filesystem for dev, Hanzo S3 for production).
## Cloud Functions
Server-side JavaScript for custom logic:
### Hooks
```javascript
// pb_hooks/posts.pb.js
// Before create hook
onRecordBeforeCreateRequest((e) => {
const record = e.record
record.set('slug', slugify(record.get('title')))
}, 'posts')
// After create hook
onRecordAfterCreateRequest((e) => {
// Send notification, update search index, etc.
$app.logger().info('Post created', 'id', e.record.id)
}, 'posts')
```
### Custom Routes
```javascript
// pb_hooks/api.pb.js
routerAdd('GET', '/api/custom/stats', (e) => {
const posts = $app.dao().findRecordsByFilter('posts', 'published = true')
return e.json(200, { total: posts.length })
})
```
### Scheduled Tasks (Cron)
```javascript
// pb_hooks/cron.pb.js
cronAdd('cleanup', '0 0 * * *', () => {
// Daily cleanup
$app.dao().deleteRecordsByFilter('logs', 'created < @now - 30d')
})
```
## Plugin System
Base's plugin system allows extending the core with custom Go code.
### Plugin Interface
```go
package myplugin
import "github.com/hanzoai/base/core"
func Register(app core.App) error {
// Add custom routes
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.GET("/api/custom", func(c echo.Context) error {
return c.JSON(200, map[string]string{"status": "ok"})
})
return nil
})
// Add hooks
app.OnRecordBeforeCreateRequest("posts").Add(func(e *core.RecordCreateEvent) error {
// Custom validation, transformation, etc.
return nil
})
// Add migrations
app.OnBeforeBootstrap().Add(func(e *core.BootstrapEvent) error {
// Run custom migrations
return nil
})
return nil
}
```
### Built-in Plugins
| Plugin | Description |
|--------|-------------|
| **IAM Auth** | Hanzo IAM OIDC authentication provider |
| **S3 Storage** | Hanzo S3 file storage backend |
| **Search** | Hanzo Search integration for full-text search |
| **ORM** | Hanzo ORM for advanced database operations |
| **Analytics** | Event tracking to Hanzo Insights |
### Creating a Plugin
1. Create a Go module that imports `github.com/hanzoai/base/core`
2. Implement the `Register(app core.App) error` function
3. Add routes, hooks, middleware, or migrations
4. Build with `go build -buildmode=plugin`
5. Place in `pb_plugins/` directory
```go
// main.go
package main
import (
"github.com/hanzoai/base/core"
"github.com/hanzoai/base/plugins/myplugin"
)
func main() {
app := core.NewBaseApp()
myplugin.Register(app)
app.Start()
}
```
## Access Control
Base provides collection-level access rules using filter expressions:
```json
{
"listRule": "@request.auth.id != ''",
"viewRule": "@request.auth.id != '' || published = true",
"createRule": "@request.auth.id != '' && @request.auth.role = 'editor'",
"updateRule": "@request.auth.id = author",
"deleteRule": "@request.auth.id = author || @request.auth.role = 'admin'"
}
```
## SDK Libraries
| Language | Package |
|----------|---------|
| **JavaScript** | `@hanzo/base-sdk` |
| **Go** | `github.com/hanzoai/base/sdk` |
| **Python** | `hanzoai-base` |
| **Dart/Flutter** | `hanzo_base` |
The admin UI is available at `http://localhost:8090/_/`. The API base path defaults to `/v1` and can be moved with `BASE_API_PREFIX` for multi-app gateways.
## Related Services
<Cards>
<Card title="Hanzo IAM" href="/docs/services/iam">
Identity and access management for SSO integration
The identity provider Base delegates all authentication to
</Card>
<Card title="Hanzo ORM" href="/docs/services/orm">
Shared ORM for advanced database operations
<Card title="Hanzo KMS" href="/docs/services/kms">
Per-tenant database encryption keys
</Card>
<Card title="Hanzo S3" href="/docs/services/s3">
S3-compatible object storage for file uploads
Object storage backend for file uploads
</Card>
<Card title="Hanzo Search" href="/docs/services/search">
Full-text search integration for collections
<Card title="Hanzo PaaS" href="/docs/services/platform">
Deploy and operate Base on your clusters
</Card>
</Cards>
@@ -23,15 +23,15 @@ They are populated from KMS at deploy time.
|----------|----------|-------------|---------|
| `IAM_ENDPOINT` | yes | IAM server URL. In-cluster: `http://iam:8000`. Public: `https://hanzo.id`. | `https://hanzo.id` |
| `IAM_ISSUER` | optional | OIDC issuer URL used for token validation. Defaults to `IAM_ENDPOINT`. | `https://hanzo.id` |
| `IAM_CLIENT_ID` | yes | OAuth client ID issued for this application. | `app-myapp` |
| `IAM_CLIENT_ID` | yes | OAuth client ID issued for this application. | `hanzo-myapp` |
| `IAM_CLIENT_SECRET` | yes | OAuth client secret. Must come from KMS, never plaintext. | `${IAM_APP_MYAPP_CLIENT_SECRET}` |
| `IAM_ORG` | optional | Organization slug this app is scoped to. | `hanzo` |
| `IAM_APP` | optional | Application name as registered in IAM. | `app-myapp` |
| `IAM_APP` | optional | Application name as registered in IAM. | `hanzo-myapp` |
| `IAM_CERT` | optional | PEM-encoded signing certificate (for offline JWT validation). | (PEM blob) |
| `IAM_USERINFO_URL` | optional | Override for the OIDC userinfo URL. Defaults to `${IAM_ENDPOINT}/oauth/userinfo`. | `https://hanzo.id/oauth/userinfo` |
| `IAM_USERINFO_URL` | optional | Override for the OIDC userinfo URL. Defaults to `${IAM_ENDPOINT}/v1/iam/oauth/userinfo`. | `https://hanzo.id/v1/iam/oauth/userinfo` |
The `<org>-<app>` naming scheme is canonical for `IAM_APP` values: `hanzo-team`,
`hanzo-tasks`, `lux-cloud`, `zoo-research`, etc.
`hanzo-tasks`, `hanzo-cloud`, `hanzo-brain`, etc.
## RFC-compliant OAuth/OIDC endpoints
@@ -46,15 +46,15 @@ available for a non-OIDC endpoint.
| Purpose | Path |
|---------|------|
| Authorization | `/oauth/authorize` |
| Token | `/oauth/token` |
| Introspection | `/oauth/introspect` |
| Revocation | `/oauth/revoke` |
| UserInfo | `/oauth/userinfo` |
| Device authorization | `/oauth/device` |
| End-session / logout | `/oauth/logout` |
| Discovery | `/.well-known/openid-configuration` |
| JSON Web Key Set | `/.well-known/jwks` |
| Authorization | `/v1/iam/oauth/authorize` |
| Token | `/v1/iam/oauth/token` |
| Introspection | `/v1/iam/oauth/introspect` |
| Revocation | `/v1/iam/oauth/revoke` |
| UserInfo | `/v1/iam/oauth/userinfo` |
| Device authorization | `/v1/iam/oauth/device` |
| End-session / logout | `/v1/iam/oauth/logout` |
| Discovery | `/.well-known/openid-configuration` (alias of `/v1/iam/.well-known/openid-configuration`) |
| JSON Web Key Set | `/v1/iam/.well-known/jwks` |
A consumer that reads `IAM_ENDPOINT` constructs every endpoint by
concatenation — there are no other env vars for individual OAuth URLs.
@@ -67,9 +67,9 @@ key for an application's secret follows the convention
| KMS key | Consumes |
|---------|----------|
| `IAM_APP_HANZO_CLIENT_SECRET` | app-hanzo |
| `IAM_APP_CLOUD_CLIENT_SECRET` | app-cloud |
| `IAM_APP_COMMERCE_CLIENT_SECRET` | app-commerce |
| `IAM_APP_HANZO_CLIENT_SECRET` | hanzo-app |
| `IAM_APP_CLOUD_CLIENT_SECRET` | hanzo-cloud |
| `IAM_APP_COMMERCE_CLIENT_SECRET` | hanzo-commerce |
The KMS operator syncs these into a service-scoped K8s `Secret`, and the
consumer pod reads them as `IAM_CLIENT_SECRET` from that Secret. See
@@ -84,10 +84,10 @@ services:
image: ghcr.io/hanzoai/myapp:v1.0.0
environment:
IAM_ENDPOINT: https://hanzo.id
IAM_CLIENT_ID: app-myapp
IAM_CLIENT_ID: hanzo-myapp
IAM_CLIENT_SECRET: ${IAM_APP_MYAPP_CLIENT_SECRET}
IAM_ORG: hanzo
IAM_APP: app-myapp
IAM_APP: hanzo-myapp
```
## Example: Kubernetes
@@ -105,7 +105,7 @@ spec:
- name: IAM_ENDPOINT
value: http://iam.hanzo.svc.cluster.local:8000
- name: IAM_CLIENT_ID
value: app-myapp
value: hanzo-myapp
- name: IAM_CLIENT_SECRET
valueFrom:
secretKeyRef:
@@ -125,9 +125,9 @@ const iam = {
issuer: process.env.IAM_ISSUER ?? process.env.IAM_ENDPOINT!,
};
const tokenUrl = `${iam.endpoint}/oauth/token`;
const tokenUrl = `${iam.endpoint}/v1/iam/oauth/token`;
const userInfoUrl =
process.env.IAM_USERINFO_URL ?? `${iam.endpoint}/oauth/userinfo`;
process.env.IAM_USERINFO_URL ?? `${iam.endpoint}/v1/iam/oauth/userinfo`;
```
```python
@@ -138,9 +138,9 @@ IAM_CLIENT_ID = os.environ["IAM_CLIENT_ID"]
IAM_CLIENT_SECRET = os.environ["IAM_CLIENT_SECRET"]
IAM_ISSUER = os.environ.get("IAM_ISSUER", IAM_ENDPOINT)
TOKEN_URL = f"{IAM_ENDPOINT}/oauth/token"
TOKEN_URL = f"{IAM_ENDPOINT}/v1/iam/oauth/token"
USERINFO_URL = os.environ.get(
"IAM_USERINFO_URL", f"{IAM_ENDPOINT}/oauth/userinfo"
"IAM_USERINFO_URL", f"{IAM_ENDPOINT}/v1/iam/oauth/userinfo"
)
```
+165 -177
View File
@@ -5,229 +5,217 @@ description: Identity and Access Management for the Hanzo AI Platform
# Hanzo IAM
Hanzo IAM (Identity and Access Management) provides unified authentication and authorization across all Hanzo services. It supports OAuth2, OIDC, SAML, Kerberos/SPNEGO, LDAP, Web3, and MPC authentication.
Hanzo IAM is the identity provider for the Hanzo platform, served at **`hanzo.id`**. It is a standards-compliant OpenID Connect (OIDC) and OAuth 2.0 authorization server that issues the JWTs every other Hanzo service trusts. It supports password, social OAuth2, Web3 wallet, WebAuthn/passkey, LDAP, SAML, device-code, and MFA authentication.
| | |
|---|---|
| **Issuer** | `https://hanzo.id` |
| **API prefix** | `/v1/iam` |
| **Discovery** | `https://hanzo.id/.well-known/openid-configuration` |
| **JWKS** | `https://hanzo.id/v1/iam/.well-known/jwks` |
| **Source** | [github.com/hanzoai/iam](https://github.com/hanzoai/iam) |
<Callout>
Every Hanzo IAM endpoint lives under the single canonical prefix `/v1/iam`. The standard discovery path `/.well-known/openid-configuration` is served as a back-compatible alias so off-the-shelf OIDC clients work out of the box.
</Callout>
## Features
- **OAuth2/OIDC**: Standard OpenID Connect flows with regex/wildcard scope support
- **Kerberos/SPNEGO**: Enterprise SSO via Kerberos authentication with per-organization realm configuration
- **LDAP**: Directory integration with configurable attribute filtering per organization
- **SAML**: SAML 2.0 service provider and identity provider
- **OAuth2 / OIDC**: Authorization Code (+ PKCE), Client Credentials, Refresh Token, Device Code, and Token Exchange grants
- **Per-host white-label**: one deployment serves many domains, each with its own issuer, branding, and JWKS
- **Web3 Authentication**: MetaMask, WalletConnect, Coinbase, Rainbow
- **MPC Authentication**: Multi-party computation for secure key recovery
- **Multi-tenancy**: Organizations, applications, and user isolation
- **SSO**: Single sign-on across all Hanzo services
- **RBAC**: Role-based access control
- **MFA**: TOTP, WebAuthn, SMS, Email verification
- **WebAuthn / Passkeys**: FIDO2 passwordless authentication with hardware keys or biometrics
- **LDAP & SAML**: Directory integration and SAML 2.0 SP/IdP
- **MFA**: TOTP, WebAuthn, SMS, and email verification
- **Post-quantum tokens**: JWT signing with `MLDSA65` (ML-DSA-65, FIPS 204) in addition to `RS256`/`ES256`
- **Multi-tenancy**: Organizations, applications, and user isolation with RBAC
## Endpoints
## OIDC Endpoints
| Environment | URL |
|-------------|-----|
| Production | `https://iam.hanzo.ai` |
| Staging | `https://stg.iam.hanzo.ai` |
All endpoints are under `https://hanzo.id/v1/iam`.
## Quick Start
| Endpoint | Path |
|----------|------|
| Discovery | `GET /.well-known/openid-configuration` (alias) · `GET /v1/iam/.well-known/openid-configuration` |
| JWKS | `GET /v1/iam/.well-known/jwks` |
| Authorize | `GET /v1/iam/oauth/authorize` |
| Token | `POST /v1/iam/oauth/token` |
| Userinfo | `GET /v1/iam/oauth/userinfo` |
| Logout / end-session | `GET,POST /v1/iam/oauth/logout` |
| Introspect (RFC 7662) | `POST /v1/iam/oauth/introspect` |
| Revoke (RFC 7009) | `POST /v1/iam/oauth/revoke` |
| Device code | `POST /v1/iam/oauth/device` |
| Dynamic client registration | `POST /v1/iam/oauth/register` |
### 1. Register Your Application
## Applications: the `<org>-<app>` convention
Create an application in Hanzo IAM to get client credentials:
Every consumer of Hanzo IAM registers an **application**, and the application's name follows one rule:
```
app slug = <org>-<app>
```
For example: `hanzo-brain`, `hanzo-cloud`, `hanzo-chat`, `acme-verify`. The organization that owns the data goes in the `organization` field; the app name is namespaced with the org so it stays globally unique. Your application's `client_id` and the `aud` (audience) claim of issued tokens both default to this `<org>-<app>` slug.
### Register an application
```bash
curl -X POST https://iam.hanzo.ai/api/add-application \
-H "Authorization: Bearer $ADMIN_TOKEN" \
curl -X POST https://hanzo.id/v1/iam/add-application \
-H "Authorization: Bearer $IAM_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"owner": "admin",
"name": "my-app",
"displayName": "My Application",
"name": "hanzo-brain",
"displayName": "Hanzo Brain",
"organization": "hanzo",
"redirectUris": ["https://myapp.com/callback"],
"enablePassword": true,
"enableSignUp": true,
"enableWebAuthn": true
"cert": "cert-built-in",
"tokenFormat": "JWT",
"grantTypes": ["authorization_code", "refresh_token"],
"redirectUris": ["https://brain.hanzo.ai/callback"]
}'
```
### 2. Implement OAuth2 Flow
`clientId` and `clientSecret` are generated automatically if omitted. Store the returned `clientSecret` in [Hanzo KMS](/docs/services/kms) — never in source or plaintext config.
```typescript
// Redirect to authorization
const authUrl = new URL('https://iam.hanzo.ai/oauth/authorize')
authUrl.searchParams.set('client_id', CLIENT_ID)
authUrl.searchParams.set('redirect_uri', REDIRECT_URI)
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('scope', 'openid profile email')
authUrl.searchParams.set('state', generateState())
<Callout type="info">
For idempotent, declarative provisioning (CI/CD, operators), use `POST /v1/iam/admin/applications/upsert` with an `Authorization: Bootstrap <token>` header instead of a session/admin JWT.
</Callout>
window.location.href = authUrl.toString()
## Authorization Code + PKCE Flow
This is the recommended flow for web apps, SPAs, and mobile clients.
<Steps>
### Discover
```bash
curl https://hanzo.id/.well-known/openid-configuration
```
### 3. Exchange Code for Token
The `issuer` and every endpoint come back scoped to the requesting host (`hanzo.id`).
### Redirect to authorize
```
https://hanzo.id/v1/iam/oauth/authorize
?response_type=code
&client_id=hanzo-brain
&redirect_uri=https://brain.hanzo.ai/callback
&scope=openid%20profile%20email
&state=RANDOM_STATE
&code_challenge=<S256(verifier)>
&code_challenge_method=S256
```
The user authenticates on the branded login page and is redirected back with `?code=...&state=...`.
### Exchange the code for tokens
```bash
curl -X POST https://hanzo.id/v1/iam/oauth/token \
-d grant_type=authorization_code \
-d code=$AUTH_CODE \
-d redirect_uri=https://brain.hanzo.ai/callback \
-d client_id=hanzo-brain \
-d client_secret=$CLIENT_SECRET \
-d code_verifier=$PKCE_VERIFIER
```
Response:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
```
### Get user info
```bash
curl https://hanzo.id/v1/iam/oauth/userinfo \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
</Steps>
## Validating tokens in a resource server
Any Hanzo service validates a `hanzo.id` token **offline**: fetch the JWKS once, then verify each request locally.
1. Fetch the signing keys from `https://hanzo.id/v1/iam/.well-known/jwks` (cache by `kid`).
2. Verify the JWT signature (`RS256` by default).
3. Require `iss == https://hanzo.id`.
4. Require `aud == <org>-<app>` (your own `client_id`).
5. Read `sub` (the user ID) and `owner` (the user's organization) to scope data.
```typescript
const response = await fetch('https://iam.hanzo.ai/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code: authorizationCode,
redirect_uri: REDIRECT_URI
import { createRemoteJWKSet, jwtVerify } from 'jose'
const JWKS = createRemoteJWKSet(new URL('https://hanzo.id/v1/iam/.well-known/jwks'))
export async function verify(token: string) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://hanzo.id',
audience: 'hanzo-brain', // your <org>-<app> client_id
})
})
const { access_token, refresh_token, id_token } = await response.json()
return { userId: payload.sub, org: payload.owner as string }
}
```
### 4. Get User Info
The Hanzo Gateway performs this validation at the edge and injects trusted `X-Org-Id`, `X-User-Id`, and `X-User-Email` headers (stripping any client-supplied identity headers) for upstream services.
```typescript
const userInfo = await fetch('https://iam.hanzo.ai/oauth/userinfo', {
headers: { 'Authorization': `Bearer ${access_token}` }
}).then(r => r.json())
## White-labeling by domain
// userInfo contains: sub, name, email, picture, etc.
```
Hanzo IAM is multi-tenant white-label by design. A single deployment serves many domains:
## Organizations
- **Per-host issuer** — the issuer, authorization endpoint, and JWKS are derived per request from `X-Forwarded-Host` (set by [Hanzo Ingress](/docs/services/ingress)). Each domain returns its own discovery document.
- **Per-app branding** — logo, title, favicon, theme color, and custom CSS are configured on each organization and application, and resolved on the login page by `client_id` (or by org in the path, `/login/{org}`).
Hanzo IAM supports multi-tenant organizations:
| Organization | Domain | Theme Color |
|--------------|--------|-------------|
| hanzo | hanzo.ai | #fd4444 (Red) |
| zoo | zoo.ngo | #10b981 (Green) |
| lux | lux.network | #8b5cf6 (Purple) |
| pars | pars.ai | #3b82f6 (Blue) |
## Applications
Pre-configured applications:
| Application | Organization | Description |
|-------------|--------------|-------------|
| app-hanzo | hanzo | Main Hanzo AI Platform |
| app-cloud | hanzo | Hanzo Cloud Console |
| app-commerce | hanzo | Hanzo Commerce Platform |
| app-console | hanzo | Hanzo Observability Console |
| app-platform | hanzo | Hanzo PaaS Platform |
| app-zoo | zoo | Zoo Labs Foundation |
| app-lux | lux | Lux Network |
| app-pars | pars | Pars AI Platform |
| app-sign | hanzo | Document Signing |
| app-dataroom | hanzo | Secure Document Sharing |
| app-captable | hanzo | Equity Management |
This is how `hanzo.id` presents Hanzo branding while the same engine can serve entirely separate, independently-branded identity surfaces for other tenants.
## Authentication Methods
### Password Authentication
| Method | Notes |
|--------|-------|
| **Password** | `argon2id` hashing |
| **OAuth2 social** | GitHub, Google, Microsoft, Apple, Discord, X |
| **Web3 wallets** | MetaMask, WalletConnect, Coinbase, Rainbow |
| **WebAuthn / Passkeys** | FIDO2 hardware keys and biometrics |
| **LDAP** | Per-organization directory integration |
| **SAML 2.0** | Service provider and identity provider |
| **Device code** | CLIs and input-constrained devices |
| **MFA** | TOTP, WebAuthn, SMS, email |
Standard username/password with argon2id hashing.
### OAuth2 Providers
- GitHub
- Google
- Microsoft
- Apple
- Discord
- Twitter/X
### Web3 Wallets
- MetaMask
- WalletConnect
- Coinbase Wallet
- Rainbow
### WebAuthn/Passkeys
FIDO2 passwordless authentication with hardware keys or biometrics.
## API Reference
See the [IAM API documentation](/docs/services/iam/api) for the complete API reference.
## SDK Integration
### JavaScript/TypeScript
## SDK
```typescript
import { HanzoAuth } from '@hanzo/auth'
import { signIn } from '@hanzo/iam/browser'
const auth = new HanzoAuth({
clientId: 'your-client-id',
redirectUri: 'https://yourapp.com/callback',
organization: 'hanzo'
await signIn({
iamUrl: 'https://hanzo.id',
clientId: 'hanzo-brain', // your <org>-<app> slug
redirectUri: 'https://brain.hanzo.ai/callback',
})
// Login
await auth.login()
// Get current user
const user = await auth.getUser()
// Logout
await auth.logout()
```
### Python
```python
from hanzoai.auth import HanzoAuth
auth = HanzoAuth(
client_id="your-client-id",
client_secret="your-client-secret",
organization="hanzo"
)
# Get access token (client credentials)
token = auth.get_access_token()
# Verify token
claims = auth.verify_token(token)
```
## OIDC Flows
Hanzo IAM supports the full OpenID Connect specification. Choose the right flow for your application:
| Application Type | Flow | Guide |
|-----------------|------|-------|
| Web app (server-side) | Authorization Code | [OIDC Flows](/docs/services/iam/how-to-connect/oidc-flows) |
| SPA / Mobile | Authorization Code + PKCE | [OIDC Flows](/docs/services/iam/how-to-connect/oidc-flows#authorization-code--pkce) |
| Service / API | Client Credentials | [OIDC Flows](/docs/services/iam/how-to-connect/oidc-flows#client-credentials-flow) |
| CLI / IoT | Device Code | [OIDC Flows](/docs/services/iam/how-to-connect/oidc-flows#device-code-flow) |
**Discovery URL:** `https://hanzo.id/.well-known/openid-configuration`
All Hanzo services authenticate via IAM using the `owner` claim in the JWT to scope data to the correct organization.
## Next Steps
<Cards>
<Card title="Configuration" href="/docs/services/iam/configuration">
Canonical `IAM_*` environment variable contract for consumer services
</Card>
<Card title="OIDC Flows" href="/docs/services/iam/how-to-connect/oidc-flows">
Step-by-step guide to authorization code, PKCE, client credentials, and device code flows
</Card>
<Card title="OIDC Discovery" href="/docs/services/iam/how-to-connect/oidc-client">
Discovery endpoints and OIDC client configuration
</Card>
<Card title="WebAuthn / FIDO2" href="/docs/services/iam/how-to-connect/webauthn">
Passwordless authentication with hardware keys and biometrics
</Card>
<Card title="SAML" href="/docs/services/iam/how-to-connect/saml/overview">
SAML 2.0 service provider and identity provider
</Card>
<Card title="MCP Auth" href="/docs/services/iam/mcp-auth/overview">
Model Context Protocol authentication for AI agents
The canonical `IAM_*` environment variable contract for consumer services
</Card>
<Card title="KMS Integration" href="/docs/services/iam/kms-integration">
How IAM uses KMS for secret management
How IAM stores and reads its own secrets from Hanzo KMS
</Card>
<Card title="MPC Auth" href="/docs/services/iam/mpc">
Multi-party computation authentication for secure key recovery
</Card>
<Card title="Hanzo KMS" href="/docs/services/kms">
Secret management for client secrets and signing keys
</Card>
</Cards>
@@ -1,57 +1,45 @@
---
title: KMS Integration
description: How Hanzo IAM uses KMS for secure secret management
description: How Hanzo IAM stores and reads its secrets from Hanzo KMS
---
# KMS Integration
Hanzo IAM stores all sensitive configuration — OAuth client secrets, social provider credentials, admin passwords — in [Hanzo KMS](/docs/services/kms). No secrets are hardcoded in deployment manifests or environment variables.
Hanzo IAM keeps all sensitive configuration — OAuth client secrets, social-provider credentials, and admin passwords — in [Hanzo KMS](/docs/services/kms). Nothing sensitive is hard-coded in manifests or environment variables.
## Architecture
## How it works
IAM authenticates to KMS with a **machine identity** and reads its secrets through two complementary paths:
1. **Direct fetch at startup** — IAM logs in to KMS (`POST /v1/kms/auth/login`) with its machine-identity `clientId`/`clientSecret` and reads the secrets it needs.
2. **KMSSecret CRD** — the kms-operator keeps an `iam-secrets` Kubernetes Secret continuously in sync as a fallback for workloads that consume secrets as env vars.
```
┌──────────────────────────────────────────────────────────────┐
│ Hanzo KMS (kms.hanzo.ai) │
│ │
│ Project: hanzo-iam │
│ Environment: prod │
│ Secrets: 23 entries (client secrets, OAuth, admin creds) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Machine Identity: iam-service │ │
│ │ Auth: Universal Auth (clientId + clientSecret) │ │
│ │ Role: Viewer on hanzo-iam project │ │
│ └─────────────────────────────────────────────────────────┘ │
└──────────────┬───────────────────────────┬───────────────────┘
│ │
Direct SDK fetch KMSSecret CRD (backup)
(startup time) (kms-operator sync)
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────┐
│ IAM Pod │ │ iam-secrets K8s Secret │
│ (reads from KMS │ │ (auto-synced every 60s) │
│ via kms-go SDK) │ │ │
└──────────────────────┘ └──────────────────────────┘
┌──────────────────────────────────────────────┐
│ Hanzo KMS (kms.hanzo.ai) │
│ org: hanzo-iam env: production │
│ machine identity: iam-service (read-only) │
└───────────────┬───────────────────┬───────────┘
direct login KMSSecret CRD
(startup) (operator sync)
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ IAM pod │ │ iam-secrets (K8s) │
└──────────────┘ └──────────────────┘
```
IAM uses **two complementary mechanisms**:
## Machine identity
1. **Direct SDK fetch** — At startup, IAM uses the `kms-go` SDK to pull secrets directly from KMS. This is the primary path.
2. **KMSSecret CRD** — The kms-operator syncs secrets to a `iam-secrets` K8s Secret every 60 seconds as a backup/fallback.
## Machine Identity Setup
IAM authenticates to KMS using a **Machine Identity** with Universal Auth credentials:
IAM uses a dedicated, least-privilege machine identity so its secrets are isolated from other services:
| Field | Value |
|-------|-------|
| Identity name | `iam-service` |
| Auth method | Universal Auth |
| Project access | `hanzo-iam` (Viewer) |
| Credential mount | `/etc/kms/auth/clientId`, `/etc/kms/auth/clientSecret` |
| Identity | `iam-service` |
| Access | read-only on the `hanzo-iam` org |
| Credential | `clientId` / `clientSecret`, mounted from a K8s Secret |
The credentials are stored in a K8s Secret and mounted as files:
The credentials live in a Kubernetes Secret — only the references, never the values, are committed:
```yaml
apiVersion: v1
@@ -61,58 +49,23 @@ metadata:
namespace: hanzo
type: Opaque
stringData:
clientId: "" # KMS Machine Identity Client ID
clientSecret: "" # KMS Machine Identity Client Secret
clientId: "" # populated out-of-band from KMS/IAM, never committed
clientSecret: ""
```
## Secrets Inventory
## What IAM stores in KMS
The `hanzo-iam` KMS project contains 23 secrets:
IAM's secrets are organized under its `hanzo-iam` org in KMS:
### Application Client Secrets
| Category | Examples |
|----------|----------|
| **Application client secrets** | One per registered `<org>-<app>` application (e.g. `hanzo-cloud`, `hanzo-chat`, `hanzo-platform`) |
| **Social provider credentials** | GitHub, Google, Microsoft, Apple OAuth client IDs and secrets |
| **Admin credentials** | The seeded organization administrator password |
Every IAM-registered application has its OAuth client secret stored in KMS:
Each application's OAuth `clientSecret` is stored under a key named for its `<org>-<app>` slug — never inlined into `init_data.json` or a manifest.
| KMS Key | Application |
|---------|-------------|
| `IAM_APP_HANZO_CLIENT_SECRET` | app-hanzo (hanzo.ai) |
| `IAM_APP_CLOUD_CLIENT_SECRET` | app-cloud (cloud.hanzo.ai) |
| `IAM_APP_COMMERCE_CLIENT_SECRET` | app-commerce (api.hanzo.ai) |
| `IAM_APP_CONSOLE_CLIENT_SECRET` | app-console (console.hanzo.ai) |
| `IAM_APP_CHAT_CLIENT_SECRET` | app-chat (hanzo.chat) |
| `IAM_APP_PLATFORM_CLIENT_SECRET` | app-platform (platform.hanzo.ai) |
| `IAM_APP_ZOO_CLIENT_SECRET` | app-zoo (zoo.ngo) |
| `IAM_APP_LUX_CLIENT_SECRET` | app-lux (lux.network) |
| `IAM_APP_PARS_CLIENT_SECRET` | app-pars (pars.ai) |
| `IAM_APP_HANZOBOT_CLIENT_SECRET` | app-hanzobot (app.hanzo.bot) |
| `IAM_APP_BOOTNODE_CLIENT_SECRET` | app-bootnode |
| `IAM_APP_HANZO_WEB3_CLIENT_SECRET` | app-hanzo-web3 |
| `IAM_APP_LUX_WEB3_CLIENT_SECRET` | app-lux-web3 |
| `IAM_APP_ZOO_WEB3_CLIENT_SECRET` | app-zoo-web3 |
| `IAM_APP_TEAM_CLIENT_SECRET` | app-team |
| `IAM_APP_ADNEXUS_CLIENT_SECRET` | app-adnexus |
| `IAM_APP_ZT_CONSOLE_CLIENT_SECRET` | app-zt-console |
### Social Provider Credentials
| KMS Key | Provider |
|---------|----------|
| `IAM_GITHUB_CLIENT_ID` | GitHub OAuth |
| `IAM_GITHUB_CLIENT_SECRET` | GitHub OAuth |
| `IAM_GOOGLE_CLIENT_ID` | Google OAuth |
| `IAM_GOOGLE_CLIENT_SECRET` | Google OAuth |
### Admin Credentials
| KMS Key | Purpose |
|---------|---------|
| `IAM_ADMIN_PASSWORD` | Built-in admin account |
| `IAM_Z_HANZO_PASSWORD` | Hanzo org admin |
| `IAM_Z_LUX_PASSWORD` | Lux org admin |
## KMSSecret CRD Configuration
The backup sync uses the KMSSecret custom resource:
## Syncing IAM's secrets to Kubernetes
```yaml
apiVersion: secrets.lux.network/v1alpha1
@@ -121,7 +74,7 @@ metadata:
name: iam-kms-sync
namespace: hanzo
spec:
hostAPI: http://kms.hanzo.svc.cluster.local/api
hostAPI: https://kms.hanzo.ai
resyncInterval: 60
authentication:
universalAuth:
@@ -129,101 +82,52 @@ spec:
secretName: iam-kms-auth
secretNamespace: hanzo
secretsScope:
projectSlug: hanzo-iam
envSlug: prod
projectSlug: hanzo-iam # IAM's own org
envSlug: production
secretsPath: /
keys:
- IAM_ADMIN_PASSWORD
- HANZO_CLOUD_CLIENT_SECRET
- GITHUB_CLIENT_SECRET
managedSecretReference:
secretName: iam-secrets
secretNamespace: hanzo
secretType: Opaque
creationPolicy: Orphan
```
Key fields:
## How other services consume an IAM secret
| Field | Description |
|-------|-------------|
| `hostAPI` | KMS internal endpoint (in-cluster, no TLS needed) |
| `resyncInterval` | Sync frequency in seconds |
| `credentialsRef` | K8s secret containing Universal Auth credentials |
| `secretsScope` | KMS project, environment, and path to sync |
| `managedSecretReference` | Target K8s Secret to write synced values into |
## Adding a New Application Secret
When registering a new application in IAM:
1. **Create the secret in KMS** at `kms.hanzo.ai` under the `hanzo-iam` project:
```
Key: IAM_APP_MYAPP_CLIENT_SECRET
Value: <generated-secret>
Environment: prod
```
2. **Reference it in IAM's init_data.json** (or update via API):
```json
{
"name": "app-myapp",
"clientSecret": "${IAM_APP_MYAPP_CLIENT_SECRET}"
}
```
3. **The KMSSecret CRD** auto-syncs the new secret to the `iam-secrets` K8s Secret within 60 seconds.
## How Other Services Consume IAM Secrets
Each service that authenticates via IAM OIDC stores its own copy of the client secret in KMS. For example, Hanzo Chat stores `OPENID_CLIENT_SECRET` under the `/chat` path in the shared `secrets-639-c` project:
```yaml
# chat/kms-secrets.yaml
apiVersion: secrets.lux.network/v1alpha1
kind: KMSSecret
metadata:
name: chat-kms-sync
spec:
authentication:
universalAuth:
credentialsRef:
secretName: universal-auth-credentials
secretsScope:
projectSlug: secrets-639-c
envSlug: prod
secretsPath: /chat
managedSecretReference:
secretName: chat-secrets
```
The deployment then references the synced K8s Secret:
A service that authenticates via IAM OIDC stores its own `client_secret` in KMS and syncs it the same way. For example, an app reads its OIDC client secret from a synced Secret:
```yaml
env:
- name: OPENID_CLIENT_SECRET
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: chat-secrets
key: OPENID_CLIENT_SECRET
name: app-secrets
key: OIDC_CLIENT_SECRET
```
This pattern is consistent across all services — see [KMS Service Integration](/docs/services/kms/service-integration) for the full list.
See [KMS Service Integration](/docs/services/kms/service-integration) for the full pattern.
## Security Model
## Security model
- **No plaintext secrets in manifests** — All K8s Secrets are populated by KMS, never checked into git
- **Least-privilege access** — IAM's Machine Identity has Viewer (read-only) access to its own project only
- **Separate projects** — IAM secrets (`hanzo-iam`) are isolated from service secrets (`secrets-639-c`)
- **In-cluster communication** — KMS API is accessed via `kms.hanzo.svc.cluster.local`, never exposed externally for secret sync
- **60-second resync** — Rotated secrets propagate to K8s within one minute
- **Audit trail** — All secret reads/writes are logged in KMS audit log
- **No plaintext secrets in git** — Kubernetes Secrets are populated from KMS, never committed.
- **Least privilege** — IAM's machine identity is read-only and scoped to its own org.
- **Isolation** — IAM's secrets live in a separate KMS org from service secrets.
- **Audit trail** — every read and write is logged in KMS.
## Next Steps
<Cards>
<Card title="KMS Overview" href="/docs/services/kms">
Hanzo KMS architecture and features
Hanzo KMS architecture and the secret API
</Card>
<Card title="KMS Service Integration" href="/docs/services/kms/service-integration">
How all Hanzo services consume secrets from KMS
How every Hanzo service consumes secrets from KMS
</Card>
<Card title="IAM Applications" href="/docs/services/iam/application">
Register and configure IAM applications
<Card title="Machine Identities" href="/docs/services/kms/universal-auth">
Service-to-KMS authentication
</Card>
</Cards>
+119 -321
View File
@@ -1,373 +1,171 @@
---
title: Hanzo KMS
description: Key Management Service for secrets, API keys, and encryption
description: Key Management Service for secrets, machine-identity access, and threshold signing keys
---
# Hanzo KMS
Hanzo KMS is an open-source Key Management Service for managing secrets, API keys, certificates, and encryption keys across infrastructure. It provides enterprise-grade secret management with MPC signer integration.
Hanzo KMS is the key management service for the Hanzo platform. It stores secrets and API keys, hands them to workloads through short-lived machine-identity tokens, syncs them into Kubernetes, and manages threshold signing keys backed by [Hanzo MPC](/docs/services/mpc).
## Features
| | |
|---|---|
| **API** | `https://kms.hanzo.ai` |
| **API prefix** | `/v1/kms` |
| **Auth** | Machine-identity login → bearer JWT (verified against [Hanzo IAM](/docs/services/iam)) |
| **K8s sync** | `KMSSecret` CRD via the kms-operator |
| **Source** | [github.com/hanzoai/kms](https://github.com/hanzoai/kms) |
- **Secrets Management**: Store and manage secrets, API keys, certificates
- **Environment Sync**: Sync secrets across development, staging, production
- **MPC Integration**: Threshold signatures via Hanzo MPC Signer
- **Dynamic Secrets**: Auto-rotating database credentials (PostgreSQL, MySQL, MongoDB, Redis, ClickHouse)
- **PAM Discovery**: Automated discovery of privileged accounts and resources with Active Directory support
- **Certificate Management**: PKI certificate lifecycle with metadata, Sectigo/DigiCert CA integration
- **Secret Versioning**: Full history and rollback
- **Audit Logging**: Complete access audit trail
- **RBAC**: Role-based access control with granular project permissions
- **Sub-Organizations**: Hierarchical organization management with membership delegation
- **Kubernetes Operator**: Native K8s secret injection
<Callout>
Hanzo KMS exposes a single surface under `/v1/kms`. Secrets are addressed by **org / path / name** with the environment as a query parameter. There is no plaintext-secret-in-config anywhere — workloads authenticate with a machine identity and read values at runtime.
</Callout>
## Architecture
## Authentication
```
┌─────────────────────────────────────────────────────────────────────────┐
│ HANZO KMS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ KMS Control Plane │ │
│ │ ┌──────────┬─────────────┬──────────────┬─────────────────┐ │ │
│ │ │ Policy │ Approvals │ Audit Log │ Key Registry │ │ │
│ │ └────┬─────┴──────┬──────┴───────┬──────┴───────┬─────────┘ │ │
│ └───────┼────────────┼──────────────┼──────────────┼─────────────┘ │
│ │ │ │ │ │
│ ┌───────▼────────────▼──────────────▼──────────────▼─────────────┐ │
│ │ Unified Signing API │ │
│ └───────┬────────────┬──────────────┬──────────────┬─────────────┘ │
│ │ │ │ │ │
│ ┌───────▼───┐ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ │
│ │ HSM │ │ MPC │ │ Software │ │ AWS/ │ │
│ │ Signer │ │ Signer │ │ Signer │ │ GCP │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Hanzo MPC Cluster (Private BFT Chain) │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ Node 0 │ │ Node 1 │ │ Node 2 │ │ Node 3 │ │ Node 4 │ │ │
│ │ │ share₀ │ │ share₁ │ │ share₂ │ │ share₃ │ │ share₄ │ │ │
│ │ │Ed25519 │ │Ed25519 │ │Ed25519 │ │Ed25519 │ │Ed25519 │ │ │
│ │ │ML-DSA │ │ML-DSA │ │ML-DSA │ │ML-DSA │ │ML-DSA │ │ │
│ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ │
│ │ Dual-cert finality: Ed25519 + ML-DSA-65 (3-of-5) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
## Endpoints
| Environment | URL |
|-------------|-----|
| Production | `https://kms.hanzo.ai` |
| Staging | `https://stg.kms.hanzo.ai` |
## Quick Start
### 1. Development Setup
KMS uses **machine identities**: a non-human `clientId` + `clientSecret` minted in [Hanzo IAM](/docs/services/iam). Exchange them for a short-lived access token, then send that token as a bearer on every request.
```bash
cd ~/work/hanzo/kms
# Copy environment
cp .env.dev.example .env
# Start with Docker Compose
docker compose -f docker-compose.dev.yml up
# Or run separately
cd backend && npm run dev
cd frontend && npm run dev
# Log in (clientId/clientSecret come from a K8s Secret or your secret store — never inline them)
ACCESS_TOKEN=$(curl -sS -X POST https://kms.hanzo.ai/v1/kms/auth/login \
-H 'Content-Type: application/json' \
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}" \
| jq -r .accessToken)
```
### 2. Create a Secret
Response:
```typescript
const kms = new HanzoKMS({
apiKey: process.env.HANZO_KMS_TOKEN
})
// Create a secret
await kms.secrets.create({
environment: 'production',
path: '/api-keys',
key: 'STRIPE_SECRET',
value: 'sk_live_xxx'
})
```json
{ "accessToken": "<RS256 JWT>", "expiresIn": 86400, "tokenType": "Bearer" }
```
### 3. Retrieve Secrets
The token is a standard JWT whose signature, issuer, and audience are validated against IAM's JWKS. Its `owner` claim is the organization it can act on — a token can only read secrets under its own `{org}`.
```typescript
// Get single secret
const secret = await kms.secrets.get({
environment: 'production',
path: '/api-keys',
key: 'STRIPE_SECRET'
})
## Secret hierarchy
// Get all secrets in path
const secrets = await kms.secrets.list({
environment: 'production',
path: '/api-keys'
})
A secret is addressed by four coordinates:
```
org / path / name ? env
hanzo / providers / OPENAI_API_KEY ? env=production
```
## Secret Types
- **org** — the organization slug; must match the token's `owner` claim.
- **path** — a folder within the org (e.g. `/`, `/providers`, `/db`).
- **name** — the exact secret key.
- **env** — a query parameter; defaults to `default` when omitted.
### Static Secrets
<Callout type="info">
KMS reads are **exact-name** — there is no "list all secrets in a folder" endpoint. Enumerate the keys your workload needs (the `KMSSecret` CRD does exactly this).
</Callout>
Standard key-value secrets:
## Reading & writing secrets
```typescript
await kms.secrets.create({
key: 'DATABASE_URL',
value: 'postgres://user:pass@host:5432/db',
type: 'static'
})
```
All routes are under `https://kms.hanzo.ai/v1/kms/orgs/{org}/secrets`.
### Dynamic Secrets
| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/v1/kms/orgs/{org}/secrets/{path}/{name}?env=` | Read a secret value + version |
| `POST` | `/v1/kms/orgs/{org}/secrets` | Create/upsert (`{path,name,env,value}`) → `{ok,version}` |
| `PATCH` | `/v1/kms/orgs/{org}/secrets/{path}/{name}` | Update (optimistic concurrency — see below) |
| `DELETE` | `/v1/kms/orgs/{org}/secrets/{path}/{name}?env=` | Delete |
Auto-rotating credentials:
```typescript
// PostgreSQL dynamic secret
await kms.dynamicSecrets.create({
name: 'postgres-readonly',
type: 'postgresql',
config: {
host: 'db.example.com',
port: 5432,
database: 'myapp',
adminUsername: 'admin',
adminPassword: 'xxx'
},
ttl: '1h',
maxTtl: '24h'
})
// Lease a credential
const lease = await kms.dynamicSecrets.lease('postgres-readonly')
console.log(lease.username, lease.password) // Auto-generated, auto-rotated
```
### Signing Keys (MPC)
Create keys backed by MPC threshold signatures:
```typescript
// Create MPC-backed signing key
const key = await kms.keys.create({
name: 'ethereum-hot-wallet',
keyType: 'secp256k1',
signer: 'mpc',
scheme: '2-of-3',
policy: {
maxDailyTransactions: 100,
maxTransactionValue: '10000000000000000000' // 10 ETH
}
})
console.log('Address:', key.ethAddress)
// Sign transaction
const signature = await kms.keys.sign(key.id, {
chain: 'ethereum',
transaction: {
to: '0x...',
value: '1000000000000000000'
}
})
```
## Environment Management
### Sync Environments
### Read
```bash
# CLI: Pull secrets to .env
hanzo-kms pull --env production --output .env
# CLI: Push secrets from .env
hanzo-kms push --env staging --input .env
curl -sS "https://kms.hanzo.ai/v1/kms/orgs/hanzo/secrets/providers/OPENAI_API_KEY?env=production" \
-H "Authorization: Bearer $ACCESS_TOKEN"
```
### Environment Inheritance
```
production
└── staging (inherits, can override)
└── development (inherits, can override)
```json
{ "secret": { "value": "<secret value>" }, "version": 3 }
```
## Kubernetes Integration
A root-path secret omits the folder segment: `…/secrets/STRIPE_SECRET_KEY?env=production`.
### Install Operator
### Create
```bash
helm repo add hanzo https://charts.hanzo.ai
helm install hanzo-kms-operator hanzo/kms-operator
curl -sS -X POST "https://kms.hanzo.ai/v1/kms/orgs/hanzo/secrets" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "path": "/providers", "name": "OPENAI_API_KEY", "env": "production", "value": "<new value>" }'
```
### Inject Secrets
```json
{ "ok": true, "version": 1 }
```
### Update (optimistic concurrency)
Updates require the current version, supplied either as an `If-Match` header or `version` in the body. A missing precondition returns `428 Precondition Required`; a stale version returns `409 Conflict` with the current version.
```bash
curl -sS -X PATCH "https://kms.hanzo.ai/v1/kms/orgs/hanzo/secrets/providers/OPENAI_API_KEY?env=production" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "If-Match: 3" \
-H "Content-Type: application/json" \
-d '{ "value": "<rotated value>" }'
```
## Threshold signing keys
When KMS is wired to an [MPC](/docs/services/mpc) vault, it manages distributed signing keys whose private material is never assembled in one place:
| Method | Path | Purpose |
|--------|------|---------|
| `POST` | `/v1/kms/keys/generate` | Distributed key generation (DKG) for a validator/signing key set |
| `GET` | `/v1/kms/keys` · `/v1/kms/keys/{id}` | List / inspect key sets |
| `POST` | `/v1/kms/keys/{id}/sign` | Threshold sign (e.g. `bls`) |
| `POST` | `/v1/kms/keys/{id}/rotate` | Reshare / rotate shares |
These operations require an admin or MPC-scoped token. See [Hanzo MPC](/docs/services/mpc) for the underlying threshold scheme.
## Kubernetes integration
In a cluster, you never call the API directly — you declare a `KMSSecret` and the **kms-operator** logs in with a machine identity, reads the named keys, and writes a Kubernetes `Secret` you reference as normal:
```yaml
apiVersion: secrets.hanzo.ai/v1
kind: HanzoSecret
apiVersion: secrets.lux.network/v1alpha1
kind: KMSSecret
metadata:
name: api-secrets
name: app-kms-sync
namespace: hanzo
spec:
environment: production
path: /api-keys
target:
name: api-secrets
type: kubernetes.io/Opaque
hostAPI: https://kms.hanzo.ai
resyncInterval: 60
authentication:
universalAuth:
credentialsRef:
secretName: app-kms-auth # K8s Secret holding clientId/clientSecret
secretNamespace: hanzo
secretsScope:
projectSlug: hanzo # -> {org}
envSlug: production # -> ?env=production
secretsPath: /providers # -> {path}
keys: # enumerated; no list endpoint
- OPENAI_API_KEY
- ANTHROPIC_API_KEY
managedSecretReference:
secretName: app-secrets
secretNamespace: hanzo
secretType: Opaque
creationPolicy: Orphan
```
## Access Control
### Roles
| Role | Permissions |
|------|-------------|
| Admin | Full access |
| Developer | Read/write specific paths |
| Viewer | Read-only access |
| Machine | Programmatic access only |
### Policies
```typescript
await kms.policies.create({
name: 'frontend-team',
rules: [
{
path: '/frontend/*',
permissions: ['read', 'write']
},
{
path: '/backend/*',
permissions: ['read']
}
]
})
```
## Audit Logging
All secret access is logged:
```typescript
const logs = await kms.audit.list({
startTime: '2024-01-01',
endTime: '2024-01-31',
actions: ['read', 'write', 'delete']
})
for (const log of logs) {
console.log({
timestamp: log.timestamp,
user: log.userId,
action: log.action,
path: log.secretPath,
ip: log.ipAddress
})
}
```
## MPC Signer Integration
Hanzo KMS integrates with Hanzo MPC for threshold signatures. MPC nodes run their own private BFT blockchain (ConsensusKV) with dual-certificate finality -- Ed25519 (classical) and ML-DSA-65 (post-quantum, FIPS 204).
```typescript
// Configure MPC backend
await kms.config.update({
mpc: {
endpoint: 'https://mpc.hanzo.ai',
threshold: '3-of-5',
kvBackend: 'consensus', // Private BFT blockchain (default)
consensusChainId: 'm-chain',
nodeEndpoints: [
'https://mpc-node0.hanzo.ai',
'https://mpc-node1.hanzo.ai',
'https://mpc-node2.hanzo.ai',
'https://mpc-node3.hanzo.ai',
'https://mpc-node4.hanzo.ai'
]
}
})
```
### TFHE-KMS Bridge
KMS includes a TFHE bridge for fully homomorphic encryption operations backed by MPC threshold decryption:
```typescript
// Create TFHE key via MPC DKG
const tfheKey = await kms.keys.create({
name: 'encrypted-policy-key',
keyType: 'tfhe-uint64',
signer: 'mpc-tfhe',
scheme: '3-of-5'
})
// Encrypt data with public key (KMS-side, no MPC needed)
const encrypted = await kms.tfhe.encrypt(tfheKey.id, { value: 1000000 })
// Threshold decrypt (requires t-of-n MPC shares)
const decrypted = await kms.tfhe.decrypt(tfheKey.id, encrypted.ciphertext)
```
### Endpoints
| Endpoint | Backend | Description |
|----------|---------|-------------|
| `mpc.hanzo.ai` | Consensus | Managed MPC (3-of-5, dual-cert PQ finality) |
| `mpc.lux.network` | Consensus | Decentralized MPC (same cluster, Lux subnet integration) |
See [MPC Consensus](/docs/services/mpc/consensus) for details on the private blockchain and [MPC Authentication](/docs/services/iam/mpc) for auth integration.
## API Reference
### Secrets
- `POST /api/v1/secrets` - Create secret
- `GET /api/v1/secrets` - List secrets
- `GET /api/v1/secrets/:id` - Get secret
- `PATCH /api/v1/secrets/:id` - Update secret
- `DELETE /api/v1/secrets/:id` - Delete secret
### Keys
- `POST /api/v1/keys` - Create signing key
- `GET /api/v1/keys/:id` - Get key info
- `POST /api/v1/keys/:id/sign` - Sign with key
### Dynamic Secrets
- `POST /api/v1/dynamic-secrets` - Create dynamic secret
- `POST /api/v1/dynamic-secrets/:id/lease` - Get credential lease
- `DELETE /api/v1/dynamic-secrets/:id/lease/:leaseId` - Revoke lease
See the [KMSSecret CRD reference](/docs/services/kms/kmssecret-crd) for the full spec.
## Next Steps
<Cards>
<Card title="Service Integration" href="/docs/services/kms/service-integration">
How all Hanzo services consume secrets from KMS
</Card>
<Card title="Universal Auth" href="/docs/services/kms/universal-auth">
Machine Identity authentication for services
<Card title="Machine Identities" href="/docs/services/kms/universal-auth">
How services authenticate to KMS and refresh tokens
</Card>
<Card title="KMSSecret CRD" href="/docs/services/kms/kmssecret-crd">
Kubernetes operator secret sync reference
Complete Kubernetes sync reference
</Card>
<Card title="IAM Integration" href="/docs/services/iam/kms-integration">
How IAM uses KMS for secret management
<Card title="Service Integration" href="/docs/services/kms/service-integration">
The pattern every Hanzo service uses to consume secrets
</Card>
<Card title="Hanzo MPC" href="/docs/services/mpc">
Threshold signing backend for KMS-managed keys
</Card>
</Cards>
@@ -27,8 +27,8 @@ metadata:
app.kubernetes.io/component: secrets
app.kubernetes.io/part-of: hanzo-platform
spec:
# KMS API endpoint (use in-cluster address)
hostAPI: http://kms.hanzo.svc.cluster.local/api
# KMS API endpoint
hostAPI: https://kms.hanzo.ai
# How often to re-sync secrets (seconds)
resyncInterval: 60
@@ -38,15 +38,19 @@ spec:
universalAuth:
credentialsRef:
# K8s Secret containing clientId and clientSecret
secretName: universal-auth-credentials
secretName: app-kms-auth
secretNamespace: hanzo
secretsScope:
# KMS project to read from
projectSlug: secrets-639-c
# KMS org to read from (matches the machine identity's owner)
projectSlug: hanzo
# KMS environment
envSlug: prod
# Path within the project
envSlug: production
# Path within the org
secretsPath: /example
# Exact secret names to sync — KMS has no list endpoint, enumerate them
keys:
- EXAMPLE_API_KEY
- EXAMPLE_DB_URL
# Target K8s Secret
managedSecretReference:
@@ -68,8 +72,8 @@ spec:
|---|---|
| Type | `string` |
| Required | Yes |
| Description | URL of the KMS API. Always use the in-cluster address for security. |
| Example | `http://kms.hanzo.svc.cluster.local/api` |
| Description | Base URL of the KMS API. The operator appends `/v1/kms`. In-cluster you may use `http://kms.hanzo.svc.cluster.local`. |
| Example | `https://kms.hanzo.ai` |
### `spec.resyncInterval`
@@ -98,9 +102,10 @@ The referenced Secret must contain two keys:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectSlug` | `string` | Yes | KMS project slug |
| `envSlug` | `string` | Yes | Environment slug (`prod`, `staging`, `dev`) |
| `secretsPath` | `string` | Yes | Path within the project (e.g., `/chat`, `/`) |
| `projectSlug` | `string` | Yes | KMS org slug — maps to `{org}` and must match the identity's `owner` |
| `envSlug` | `string` | Yes | Environment — maps to `?env=` (`production`, `staging`, `dev`) |
| `secretsPath` | `string` | Yes | Path within the org (e.g., `/providers`, `/`) |
| `keys` | `string[]` | Yes | Exact secret names to sync (1–128). KMS has no list endpoint — enumerate every key |
### `spec.managedSecretReference`
@@ -25,7 +25,6 @@ Every Hanzo service retrieves its secrets from KMS rather than storing them in e
│ /bootnode /studio │
│ /app /bot-hub │
│ /crawl /auto │
│ /lux-chat │
└──────────┬──────────────────────────────────┬────────────────────┘
│ │
Universal Auth Machine Identity
@@ -48,7 +47,7 @@ Every Hanzo service retrieves its secrets from KMS rather than storing them in e
│ pricing-secrets search-secrets vector-secrets │
│ bootnode-secrets studio-secrets bot-hub-secrets │
│ hanzo-app-secrets crawl-secrets auto-secrets │
│ lux-chat-secrets iam-secrets │
│ iam-secrets │
└──────────────────────────────────────────────────────┘
```
@@ -91,7 +90,7 @@ metadata:
app.kubernetes.io/component: secrets
app.kubernetes.io/part-of: hanzo-platform
spec:
hostAPI: http://kms.hanzo.svc.cluster.local/api
hostAPI: http://kms.hanzo.svc.cluster.local
resyncInterval: 60
authentication:
universalAuth:
@@ -113,7 +112,7 @@ spec:
| Field | Required | Description |
|-------|----------|-------------|
| `hostAPI` | Yes | KMS API URL. Use in-cluster address `http://kms.hanzo.svc.cluster.local/api` |
| `hostAPI` | Yes | KMS API URL. Use in-cluster address `http://kms.hanzo.svc.cluster.local` |
| `resyncInterval` | Yes | How often (seconds) to re-fetch secrets. Default: `60` |
| `authentication.universalAuth.credentialsRef` | Yes | K8s Secret containing `clientId` and `clientSecret` |
| `authentication.universalAuth.secretsScope.projectSlug` | Yes | KMS project slug |
@@ -146,7 +145,6 @@ Every service in the Hanzo platform uses this pattern. Here is the full list:
| Hanzo App | `/app` | `hanzo-app-secrets` | Universal Auth |
| Crawl | `/crawl` | `crawl-secrets` | Universal Auth |
| Auto | `/auto` | `auto-secrets` | Universal Auth |
| Lux Chat | `/lux-chat` | `lux-chat-secrets` | Universal Auth |
## Example: Adding Secrets for a New Service
@@ -167,7 +165,7 @@ metadata:
app.kubernetes.io/component: secrets
app.kubernetes.io/part-of: hanzo-platform
spec:
hostAPI: http://kms.hanzo.svc.cluster.local/api
hostAPI: http://kms.hanzo.svc.cluster.local
resyncInterval: 60
authentication:
universalAuth:
@@ -49,7 +49,7 @@ stringData:
clientSecret: "<machine-identity-client-secret>"
```
**Access scope**: `secrets-639-c` project, `prod` environment, all paths.
**Access scope**: the shared services org, `production` environment, all paths.
### Dedicated Identity (IAM)
@@ -80,24 +80,26 @@ stringData:
5. Copy the generated `clientId` and `clientSecret`
6. Under **Projects**, grant access to the appropriate project with the required role (typically **Viewer** for read-only)
### Via API
### Via Hanzo IAM
A machine identity is a client-credentials application in [Hanzo IAM](/docs/services/iam). Register it (which mints a `clientId`/`clientSecret`), then grant it read access to the appropriate KMS org. The credentials are what your workload exchanges for a token.
### Exchange credentials for a token
Once you have a `clientId`/`clientSecret`, log in to KMS:
```bash
# Create identity
curl -X POST https://kms.hanzo.ai/api/v1/identities \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"name": "my-service", "role": "member"}'
# Enable Universal Auth
curl -X POST https://kms.hanzo.ai/api/v1/auth/universal-auth/identities/$IDENTITY_ID \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Grant project access
curl -X POST https://kms.hanzo.ai/api/v1/projects/$PROJECT_ID/identity-memberships \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"identityId": "$IDENTITY_ID", "role": "viewer"}'
curl -X POST https://kms.hanzo.ai/v1/kms/auth/login \
-H "Content-Type: application/json" \
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}"
```
```json
{ "accessToken": "<JWT>", "expiresIn": 86400, "tokenType": "Bearer" }
```
Use the returned `accessToken` as `Authorization: Bearer <accessToken>` on every secret read.
## Storing Credentials in K8s
After creating the Machine Identity, store the credentials as a K8s Secret:
@@ -125,7 +127,7 @@ spec:
| Property | Value |
|----------|-------|
| Token type | JWT |
| Default TTL | 7200 seconds (2 hours) |
| Default TTL | 86400 seconds (24 hours) |
| Max TTL | Configurable per identity |
| Refresh | Automatic by kms-operator |
| Revocation | Delete the Machine Identity or its Universal Auth config |
+111 -383
View File
@@ -1,486 +1,214 @@
---
title: API Reference
description: Complete REST API reference for Hanzo MPC
description: REST API reference for the Hanzo MPC custody service
---
# API Reference
All endpoints are served at `https://mpc.hanzo.ai` and require a Bearer token from Hanzo IAM.
The Hanzo MPC custody API is served at `https://mpc.hanzo.ai` under the `/v1` prefix. Signing is session-based: create a wallet, open a session, then sign.
## Authentication
Every request must include an `Authorization` header:
Every request (except health) carries a bearer token issued by [Hanzo IAM](/docs/services/iam):
```
Authorization: Bearer <token>
Authorization: Bearer <iam-access-token>
```
Obtain tokens from Hanzo IAM at `https://hanzo.id`. See [IAM documentation](/docs/services/iam) for details.
A service holding an external OIDC token can exchange it for an MPC token (the issuer must be allow-listed):
```bash
curl -X POST https://mpc.hanzo.ai/v1/auth/oidc \
-H "Content-Type: application/json" \
-d '{ "token": "<external-oidc-access-token>" }'
```
Machine callers may instead present a long-lived API key via `X-API-Key`. Roles (`owner`, `admin`, `signer`, `viewer`) and per-route permissions (e.g. `mpc:sign`) gate access.
## Base URL
| Environment | URL |
|-------------|-----|
| Production | `https://mpc.hanzo.ai` |
| Staging | `https://stg.mpc.hanzo.ai` |
---
## POST /api/wallets
## POST /v1/mpc/wallets
Create a new MPC wallet. This triggers a distributed key generation (DKG) ceremony across the configured number of parties.
Create a wallet via distributed key generation (DKG) across the configured parties.
### Request
```bash
curl -X POST https://mpc.hanzo.ai/api/wallets \
-H "Authorization: Bearer $HANZO_TOKEN" \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"protocol": "cggmp21",
"curve": "secp256k1",
"threshold": 2,
"parties": 3,
"label": "eth-hot-wallet",
"metadata": {
"department": "treasury",
"owner": "[email protected]"
}
}'
-d '{ "protocol": "cggmp21" }'
```
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `protocol` | string | Yes | Cryptographic protocol. One of: `cggmp21`, `frost`, `lsss`, `tfhe`, `corona`, `quasar`, `bls`, `doerner` |
| `curve` | string | Yes | Elliptic curve. One of: `secp256k1`, `ed25519`, `ristretto`, `bls12-381`, `p256`, `sr25519` |
| `threshold` | integer | Yes | Minimum signers required (t) |
| `parties` | integer | Yes | Total number of key share holders (n) |
| `label` | string | No | Human-readable wallet label |
| `metadata` | object | No | Arbitrary key-value metadata |
| `protocol` | string | Yes | `cggmp21` (ECDSA) or `frost` (Schnorr) |
### Protocol-Curve Compatibility
| Protocol | Compatible Curves |
|----------|------------------|
| `cggmp21` | `secp256k1`, `p256` |
| `frost` | `ed25519`, `ristretto`, `secp256k1` |
| `lsss` | Any curve |
| `tfhe` | Lattice (internal) |
| `corona` | Module-LWE (internal) |
| `quasar` | `bls12-381` |
| `bls` | `bls12-381` |
| `doerner` | `secp256k1` |
For a wallet in a specific vault with an explicit key type, use `POST /v1/vaults/{vaultId}/wallets` with `{ "name", "key_type", "protocol" }`, where `protocol` is `cggmp21`, `frost`, `lss`, or `sr25519`.
### Response `201 Created`
```json
{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"walletId": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"protocol": "cggmp21",
"curve": "secp256k1",
"threshold": 2,
"parties": 3,
"public_key": "04a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"addresses": {
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"bitcoin_legacy": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
},
"label": "eth-hot-wallet",
"metadata": {
"department": "treasury",
"owner": "[email protected]"
},
"keygen_duration_ms": 22,
"created_at": "2026-02-23T10:30:00Z"
"threshold": "2-of-3",
"isDefault": true,
"status": "active",
"createdAt": "2026-06-26T10:30:00Z"
}
```
### Response Fields
---
| Field | Type | Description |
|-------|------|-------------|
| `wallet_id` | string | Unique wallet identifier (prefixed `w_`) |
| `protocol` | string | Protocol used for DKG |
| `curve` | string | Elliptic curve |
| `threshold` | integer | Signing threshold (t) |
| `parties` | integer | Total parties (n) |
| `public_key` | string | Hex-encoded public key |
| `addresses` | object | Derived chain addresses |
| `label` | string | Wallet label |
| `metadata` | object | Custom metadata |
| `keygen_duration_ms` | integer | DKG ceremony duration |
| `created_at` | string | ISO 8601 timestamp |
## GET /v1/wallets/&#123;id&#125;/addresses
### Errors
Return the chain addresses derived from a wallet's public key.
| Status | Code | Description |
|--------|------|-------------|
| 400 | `invalid_protocol` | Unsupported protocol name |
| 400 | `invalid_curve` | Unsupported curve or incompatible with protocol |
| 400 | `invalid_threshold` | Threshold must be `>= 1` and `≤ parties` |
| 401 | `unauthorized` | Missing or invalid Bearer token |
| 403 | `forbidden` | Token lacks `mpc:wallets:create` scope |
| 500 | `keygen_failed` | DKG ceremony failed (check node health) |
```bash
curl https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/addresses \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN"
```
```json
{
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"solana": "7nYBzS3Yz5mY8Qm4iXqz1bWmHnf8QpHq3xWv6Yc9aBcD",
"xrp": "rPdvC6ccq8hCdPKSPJkPmyZ4Mi1oG2FFkT",
"ton": "EQByY8B0iZ0a..."
}
```
---
## POST /api/sign
## POST /v1/mpc/wallets/&#123;id&#125;/sessions
Sign a message or transaction hash using the specified wallet. The coordinator selects `t` available nodes and runs the threshold signing protocol.
Open a signing session. A session scopes and authorizes the signatures that follow, and is consumed atomically — which makes every signature an explicit, auditable action.
```bash
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets/$WALLET_ID/sessions \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN"
```
```json
{ "sessionId": "s_a1b2c3d4", "walletId": "w_3fa85f64-...", "expiresAt": "2026-06-26T10:45:00Z" }
```
List or revoke sessions with `GET /v1/mpc/wallets/{id}/sessions` and `DELETE /v1/mpc/wallets/{id}/sessions/{sessionId}`.
---
## POST /v1/mpc/sign
Threshold-sign a message hash. The coordinator selects `t` available nodes and runs the signing protocol. Requires an open `sessionId`.
### Request
```bash
curl -X POST https://mpc.hanzo.ai/api/sign \
-H "Authorization: Bearer $HANZO_TOKEN" \
curl -X POST https://mpc.hanzo.ai/v1/mpc/sign \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"walletId": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"sessionId": "s_a1b2c3d4",
"message": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"encoding": "hex"
}'
```
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `wallet_id` | string | Yes | Target wallet ID |
| `walletId` | string | Yes | Target wallet |
| `sessionId` | string | Yes | An open signing session |
| `message` | string | Yes | Message or transaction hash to sign |
| `encoding` | string | No | Message encoding: `hex` (default), `base64`, `utf8` |
| `signers` | integer[] | No | Specific signer indices to use. If omitted, coordinator selects automatically |
| `metadata` | object | No | Audit metadata (e.g., transaction context) |
| `encoding` | string | No | `hex` (default) or `base64` |
### Response `200 OK`
```json
{
"signature": {
"r": "0x1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b",
"s": "0x4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e",
"v": 27
},
"signers": [1, 3],
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"protocol": "cggmp21",
"duration_ms": 45,
"timestamp": "2026-02-23T10:31:00Z"
"signature": "0x1a2b3c...4d5e6f...1b",
"r": "0x1a2b3c4d5e6f...",
"s": "0x4d5e6f1a2b3c..."
}
```
### Signature Format by Protocol
### Signature format by protocol
| Protocol | Signature Fields |
|----------|-----------------|
| CGGMP21 | `r`, `s`, `v` (ECDSA) |
| FROST | `R`, `s` (Schnorr) |
| BLS | `signature` (compressed G1 point) |
| Quasar | `bls_signature`, `pq_signature` (dual) |
| Doerner | `r`, `s`, `v` (ECDSA) |
| Protocol | Fields |
|----------|--------|
| CGGMP21 | `r`, `s`, and a recovery byte (ECDSA) |
| FROST | 64-byte Schnorr `signature` |
| BLS | compressed G1 point `signature` |
### Errors
| Status | Code | Description |
|--------|------|-------------|
| 400 | `invalid_message` | Message is empty or malformed |
| 400 | `invalid_encoding` | Unsupported encoding format |
| 404 | `wallet_not_found` | Wallet ID does not exist |
| 401 | `unauthorized` | Missing or invalid Bearer token |
| 403 | `forbidden` | Token lacks `mpc:sign` scope |
| 503 | `insufficient_signers` | Fewer than `t` nodes are available |
| 500 | `signing_failed` | Protocol execution failed |
| 500 | `identifiable_abort` | Malicious signer detected (includes blame proof) |
### Identifiable Abort Response
When a signer deviates from the protocol:
```json
{
"error": "identifiable_abort",
"blame": {
"party_index": 2,
"proof": "base64-encoded-blame-proof",
"reason": "invalid_commitment"
},
"timestamp": "2026-02-23T10:31:00Z"
}
```
For settlement workflows with an explicit reason and policy check, use `POST /v1/mpc/settlement/sign` with `{ "walletId", "sessionId", "payload", "reason" }`.
---
## POST /api/reshare
## POST /v1/wallets/&#123;id&#125;/reshare
Perform a proactive share refresh. This generates new shares for the same underlying key, optionally changing the threshold or party count. The public key and all derived addresses remain unchanged.
### Request
Proactive share refresh. Generates new shares for the same key, optionally changing the participant set or threshold. The public key and every derived address are unchanged.
```bash
curl -X POST https://mpc.hanzo.ai/api/reshare \
-H "Authorization: Bearer $HANZO_TOKEN" \
curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"new_threshold": 3,
"new_parties": 5
}'
-d '{ "new_threshold": 3, "new_participants": ["node-0","node-1","node-2","node-3","node-4"] }'
```
### Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `wallet_id` | string | Yes | Target wallet ID |
| `new_threshold` | integer | No | New signing threshold. Defaults to current |
| `new_parties` | integer | No | New total parties. Defaults to current |
| `reason` | string | No | Audit reason (e.g., `scheduled_rotation`, `suspected_compromise`) |
### Response `200 OK`
```json
{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"previous": {
"threshold": 2,
"parties": 3,
"epoch": 1
},
"current": {
"threshold": 3,
"parties": 5,
"epoch": 2
},
"public_key": "04a1b2c3d4e5f6...",
"addresses_unchanged": true,
"reshare_duration_ms": 180,
"timestamp": "2026-02-23T10:32:00Z"
}
```
### Errors
| Status | Code | Description |
|--------|------|-------------|
| 400 | `invalid_threshold` | New threshold exceeds new party count |
| 404 | `wallet_not_found` | Wallet ID does not exist |
| 401 | `unauthorized` | Missing or invalid Bearer token |
| 403 | `forbidden` | Token lacks `mpc:reshare` scope |
| 409 | `reshare_in_progress` | Another reshare is already running for this wallet |
| 500 | `reshare_failed` | Protocol execution failed |
---
## GET /api/wallets
List all wallets accessible to the authenticated user.
### Request
```bash
curl https://mpc.hanzo.ai/api/wallets \
-H "Authorization: Bearer $HANZO_TOKEN"
```
### Query Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | integer | 20 | Results per page (max 100) |
| `offset` | integer | 0 | Pagination offset |
| `protocol` | string | - | Filter by protocol |
| `label` | string | - | Filter by label (substring match) |
### Response `200 OK`
```json
{
"wallets": [
{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"protocol": "cggmp21",
"curve": "secp256k1",
"threshold": 2,
"parties": 3,
"public_key": "04a1b2c3d4e5f6...",
"addresses": {
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
},
"label": "eth-hot-wallet",
"epoch": 2,
"created_at": "2026-02-23T10:30:00Z",
"last_reshare": "2026-02-23T10:32:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}
{ "walletId": "w_3fa85f64-...", "newThreshold": 3, "keyType": "secp256k1", "status": "completed" }
```
---
## GET /api/wallets/:id
## GET /v1/status · GET /v1/info
Get detailed information about a specific wallet.
### Request
Cluster status and capabilities.
```bash
curl https://mpc.hanzo.ai/api/wallets/w_3fa85f64-5717-4562-b3fc-2c963f66afa6 \
-H "Authorization: Bearer $HANZO_TOKEN"
curl https://mpc.hanzo.ai/v1/status -H "Authorization: Bearer $IAM_ACCESS_TOKEN"
```
### Response `200 OK`
```json
{ "status": "healthy", "nodes": { "total": 5, "healthy": 5 }, "threshold": "3-of-5", "transport": "nats" }
```
## GET /healthz
Liveness probe, no authentication.
```bash
curl https://mpc.hanzo.ai/healthz
```
```json
{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"protocol": "cggmp21",
"curve": "secp256k1",
"threshold": 2,
"parties": 3,
"public_key": "04a1b2c3d4e5f6...",
"addresses": {
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"bitcoin_legacy": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"bitcoin_taproot": "bc1p...",
"solana": "7nYB...",
"cosmos": "cosmos1...",
"lux_c": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"lux_x": "X-lux1...",
"lux_p": "P-lux1..."
},
"label": "eth-hot-wallet",
"metadata": {
"department": "treasury",
"owner": "[email protected]"
},
"epoch": 2,
"node_status": [
{"index": 0, "healthy": true, "last_seen": "2026-02-23T10:35:00Z"},
{"index": 1, "healthy": true, "last_seen": "2026-02-23T10:35:00Z"},
{"index": 2, "healthy": true, "last_seen": "2026-02-23T10:35:00Z"}
],
"signing_count": 142,
"last_signed": "2026-02-23T10:34:00Z",
"created_at": "2026-02-23T10:30:00Z",
"last_reshare": "2026-02-23T10:32:00Z"
}
{ "status": "ok", "node_id": "node-0", "connected_peers": 4, "threshold": 3 }
```
---
## GET /health
Health check endpoint. No authentication required.
### Request
```bash
curl https://mpc.hanzo.ai/health
```
### Response `200 OK`
## Error format
```json
{
"status": "healthy",
"version": "1.4.0",
"nodes": {
"total": 3,
"healthy": 3,
"degraded": 0,
"offline": 0
},
"nats": "connected",
"consul": "connected",
"uptime_seconds": 864000,
"timestamp": "2026-02-23T10:35:00Z"
}
{ "error": "insufficient_signers", "message": "Fewer than t nodes are available", "request_id": "req_abc123" }
```
### Status Values
| Status | Code | Meaning |
|--------|------|---------|
| 400 | `invalid_protocol` / `invalid_encoding` | Bad request input |
| 401 | `unauthorized` | Missing or invalid bearer token |
| 403 | `forbidden` | Token lacks the required role/permission |
| 404 | `wallet_not_found` | Unknown wallet |
| 409 | `reshare_in_progress` | A reshare is already running for this wallet |
| 503 | `insufficient_signers` | Fewer than `t` nodes available |
| Status | Meaning |
|--------|---------|
| `healthy` | All nodes operational, messaging and discovery connected |
| `degraded` | Some nodes offline but threshold still achievable |
| `unhealthy` | Fewer healthy nodes than minimum threshold |
---
## Error Response Format
All errors follow a consistent structure:
```json
{
"error": "error_code",
"message": "Human-readable description of what went wrong",
"details": {},
"request_id": "req_abc123",
"timestamp": "2026-02-23T10:35:00Z"
}
```
## Rate Limits
| Endpoint | Limit | Window |
|----------|-------|--------|
| `POST /api/wallets` | 10 | per minute |
| `POST /api/sign` | 100 | per minute |
| `POST /api/reshare` | 5 | per minute |
| `GET /api/wallets` | 60 | per minute |
| `GET /health` | 300 | per minute |
Rate limit headers are included in every response:
```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1708689360
```
## Pagination
List endpoints support cursor-based pagination:
```bash
# First page
curl "https://mpc.hanzo.ai/api/wallets?limit=10"
# Next page
curl "https://mpc.hanzo.ai/api/wallets?limit=10&offset=10"
```
## Webhook Notifications
Configure webhooks to receive real-time events:
```bash
curl -X POST https://mpc.hanzo.ai/api/webhooks \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/mpc",
"events": ["wallet.created", "sign.completed", "sign.failed", "reshare.completed"],
"secret": "whsec_your_webhook_secret"
}'
```
### Event Types
| Event | Trigger |
|-------|---------|
| `wallet.created` | DKG ceremony completed |
| `sign.completed` | Signature produced |
| `sign.failed` | Signing failed or aborted |
| `reshare.completed` | Share refresh completed |
| `reshare.failed` | Share refresh failed |
| `node.offline` | An MPC node became unreachable |
| `node.recovered` | An offline node came back |
When a signer deviates, signing aborts with an identifiable-abort error that names the offending party and includes a signed blame proof.
@@ -34,7 +34,7 @@ A block is finalized **only** when both signature types reach the t-of-n thresho
| Security level | NIST Level 3 (192-bit classical equivalent) |
| Package | `github.com/luxfi/crypto/mldsa` |
The same ML-DSA implementation is used by the [Lux Network](/docs/services/lux) mainnet precompiles for on-chain post-quantum signature verification.
ML-DSA is the NIST-standardized (FIPS 204) post-quantum digital signature scheme used for on-chain and consensus signature verification.
## Consensus Protocol
@@ -214,18 +214,12 @@ identity:
| **External dependency** | None (self-sovereign) | Consul servers | NATS servers |
| **Post-quantum** | ML-DSA-65 dual-cert | No | No |
| **Finality model** | Dual-cert threshold | Raft leader commit | JetStream ack |
| **Latency** | <100ms (typical) | ~50ms | ~10ms |
| **Latency** | &lt;100ms (typical) | ~50ms | ~10ms |
| **Trust model** | Same as MPC threshold | Trust Consul cluster | Trust NATS cluster |
| **State integrity** | Blockchain (hash chain) | Raft log | Stream sequence |
The key advantage: ConsensusKV uses the **same trust model** as MPC signing. If you trust 3-of-5 nodes to sign transactions, you can trust 3-of-5 nodes to finalize state -- no additional infrastructure needed.
## Lux Network Integration
## Post-quantum cryptography
ConsensusKV uses the same post-quantum crypto library as the Lux Network:
- **ML-DSA precompile**: `lux/precompile/mldsa/` -- on-chain PQ signature verification
- **Quasar consensus**: `lux/consensus/protocol/quasar/` -- dual BLS+Corona threshold finality
- **Shared crypto**: `github.com/luxfi/crypto/mldsa` -- ML-DSA-44/65/87 implementation
The MPC private chain can optionally register as a **Lux subnet** (M-Chain or T-Chain) for network-level integration and cross-chain state anchoring.
ConsensusKV's dual-certificate finality combines a classical Ed25519 certificate with a post-quantum ML-DSA-65 certificate, so cluster state stays verifiable even against a quantum adversary. The ML-DSA implementation supports the FIPS 204 parameter sets (ML-DSA-44/65/87); the cluster uses ML-DSA-65 (NIST Level 3) by default.
@@ -265,14 +265,11 @@ tls:
# Ingress (dual endpoints)
ingress:
enabled: true
className: nginx
className: hanzo
hosts:
- host: mpc.hanzo.ai
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
- host: mpc.lux.network
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
# Monitoring
metrics:
@@ -627,7 +624,7 @@ If a node is permanently lost but the cluster still has `t` healthy nodes:
4. Old shares from the lost node are automatically invalidated
```bash
curl -X POST https://mpc.hanzo.ai/api/reshare \
curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
+138 -251
View File
@@ -1,317 +1,204 @@
---
title: Hanzo MPC
description: Enterprise-grade multi-party computation for digital asset custody with blockchain-native consensus, dual-certificate post-quantum finality, 8 cryptographic protocols, and configurable t-of-n thresholds.
description: Threshold multi-party signing for digital asset custody — distributed key generation, t-of-n signing, and proactive share refresh with post-quantum consensus.
---
# Hanzo MPC
Hanzo MPC is a self-sovereign wallet infrastructure for enterprise digital asset custody. It eliminates single points of failure by splitting private keys into shares distributed across independent nodes, requiring a configurable threshold of cooperating parties to produce valid signatures.
Hanzo MPC is wallet infrastructure for enterprise digital asset custody. Private keys are never assembled in one place — they are split into shares held by independent nodes, and a configurable threshold of nodes must cooperate to produce a valid signature. Think of it as a self-hosted alternative to Fireblocks: you own the infrastructure, control the key shares, and operate under your own policies.
MPC nodes form their own **private BFT blockchain** for cluster state management, with **dual-certificate finality** combining Ed25519 (classical) and ML-DSA-65 (post-quantum, FIPS 204) signatures. Think of it as a self-hosted alternative to Fireblocks -- you own the infrastructure, control the key shares, and operate under your own security policies.
| | |
|---|---|
| **Custody API** | `https://mpc.hanzo.ai` |
| **API prefix** | `/v1` |
| **Auth** | Hanzo IAM (OIDC) bearer tokens |
| **Default threshold** | t-of-n, default t = ⌊n/2⌋ + 1 |
| **Source** | [github.com/hanzoai/mpc](https://github.com/hanzoai/mpc) |
## Key Properties
- **No single point of failure**: No individual node ever holds a complete private key
- **Blockchain-native consensus**: MPC nodes are validators on their own private BFT chain
- **Dual-certificate finality**: Ed25519 + ML-DSA-65 (NIST Level 3) -- quantum-safe state management
- **Configurable thresholds**: Any t-of-n scheme (2-of-3, 3-of-5, 5-of-9, etc.)
- **Identifiable aborts**: Malicious participants are detected and attributed
- **Proactive share refresh**: Rotate key shares without changing the public key or address
- **Multi-chain**: 16+ blockchain networks from a single key management plane
- **8 cryptographic protocols**: From production ECDSA to post-quantum lattice signatures
- **17 zero-knowledge proof systems**: Comprehensive verification at every protocol stage
- **Encrypted at rest**: AES-256-GCM for all stored key shares
- **KMS-first key management**: Identity and consensus keys stored in KMS/HSM when configured
- **No single point of failure** — no node ever holds a complete private key
- **Distributed key generation (DKG)** — keys are generated collaboratively; no dealer ever sees the whole key
- **Configurable thresholds** — any t-of-n scheme (2-of-3, 3-of-5, 5-of-9, …)
- **Proactive share refresh** — rotate shares without changing the public key or any derived address
- **Identifiable aborts** — a deviating participant is detected and attributed
- **Multi-chain** — one key plane derives addresses for Bitcoin, Ethereum/EVM, Solana, and more
- **Encrypted at rest** — key shares are stored AES-256-GCM encrypted; the encryption key is supplied by [Hanzo KMS](/docs/services/kms)/HSM, never plaintext config
- **Post-quantum consensus** — cluster state is managed by a private BFT chain with dual-certificate finality (Ed25519 + ML-DSA-65, FIPS 204)
## Supported Protocols
Hanzo MPC builds on the `luxfi/threshold` engine. Four threshold schemes are in production:
| Protocol | Type | Curve | Best for |
|----------|------|-------|----------|
| **CGGMP21** | Threshold ECDSA | secp256k1 | Bitcoin, Ethereum, EVM chains |
| **FROST** | Threshold Schnorr | secp256k1 (BIP-340), Ristretto255 | Bitcoin Taproot, Ed25519/sr25519 chains |
| **LSS** | Linear secret sharing | secp256k1 | Dynamic resharing, blinded signing |
| **BLS** | Threshold aggregate | BLS12-381 | Validator keys, aggregate signatures |
Key types accepted by the API: `secp256k1`, `ed25519`, `sr25519`, `bls`.
## Authentication
The custody API trusts tokens issued by [Hanzo IAM](/docs/services/iam). Present an IAM-issued JWT as a bearer token:
```bash
curl -H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
https://mpc.hanzo.ai/v1/info
```
Services that already hold an external OIDC token can exchange it for an MPC session token (issuer must be on the allowlist):
```bash
curl -X POST https://mpc.hanzo.ai/v1/auth/oidc \
-H "Content-Type: application/json" \
-d '{ "token": "<external-oidc-access-token>" }'
```
Roles (`owner`, `admin`, `signer`, `viewer`) gate each route; signing additionally requires `mpc:sign`. Long-lived API keys are also supported for machine callers — store them in [Hanzo KMS](/docs/services/kms), never inline.
## Quick Start
### Create a Wallet
Signing is a three-step flow: create a wallet (DKG), open a signing session, then sign.
<Steps>
### Create a wallet (DKG)
```bash
curl -X POST https://mpc.hanzo.ai/api/wallets \
-H "Authorization: Bearer $HANZO_TOKEN" \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"protocol": "cggmp21",
"curve": "secp256k1",
"threshold": 2,
"parties": 3,
"label": "eth-hot-wallet"
}'
-d '{ "protocol": "cggmp21" }'
```
Response:
```json
{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"walletId": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"protocol": "cggmp21",
"curve": "secp256k1",
"threshold": 2,
"parties": 3,
"public_key": "04a1b2c3d4e5f6...",
"addresses": {
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
},
"created_at": "2026-02-23T10:30:00Z"
"threshold": "2-of-3",
"status": "active",
"createdAt": "2026-06-26T10:30:00Z"
}
```
### Sign a Transaction
### Open a signing session
```bash
curl -X POST https://mpc.hanzo.ai/api/sign \
-H "Authorization: Bearer $HANZO_TOKEN" \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets/$WALLET_ID/sessions \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN"
```
Returns a `sessionId`. Signing consumes a session, which makes each signature an explicit, auditable authorization.
### Sign
```bash
curl -X POST https://mpc.hanzo.ai/v1/mpc/sign \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "0xdeadbeef...",
"walletId": "'$WALLET_ID'",
"sessionId": "'$SESSION_ID'",
"message": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"encoding": "hex"
}'
```
Response:
Response (ECDSA):
```json
{ "signature": "0x...", "r": "0x1a2b3c...", "s": "0x4d5e6f..." }
```
</Steps>
## Core Endpoints
All under `https://mpc.hanzo.ai/v1`.
| Method | Path | Purpose |
|--------|------|---------|
| `POST` | `/v1/mpc/wallets` | Create a wallet (DKG); `{ "protocol": "cggmp21" \| "frost" }` |
| `GET` | `/v1/wallets/{id}/addresses` | Derived chain addresses for a wallet |
| `POST` | `/v1/mpc/wallets/{id}/sessions` | Open a signing session |
| `GET` | `/v1/mpc/wallets/{id}/sessions` | List sessions |
| `POST` | `/v1/mpc/sign` | Threshold-sign a message (requires `sessionId`) |
| `POST` | `/v1/wallets/{id}/reshare` | Proactive share refresh / change t-of-n |
| `GET` | `/v1/status` · `/v1/info` | Cluster status and capabilities |
| `GET` | `/healthz` | Liveness (no auth) |
A single wallet derives addresses for many chains:
```bash
curl https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/addresses \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN"
```
```json
{
"signature": {
"r": "0x1a2b3c...",
"s": "0x4d5e6f...",
"v": 27
},
"signers": [1, 3],
"protocol": "cggmp21",
"duration_ms": 45
"ethereum": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"bitcoin": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"solana": "7nYBzS3Yz5mY8Qm4iXqz1bWmHnf8QpHq3xWv6Yc9aBcD"
}
```
### Reshare Key Shares
## Proactive Share Refresh
Rotate shares without changing the public key or any on-chain addresses:
Rotate the key shares without changing the public key or any on-chain address. Old shares become useless after a refresh, so a share compromised in a past epoch cannot be combined with current shares.
```bash
curl -X POST https://mpc.hanzo.ai/api/reshare \
-H "Authorization: Bearer $HANZO_TOKEN" \
curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
-H "Authorization: Bearer $IAM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"wallet_id": "w_3fa85f64-5717-4562-b3fc-2c963f66afa6",
"new_threshold": 3,
"new_parties": 5
}'
-d '{ "new_threshold": 3, "new_participants": ["node-0","node-1","node-2","node-3","node-4"] }'
```
## Supported Protocols
## In-cluster Go client (NATS)
Hanzo MPC ships 8 cryptographic protocols, each optimized for different use cases:
Inside the cluster, the canonical interface is an asynchronous Go client over NATS — every keygen/sign/reshare request is Ed25519-signed by an initiator key and answered with a result event:
| Protocol | Type | Curve | Best For |
|----------|------|-------|----------|
| **CGGMP21** | Threshold ECDSA | secp256k1, P-256 | Bitcoin, Ethereum, EVM chains |
| **FROST** | Threshold Schnorr/EdDSA | Ed25519, Ristretto | Solana, Polkadot, Cosmos |
| **LSSS** | Linear secret sharing | Any | Dynamic resharing, key rotation |
| **TFHE** | Threshold FHE | Lattice | Encrypted computation on shares |
| **Corona** | Post-quantum lattice | Module-LWE | Future-proof custody |
| **Quasar** | Hybrid BLS + PQ | BLS12-381 + Dilithium | Ethereum 2.0, PQ hedge |
| **BLS** | Aggregate signatures | BLS12-381 | Validator keys, aggregate sigs |
| **Doerner** | Optimized 2-of-2 | secp256k1 | High-frequency 2-party signing |
```go
import (
"github.com/luxfi/mpc/pkg/client"
"github.com/luxfi/mpc/pkg/event"
)
See the [Protocols deep dive](/docs/services/mpc/protocols) for detailed specifications.
mpc := client.NewMPCClient(client.Options{NatsConn: nc, KeyPath: "./event_initiator.key"})
## Multi-Chain Support
A single MPC wallet can derive addresses for any supported chain:
| Chain | Curve | Protocol | Notes |
|-------|-------|----------|-------|
| Bitcoin | secp256k1 | CGGMP21 | Legacy, SegWit, Native SegWit |
| Bitcoin Taproot | secp256k1 (BIP-340) | FROST | Schnorr signatures |
| Ethereum | secp256k1 | CGGMP21 | EIP-155 replay protection |
| All EVM chains | secp256k1 | CGGMP21 | Polygon, Arbitrum, BNB, etc. |
| Solana | Ed25519 | FROST | SPL token support |
| TON | Ed25519 | FROST | |
| Cardano | Ed25519 | FROST | Byron and Shelley |
| NEAR | Ed25519 | FROST | |
| Polkadot | Sr25519 | FROST | Substrate chains |
| Cosmos | secp256k1 | CGGMP21 | IBC-compatible |
| Lux Network | secp256k1 | CGGMP21 | X/P/C chains |
| XRPL | secp256k1 | CGGMP21 | |
| BNB Chain | secp256k1 | CGGMP21 | BSC and BNB Beacon |
| Polygon | secp256k1 | CGGMP21 | PoS and zkEVM |
| Arbitrum | secp256k1 | CGGMP21 | L2 rollup |
| Ethereum 2.0 | BLS12-381 | BLS / Quasar | Validator signing |
## Performance
Real benchmarks from production clusters:
### FROST Key Generation
| Parties | Time | Notes |
|---------|------|-------|
| 3 | 22 ms | Typical hot wallet |
| 10 | 38 ms | Team custody |
| 20 | 332 ms | Institutional |
| 30 | 535 ms | Large consortium |
| 50 | 1.85 s | DAO governance |
### FROST Signing
| Signers | Time | Notes |
|---------|------|-------|
| 3 | 25 ms | Standard threshold |
| 5 | 21 ms | Batch-optimized |
Signing is **O(t)** -- it depends only on the threshold, not the total number of parties. A 3-of-100 wallet signs just as fast as a 3-of-3.
### Cryptographic Primitives
| Operation | Throughput |
|-----------|-----------|
| Scalar multiplication | 23 us/op |
| Lagrange interpolation | O(n^2) |
| Polynomial evaluation | O(n * t) |
| Keygen communication | O(n^2) rounds |
## Architecture
```
┌──────────────────────────────────┐
│ Client / KMS │
│ (REST API + Bearer Auth) │
└──────────────┬───────────────────┘
│
┌──────────────▼───────────────────┐
│ MPC Coordinator │
│ - Protocol orchestration │
│ - IAM token validation │
│ - Audit logging │
└──────────────┬───────────────────┘
│ NATS JetStream
┌──────────────▼─────────────────────────┐
│ MPC Nodes (StatefulSet / Validators) │
│ │
│ ┌────────┐ ┌────────┐ ... ┌────────┐ │
│ │ Node 0 │ │ Node 1 │ │ Node N │ │
│ │ share₀ │ │ share₁ │ │ shareₙ │ │
│ │ Ed25519│ │ Ed25519│ │ Ed25519│ │
│ │ ML-DSA │ │ ML-DSA │ │ ML-DSA │ │
│ └────┬───┘ └────┬───┘ └────┬───┘ │
│ │ │ │ │
│ ┌────▼──────────▼────────────────▼───┐ │
│ │ Private BFT Blockchain (ConsensusKV)│ │
│ │ Dual-cert: Ed25519 + ML-DSA-65 │ │
│ │ t-of-n threshold finality │ │
│ └────────────────────────────────────┘ │
│ │ │
│ ┌────▼──────────────────────────────┐ │
│ │ BadgerDB (encrypted) │ │
│ │ AES-256-GCM per node │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
mpc.OnWalletCreationResult(func(e event.KeygenResultEvent) {
// e.ECDSAPubKey (CGGMP21), e.EDDSAPubKey (FROST), …
})
mpc.CreateWallet(walletID)
```
### Components
| Component | Technology | Purpose |
|-----------|-----------|---------|
| Coordinator | Go | Protocol orchestration, API gateway |
| MPC Nodes | Go + Rust (crypto) | Key share storage, protocol execution, consensus validators |
| ConsensusKV | Private BFT chain | Cluster state (peers, keys, wallets) with dual-cert finality |
| Message Bus | NATS JetStream | Inter-node messaging + consensus transport |
| Storage | BadgerDB | Encrypted key share persistence |
| Auth | Hanzo IAM (hanzo.id) | Bearer token validation, RBAC |
| Identity | Ed25519 + ML-DSA-65 | Dual post-quantum node identity, stored in KMS/HSM |
### State Management Backends
All cluster state flows through an abstract KV interface. Three backends are available:
| Backend | Config | Transport | Finality | Use Case |
|---------|--------|-----------|----------|----------|
| `consensus` | `kv_backend: consensus` | NATS + Private BFT | Dual-cert threshold (<100ms) | **Production** (default) |
| `nats` | `kv_backend: nats` | NATS JetStream KV | JetStream replication | Development/staging |
| `consul` | `kv_backend: consul` | Consul HTTP | Raft | Legacy/parity testing |
See the [Consensus deep dive](/docs/services/mpc/consensus) for how the private blockchain works.
## Authentication
All API requests require a Bearer token issued by Hanzo IAM:
```bash
# Obtain token from Hanzo IAM
TOKEN=$(curl -s https://hanzo.id/v1/iam/login \
-d '{"username":"[email protected]","password":"..."}' | jq -r .token)
# Use with MPC API
curl -H "Authorization: Bearer $TOKEN" \
https://mpc.hanzo.ai/api/wallets
```
See [Hanzo IAM](/docs/services/iam) for full authentication documentation.
The REST custody API above is a facade over this same backend.
## Security Model
### Key Share Isolation
- Each node runs in its own Kubernetes pod with dedicated persistent volume
- Shares are encrypted at rest with AES-256-GCM using node-specific keys
- Node-to-node communication uses mTLS via Consul Connect
- No single node ever holds enough information to reconstruct a private key
### Zero-Knowledge Proofs
17 ZK proof systems verify correctness at every protocol stage:
- Schnorr proofs of discrete log knowledge
- Paillier encryption correctness proofs
- Range proofs for secret share values
- Commitment consistency proofs
- Feldman VSS verification
- Pedersen commitment proofs
### Identifiable Aborts
If any participant deviates from the protocol, the system:
1. Detects the deviation through ZK verification
2. Identifies the specific malicious party
3. Aborts the operation with a signed blame proof
4. Logs the incident to the audit trail
### Proactive Share Refresh
Key shares can be refreshed on a schedule or on-demand without changing the underlying key:
- Old shares become useless after refresh
- Compromised shares from a past epoch cannot be combined with current shares
- The public key and all derived addresses remain unchanged
## Endpoints
| Environment | URL | Backend | Threshold |
|-------------|-----|---------|-----------|
| Production (managed) | `https://mpc.hanzo.ai` | consensus | 3-of-5 |
| Production (decentralized) | `https://mpc.lux.network` | consensus | 3-of-5 |
| Staging | `https://stg.mpc.hanzo.ai` | nats | 2-of-3 |
- **Key share isolation** — each node runs in its own pod with a dedicated encrypted volume; no node holds enough to reconstruct a key.
- **Encrypted shares** — shares are AES-256-GCM encrypted in an embedded key-value store; the encryption key comes from an HSM or [Hanzo KMS](/docs/services/kms) (`kms.hanzo.ai`). In production, plaintext-password backends are rejected outright.
- **Signed messaging** — every inter-node and initiator message is Ed25519-signed and verified before processing.
- **Zero-knowledge proofs** verify correctness at each protocol stage; a deviating party is identified and an operation aborts with a signed blame proof.
- **Post-quantum cluster state** — peers, keys, and wallets live in a private BFT chain with dual-certificate (Ed25519 + ML-DSA-65) threshold finality.
## Next Steps
<Cards>
<Card title="Consensus" href="/docs/services/mpc/consensus">
How the private BFT blockchain and dual-certificate finality work
<Card title="API Reference" href="/docs/services/mpc/api">
Complete REST API: wallets, sessions, signing, resharing
</Card>
<Card title="Protocols" href="/docs/services/mpc/protocols">
Deep dive into all 8 cryptographic protocols
CGGMP21, FROST, LSS, and BLS threshold schemes
</Card>
<Card title="API Reference" href="/docs/services/mpc/api">
Complete REST API documentation
</Card>
<Card title="Deployment" href="/docs/services/mpc/deployment">
Helm charts, Docker, and Kubernetes setup
<Card title="Consensus" href="/docs/services/mpc/consensus">
The private BFT chain and dual-certificate finality
</Card>
<Card title="KMS Integration" href="/docs/services/kms">
Use MPC as a signing backend for Hanzo KMS
Use MPC as a threshold signing backend for Hanzo KMS
</Card>
</Cards>
@@ -86,14 +86,13 @@ The workhorse protocol for Bitcoin, Ethereum, and all EVM-compatible chains. Bas
- Bitcoin (legacy, SegWit, Native SegWit)
- Ethereum and all EVM chains
- Cosmos (secp256k1)
- Lux Network (C/X/P chains)
- XRPL
- Any chain using ECDSA over secp256k1 or P-256
### Example
```bash
curl -X POST https://mpc.hanzo.ai/api/wallets \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -163,7 +162,7 @@ Signing time depends only on `t` (threshold), not `n` (total parties). A 3-of-10
### Example
```bash
curl -X POST https://mpc.hanzo.ai/api/wallets \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -221,7 +220,7 @@ LSSS generalizes Shamir's Secret Sharing with the addition of proactive refresh
```bash
# Reshare: change from 2-of-3 to 3-of-5
curl -X POST https://mpc.hanzo.ai/api/reshare \
curl -X POST https://mpc.hanzo.ai/v1/wallets/$WALLET_ID/reshare \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -307,7 +306,7 @@ Corona provides threshold signatures using Module-LWE, offering resistance again
### Example
```bash
curl -X POST https://mpc.hanzo.ai/api/wallets \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -352,7 +351,7 @@ Quasar produces dual signatures -- one BLS12-381 for immediate blockchain compat
### Example
```bash
curl -X POST https://mpc.hanzo.ai/api/wallets \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -395,12 +394,12 @@ Signing is non-interactive -- once nonce commitments are exchanged during a setu
- Ethereum 2.0 validator signing
- Aggregate multi-party attestations
- Consensus protocol signing (Lux Network validators)
- Consensus protocol signing (PoS validator keys)
### Example
```bash
curl -X POST https://mpc.hanzo.ai/api/wallets \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -449,7 +448,7 @@ Doerner is a specialized protocol for the 2-of-2 case that achieves the lowest p
### Example
```bash
curl -X POST https://mpc.hanzo.ai/api/wallets \
curl -X POST https://mpc.hanzo.ai/v1/mpc/wallets \
-H "Authorization: Bearer $HANZO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
+28 -579
View File
@@ -1,601 +1,50 @@
---
title: Hanzo PaaS
description: Kubernetes-native container orchestration and deployment layer for Hanzo infrastructure. Manages DOKS clusters, namespace isolation, ingress, secrets, CI/CD, and auto-scaling.
description: The platform.hanzo.ai control plane for deploying and operating services on Hanzo's Kubernetes clusters.
---
# Hanzo PaaS
Hanzo PaaS is the container orchestration and deployment layer that runs all Hanzo and Lux production infrastructure. It provides Kubernetes-native deployments on DigitalOcean Kubernetes (DOKS) clusters with namespace isolation, automated TLS, secrets management via KMS, CI/CD pipelines, and horizontal auto-scaling.
Hanzo PaaS is the deployment and operations control plane for the Hanzo platform, served at **`platform.hanzo.ai`**. You register an application, point it at a container image or Git repository, attach a domain, and the platform builds, deploys, and operates it on Hanzo's Kubernetes clusters — with deployment history, rolling updates, and drift detection.
**Endpoint:** `paas.hanzo.ai`
**Gateway:** `api.hanzo.ai/v1/paas/*`
| | |
|---|---|
| **Control plane** | `https://platform.hanzo.ai` |
| **Canonical API** | `https://platform.hanzo.ai/v1` |
| **Auth** | [Hanzo IAM](/docs/services/iam) (interactive) · API key / service token (machine) |
| **Secrets** | [Hanzo KMS](/docs/services/kms) via `KMSSecret` CRDs — zero plaintext |
| **Ingress** | Hanzo Ingress + cert-manager (automatic Let's Encrypt TLS) |
## Features
- **Kubernetes-Native Deployments**: All services run as K8s Deployments, StatefulSets, or DaemonSets with declarative manifests
- **Multi-Cluster Management**: Two DOKS clusters purpose-separated for Hanzo services and Lux blockchain
- **Namespace Isolation**: Service groups are isolated by namespace with RBAC boundaries
- **Ingress + TLS**: nginx-ingress with cert-manager for automatic Let's Encrypt certificate provisioning
- **Secrets Management**: KMS integration via `KMSSecret` custom resources for zero-plaintext secrets
- **CI/CD Pipelines**: GitHub Actions workflows build, push, and deploy on every merge to main
- **Container Registry**: All images published to `ghcr.io/hanzoai/*` with multi-arch support
- **Helm Charts**: Complex multi-component services deployed via Helm for repeatability
- **Horizontal Auto-Scaling**: HPA policies based on CPU, memory, and custom metrics
- **Persistent Volumes**: Block storage PVCs for stateful services (databases, validators, storage)
- **Internal DNS**: Service discovery via `*.hanzo.svc` cluster DNS
## Architecture
## How it fits together
```
┌──────────────────────────────────────┐
│ Cloudflare DNS │
│ *.hanzo.ai *.lux.network *.zoo.id│
└──────────┬──────────┬────────────────┘
│ │
┌─────────────────▼──┐ ┌────▼─────────────────┐
│ hanzo-k8s │ │ lux-k8s │
│ 24.199.76.156 │ │ 24.144.69.101 │
│ │ │ │
│ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ nginx-ingress │ │ │ │ nginx-ingress │ │
│ │ + cert-manager│ │ │ │ + cert-manager│ │
│ └──────┬────────┘ │ │ └──────┬────────┘ │
│ │ │ │ │ │
│ ┌──────▼────────┐ │ │ ┌──────▼────────┐ │
│ │ Namespaces │ │ │ │ Namespaces │ │
│ │ │ │ │ │ │ │
│ │ hanzo │ │ │ │ hanzo │ │
│ │ hanzo-zt │ │ │ │ lux │ │
│ │ monitoring │ │ │ │ monitoring │ │
│ │ cert-manager │ │ │ │ cert-manager │ │
│ └──────┬────────┘ │ │ └──────┬────────┘ │
│ │ │ │ │ │
│ ┌──────▼────────┐ │ │ ┌──────▼────────┐ │
│ │ Data Layer │ │ │ │ Data Layer │ │
│ │ PostgreSQL │ │ │ │ PostgreSQL │ │
│ │ Redis/Valkey │ │ │ │ Redis/Valkey │ │
│ │ MongoDB │ │ │ │ Datastore │ │
│ │ Hanzo S3 │ │ │ └───────────────┘ │
│ │ ClickHouse │ │ │ │
│ └───────────────┘ │ │ ┌───────────────┐ │
│ │ │ │ Lux Network │ │
│ ┌───────────────┐ │ │ │ 15 validators│ │
│ │ KMS Operator │ │ │ │ gateway │ │
│ │ KMS Operator │ │ │ │ markets │ │
│ └───────────────┘ │ │ └───────────────┘ │
└─────────────────────┘ └──────────────────────┘
│ │
┌─────────▼────────────────────────▼──────────┐
│ ghcr.io/hanzoai/* │
│ Container Image Registry │
└─────────────────────────────────────────────┘
push image / git ──▶ platform.hanzo.ai ──▶ operator ──▶ Kubernetes
(declare + drive) (reconcile) (run + serve)
▲ │
└────────── observe (drift) ────────┘
```
## Quick Start
- **Declare & drive** — register applications and trigger deploys through the control plane.
- **Reconcile** — the operator applies the declared state to the cluster.
- **Observe** — the control plane tracks declared vs running vs latest image tags and surfaces drift.
### Prerequisites
Secrets are never embedded in manifests — workloads read them from [Hanzo KMS](/docs/services/kms), synced into Kubernetes with [KMSSecret CRDs](/docs/services/kms/kmssecret-crd).
```bash
# Install kubectl and configure cluster access
brew install kubectl helm
## Get started
# Configure contexts for both clusters
doctl kubernetes cluster kubeconfig save hanzo-k8s
doctl kubernetes cluster kubeconfig save lux-k8s
# Verify access
kubectl --context do-sfo3-hanzo-k8s get nodes
kubectl --context do-sfo3-lux-k8s get nodes
```
### Deploy a Service
```yaml
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
namespace: hanzo
labels:
app: my-service
spec:
replicas: 2
selector:
matchLabels:
app: my-service
template:
metadata:
labels:
app: my-service
spec:
imagePullSecrets:
- name: ghcr-secret
containers:
- name: my-service
image: ghcr.io/hanzoai/my-service:latest
ports:
- containerPort: 8080
envFrom:
- secretRef:
name: my-service-secrets
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
```
```bash
# Apply to hanzo-k8s cluster
kubectl --context do-sfo3-hanzo-k8s apply -f deployment.yaml
```
### Expose via Ingress
```yaml
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-service
namespace: hanzo
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
ingressClassName: nginx
tls:
- hosts:
- my-service.hanzo.ai
secretName: my-service-tls
rules:
- host: my-service.hanzo.ai
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 8080
```
### Deploy with Helm
```bash
# Add Hanzo chart repository
helm repo add hanzo https://charts.hanzo.ai
helm repo update
# Install a chart
helm install my-release hanzo/my-chart \
--namespace hanzo \
--set image.tag=v1.2.3 \
--set replicas=3
```
## Cluster Layout
### hanzo-k8s (24.199.76.156)
Primary cluster for all Hanzo platform services. Region: SFO3.
| Service | Namespace | Domain | Image |
|---------|-----------|--------|-------|
| IAM | hanzo | hanzo.id, lux.id, zoo.id, pars.id | `ghcr.io/hanzoai/iam` |
| KMS | hanzo | kms.hanzo.ai | `ghcr.io/hanzoai/kms` |
| Console | hanzo | console.hanzo.ai | `hanzoai/console` |
| Console Worker | hanzo | (internal) | `ghcr.io/hanzoai/console-worker` |
| Cloud Backend | hanzo | cloud.hanzo.ai | `ghcr.io/hanzoai/cloud` |
| Gateway | hanzo | api.hanzo.ai | `ghcr.io/hanzoai/gateway` |
| LLM Proxy | hanzo | llm.hanzo.ai | `ghcr.io/hanzoai/llm` |
| Commerce | hanzo | commerce.hanzo.ai | `ghcr.io/hanzoai/commerce` |
| Platform | hanzo | platform.hanzo.ai | `ghcr.io/hanzoai/platform` |
| Storage (Hanzo S3) | hanzo | s3.hanzo.ai, s3-api.hanzo.ai | `ghcr.io/hanzoai/storage` |
| PostgreSQL | hanzo | postgres.hanzo.svc | `postgres:16` |
| Redis/Valkey | hanzo | redis.hanzo.svc | `valkey/valkey:8` |
| MongoDB | hanzo | mongodb.hanzo.svc | `mongo:7` |
| ClickHouse | hanzo | clickhouse.hanzo.svc | `clickhouse/clickhouse-server` |
| ZT Controller | hanzo-zt | zt.hanzo.ai | `ghcr.io/hanzoai/ziti` |
| ZT Router | hanzo-zt | (mesh) | `ghcr.io/hanzoai/ziti-router` |
### lux-k8s (24.144.69.101)
Dedicated cluster for Lux blockchain infrastructure. Region: SFO3.
| Service | Namespace | Domain | Image |
|---------|-----------|--------|-------|
| Lux Validators (x15) | lux | (P2P) | `ghcr.io/luxfi/node` |
| Hanzo Gateway | hanzo | api.lux.network | `ghcr.io/hanzoai/gateway` |
| Lux Cloud | hanzo | cloud.lux.network | `ghcr.io/luxfi/cloud-web` |
| Markets | hanzo | markets.lux.network | `ghcr.io/luxfi/markets` |
| PostgreSQL | hanzo | postgres.hanzo.svc | `postgres:16` |
| Redis/Valkey | hanzo | redis.hanzo.svc | `valkey/valkey:8` |
## Namespace Organization
Namespaces provide security boundaries and resource isolation.
```
hanzo-k8s:
hanzo/ # All core Hanzo services
hanzo-zt/ # Zero Trust mesh networking (Ziti)
monitoring/ # Prometheus, Grafana, alerting
cert-manager/ # TLS certificate automation
kube-system/ # K8s system components
lux-k8s:
hanzo/ # Platform services (gateway, cloud)
lux/ # Blockchain validators and infra
monitoring/ # Cluster monitoring
cert-manager/ # TLS certificate automation
kube-system/ # K8s system components
```
Each namespace has:
- **ResourceQuotas**: CPU/memory limits per namespace
- **NetworkPolicies**: Restrict inter-namespace traffic (only explicit allowlists)
- **RBAC**: Service accounts scoped to namespace resources
- **LimitRanges**: Default container resource constraints
## Secrets Management
All secrets are managed through Hanzo KMS and synced into Kubernetes via the KMS operator. No plaintext secrets in manifests or Git.
### KMSSecret Custom Resource
```yaml
# secrets.yaml
apiVersion: secrets.hanzo.ai/v1
kind: KMSSecret
metadata:
name: my-service-secrets
namespace: hanzo
spec:
# KMS project and environment
projectId: "proj-xxxxxxxx"
environment: "production"
secretPath: "/my-service"
# Authentication via Universal Auth
authentication:
universalAuth:
credentialsRef:
secretName: kms-machine-identity
secretNamespace: hanzo
# Target K8s secret
managedSecretReference:
secretName: my-service-secrets
secretNamespace: hanzo
secretType: Opaque
# Sync interval
resyncInterval: 60
```
### Secret Workflow
```
Developer Hanzo KMS K8s Cluster
│ │ │
├── Create secret ───────►│ │
│ via UI or CLI │ │
│ ├── KMS Operator sync ───►│
│ │ (every 60s) │
│ │ ├── Pod mounts
│ │ │ secret as env
│ │ │
```
### Migrating from Static Secrets
Existing manifests with inline secrets should be replaced with `KMSSecret` resources:
```bash
# 1. Store secret in KMS
hanzo-kms secrets set DATABASE_URL "postgres://..." \
--env production --path /my-service
# 2. Replace K8s secret manifest with KMSSecret
# 3. Reference the synced secret in your Deployment envFrom
```
## CI/CD Pipeline
All services use GitHub Actions for continuous integration and deployment.
### Standard Workflow
```yaml
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 1. Checkout
- uses: actions/checkout@v4
# 2. Authenticate with KMS for deploy secrets
- name: Fetch deploy secrets from KMS
uses: hanzoai/kms-action@v1
with:
client-id: ${{ secrets.KMS_CLIENT_ID }}
client-secret: ${{ secrets.KMS_CLIENT_SECRET }}
project-id: ${{ secrets.KMS_PROJECT_ID }}
environment: production
path: /ci-cd
env:
KMS_URL: https://kms.hanzo.ai
# 3. Build and push to GHCR
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/hanzoai/${{ github.event.repository.name }}:latest
# 4. Deploy to K8s
- uses: digitalocean/action-doctl@v2
with:
token: ${{ env.DIGITALOCEAN_ACCESS_TOKEN }}
- run: |
doctl kubernetes cluster kubeconfig save hanzo-k8s
kubectl set image deployment/my-service \
my-service=ghcr.io/hanzoai/my-service:${{ github.sha }} \
--namespace hanzo
kubectl rollout status deployment/my-service \
--namespace hanzo --timeout=300s
```
### Image Registry
All container images are stored in the GitHub Container Registry under the `hanzoai` organization:
```
ghcr.io/hanzoai/
iam:latest # IAM
kms:latest # KMS
console:latest # Console web
console-worker:latest # Console MQ worker
cloud:latest # Cloud backend
cloud-site:latest # Cloud landing page
gateway:latest # API gateway
llm:latest # LLM proxy
commerce:latest # Commerce engine
platform:latest # Platform (PaaS)
storage:latest # Hanzo S3 storage
bot-site:latest # hanzo.bot site
ziti:latest # ZT controller
ziti-router:latest # ZT router
```
Authentication for pulling images in-cluster:
```bash
# Create GHCR pull secret (one-time per cluster)
kubectl create secret docker-registry ghcr-secret \
--docker-server=ghcr.io \
--docker-username=hanzoai \
--docker-password=$GHCR_TOKEN \
--namespace hanzo
```
## Auto-Scaling
Horizontal Pod Autoscaler (HPA) policies ensure services scale with demand.
```yaml
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-service
namespace: hanzo
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 120
```
## Persistent Volumes
Stateful services use DigitalOcean Block Storage via the `do-block-storage` StorageClass.
```yaml
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: hanzo
spec:
accessModes:
- ReadWriteOnce
storageClassName: do-block-storage
resources:
requests:
storage: 50Gi
```
Current persistent volumes:
| Service | Size | Mount Path |
|---------|------|------------|
| PostgreSQL (hanzo-k8s) | 50Gi | /var/lib/postgresql/data |
| PostgreSQL (lux-k8s) | 50Gi | /var/lib/postgresql/data |
| MongoDB | 20Gi | /data/db |
| ClickHouse | 100Gi | /var/lib/clickhouse |
| Hanzo S3 | 200Gi | /data |
| ZT Controller | 2Gi | /persistent |
| ZT Router | 100Mi | /persistent |
| Lux Validators (x15) | 50Gi each | /data |
## Monitoring
Cluster health and service metrics are collected via the monitoring stack.
### Stack
- **Prometheus**: Metrics collection from all pods (ServiceMonitor CRDs)
- **Grafana**: Dashboards and alerting (accessible via console.hanzo.ai)
- **AlertManager**: PagerDuty and Slack alert routing
- **Node Exporter**: Host-level metrics from all cluster nodes
### Health Checks
Every service exposes standard health endpoints:
```
GET /health # Liveness - is the process alive?
GET /health/ready # Readiness - is the service ready for traffic?
```
### Key Alerts
| Alert | Condition | Severity |
|-------|-----------|----------|
| PodCrashLooping | Restart count > 3 in 5m | critical |
| HighCPU | CPU > 90% for 10m | warning |
| HighMemory | Memory > 85% for 10m | warning |
| CertExpiringSoon | TLS cert expires < 14d | warning |
| PVCNearFull | Volume usage > 80% | warning |
| NodeNotReady | Node unschedulable > 5m | critical |
## Internal DNS
All services within a cluster are discoverable via Kubernetes DNS:
```
# Format: <service>.<namespace>.svc.cluster.local
# Short form (same namespace): <service>.hanzo.svc
postgres.hanzo.svc:5432 # PostgreSQL
redis.hanzo.svc:6379 # Redis/Valkey
mongodb.hanzo.svc:27017 # MongoDB
clickhouse.hanzo.svc:9000 # ClickHouse
s3.hanzo.svc:9000 # Hanzo S3 API
s3.hanzo.svc:9001 # Hanzo S3 Console
```
Services reference each other by internal DNS, keeping traffic in-cluster and avoiding external round-trips.
## Infrastructure as Code
All cluster manifests live in the `universe/infra/k8s/` directory, organized by service:
```
universe/infra/k8s/
iam/ # IAM deployment, service, ingress, configmap
kms/ # KMS Helm values, secrets
console/ # Console web + worker deployments
cloud/ # Cloud backend
gateway/ # API gateway
commerce/ # Commerce engine
storage/ # Hanzo S3 deployment, ingress, buckets
paas/ # PaaS platform
zt/ # Zero Trust controller + router
monitoring/ # Prometheus, Grafana, AlertManager
cert-manager/ # ClusterIssuer, cert resources
```
Changes to manifests follow GitOps: push to main, CI applies to cluster.
## API Reference
### Deployments
- `GET /v1/paas/deployments` - List all deployments
- `POST /v1/paas/deployments` - Create deployment
- `GET /v1/paas/deployments/:id` - Get deployment status
- `PATCH /v1/paas/deployments/:id` - Update deployment
- `DELETE /v1/paas/deployments/:id` - Delete deployment
- `POST /v1/paas/deployments/:id/rollback` - Rollback to previous revision
### Clusters
- `GET /v1/paas/clusters` - List managed clusters
- `GET /v1/paas/clusters/:id/nodes` - List cluster nodes
- `GET /v1/paas/clusters/:id/namespaces` - List namespaces
### Scaling
- `GET /v1/paas/deployments/:id/hpa` - Get HPA configuration
- `PUT /v1/paas/deployments/:id/hpa` - Update HPA policy
- `POST /v1/paas/deployments/:id/scale` - Manual scale
See the [full API reference](/docs/openapi) for all endpoints.
## Related Services
The full control-plane reference — the project/environment/application model, the `/v1` container redeploy, the build pipeline, clusters, domains, and the observe/drift board — lives in the Platform docs:
<Cards>
<Card title="Platform" href="/docs/services/platform">
PaaS UI for managing deployments, domains, and databases via web interface
<Card title="Platform Overview" href="/docs/services/platform">
The container/app model and deployment workflow
</Card>
<Card title="Registry" href="/docs/services/registry">
Container image registry and artifact management at ghcr.io/hanzoai
<Card title="API Reference" href="/docs/services/platform/api">
The `/v1` control surface and the `/api` build pipeline
</Card>
<Card title="KMS" href="/docs/services/kms">
Secrets management with Kubernetes operator for zero-plaintext secret sync
<Card title="Authentication" href="/docs/services/platform/api/authentication">
API keys, service tokens, and IAM tokens
</Card>
<Card title="Observability" href="/docs/services/o11y">
Prometheus, Grafana, and AlertManager for cluster and service monitoring
<Card title="Containers API" href="/docs/services/platform/api/containers">
Create, deploy, redeploy, and operate applications
</Card>
</Cards>
@@ -1,66 +1,51 @@
---
title: Authentication
description: OAuth2 via Hanzo ID, API tokens, and bearer authentication
description: IAM tokens, API keys, and service tokens for the Hanzo PaaS API
---
All API requests to Hanzo Platform require authentication. The platform supports OAuth2 via Hanzo ID and long-lived API tokens.
Hanzo PaaS has three credential types, matched to its two API surfaces. Machine credentials are stored in [Hanzo KMS](/docs/services/kms) and read at runtime — never hard-coded.
## Authentication Methods
| Method | Lifetime | Use Case |
|--------|----------|----------|
| **OAuth2 Access Token** | 1 hour | Dashboard sessions, user-facing apps |
| **API Token** | Until revoked | CI/CD, scripts, server-to-server |
Both methods use the same `Authorization: Bearer <token>` header.
| Credential | Header | Surface | Use |
|------------|--------|---------|-----|
| **IAM token** | `Authorization: Bearer <jwt>` | `/api` | Interactive / user access via SSO |
| **API key** | `x-api-key: <key>` | `/api` | Machine access to the build pipeline; org-scoped |
| **Service token** | `Authorization: Bearer <token>` | `/v1` | Container redeploy and observe (M2M) |
## OAuth2 via Hanzo ID
Hanzo Platform uses [Hanzo ID](https://hanzo.id) as its identity provider. The OAuth2 Authorization Code flow is used for user authentication.
### Authorization Code Flow
Interactive access uses the [Hanzo ID](https://hanzo.id) OAuth2 Authorization Code flow.
<Steps>
### Redirect to Hanzo ID
```
https://hanzo.id/oauth/authorize?
client_id=YOUR_CLIENT_ID&
https://hanzo.id/v1/iam/oauth/authorize?
client_id=hanzo-platform&
redirect_uri=https://yourapp.com/callback&
response_type=code&
scope=openid profile email&
state=RANDOM_STATE
```
### User authenticates
The user logs in at Hanzo ID and approves access.
### Handle the callback
Hanzo ID redirects to your `redirect_uri` with an authorization code:
Hanzo ID redirects back with `?code=...&state=...`.
```
https://yourapp.com/callback?code=AUTH_CODE&state=RANDOM_STATE
```
### Exchange code for tokens
### Exchange the code for tokens
```bash
curl -X POST https://hanzo.id/oauth/token \
curl -X POST https://hanzo.id/v1/iam/oauth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"code": "AUTH_CODE",
"client_id": "hanzo-platform",
"client_secret": "'$CLIENT_SECRET'",
"code": "'$AUTH_CODE'",
"redirect_uri": "https://yourapp.com/callback",
"grant_type": "authorization_code"
}'
```
**Response:**
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
@@ -72,89 +57,43 @@ curl -X POST https://hanzo.id/oauth/token \
</Steps>
### Refreshing Tokens
Refresh with `grant_type=refresh_token`. See [Hanzo IAM](/docs/services/iam) for the full flow.
Access tokens expire after 1 hour. Use the refresh token to obtain a new access token:
## API keys (build pipeline)
```bash
curl -X POST https://hanzo.id/oauth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"refresh_token": "YOUR_REFRESH_TOKEN",
"grant_type": "refresh_token"
}'
```
## API Tokens
API tokens are long-lived credentials for programmatic access. They do not expire unless revoked.
### Creating a Token
<Tabs items={["Dashboard", "CLI"]}>
<Tab value="Dashboard">
Go to **Settings > API Tokens > Create Token**. Name the token and select its scope. The token is shown once -- copy it immediately.
</Tab>
<Tab value="CLI">
```bash
hanzo token create --name "CI Pipeline" --scope "deploy:*"
# Output: tk_abc123def456...
```
</Tab>
</Tabs>
API keys authenticate machine callers to the `/api` build-pipeline surface. Create one in the Studio under **Settings → API Keys** — the key is scoped to the organization you create it under and shown once.
<Callout type="warn">
API tokens are shown only once at creation time. Store them securely. If lost, revoke the old token and create a new one.
Store API keys in [Hanzo KMS](/docs/services/kms) immediately. They are shown only at creation. If lost, revoke and create a new one.
</Callout>
### Using a Token
```bash
curl https://app.platform.hanzo.ai/api/cluster.all \
-H "Authorization: Bearer tk_abc123def456"
curl https://platform.hanzo.ai/api/application.all \
-H "x-api-key: $PLATFORM_API_KEY"
```
### Revoking a Token
## Service tokens (`/v1` surface)
The canonical `/v1` control surface (container redeploy, `/v1/apps` observe) uses a service token presented as a bearer:
```bash
# Via CLI
hanzo token revoke tk_abc123def456
curl -fsS -X POST \
"https://platform.hanzo.ai/v1/org/$ORG_ID/project/$PROJECT_ID/env/$ENV_ID/container/$CONTAINER_ID/redeploy" \
-H "Authorization: Bearer $PAAS_SERVICE_TOKEN"
```
# Via API
curl -X POST https://app.platform.hanzo.ai/api/user.revokeToken \
-H "Authorization: Bearer YOUR_TOKEN" \
The service token is issued out-of-band and stored in KMS (e.g. `kms.hanzo.ai/v1/kms/orgs/hanzo/secrets/paas/SERVICE_TOKEN`). It is a pure machine-to-machine credential with no user session.
## Request examples
```bash
# List organizations (build pipeline, API key)
curl https://platform.hanzo.ai/api/organization.all \
-H "x-api-key: $PLATFORM_API_KEY"
# Register an application
curl -X POST https://platform.hanzo.ai/api/application.create \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "tokenId": "tk_abc123def456" }'
```
## Token Scopes
Tokens can be scoped to limit access:
| Scope | Access |
|-------|--------|
| `*` | Full access (default) |
| `read:*` | Read-only across all resources |
| `deploy:*` | Deploy to any cluster |
| `deploy:cluster_abc` | Deploy to a specific cluster |
| `admin:*` | Administrative operations |
## Request Examples
```bash
# List organizations
curl https://app.platform.hanzo.ai/api/organization.all \
-H "Authorization: Bearer YOUR_TOKEN"
# Create a container
curl -X POST https://app.platform.hanzo.ai/api/container.create \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "web",
"image": "ghcr.io/myorg/myapp:latest",
"clusterId": "cluster_abc123"
}'
-d '{ "name": "web", "environmentId": "'$ENV_ID'" }'
```
@@ -26,7 +26,7 @@ The Clusters API handles the full lifecycle of compute clusters: creation, confi
`GET /api/cluster.all?organizationId=org_abc123`
```bash
curl "https://app.platform.hanzo.ai/api/cluster.all?organizationId=org_abc123" \
curl "https://platform.hanzo.ai/api/cluster.all?organizationId=org_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
```
@@ -66,7 +66,7 @@ curl "https://app.platform.hanzo.ai/api/cluster.all?organizationId=org_abc123" \
`GET /api/cluster.one?clusterId=cluster_abc123`
```bash
curl "https://app.platform.hanzo.ai/api/cluster.one?clusterId=cluster_abc123" \
curl "https://platform.hanzo.ai/api/cluster.one?clusterId=cluster_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
```
@@ -110,7 +110,7 @@ curl "https://app.platform.hanzo.ai/api/cluster.one?clusterId=cluster_abc123" \
`POST /api/cluster.create`
```bash
curl -X POST https://app.platform.hanzo.ai/api/cluster.create \
curl -X POST https://platform.hanzo.ai/api/cluster.create \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -154,7 +154,7 @@ Cluster provisioning is asynchronous. The cluster enters `provisioning` status a
`PATCH /api/cluster.update`
```bash
curl -X PATCH https://app.platform.hanzo.ai/api/cluster.update \
curl -X PATCH https://platform.hanzo.ai/api/cluster.update \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -168,7 +168,7 @@ curl -X PATCH https://app.platform.hanzo.ai/api/cluster.update \
`POST /api/cluster.addNode`
```bash
curl -X POST https://app.platform.hanzo.ai/api/cluster.addNode \
curl -X POST https://platform.hanzo.ai/api/cluster.addNode \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -185,7 +185,7 @@ The node joins the cluster automatically. For Kubernetes clusters, it is registe
`DELETE /api/cluster.removeNode`
```bash
curl -X DELETE https://app.platform.hanzo.ai/api/cluster.removeNode \
curl -X DELETE https://platform.hanzo.ai/api/cluster.removeNode \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -203,7 +203,7 @@ Workloads on the removed node are automatically rescheduled to remaining nodes.
`DELETE /api/cluster.remove`
```bash
curl -X DELETE https://app.platform.hanzo.ai/api/cluster.remove \
curl -X DELETE https://platform.hanzo.ai/api/cluster.remove \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -1,281 +1,106 @@
---
title: Containers API
description: Deploy, manage, and monitor container workloads
description: Deploy, redeploy, and operate container workloads
---
The Containers API manages container deployments across your clusters. It supports Docker images from any registry and handles the full deployment lifecycle.
A **container** is a deployable workload on Hanzo PaaS. There are two ways to act on one:
## Endpoints
- the canonical **`/v1` container surface** — a machine-driven, zero-downtime **redeploy** (rolling restart) of a running workload, authenticated with a service token;
- the **`/api` build pipeline** — the Dokploy-derived tRPC procedures (`application.*`) that register sources and run builds, authenticated with an API key.
| Procedure | Method | Description |
|-----------|--------|-------------|
| `container.all` | GET | List all containers in a project |
| `container.one` | GET | Get container details |
| `container.create` | POST | Create a new container deployment |
| `container.update` | PATCH | Update container configuration |
| `container.remove` | DELETE | Remove a container |
| `container.deploy` | POST | Trigger a new deployment |
| `container.restart` | POST | Restart running instances |
| `container.stop` | POST | Stop a container |
| `container.start` | POST | Start a stopped container |
| `container.logs` | GET | Stream container logs |
Under the hood a container maps to an `application` row whose `appName` is the live workload's name.
## List Containers
`GET /api/container.all?projectId=proj_abc123`
## Redeploy a running container (`/v1`)
```bash
curl "https://app.platform.hanzo.ai/api/container.all?projectId=proj_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
curl -fsS -X POST \
"https://platform.hanzo.ai/v1/org/$ORG_ID/project/$PROJECT_ID/env/$ENV_ID/container/$CONTAINER_ID/redeploy" \
-H "Authorization: Bearer $PAAS_SERVICE_TOKEN"
```
**Response:**
```json
{
"result": {
"data": [
{
"id": "ctr_abc123",
"name": "web",
"image": "ghcr.io/myorg/web:v1.2.3",
"status": "running",
"replicas": 3,
"port": 3000,
"clusterId": "cluster_abc123",
"createdAt": "2025-09-01T10:00:00Z"
},
{
"id": "ctr_def456",
"name": "worker",
"image": "ghcr.io/myorg/worker:v1.2.3",
"status": "running",
"replicas": 1,
"port": null,
"clusterId": "cluster_abc123",
"createdAt": "2025-09-01T10:05:00Z"
}
]
}
}
{ "ok": true }
```
## Get Container
This re-pulls the image and recreates pods with a rolling update — no new build. The service token comes from [Hanzo KMS](/docs/services/kms).
`GET /api/container.one?containerId=ctr_abc123`
## Build pipeline procedures (`/api`)
Each procedure is an HTTP `POST` to `https://platform.hanzo.ai/api/<procedure>` with `x-api-key`.
| Procedure | Purpose |
|-----------|---------|
| `application.create` | Register a new application (does not deploy) |
| `application.one` | Get application details |
| `application.all` | List applications in a project |
| `application.update` | Update configuration |
| `application.deploy` | Build & deploy (manual deployment) |
| `application.redeploy` | Rebuild an existing application |
| `application.start` · `application.stop` · `application.reload` | Lifecycle operations |
| `application.remove` | Delete the application |
### Create an application
`name` and `environmentId` are required; `appName` is generated if omitted.
```bash
curl "https://app.platform.hanzo.ai/api/container.one?containerId=ctr_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Response:**
```json
{
"result": {
"data": {
"id": "ctr_abc123",
"name": "web",
"image": "ghcr.io/myorg/web:v1.2.3",
"status": "running",
"replicas": 3,
"port": 3000,
"clusterId": "cluster_abc123",
"env": [
{ "key": "NODE_ENV", "value": "production" },
{ "key": "DATABASE_URL", "value": "***" }
],
"resources": {
"cpuLimit": "500m",
"memoryLimit": "512Mi"
},
"domains": ["app.example.com"],
"healthCheck": {
"path": "/health",
"port": 3000,
"interval": 30
},
"createdAt": "2025-09-01T10:00:00Z",
"lastDeployedAt": "2026-02-20T15:30:00Z"
}
}
}
```
## Create Container
`POST /api/container.create`
```bash
curl -X POST https://app.platform.hanzo.ai/api/container.create \
-H "Authorization: Bearer YOUR_TOKEN" \
curl -fsS -X POST https://platform.hanzo.ai/api/application.create \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "proj_abc123",
"clusterId": "cluster_abc123",
"name": "web",
"image": "ghcr.io/myorg/web:v1.2.3",
"port": 3000,
"replicas": 3,
"env": [
{ "key": "NODE_ENV", "value": "production" }
],
"resources": {
"cpuLimit": "500m",
"memoryLimit": "512Mi"
},
"healthCheck": {
"path": "/health",
"port": 3000,
"interval": 30
}
"environmentId": "'$ENV_ID'",
"description": "Public web app"
}'
```
**Parameters:**
Attach a Docker image source with `application.saveDockerProvider` (or a Git source with `application.saveGithubProvider`), then deploy.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `projectId` | string | Yes | Project ID |
| `clusterId` | string | Yes | Target cluster |
| `name` | string | Yes | Container name |
| `image` | string | Yes | Docker image reference |
| `port` | number | No | Exposed port |
| `replicas` | number | No | Number of instances (default: 1) |
| `env` | array | No | Environment variables |
| `resources` | object | No | CPU and memory limits |
| `healthCheck` | object | No | Health check configuration |
**Response:** `201 Created`
## Update Container
`PATCH /api/container.update`
Update configuration without triggering a new deployment. Use `container.deploy` to roll out changes.
### Deploy & redeploy
```bash
curl -X PATCH https://app.platform.hanzo.ai/api/container.update \
-H "Authorization: Bearer YOUR_TOKEN" \
# Build and deploy
curl -fsS -X POST https://platform.hanzo.ai/api/application.deploy \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"containerId": "ctr_abc123",
"replicas": 5,
"env": [
{ "key": "NODE_ENV", "value": "production" },
{ "key": "LOG_LEVEL", "value": "info" }
]
}'
```
-d '{ "applicationId": "'$APP_ID'" }'
## Deploy
`POST /api/container.deploy`
Trigger a new deployment with an updated image tag or configuration.
```bash
curl -X POST https://app.platform.hanzo.ai/api/container.deploy \
-H "Authorization: Bearer YOUR_TOKEN" \
# Rebuild an existing application
curl -fsS -X POST https://platform.hanzo.ai/api/application.redeploy \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"containerId": "ctr_abc123",
"image": "ghcr.io/myorg/web:v1.3.0"
}'
-d '{ "applicationId": "'$APP_ID'", "title": "ship v1.3.0" }'
```
**Response:**
`application.deploy` and `application.redeploy` enqueue a build job and return after it is scheduled. Deployments roll out with a zero-downtime strategy by default.
```json
{
"result": {
"data": {
"deploymentId": "dep_xyz789",
"status": "in_progress",
"previousImage": "ghcr.io/myorg/web:v1.2.3",
"newImage": "ghcr.io/myorg/web:v1.3.0",
"triggeredAt": "2026-02-26T10:00:00Z"
}
}
}
```
Deployments use a rolling update strategy by default. If the health check fails, the deployment is automatically rolled back.
## Lifecycle Operations
### Restart
### Lifecycle
```bash
curl -X POST https://app.platform.hanzo.ai/api/container.restart \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "containerId": "ctr_abc123" }'
# Stop / start / restart-in-place
curl -fsS -X POST https://platform.hanzo.ai/api/application.stop -H "x-api-key: $PLATFORM_API_KEY" -d '{"applicationId":"'$APP_ID'"}'
curl -fsS -X POST https://platform.hanzo.ai/api/application.start -H "x-api-key: $PLATFORM_API_KEY" -d '{"applicationId":"'$APP_ID'"}'
curl -fsS -X POST https://platform.hanzo.ai/api/application.reload -H "x-api-key: $PLATFORM_API_KEY" -d '{"applicationId":"'$APP_ID'","appName":"'$APP_NAME'"}'
```
### Stop
## Inspect & observe
Use the [observe surface](/docs/services/platform) to see declared vs running vs latest image tags and drift:
```bash
curl -X POST https://app.platform.hanzo.ai/api/container.stop \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "containerId": "ctr_abc123" }'
curl -fsS "https://platform.hanzo.ai/v1/apps?org=hanzo" \
-H "Authorization: Bearer $PLATFORM_SERVICE_TOKEN"
```
### Start
```bash
curl -X POST https://app.platform.hanzo.ai/api/container.start \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "containerId": "ctr_abc123" }'
```
## Logs
`GET /api/container.logs?containerId=ctr_abc123`
Stream logs from all replicas of a container.
```bash
curl "https://app.platform.hanzo.ai/api/container.logs?containerId=ctr_abc123&tail=100" \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Query parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `containerId` | string | -- | Container ID |
| `tail` | number | 100 | Number of recent lines |
| `since` | string | -- | ISO 8601 timestamp |
| `follow` | boolean | false | Stream logs in real time |
## Delete Container
`DELETE /api/container.remove`
```bash
curl -X DELETE https://app.platform.hanzo.ai/api/container.remove \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "containerId": "ctr_abc123" }'
```
**Response:** `204 No Content`
<Callout type="warn">
Deleting a container stops all running instances and removes associated domains and volumes. This cannot be undone.
</Callout>
## Container Status Values
## Application status values
| Status | Description |
|--------|-------------|
| `creating` | Container is being provisioned |
| `running` | All replicas are healthy |
| `deploying` | A new deployment is rolling out |
| `stopped` | Container is stopped |
| `error` | One or more replicas have failed |
| `deleting` | Container is being removed |
| `idle` | Registered, not running |
| `running` | Deployed and serving |
| `done` | Last deployment completed |
| `error` | A deployment or replica failed |
<Callout type="warn">
`application.remove` stops all instances and removes associated domains and volumes. This cannot be undone.
</Callout>
@@ -5,44 +5,37 @@ description: Platform REST and tRPC API for managing organizations, clusters, co
Hanzo Platform exposes two API surfaces: **tRPC** for the dashboard and **REST** for external integrations. Both share the same authentication and authorization layer.
## Base URL
## Base URLs
```
https://app.platform.hanzo.ai/api
```
| Surface | Base | Auth | Use |
|---------|------|------|-----|
| **Canonical control** | `https://platform.hanzo.ai/v1` | `Authorization: Bearer <service-token>` | Container redeploy, observe/drift (machine) |
| **Build pipeline** | `https://platform.hanzo.ai/api` | `x-api-key: <key>` | Build/deploy applications via tRPC-over-OpenAPI |
All endpoints are relative to this base URL.
## API Surfaces
| Surface | Protocol | Use Case |
|---------|----------|----------|
| **tRPC** | HTTP POST with JSON | Dashboard, SDK, programmatic access |
| **REST** | Standard HTTP methods | External integrations, webhooks, CI/CD |
The tRPC API uses procedure calls (e.g., `organization.all`, `cluster.create`). The REST API uses standard resource paths (e.g., `GET /clusters`, `POST /containers`).
The `/v1` surface is the canonical Hanzo control plane. The `/api` surface is the Dokploy-derived build pipeline, where each endpoint is a tRPC procedure exposed over HTTP as `<router>.<procedure>` (e.g. `application.create`, `domain.create`).
## Authentication
All requests require a Bearer token in the `Authorization` header:
The build-pipeline surface accepts an API key (machine) or an IAM bearer token (interactive):
```bash
curl https://app.platform.hanzo.ai/api/organization.all \
-H "Authorization: Bearer YOUR_TOKEN"
curl https://platform.hanzo.ai/api/organization.all \
-H "x-api-key: $PLATFORM_API_KEY"
```
See [Authentication](/docs/services/platform/api/authentication) for details on obtaining tokens.
The canonical `/v1` surface uses a service token: `Authorization: Bearer $PAAS_SERVICE_TOKEN`. Both credential types come from [Hanzo KMS](/docs/services/kms). See [Authentication](/docs/services/platform/api/authentication) for details.
## tRPC Procedures
The platform exposes 16 tRPC routers:
The build pipeline exposes these tRPC routers:
| Router | Description |
|--------|-------------|
| `organization` | Organization CRUD |
| `orgTeam` | Team member management |
| `cluster` | Cluster lifecycle |
| `container` | Container deployments |
| `application` | Application deployments (the unit a container maps to) |
| `compose` | Multi-service compose/stack units |
| `project` | Project management |
| `environment` | Environment configuration |
| `build` | Build pipelines |
@@ -61,7 +54,7 @@ The platform exposes 16 tRPC routers:
tRPC requests use HTTP POST with a JSON body:
```bash
curl -X POST https://app.platform.hanzo.ai/api/cluster.create \
curl -X POST https://platform.hanzo.ai/api/cluster.create \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -22,7 +22,7 @@ The Organizations API manages the top-level entity in Hanzo Platform. Every clus
Returns all organizations the authenticated user is a member of.
```bash
curl https://app.platform.hanzo.ai/api/organization.all \
curl https://platform.hanzo.ai/api/organization.all \
-H "Authorization: Bearer YOUR_TOKEN"
```
@@ -58,7 +58,7 @@ curl https://app.platform.hanzo.ai/api/organization.all \
`GET /api/organization.one?organizationId=org_abc123`
```bash
curl "https://app.platform.hanzo.ai/api/organization.one?organizationId=org_abc123" \
curl "https://platform.hanzo.ai/api/organization.one?organizationId=org_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
```
@@ -92,7 +92,7 @@ curl "https://app.platform.hanzo.ai/api/organization.one?organizationId=org_abc1
`POST /api/organization.create`
```bash
curl -X POST https://app.platform.hanzo.ai/api/organization.create \
curl -X POST https://platform.hanzo.ai/api/organization.create \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -130,7 +130,7 @@ The authenticated user is automatically assigned the **Owner** role.
`PATCH /api/organization.update`
```bash
curl -X PATCH https://app.platform.hanzo.ai/api/organization.update \
curl -X PATCH https://platform.hanzo.ai/api/organization.update \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -155,7 +155,7 @@ Requires **Owner** or **Admin** role.
`DELETE /api/organization.remove`
```bash
curl -X DELETE https://app.platform.hanzo.ai/api/organization.remove \
curl -X DELETE https://platform.hanzo.ai/api/organization.remove \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -181,11 +181,11 @@ Team members are managed via the `orgTeam` router:
```bash
# List members
curl "https://app.platform.hanzo.ai/api/orgTeam.all?organizationId=org_abc123" \
curl "https://platform.hanzo.ai/api/orgTeam.all?organizationId=org_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
# Change role
curl -X PATCH https://app.platform.hanzo.ai/api/orgTeam.update \
curl -X PATCH https://platform.hanzo.ai/api/orgTeam.update \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -195,7 +195,7 @@ curl -X PATCH https://app.platform.hanzo.ai/api/orgTeam.update \
}'
# Remove member
curl -X DELETE https://app.platform.hanzo.ai/api/orgTeam.remove \
curl -X DELETE https://platform.hanzo.ai/api/orgTeam.remove \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -26,7 +26,7 @@ The Virtual Machines API manages Visor VMs -- full Linux virtual machines with r
`GET /api/vm.all?organizationId=org_abc123`
```bash
curl "https://app.platform.hanzo.ai/api/vm.all?organizationId=org_abc123" \
curl "https://platform.hanzo.ai/api/vm.all?organizationId=org_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
```
@@ -56,7 +56,7 @@ curl "https://app.platform.hanzo.ai/api/vm.all?organizationId=org_abc123" \
`GET /api/vm.one?vmId=vm_abc123`
```bash
curl "https://app.platform.hanzo.ai/api/vm.one?vmId=vm_abc123" \
curl "https://platform.hanzo.ai/api/vm.one?vmId=vm_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
```
@@ -100,7 +100,7 @@ curl "https://app.platform.hanzo.ai/api/vm.one?vmId=vm_abc123" \
`POST /api/vm.launch`
```bash
curl -X POST https://app.platform.hanzo.ai/api/vm.launch \
curl -X POST https://platform.hanzo.ai/api/vm.launch \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -161,7 +161,7 @@ The VM enters `provisioning` status. It transitions to `running` with an assigne
Gracefully shuts down the VM. The VM retains its IP address and storage. You are not billed for compute while stopped, but storage charges continue.
```bash
curl -X POST https://app.platform.hanzo.ai/api/vm.stop \
curl -X POST https://platform.hanzo.ai/api/vm.stop \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "vmId": "vm_abc123" }'
@@ -174,7 +174,7 @@ curl -X POST https://app.platform.hanzo.ai/api/vm.stop \
Boots a stopped VM. The VM retains its previous IP and all attached volumes.
```bash
curl -X POST https://app.platform.hanzo.ai/api/vm.start \
curl -X POST https://platform.hanzo.ai/api/vm.start \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "vmId": "vm_abc123" }'
@@ -187,7 +187,7 @@ curl -X POST https://app.platform.hanzo.ai/api/vm.start \
Performs a graceful reboot.
```bash
curl -X POST https://app.platform.hanzo.ai/api/vm.restart \
curl -X POST https://platform.hanzo.ai/api/vm.restart \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "vmId": "vm_abc123" }'
@@ -200,7 +200,7 @@ curl -X POST https://app.platform.hanzo.ai/api/vm.restart \
Permanently destroys the VM and its boot disk. Attached volumes are detached but not deleted.
```bash
curl -X DELETE https://app.platform.hanzo.ai/api/vm.destroy \
curl -X DELETE https://platform.hanzo.ai/api/vm.destroy \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "vmId": "vm_abc123" }'
@@ -217,14 +217,14 @@ Destroying a VM deletes its boot disk permanently. Attached volumes are preserve
### List Volumes
```bash
curl "https://app.platform.hanzo.ai/api/vm.volumes?vmId=vm_abc123" \
curl "https://platform.hanzo.ai/api/vm.volumes?vmId=vm_abc123" \
-H "Authorization: Bearer YOUR_TOKEN"
```
### Attach Volume
```bash
curl -X POST https://app.platform.hanzo.ai/api/vm.attachVolume \
curl -X POST https://platform.hanzo.ai/api/vm.attachVolume \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
@@ -239,7 +239,7 @@ The volume is attached as a block device. If `mountPath` is provided, the platfo
### Detach Volume
```bash
curl -X POST https://app.platform.hanzo.ai/api/vm.detachVolume \
curl -X POST https://platform.hanzo.ai/api/vm.detachVolume \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
+131 -373
View File
@@ -1,435 +1,193 @@
---
title: Hanzo Platform
description: GitOps deployment platform for applications, databases, containers, and custom domains with IAM-integrated multi-org support.
title: Hanzo PaaS
description: The platform.hanzo.ai control plane — deploy and operate applications, containers, and custom domains across your clusters, with IAM-native multi-org access.
---
# Hanzo Platform
# Hanzo PaaS
Hanzo Platform is a self-hosted Platform as a Service (PaaS) for deploying and
managing applications, databases, and containers. It provides Git-based
deployments, Docker orchestration, auto-scaling, SSL/TLS certificates, custom
domains, environment variable management, and real-time monitoring -- all
integrated with Hanzo IAM for multi-organization access control.
Hanzo PaaS (`platform.hanzo.ai`) is the control plane for shipping software on Hanzo infrastructure. You register an application, point it at a Docker image or Git repo, attach a domain, and the platform builds, deploys, and operates it on your clusters — with deployment history, rolling updates, and drift detection. Authentication is delegated to [Hanzo IAM](/docs/services/iam) with per-organization isolation.
**Studio:** `platform.hanzo.ai`
**API:** `api.platform.hanzo.ai`
**Sync:** `sync.platform.hanzo.ai`
**Webhook:** `webhook.platform.hanzo.ai`
**OAuth:** `oauth.platform.hanzo.ai`
**Source:** [github.com/hanzoai/platform](https://github.com/hanzoai/platform)
| | |
|---|---|
| **Studio** | `https://platform.hanzo.ai` |
| **Canonical API** | `https://platform.hanzo.ai/v1` |
| **Build pipeline API** | `https://platform.hanzo.ai/api` (tRPC + OpenAPI) |
| **Auth** | Hanzo IAM (interactive) · API key / service token (machine) |
| **Source** | [github.com/hanzoai/platform](https://github.com/hanzoai/platform) |
## Features
## The model
- **Git-Based Deployments** -- Push to deploy from GitHub, GitLab, Gitea, or
Bitbucket. Automatic builds on push with configurable branch tracking.
- **Docker Container Orchestration** -- Native Docker Compose support, Swarm
mode for multi-node clusters, and direct container management.
- **Auto-Scaling** -- Horizontal scaling with configurable replicas, rolling
updates with zero downtime, and pod anti-affinity for high availability.
- **SSL/TLS Certificates** -- Automatic certificate provisioning and renewal
via cert-manager with Let's Encrypt integration.
- **Custom Domains** -- Map any domain to your application with automatic DNS
verification and TLS termination.
- **Environment Variables** -- Secure environment variable management per
application with KMS integration for secrets.
- **Managed Databases** -- One-click provisioning for PostgreSQL, MySQL,
MariaDB, MongoDB, and Redis with automated backups.
- **Real-Time Monitoring** -- Live CPU, memory, storage, and network metrics
for every deployed resource.
- **Multi-Org Support** -- Full IAM integration via Hanzo IAM (hanzo.id) with
per-organization project isolation and RBAC.
- **Preview Deployments** -- Automatic per-PR preview environments with
unique URLs for review workflows.
- **Rollbacks** -- One-click rollback to any previous deployment version with full context preservation and registry authentication.
- **Volume Backups** -- Scheduled volume backups with S3 destination, retention policies, and success/error notifications.
- **Notifications** -- Deploy status alerts via Slack, Discord, Telegram,
and email.
- **AI Assistant** -- Built-in AI provider selection for deployment assistance.
## Architecture
Hanzo Platform runs as six coordinated services on Kubernetes:
Hanzo PaaS organizes work into a simple hierarchy:
```
platform.hanzo.ai
|
+-----------+
| Ingress |
| (nginx) |
+-----------+
/ | | \
/ | | \
+------+ +----+ +----+ +-------+
|Studio| |API | |Sync| |Webhook|
| (UI) | | | | | | (Go) |
+------+ +----+ +----+ +-------+
:4000 :4000 :4000 :8080
| |
+---------+------+---------+
| | |
+-------+ +-------+ +---------+
|MongoDB| | Redis | | Storage |
| | | | |(Hanzo S3)|
+-------+ +-------+ +---------+
organization
└── project
└── environment (e.g. production, staging)
├── application (a Docker image or Git source → one workload)
└── compose (a multi-service docker-compose / stack unit)
```
### Service Components
| Object | What it is |
|--------|------------|
| **project** | A grouping of related services, owned by an organization |
| **environment** | An isolated slice of a project (production, staging, preview) |
| **application** | A single deployable unit. Its `appName` is the name of the live workload it drives. `sourceType` is one of `docker`, `git`, `github`, `gitlab`, `bitbucket`, `gitea`, or `drop`; `deployTarget` is `local`, `cloud`, or `k8s` |
| **deployment** | One build/deploy run, with status and start/finish timestamps |
| **domain** | A hostname mapped to an application or compose service, with automatic TLS |
| Service | Image | Replicas | Port | Purpose |
|---------|-------|----------|------|---------|
| **Platform** (API) | `ghcr.io/hanzoai/paas-api` | 2 | 4000 | Core API server, deployment orchestration, RBAC |
| **Studio** (UI) | `ghcr.io/hanzoai/paas-ui` | 2 | 4000 | Web dashboard (React, Hanzo-branded) |
| **Monitor** | `ghcr.io/hanzoai/paas-monitor` | 1 | 4000 | Resource metrics collection and alerting |
| **Sync** | `ghcr.io/hanzoai/paas-sync` | 2 | 4000 | Real-time WebSocket state synchronization |
| **Webhook** | `ghcr.io/hanzoai/paas-webhook` | 1 | 8080 | Git provider webhook receiver (Go) |
| **OAuth Proxy** | `ghcr.io/hanzoai/platform-oauth-proxy` | 1 | - | IAM OAuth2 proxy at oauth.platform.hanzo.ai |
State is stored in [Hanzo Base](/docs/services/base) (embedded SQLite, replicated to object storage) — there is no external database to run.
### Studio Branding
## Authentication
The Studio UI uses Hanzo's visual identity:
- Pure black background (`#000000`)
- Monochrome white Hanzo "H" logo
- Hanzo red (`#fd4444`) accent color for interactive elements
Two credential types, both retrieved from [Hanzo KMS](/docs/services/kms) — never hard-coded:
### Data Stores
| Credential | Header | Use |
|------------|--------|-----|
| **API key** | `x-api-key: <key>` | Machine access to the build-pipeline API (`/api/*`). Org-scoped; issued under **Settings → API Keys** |
| **Service token** | `Authorization: Bearer <token>` | The canonical `/v1` container surface (redeploy, observe) |
| **IAM token** | `Authorization: Bearer <iam-jwt>` | Interactive / user access, via [Hanzo IAM](/docs/services/iam) SSO |
| Store | Purpose |
|-------|---------|
| **MongoDB** | Application state, project configs, deployment history |
| **Redis** | Cache, real-time sync pub/sub, session state |
| **Hanzo S3** | Build artifacts, backup storage, asset uploads |
## Deploy
## Quick Start
### From the Studio
### Deploy via Studio UI
1. Sign in at [platform.hanzo.ai](https://platform.hanzo.ai) with your Hanzo IAM account.
2. Create a project and an environment.
3. Add an application — choose a Docker image or connect a Git repository.
4. Attach a domain and deploy.
1. Navigate to [platform.hanzo.ai](https://platform.hanzo.ai)
2. Sign in with your Hanzo IAM account
3. Create a new project
4. Connect your Git repository (GitHub, GitLab, Gitea, or Bitbucket)
5. Configure build settings and environment variables
6. Deploy
### Redeploy a running container (canonical `/v1`)
### Deploy via API
Create a project and deploy an application using the Platform API:
The canonical, machine-driven redeploy performs a zero-downtime rolling restart of the live workload — it re-pulls the image and recreates pods. It takes a service token and no body:
```bash
# Authenticate (get token from hanzo.id)
export TOKEN="your-iam-access-token"
# Service token comes from KMS — never inline a real value
TOKEN=$(curl -sS -X POST https://kms.hanzo.ai/v1/kms/auth/login \
-H 'Content-Type: application/json' \
-d "{\"clientId\":\"$KMS_CLIENT_ID\",\"clientSecret\":\"$KMS_CLIENT_SECRET\"}" | jq -r .accessToken)
PAAS_TOKEN=$(curl -sS "https://kms.hanzo.ai/v1/kms/orgs/hanzo/secrets/paas/SERVICE_TOKEN?env=production" \
-H "Authorization: Bearer $TOKEN" | jq -r .secret.value)
# Create a project
curl -X POST https://api.platform.hanzo.ai/api/project.create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-app",
"description": "My application"
}'
# Create an application in the project
curl -X POST https://api.platform.hanzo.ai/api/application.create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "web",
"projectId": "PROJECT_ID",
"sourceType": "github",
"repository": "https://github.com/org/repo",
"branch": "main",
"buildPath": "/",
"publishDirectory": "dist"
}'
# Trigger a deployment
curl -X POST https://api.platform.hanzo.ai/api/application.deploy \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "APP_ID"
}'
curl -fsS -X POST \
"https://platform.hanzo.ai/v1/org/$ORG_ID/project/$PROJECT_ID/env/$ENV_ID/container/$APP_ID/redeploy" \
-H "Authorization: Bearer $PAAS_TOKEN"
# → {"ok":true}
```
### Via Unified Gateway
### Build and deploy (pipeline `/api`)
All Platform API endpoints are also accessible through the Hanzo API gateway:
To register a new app and run a build through the deployment pipeline, use the OpenAPI surface with an API key. Procedures are `<router>.<procedure>`:
```bash
curl -H "Authorization: Bearer hk-your-api-key" \
https://api.hanzo.ai/v1/platform/projects
# 1. Register the application (does not deploy)
curl -fsS -X POST https://platform.hanzo.ai/api/application.create \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "web", "environmentId": "'$ENV_ID'" }'
# 2. Attach a Docker image source, then deploy
curl -fsS -X POST https://platform.hanzo.ai/api/application.deploy \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "applicationId": "'$APP_ID'" }'
# Rebuild an existing application
curl -fsS -X POST https://platform.hanzo.ai/api/application.redeploy \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "applicationId": "'$APP_ID'", "title": "ship v1.3.0" }'
```
## Deployment Workflow
`application.deploy` and `application.redeploy` enqueue a build job; `application.start`, `application.stop`, and `application.reload` manage the running workload. See the [Containers API](/docs/services/platform/api/containers) for the full procedure set.
### Git Push Workflow
## Observe & drift
```
Developer pushes code
|
v
+----------+ +---------+ +----------+
| Git Host | ----> |Webhook | ----> | Platform |
| (GitHub) | POST | Service | | API |
+----------+ +---------+ +----------+
|
+--------+--------+
| |
Build Image Run Migrations
| |
+--------+--------+
|
Rolling Deploy
|
Health Check Pass
|
Traffic Switched
```
The platform continuously reconciles three views of every app and computes drift:
1. **Push**: Developer pushes to the configured branch
2. **Webhook**: Git provider sends a webhook to `webhook.platform.hanzo.ai`
3. **Build**: Platform builds a Docker image from source (Nixpacks, Dockerfile, or Buildpacks)
4. **Deploy**: Rolling update with zero downtime -- new pods start before old ones terminate
5. **Health Check**: Readiness probes confirm the new deployment is serving traffic
6. **Notify**: Deployment status sent to configured notification channels
### Build Methods
| Method | Description |
|--------|-------------|
| **Nixpacks** | Auto-detected build packs for Node.js, Python, Go, Ruby, PHP, Rust, Java |
| **Dockerfile** | Custom Dockerfile in repository root or specified path |
| **Docker Compose** | Full `compose.yml` support for multi-container applications |
| **Buildpacks** | Cloud Native Buildpacks (CNB) for standardized builds |
### Deployment Strategies
| Strategy | Description |
|----------|-------------|
| **Rolling Update** | Default. `maxSurge: 1, maxUnavailable: 0` for zero-downtime deploys |
| **Recreate** | Stop existing pods before starting new ones (for stateful workloads) |
| **Preview** | Per-PR ephemeral environments with unique URLs |
## Custom Domains
Add custom domains to any deployed application:
| Tag | Meaning |
|-----|---------|
| `declared` | The version your operator/manifest declares |
| `running` | The image actually live in the cluster |
| `latest` | The newest released version available |
```bash
# Add a domain
curl -X POST https://api.platform.hanzo.ai/api/domain.create \
-H "Authorization: Bearer $TOKEN" \
# List apps with drift, scoped to an org
curl -fsS "https://platform.hanzo.ai/v1/apps?org=hanzo&drift=true" \
-H "Authorization: Bearer $PLATFORM_SERVICE_TOKEN"
```
```json
{
"apps": [
{ "org": "hanzo", "app": "docs", "env": "production",
"declared_tag": "v1.4.0", "running_tag": "v1.3.0", "latest_tag": "v1.4.0",
"health": "yellow", "drift": ["un-rolled"] }
],
"summary": { "total": 1, "byDrift": { "un-rolled": 1 } }
}
```
Drift is never stored — it is recomputed from declared/running/latest each time. `POST /v1/apps/sync` refreshes the observed columns from the cluster and release feeds.
## Custom domains & TLS
Attach a hostname to an application; TLS is provisioned automatically (Let's Encrypt) and a DNS record is created:
```bash
curl -fsS -X POST https://platform.hanzo.ai/api/domain.create \
-H "x-api-key: $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "APP_ID",
"applicationId": "'$APP_ID'",
"host": "app.example.com",
"https": true,
"certificateType": "letsencrypt"
}'
```
### DNS Configuration
Point your DNS at the platform ingress IP shown on the project's **Domains** tab.
Point your domain to the Platform ingress:
| Record Type | Name | Value |
|-------------|------|-------|
| A | `app.example.com` | `24.199.76.156` |
| CNAME | `app.example.com` | `platform.hanzo.ai` |
SSL/TLS certificates are automatically provisioned and renewed via
cert-manager with Let's Encrypt. Wildcard certificates are supported using
DNS-01 challenges.
## Environment Variables
Manage environment variables per application with support for secrets:
```bash
# Set environment variables
curl -X POST https://api.platform.hanzo.ai/api/environment.create \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "APP_ID",
"variables": {
"DATABASE_URL": "postgresql://user:pass@db:5432/app",
"REDIS_URL": "redis://redis:6379",
"API_KEY": "secret-value"
}
}'
```
### KMS Integration
For sensitive values, Platform integrates with [Hanzo KMS](/docs/services/kms)
to sync secrets directly from KMS vaults:
- Secrets are referenced by KMS path, never stored in Platform's database
- Automatic rotation when KMS secrets are updated
- Audit trail for all secret access via KMS logs
### Variable Scoping
| Scope | Description |
|-------|-------------|
| **Application** | Available only to a specific application |
| **Project** | Shared across all applications in a project |
| **Global** | Available to all projects in an organization |
| **Build-time** | Injected during the build phase only |
| **Runtime** | Injected at container start only |
## Managed Databases
Platform can provision and manage database instances:
| Database | Versions | Backups | Replicas |
|----------|----------|---------|----------|
| PostgreSQL | 14, 15, 16 | Automated daily | Read replicas |
| MySQL | 8.0, 8.4 | Automated daily | Read replicas |
| MariaDB | 10.11, 11.x | Automated daily | Read replicas |
| MongoDB | 6.0, 7.0 | Automated daily | Replica sets |
| Redis | 7.x | Snapshot | Sentinel HA |
### Automated Backups
Backups are stored in Hanzo S3 and can be configured with:
- Custom retention policies
- Point-in-time recovery (PostgreSQL, MySQL)
- Cross-region backup destinations
- Backup encryption at rest
## Monitoring
Platform provides real-time resource monitoring for all deployed services.
### Metrics Collected
| Metric | Granularity | Retention |
|--------|-------------|-----------|
| CPU usage | Per container | 30 days |
| Memory usage | Per container | 30 days |
| Network I/O | Per container | 30 days |
| Disk usage | Per volume | 30 days |
| HTTP requests | Per application | 30 days |
| Response times | Per application | 30 days |
### Accessing Metrics
View metrics in the Studio dashboard under **Monitoring**, or query via API:
```bash
curl https://api.platform.hanzo.ai/api/monitoring.stats \
-H "Authorization: Bearer $TOKEN" \
-G -d "applicationId=APP_ID" \
-d "period=24h"
```
### Alerts
Configure alerts for resource thresholds:
- CPU usage exceeds percentage
- Memory usage exceeds limit
- Container restart count
- Health check failures
- Deployment failures
Notifications can be sent to Slack, Discord, Telegram, or email.
## Authentication
Platform uses Hanzo IAM (hanzo.id) for all authentication:
| Provider | Method |
|----------|--------|
| **Hanzo IAM** | OAuth2/OIDC SSO via hanzo.id (primary) |
| **GitHub** | Git provider OAuth for repository access |
| **GitLab** | Git provider OAuth for repository access |
| **Bitbucket** | Git provider OAuth for repository access |
The OAuth proxy at `oauth.platform.hanzo.ai` handles the IAM authorization
code exchange. The Studio login page uses `provider=hanzo` to initiate the
Better Auth generic OAuth flow against hanzo.id.
### IAM Application
| Field | Value |
|-------|-------|
| App Name | `app-platform` |
| Organization | `hanzo` |
| IAM Endpoint | `https://hanzo.id` |
## Self-Hosting
### Docker Compose (Local Development)
## Self-hosting
```bash
git clone https://github.com/hanzoai/platform
cd platform
cp .env.example .env
# Edit .env with your configuration
cp .env.example .env # set IAM endpoint, service token, etc.
docker compose up -d
```
### Kubernetes (Production)
The recommended production deployment uses the K8s manifests in
`universe/infra/k8s/paas/`:
```bash
kubectl apply -k infra/k8s/paas/
```
This creates:
- Platform API (2 replicas, rolling update)
- Studio UI (2 replicas, rolling update)
- Monitor (1 replica)
- Sync (2 replicas, rolling update)
- Webhook (1 replica)
- Ingress rules for all subdomains
- RBAC service account (`hanzo-paas-sa`)
- Pod disruption budgets
### Required Secrets
| Secret | Keys |
|--------|------|
| `mongodb` | `databaseURI`, `username`, `password` |
| `redis` | `hostname`, `password` |
| `storage` | `endpoint`, `port`, `accessKey`, `secretKey` |
| `hanzo-paas` | `passPhrase`, `clusterAccessToken`, `masterToken`, `clusterSlug` |
The platform runs as a Next.js application with an embedded SQLite database; for production it deploys to a cluster via the operator. It authenticates against [Hanzo IAM](/docs/services/iam) using the `hanzo-platform` application.
## API Reference
The Platform API uses tRPC-style endpoints. Key resource groups:
| Resource | Endpoints |
|----------|-----------|
| **Projects** | `project.create`, `project.list`, `project.delete` |
| **Applications** | `application.create`, `application.deploy`, `application.stop` |
| **Compose** | `compose.create`, `compose.deploy`, `compose.redeploy` |
| **Domains** | `domain.create`, `domain.delete`, `domain.verify` |
| **Certificates** | `certificate.create`, `certificate.renew` |
| **Environment** | `environment.create`, `environment.update` |
| **Databases** | `postgres.create`, `mysql.create`, `mongo.create`, `redis.create` |
| **Monitoring** | `monitoring.stats`, `monitoring.logs` |
| **Backups** | `backup.create`, `backup.restore`, `backup.list` |
| **Deployments** | `deployment.list`, `deployment.cancel`, `deployment.rollback` |
| **Servers** | `server.create`, `server.list`, `server.validate` |
Full Swagger documentation is available at `platform.hanzo.ai/swagger`.
<Cards>
<Card title="API Overview" href="/docs/services/platform/api">
The `/v1` control surface and the `/api` build pipeline
</Card>
<Card title="Authentication" href="/docs/services/platform/api/authentication">
API keys, service tokens, and IAM tokens
</Card>
<Card title="Containers API" href="/docs/services/platform/api/containers">
Create, deploy, redeploy, and operate applications
</Card>
<Card title="Clusters" href="/docs/services/platform/clusters">
Register and manage clusters and node pools
</Card>
</Cards>
## Related Services
<Cards>
<Card title="Hanzo PaaS" href="/docs/services/paas">
K8s-native fleet operations and per-org cluster management
</Card>
<Card title="Hanzo Console" href="/docs/services/console">
Observability, tracing, and monitoring for deployed applications
</Card>
<Card title="Hanzo IAM" href="/docs/services/iam">
Identity and access management with multi-org SSO
</Card>
<Card title="Container Registry" href="/docs/services/registry">
Private Docker registry with IAM-based token authentication
<Card title="Hanzo KMS" href="/docs/services/kms">
Secrets and service tokens for deployments
</Card>
<Card title="Hanzo Base" href="/docs/services/base">
The embedded data store behind the control plane
</Card>
<Card title="Hanzo S3" href="/docs/services/s3">
Object storage for build artifacts and backups
</Card>
</Cards>
+181 -402
View File
@@ -7,268 +7,147 @@ description: S3-compatible object storage for AI infrastructure
Hanzo S3 is a high-performance, S3-compatible object storage service built for AI workloads. It provides durable, scalable storage for model artifacts, training datasets, media assets, event streams, and application data across the Hanzo platform.
**Console:** `s3.hanzo.ai`
**S3 API:** `s3.hanzo.ai` / `api.s3.hanzo.ai`
**Server:** [github.com/hanzoai/s3](https://github.com/hanzoai/s3)
**Console:** [github.com/hanzos3/console](https://github.com/hanzos3/console)
**CLI:** [github.com/hanzos3/cli](https://github.com/hanzos3/cli)
Hanzo S3 is built on [SeaweedFS](https://github.com/seaweedfs/seaweedfs) (Apache-2.0) and exposes the **AWS S3 API** with Signature V4 authentication. Any S3 client, SDK, or tool works unmodified — point it at the endpoint and use your access key and secret key.
| | |
|---|---|
| **S3 API** | `https://s3.hanzo.ai` |
| **Console** | `https://s3.hanzo.ai` (web admin) |
| **Protocol** | AWS S3 (SigV4), path-style and virtual-host style |
| **Region** | `us-east-1` (default; cosmetic — set it on your client) |
| **Source** | [github.com/hanzoai/s3](https://github.com/hanzoai/s3) |
## Features
- **S3 API Compatible**: Drop-in replacement for Amazon S3 -- works with any S3 client, SDK, or tool
- **Object Versioning**: Track and restore previous versions of any object
- **Lifecycle Management**: Automated expiration, transition, and tiering rules
- **Erasure Coding**: Configurable data redundancy across drives and nodes
- **Server-Side Encryption**: SSE-S3 and SSE-KMS for data at rest and in transit
- **OIDC SSO**: Single sign-on via `hanzo.id` for console access
- **Bucket Policies**: Fine-grained S3-compatible IAM policy documents
- **Multi-Tenancy**: Isolated namespaces and access boundaries for teams and services
- **Event Notifications**: Webhook and queue notifications on object mutations
- **High Throughput**: Designed to saturate NVMe and modern network hardware
- **S3 API Compatible**: Drop-in replacement for Amazon S3 — works with `aws` CLI, `mc`, `boto3`, `@aws-sdk/client-s3`, and any other S3 SDK
- **Buckets & Objects**: Full create/list/delete, put/get/head/copy/delete, and `ListObjects` v1 + v2 + versions
- **Multipart Uploads**: Create, upload-part, copy-part, complete, abort, and list — for large objects
- **Presigned URLs**: Time-limited GET/PUT URLs for browser and client-side transfers
- **POST-Policy Uploads**: Direct browser form uploads with policy constraints
- **Versioning**: Track and restore previous versions of any object
- **Object Lock**: Retention and legal-hold for compliance (WORM)
- **Lifecycle Rules**: Automated expiration and tiering
- **Server-Side Encryption**: SSE-S3 and SSE-KMS (keys managed by [Hanzo KMS](/docs/services/kms))
- **Bucket Policies, ACLs, CORS, Tagging**: Standard S3 access and metadata controls
- **Advanced IAM (optional)**: OIDC/JWT identity providers, STS role assumption, and AWS-style policy documents
## Architecture
Hanzo S3 runs the SeaweedFS storage stack behind the S3 gateway:
```
┌─────────────────────────────────────────────────────────────────┐
│ Clients │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ s3 CLI │ │ AWS SDKs │ │ Hanzo │ │ Console │ │
│ │ │ │ boto3 │ │ S3 SDKs │ │ s3.hanzo.ai │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │ │
└───────┼──────────────┼──────────────┼───────────────┼───────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ s3.hanzo.ai / api.s3.hanzo.ai │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Hanzo S3 Server │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ S3 API │ │ IAM / │ │ Erasure │ │ Lifecycle│ │ │
│ │ │ Handler │ │ Policies │ │ Coding │ │ Manager │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ SSE-KMS │ │ Version │ │ Event │ │ Console │ │ │
│ │ │ Encrypt │ │ Control │ │ Notify │ │ UI :9001 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ │ Persistent Data │ │
│ │ (NVMe / Block) │ │
│ └───────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────┐
│ Clients │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ aws CLI │ │ mc │ │ boto3 / │ │ Console │ │
│ │ │ │ │ │ aws-sdk │ │ s3.hanzo.ai │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
└───────┼─────────────┼─────────────┼───────────────┼──────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────────────┐
│ s3.hanzo.ai — S3 Gateway (SigV4 auth, S3 → Filer translation) │
└───────────────┬───────────────────────────────────────────────┘
│
┌────────────┼─────────────────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌──────────────────┐
│ Master │ │ Filer │ │ Volume servers │
│ (topo) │ │ (meta) │ │ (object data) │
└────────┘ └────────┘ └──────────────────┘
```
| Component | Role |
|-----------|------|
| **S3 Gateway** | Translates S3 API calls to Filer operations, enforces SigV4 auth |
| **Master** | Cluster topology and volume assignment |
| **Filer** | Object metadata, directory tree, bucket configuration |
| **Volume servers** | Durable object data storage (Haystack-style needle storage) |
## Credentials
Hanzo S3 authenticates with an **access key + secret key** using AWS Signature V4 — exactly like Amazon S3. Each identity is granted a set of actions (`Admin`, `Read`, `Write`, `List`, `Tagging`), optionally scoped to specific buckets.
Store your S3 credentials in [Hanzo KMS](/docs/services/kms) and inject them at runtime — never hard-code them:
```bash
# Pull credentials from KMS (machine-identity auth), then use any S3 client
export AWS_ACCESS_KEY_ID=$(hanzo kms secret get --env prod --path /s3 ACCESS_KEY)
export AWS_SECRET_ACCESS_KEY=$(hanzo kms secret get --env prod --path /s3 SECRET_KEY)
```
In Kubernetes, sync the keys into a Secret with a [KMSSecret CRD](/docs/services/kms/kmssecret-crd) instead of embedding them in manifests.
## Quick Start
### Install the CLI
Any S3 client works. The examples below use the host `s3.hanzo.ai` with credentials from the environment.
### MinIO Client (`mc`)
```bash
# macOS
brew install hanzos3/tap/s3
mc alias set hanzo https://s3.hanzo.ai "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY"
# Linux (amd64)
curl -fsSL https://dl.hanzo.ai/s3/cli/linux-amd64/s3 -o /usr/local/bin/s3
chmod +x /usr/local/bin/s3
# From source
go install github.com/hanzos3/cli/cmd/s3@latest
mc mb hanzo/my-models # create a bucket
mc cp ./model.safetensors hanzo/my-models/ # upload
mc ls hanzo/my-models/ # list
mc cp hanzo/my-models/model.safetensors ./ # download
mc mirror ./training-data/ hanzo/datasets/ # recursive sync
```
### Configure
### AWS CLI
The AWS CLI works against Hanzo S3 with `--endpoint-url`. Set any region (it is not used for routing).
```bash
# Add Hanzo S3 as an alias
s3 alias set hanzo https://s3.hanzo.ai $HANZO_S3_ACCESS_KEY $HANZO_S3_SECRET_KEY
# Verify connectivity
s3 admin info hanzo
aws --endpoint-url https://s3.hanzo.ai --region us-east-1 s3 mb s3://my-models
aws --endpoint-url https://s3.hanzo.ai --region us-east-1 s3 cp ./model.safetensors s3://my-models/
aws --endpoint-url https://s3.hanzo.ai --region us-east-1 s3 ls s3://my-models/
aws --endpoint-url https://s3.hanzo.ai --region us-east-1 s3 sync ./run-042/ s3://datasets/run-042/
```
### Basic Operations
```bash
# Create a bucket
s3 mb hanzo/my-models
# Upload a model artifact
s3 cp ./model.safetensors hanzo/my-models/v1/
# List objects
s3 ls hanzo/my-models/
# Download
s3 cp hanzo/my-models/v1/model.safetensors ./local-copy.safetensors
# Recursive sync
s3 mirror ./training-data/ hanzo/datasets/run-042/
# Remove an object
s3 rm hanzo/my-models/v1/model.safetensors
# Set bucket policy (public read)
s3 anonymous set download hanzo/public-assets
```
### Python SDK
### Python (boto3)
```python
from hanzos3 import S3Client
client = S3Client(
endpoint="s3.hanzo.ai",
access_key="your-access-key",
secret_key="your-secret-key",
secure=True,
)
# Create a bucket
client.make_bucket("my-models")
# Upload a file
client.fput_object("my-models", "v1/model.safetensors", "./model.safetensors")
# Download a file
client.fget_object("my-models", "v1/model.safetensors", "./downloaded.safetensors")
# List objects
for obj in client.list_objects("my-models", prefix="v1/", recursive=True):
print(f"{obj.object_name} {obj.size} bytes")
# Presigned URL (7 days)
from datetime import timedelta
url = client.presigned_get_object("my-models", "v1/model.safetensors", expires=timedelta(days=7))
print(url)
```
Install:
```bash
pip install hanzos3
```
### Go SDK
```go
package main
import (
"context"
"fmt"
"log"
"github.com/hanzos3/go-sdk"
)
func main() {
client, err := hanzos3.New("s3.hanzo.ai", &hanzos3.Options{
Creds: hanzos3.NewStaticV4("access-key", "secret-key", ""),
Secure: true,
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Create bucket
err = client.MakeBucket(ctx, "my-models", hanzos3.MakeBucketOptions{})
if err != nil {
log.Fatal(err)
}
// Upload
_, err = client.FPutObject(ctx, "my-models", "v1/model.safetensors",
"./model.safetensors", hanzos3.PutObjectOptions{})
if err != nil {
log.Fatal(err)
}
// List
for obj := range client.ListObjects(ctx, "my-models", hanzos3.ListObjectsOptions{
Prefix: "v1/",
Recursive: true,
}) {
if obj.Err != nil {
log.Fatal(obj.Err)
}
fmt.Printf("%s %d bytes\n", obj.Key, obj.Size)
}
}
```
Install:
```bash
go get github.com/hanzos3/go-sdk
```
### JavaScript / TypeScript SDK
```typescript
import * as HanzoS3 from '@hanzos3/js-sdk'
const client = new HanzoS3.Client({
endPoint: 's3.hanzo.ai',
port: 443,
useSSL: true,
accessKey: 'your-access-key',
secretKey: 'your-secret-key',
})
// Create bucket
await client.makeBucket('my-models')
// Upload
await client.fPutObject('my-models', 'v1/model.safetensors', './model.safetensors')
// List
const stream = client.listObjects('my-models', 'v1/', true)
stream.on('data', (obj) => {
console.log(`${obj.name} ${obj.size} bytes`)
})
stream.on('error', (err) => console.error(err))
// Presigned download URL
const url = await client.presignedGetObject('my-models', 'v1/model.safetensors', 7 * 24 * 60 * 60)
console.log(url)
```
Install:
```bash
npm install @hanzos3/js-sdk
```
### AWS SDK Compatibility
Any standard AWS S3 SDK works with Hanzo S3. Override the endpoint:
```python
# Python (boto3)
import boto3
from datetime import timedelta
s3 = boto3.client(
"s3",
endpoint_url="https://s3.hanzo.ai",
aws_access_key_id="your-access-key",
aws_secret_access_key="your-secret-key",
aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],
aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"],
region_name="us-east-1",
)
s3.put_object(Bucket="my-models", Key="v1/model.safetensors", Body=open("model.safetensors", "rb"))
# Create a bucket and upload
s3.create_bucket(Bucket="my-models")
s3.upload_file("model.safetensors", "my-models", "v1/model.safetensors")
# List objects
for obj in s3.list_objects_v2(Bucket="my-models", Prefix="v1/").get("Contents", []):
print(obj["Key"], obj["Size"])
# Presigned download URL (1 hour)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "my-models", "Key": "v1/model.safetensors"},
ExpiresIn=3600,
)
print(url)
```
### JavaScript / TypeScript (`@aws-sdk/client-s3`)
```typescript
// TypeScript (@aws-sdk/client-s3)
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { S3Client, PutObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3'
import { readFileSync } from 'node:fs'
const client = new S3Client({
endpoint: 'https://s3.hanzo.ai',
region: 'us-east-1',
credentials: {
accessKeyId: 'your-access-key',
secretAccessKey: 'your-secret-key',
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
forcePathStyle: true,
})
@@ -278,115 +157,34 @@ await client.send(new PutObjectCommand({
Key: 'v1/model.safetensors',
Body: readFileSync('model.safetensors'),
}))
const out = await client.send(new ListObjectsV2Command({ Bucket: 'my-models', Prefix: 'v1/' }))
for (const obj of out.Contents ?? []) console.log(obj.Key, obj.Size)
```
## Platform Buckets
## Multipart Uploads
The Hanzo platform uses the following preconfigured buckets for internal services:
| Bucket | Purpose |
|--------|---------|
| `chat` | Chat message history and attachments |
| `commerce` | Product media, invoices, and transaction artifacts |
| `deploy` | Deployment artifacts and build outputs |
| `hanzo` | Core platform assets and configuration |
| `hanzo-events` | Event stream uploads (analytics, audit logs) |
| `hanzo-media` | User-uploaded media (images, video, documents) |
| `lux` | Lux blockchain snapshots and state data |
| `pars` | Pars network artifacts |
| `zen` | Zen model weights and checkpoints |
| `zoo` | Zoo research datasets and experiment outputs |
You can create additional buckets for your own projects at any time.
## Console
The Hanzo S3 web console is available at [s3.hanzo.ai](https://s3.hanzo.ai):
```
┌─────────────────────────────────────────────────────────────┐
│ Hanzo S3 [email protected] ▾ │
├─────────────────────────────────────────────────────────────┤
│ │
│ Buckets (10) + Create Bucket │
│ ───────────────────────────────────────────────────────── │
│ Name Objects Size Created │
│ ───────────────────────────────────────────────────────── │
│ chat 12,847 3.2 GB 2025-11-01 │
│ commerce 4,231 1.8 GB 2025-11-01 │
│ deploy 8,912 24.1 GB 2025-11-01 │
│ hanzo 15,003 8.7 GB 2025-11-01 │
│ hanzo-events 142,500 12.4 GB 2025-11-01 │
│ hanzo-media 28,744 45.2 GB 2025-11-01 │
│ lux 2,100 67.3 GB 2025-12-15 │
│ pars 450 2.1 GB 2025-12-15 │
│ zen 1,205 312.8 GB 2026-01-10 │
│ zoo 3,891 89.4 GB 2026-01-10 │
│ │
│ Total: 219,883 objects, 567.0 GB │
│ │
└─────────────────────────────────────────────────────────────┘
```
Sign in with your Hanzo account via OIDC SSO (powered by `hanzo.id`).
## OIDC Integration
Hanzo S3 console authentication is integrated with `hanzo.id` via OpenID Connect:
- **Provider:** `hanzo.id` (Hanzo IAM)
- **IAM App:** `app-storage`
- **Client ID:** Configured in K8s secret
- **Scopes:** `openid`, `profile`, `email`
- **Flow:** Authorization Code with PKCE
Users with a Hanzo account can sign in to the console at `s3.hanzo.ai` without creating separate S3 credentials. Access policies are managed through bucket policies and IAM service accounts.
### Service Accounts
For programmatic access (CI/CD, applications, agents), create service accounts:
Large objects upload in parts. High-level SDK helpers (`aws s3 cp`, `boto3 upload_file`, `mc cp`) use multipart automatically. To drive it explicitly:
```bash
# Create a service account with read/write to specific buckets
s3 admin user svcacct add hanzo my-user \
--access-key "svc-myapp-key" \
--secret-key "svc-myapp-secret" \
--policy /path/to/policy.json
# The AWS CLI automatically switches to multipart above the threshold
aws --endpoint-url https://s3.hanzo.ai s3 cp ./checkpoint-70b.safetensors s3://my-models/ \
--expected-size 140000000000
```
Example policy (`policy.json`):
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-models",
"arn:aws:s3:::my-models/*"
]
}
]
}
```
The underlying operations — `CreateMultipartUpload`, `UploadPart`, `CompleteMultipartUpload`, `AbortMultipartUpload`, `ListParts`, `ListMultipartUploads` — are all available via the S3 API for clients that manage parts directly.
## Bucket Policies
Apply fine-grained access control with S3-compatible policy documents:
Hanzo S3 supports standard S3 policy documents via `PutBucketPolicy`:
```bash
# Set a bucket policy
s3 anonymous set-json /path/to/policy.json hanzo/my-models
# View current policy
s3 anonymous get-json hanzo/my-models
aws --endpoint-url https://s3.hanzo.ai s3api put-bucket-policy \
--bucket public-assets \
--policy file://policy.json
```
### Common Policies
**Public read-only:**
**Public read-only** (`policy.json`):
```json
{
@@ -402,124 +200,105 @@ s3 anonymous get-json hanzo/my-models
}
```
**Restrict to specific IP range:**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": ["arn:aws:s3:::internal-data/*"],
"Condition": {
"IpAddress": {"aws:SourceIp": "10.0.0.0/8"}
}
}
]
}
```
## Object Lifecycle
Automate object management with lifecycle rules:
```bash
# Set lifecycle rule: expire objects after 90 days
s3 ilm rule add hanzo/logs --expiry-days 90
# Transition to lower tier after 30 days
s3 ilm rule add hanzo/archives --transition-days 30 --storage-class GLACIER
# List lifecycle rules
s3 ilm rule ls hanzo/logs
```
## Versioning
Enable versioning to preserve every revision of an object:
```bash
# Enable versioning
s3 version enable hanzo/my-models
aws --endpoint-url https://s3.hanzo.ai s3api put-bucket-versioning \
--bucket my-models --versioning-configuration Status=Enabled
# List object versions
s3 ls --versions hanzo/my-models/v1/model.safetensors
# List versions
aws --endpoint-url https://s3.hanzo.ai s3api list-object-versions --bucket my-models
# Restore a previous version
s3 cp --version-id "abc123" hanzo/my-models/v1/model.safetensors ./restored.safetensors
# Get a specific version
aws --endpoint-url https://s3.hanzo.ai s3api get-object \
--bucket my-models --key v1/model.safetensors --version-id "<id>" restored.safetensors
```
## Encryption
All data at rest is encrypted by default. Choose the encryption method:
All data is encrypted at rest. Choose the key management model per bucket:
| Method | Description |
|--------|-------------|
| **SSE-S3** | Server-managed keys (default) |
| **SSE-KMS** | Keys managed by Hanzo KMS (`kms.hanzo.ai`) |
| **SSE-KMS** | Keys managed by [Hanzo KMS](/docs/services/kms) (`kms.hanzo.ai`) |
```bash
# Enable SSE-KMS on a bucket
s3 encrypt set sse-kms hanzo-kms-key hanzo/sensitive-data
aws --endpoint-url https://s3.hanzo.ai s3api put-bucket-encryption \
--bucket sensitive-data \
--server-side-encryption-configuration '{
"Rules": [{ "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "aws:kms" } }]
}'
```
## Event Notifications
Receive notifications when objects are created, accessed, or deleted:
## Object Lock & Lifecycle
```bash
# Add webhook notification for object creation
s3 event add hanzo/uploads arn:hanzo:sqs::my-queue \
--event put --suffix .safetensors
# Lifecycle: expire objects after 90 days
aws --endpoint-url https://s3.hanzo.ai s3api put-bucket-lifecycle-configuration \
--bucket logs --lifecycle-configuration file://lifecycle.json
# List configured events
s3 event ls hanzo/uploads
# Object Lock retention (WORM) is set at bucket creation and per-object
aws --endpoint-url https://s3.hanzo.ai s3api put-object-retention \
--bucket archives --key 2026/report.pdf \
--retention '{ "Mode": "COMPLIANCE", "RetainUntilDate": "2027-01-01T00:00:00Z" }'
```
## SDKs
## Console
| Language | Package | Repository |
|----------|---------|------------|
| Python | `hanzos3` | [github.com/hanzos3/py-sdk](https://github.com/hanzos3/py-sdk) |
| Go | `github.com/hanzos3/go-sdk` | [github.com/hanzos3/go-sdk](https://github.com/hanzos3/go-sdk) |
| JavaScript | `@hanzos3/js-sdk` | [github.com/hanzos3/js-sdk](https://github.com/hanzos3/js-sdk) |
| Java | `io.hanzo.s3` | [github.com/hanzos3/java-sdk](https://github.com/hanzos3/java-sdk) |
| Rust | `hanzos3` | [github.com/hanzos3/rust-sdk](https://github.com/hanzos3/rust-sdk) |
| .NET | `HanzoS3` | [github.com/hanzos3/dotnet-sdk](https://github.com/hanzos3/dotnet-sdk) |
| C++ | `hanzos3-cpp` | [github.com/hanzos3/cpp-sdk](https://github.com/hanzos3/cpp-sdk) |
The Hanzo S3 web admin console is available at [s3.hanzo.ai](https://s3.hanzo.ai). It provides a file browser, bucket management, and cluster status. The console is protected by an administrator login configured at deployment.
All standard AWS S3 SDKs (`boto3`, `@aws-sdk/client-s3`, `aws-sdk-go`) also work by setting the endpoint to `https://s3.hanzo.ai`.
For day-to-day object operations, use the S3 API (CLI/SDK) rather than the console.
## Docker
## Self-Hosting
Run a standalone Hanzo S3 server:
Run a standalone Hanzo S3 server (SeaweedFS in S3 mode):
```bash
docker run -p 9000:9000 -p 9001:9001 \
-e "HANZO_S3_ROOT_USER=admin" \
-e "HANZO_S3_ROOT_PASSWORD=changeme-strong-password" \
# Single-node server with the S3 gateway enabled
docker run -p 8333:8333 -p 8888:8888 -p 9333:9333 \
-v /data:/data \
ghcr.io/hanzoai/storage:latest server /data --console-address :9001
ghcr.io/hanzoai/s3:latest \
server -dir=/data -s3 -filer
```
- **Port 9000**: S3 API
- **Port 9001**: Web console
| Port | Service |
|------|---------|
| `8333` | S3 API gateway |
| `8888` | Filer (metadata + web UI) |
| `9333` | Master (topology) |
| `8080` | Volume server |
| `23646` | Web admin console |
Provide credentials with an identity config file (`-config`) or the `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` environment variables. The public hostnames `s3.hanzo.ai` are wired at the ingress layer (`-domainName` / `S3_EXTERNAL_URL`), not baked into the binary.
## SDK Compatibility
Hanzo S3 has no bespoke SDK — and needs none. Every standard AWS S3 SDK works by setting the endpoint to `https://s3.hanzo.ai`:
| Language | Package |
|----------|---------|
| Python | `boto3`, `aioboto3` |
| JavaScript / TypeScript | `@aws-sdk/client-s3` |
| Go | `github.com/aws/aws-sdk-go-v2` |
| Java | `software.amazon.awssdk:s3` |
| Rust | `aws-sdk-s3` |
| CLI | `aws`, `mc` (MinIO Client), `rclone` |
## Related Services
<Cards>
<Card title="KV" href="/docs/services/kv">
Key-value store for caching and session data
</Card>
<Card title="DB" href="/docs/services/db">
Serverless PostgreSQL for structured data
</Card>
<Card title="Gateway" href="/docs/services/gateway">
API gateway routing and rate limiting
</Card>
<Card title="KMS" href="/docs/services/kms">
Key management for SSE-KMS encryption
Key management for SSE-KMS encryption and S3 credential storage
</Card>
<Card title="Base" href="/docs/services/base">
Backend-as-a-service that uses Hanzo S3 for file storage
</Card>
<Card title="IAM" href="/docs/services/iam">
Identity provider for advanced OIDC-based S3 access
</Card>
<Card title="PaaS" href="/docs/services/platform">
Deploy and operate Hanzo S3 on your clusters
</Card>
</Cards>