Hanzo Base
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
API reference · Hanzo Base — Collections → — every endpoint, generated from the OpenAPI spec.
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.
| 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 |
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.
Features
- Auto-generated REST API: Full CRUD for every collection under
/v1/collections/{name}/records - IAM-native auth: Tokens issued by Hanzo 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
- 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/sqlfor 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
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; the write-ahead log is streamed (age-encrypted) to S3/GCS for durability.
Local dev / single node → SQLite (encrypted, per-org)
Production multi-instance → PostgreSQL (pgx)Authentication
Base does not authenticate users itself — it trusts Hanzo IAM. The flow:
Discover the auth provider
curl https://base.hanzo.ai/v1/collections/users/auth-methodsReturns the single configured iam OAuth2/PKCE provider with its authorize URL and PKCE challenge.
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 for the full flow.
Call Base with the IAM-issued JWT
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).
A token can be rotated without re-running the full flow:
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
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
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)
# 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.
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 in production.
# Upload (multipart)
curl -X POST https://base.hanzo.ai/v1/collections/posts/records \
-H "Authorization: Bearer $IAM_JWT" \
-F "title=My Post" \
-F "cover=@image.jpg"
# 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:
{
"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 (z@hanzo.ai) is seeded in Hanzo 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: 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
# compose.yml
services:
base:
image: ghcr.io/hanzoai/base:latest
ports:
- "8090:8090"
environment:
IAM_ENDPOINT: https://hanzo.id
volumes:
- base-data:/app/data
restart: unless-stopped
volumes:
base-data: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
The identity provider Base delegates all authentication to
Per-tenant database encryption keys
Object storage backend for file uploads
Deploy and operate Base on your clusters
How is this guide?
Last updated on