Hanzo

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.

Productionhttps://base.hanzo.ai
API prefix/v1
Admin UIhttps://base.hanzo.ai/_/
AuthHanzo IAM (OIDC / JWKS) — no local passwords
Sourcegithub.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/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

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.

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.

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-methods

Returns 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.

MethodEndpointDescription
GET/v1/collections/{name}/recordsList/search (filter, sort, page, expand, fields)
GET/v1/collections/{name}/records/{id}Get one record
POST/v1/collections/{name}/recordsCreate a record
PATCH/v1/collections/{name}/records/{id}Update a record
DELETE/v1/collections/{name}/records/{id}Delete a record
POST/v1/batchBatched 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:

EndpointPurpose
POST /v1/sqlRun a parameterized SQL query (superuser)
/v1/tasksDurable task & workflow queue (claim, start, complete, fail, signal)
/v1/privatePer-user private key-value store
/v1/backupsCreate, download, and restore database backups (superuser)
/v1/cronsList and trigger scheduled jobs (superuser)
/v1/logsRequest 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

LanguagePackage
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.

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

On this page