Hanzo AI

IAM

Hanzo's identity provider: users, organizations, applications, and the OIDC/OAuth2 endpoints every Hanzo service authenticates against.

Also for this capability: API · CLI · MCP · SDKs

Hanzo's identity provider: users, organizations, applications, and the OIDC/OAuth2 endpoints every Hanzo service authenticates against.

Base URLhttps://api.hanzo.ai
Operations150
AuthAuthorization: Bearer $HANZO_API_KEY

Specification

HIP-0026 · Identity & Access Management Standard — Active · read the specification →

Hanzo IAM is the unified identity and access management provider for the Hanzo ecosystem, serving production traffic at hanzo.id. It is a clean-room native rewrite on the Hanzo stack -- zip over hanzoai/orm -- and carries no Beego and no xorm. (This paragraph asserted a Go/Beego platform until 2026-08-13; the Casdoor-derived Beego/xorm tree is the retired v1 line at hanzoai/iam-v1. iam/go.mod requires no beego module and no Go file imports one -- the only occurrences in the tree are comments describing what v1 did.)

Hanzo IAM implements OAuth 2.0 and OpenID Connect (OIDC) — authorization code with PKCE, client credentials, the device grant, introspection and revocation — plus WebAuthn and TOTP MFA. (Earlier revisions also claimed SAML 2.0 and CAS; no SAML or CAS route exists in the served surface, plugin/iam/openapi.json, and the claim is withdrawn until one does.) It provides multi-tenant authentication with per-organization white-label identity domains — any organization registered in IAM can get its own branded login page and identity domain. The default deployment ships with hanzo.id, lux.id, zoo.id, pars.id, and id.ad.nexus, but the system supports arbitrary additional tenants via configuration.

IAM is the source of truth for identity. It is NOT the source of truth for spend: prepaid credit is the finance ledger's, read at the caller's own wallet address by the one spend predicate in hanzoai/cloud (spend.go), and IAM serves no balance or transaction route.

Repository: github.com/hanzoai/iam Port: 8000 Docker: ghcr.io/hanzoai/iam:latest

Motivation

Keycloak is the most popular open-source IAM. It is also a 500MB+ Java application that requires a JVM, takes 30+ seconds to start, and consumes 512MB of heap at idle. In the Hanzo ecosystem, where the blockchain node, CLI tools, SDK, and wallet are all written in Go, introducing a Java dependency for IAM is a poor fit.

Hanzo IAM compiles to a single Go binary (~50MB), starts in under 2 seconds, and idles at ~50MB RSS. It ships a React frontend (easy to customize for branding) and serves OAuth 2.0, OIDC, WebAuthn and TOTP MFA. The tradeoff is a smaller community and fewer enterprise features (no fine-grained RBAC policies, no UMA). For our use case -- OAuth SSO across a handful of first-party services -- the Hanzo IAM feature set is sufficient, and the operational simplicity is decisive.

FactorHanzo IAMKeycloak
LanguageGoJava
Binary size~50 MB~500 MB+
Idle memory~50 MB RSS~512 MB heap
Startup time< 2s30-60s
FrontendReact (customizable)Freemarker (limited)
Protocol supportOAuth2, OIDC, WebAuthnOAuth2, OIDC, SAML, UMA
Stack alignmentSame as Lux node, CLI, SDKRequires JVM

Specification

Architecture

                           Internet

                    ┌─────────┴─────────┐
                    │     Traefik        │
                    │  (TLS termination) │
                    │   :80 → :443      │
                    └─────────┬─────────┘

              ┌───────────────┼───────────────┐
              │               │               │
         hanzo.id         lux.id          zoo.id ...
              │               │               │
              └───────────────┼───────────────┘

                    ┌─────────┴─────────┐
                    │    Hanzo IAM       │
                    │   (zip + orm)      │
                    │     :8000          │
                    └─────────┬─────────┘

                    ┌─────────┴─────────┐
                    │      iam.db        │
                    │ (SQLite, encrypted │
                    │     at rest)       │
                    └───────────────────┘

One store: {DataDir}/iam/iam.db, an encrypted-at-rest SQLite file opened by IAM's own orm.DB, converted in place if it arrived plaintext — the same file the standalone binary is pointed at with --db, so the graft in hanzoai/cloud and the standalone process serve the same identities (cloud apps/iam/iam.go:36-48). There is no external database and no session cache beside it. (Earlier revisions drew SQL on :5432 and a KV session store on :6379; both belonged to the retired v1 line.)

OAuth 2.0 Flow: Authorization Code Grant with PKCE

Every Hanzo application uses Authorization Code Grant with PKCE (RFC 7636). Implicit grant is not supported. This is the flow:

1. Client generates code_verifier (random 43-128 chars)
2. Client computes code_challenge = BASE64URL(SHA256(code_verifier))

3. Client redirects user to:
   GET https://iam.hanzo.ai/v1/iam/oauth/authorize
     ?client_id=hanzo-app-client-id
     &redirect_uri=https://hanzo.ai/callback
     &response_type=code
     &scope=openid profile email
     &state=<random>
     &code_challenge=<code_challenge>
     &code_challenge_method=S256

4. User authenticates at the brand login UI (password, WebAuthn, or social login)

5. IAM redirects back:
   GET https://hanzo.ai/callback
     ?code=<authorization_code>
     &state=<random>

6. Client exchanges code for tokens:
   POST https://iam.hanzo.ai/v1/iam/oauth/token
     grant_type=authorization_code
     &code=<authorization_code>
     &redirect_uri=https://hanzo.ai/callback
     &client_id=hanzo-app-client-id
     &code_verifier=<code_verifier>

7. IAM returns:
   {
     "access_token": "eyJhbGciOi...",
     "token_type": "Bearer",
     "expires_in": 604800,
     "refresh_token": "eyJhbGciOi...",
     "id_token": "eyJhbGciOi...",
     "scope": "openid profile email"
   }

Access tokens are JWTs signed with the application's certificate (e.g., cert-hanzo). Lifetimes are per-application: expireInHours sets the access-token lifetime (1 hour when undeclared), and refreshExpireInHours sets the refresh lifetime, clamping to the access lifetime when unset (iam pkg/schema/application.go:28, internal/oidc/token.go:533-548). The example registration in Bootstrap below declares 168h/720h, which is what the flow example's expires_in reflects.

Multi-Tenant Domain Resolution

When a request arrives, IAM resolves the organization context through the following chain:

  1. Application lookup via /v1/iam/get-app-login: The login UI (hosted at hanzo.id, served by the hanzo.id-worker Cloudflare Worker) calls this endpoint with the clientId from the OAuth authorize URL. IAM returns the application name and organization name. This is the source of truth.

  2. Direct login via /v1/iam/login: The payload includes application and organization fields. These must match the application's configured organization. Hardcoding organization: "hanzo" for all requests breaks scoped SSO clients (e.g., KMS has its own client ID and expects the correct org context).

  3. The Host decides the issuer, not the org: there is no Host-based org fallback — get-app-login refuses a missing clientId with a 400 (iam internal/oidc/frontdoor.go:101). What the request Host does decide is the issuer the tokens carry, derived per request so each brand domain emits its own (internal/oidc/token.go:608-609).

Application Configuration

Each service in the ecosystem registers as an OAuth application with its own client credentials, redirect URIs, and scopes:

ApplicationClient IDOrganizationRedirect URIs (production)
app-hanzohanzo-app-client-idhanzohanzo.ai/callback, hanzo.app/callback, cloud.hanzo.ai/callback
app-cloudhanzo-cloud-client-idhanzocloud.hanzo.ai/callback
app-commercehanzo-commerce-client-idhanzocommerce.hanzo.ai/callback
app-consolehanzo-console-client-idhanzoconsole.hanzo.ai/api/auth/callback/hanzo-iam
app-platformhanzo-platform-client-idhanzoplatform.hanzo.ai/callback
app-zoozoo-app-client-idzoozoo.ngo/callback, zips.zoo.ngo/callback
app-luxlux-app-client-idluxlux.network/callback, wallet.lux.network/callback
app-parspars-app-client-idparspars.ai/callback
app-adnexusadnexus-app-client-idadnexusad.nexus/callback

All applications use:

  • Grant types: authorization_code, refresh_token, client_credentials, password, token exchange (RFC 8693), device code (RFC 8628)
  • Response types: code (the only one the discovery document advertises)
  • Token format: JWT
  • Password hashing: argon2id
  • WebAuthn: Enabled

Client secrets use KMS-managed placeholders (${IAM_APP_HANZO_CLIENT_SECRET}) resolved at startup via the resolveSecrets() function. Plaintext secrets never appear in configuration files or init_data.json.

Identity, Not Money

IAM serves no balance and no transaction route (plugin/iam/openapi.json carries neither noun), and earlier revisions of this section — a per-user balance field, /api/add-balance, /api/add-transaction, a Transaction model — described the retired v1 line. Prepaid credit is the finance ledger's: the one spend predicate reads the caller's own wallet address, exact to the atto-USD, and composes it with the subscription answer commerce resolves (hanzoai/cloud spend.go). IAM's contribution to that decision is the identity the wallet is derived from, nothing more.

Bootstrap: init_data.json

IAM bootstraps from init_data.json on first startup. This file defines the initial state of the system:

{
  "organizations": [
    {
      "name": "hanzo",
      "displayName": "Hanzo",
      "websiteUrl": "https://hanzo.ai",
      "passwordType": "argon2id",
      "defaultApplication": "app-hanzo",
      "themeData": {
        "themeType": "dark",
        "colorPrimary": "#fd4444"
      }
    },
    { "name": "zoo", "displayName": "Zoo Labs", "colorPrimary": "#10b981" },
    { "name": "lux", "displayName": "Lux Network", "colorPrimary": "#e4e4e7" },
    { "name": "pars", "displayName": "Pars", "colorPrimary": "#3b82f6" },
    { "name": "adnexus", "displayName": "AdNexus", "colorPrimary": "#3b82f6" }
  ],
  "applications": [
    {
      "name": "app-hanzo",
      "organization": "hanzo",
      "clientId": "hanzo-app-client-id",
      "clientSecret": "${IAM_APP_HANZO_CLIENT_SECRET}",
      "grantTypes": ["authorization_code", "refresh_token", "client_credentials", "password"],
      "tokenFormat": "JWT",
      "expireInHours": 168,
      "refreshExpireInHours": 720
    }
  ],
  "certs": [
    {
      "name": "cert-hanzo",
      "cryptoAlgorithm": "RS256",
      "bitSize": 4096
    }
  ]
}

Seeding is new-only and idempotent: an entity that already exists is left untouched, and ${VAR} references are substituted from the environment before parsing (iam internal/seed/seed.go:10-13). Users are deliberately excluded from the seed — accounts and service-account applications are provisioned through the operator-driven bootstrap endpoints (POST /v1/iam/admin/{applications,users}/upsert, internal/bootstrap/bootstrap.go:4-16), which fail closed when no service token is configured.

API Endpoints

Authentication (canonical OIDC endpoints)

These /v1/iam/oauth/* paths are the only OIDC endpoints. There is no /oauth/*, no /api/login/*, no /api/-prefixed auth path. Clients reach them only through @hanzo/iam; see HIP-0111 (Hanzo IAM Authentication Standard), which is authoritative for the client contract. IAM serves a 200 text/html SPA catch-all for any unregistered path — a wrong path is silent breakage, not a 404.

MethodEndpointRFCDescription
GET/v1/iam/get-app-loginResolve application and org from client ID
POST/v1/iam/loginPassword login (returns session or redirects)
GET/v1/iam/oauth/authorizeRFC 6749 §3.1Authorization endpoint (PKCE S256 required)
POST/v1/iam/oauth/tokenRFC 6749 §3.2Token exchange (client_secret_basic for confidential clients)
GET/v1/iam/oauth/userinfoOIDC Core §5.3UserInfo endpoint
POST/v1/iam/oauth/introspectRFC 7662Token introspection
POST/v1/iam/oauth/revokeRFC 7009Token revocation
GET/v1/iam/oauth/logoutOIDC RP-Initiated LogoutEnd session endpoint
GET/v1/iam/.well-known/jwksRFC 7517JSON Web Key Set
GET/.well-known/openid-configurationOIDC Discovery 1.0Discovery (host-relative; issuer derived from the request Host)
User Management
MethodEndpointDescription
GET/v1/iam/get-accountGet current user (from session/token)
GET/v1/iam/oauth/userinfoOIDC UserInfo endpoint
GET/v1/iam/get-userGet user by ID
POST/v1/iam/update-userUpdate user profile
POST/v1/iam/add-userCreate new user (admin)
POST/v1/iam/delete-userDelete user (admin)

(There is no billing table any more: earlier revisions listed /api/add-transaction and /api/add-balance here, routes the served surface does not carry — see Identity, Not Money above.)

Discovery
MethodEndpointRFCDescription
GET/.well-known/openid-configurationOIDC Discovery 1.0OIDC discovery document (host-relative)
GET/v1/iam/.well-known/jwksRFC 7517JSON Web Key Set

The OIDC discovery document is host-relative and self-consistent — issuer, authorize, token, userinfo, and jwks all share one origin, because every endpoint URL is built from the same request-derived issuer (iam internal/oidc/oidc.go:134-146):

{
  "issuer": "https://iam.hanzo.ai",
  "authorization_endpoint": "https://iam.hanzo.ai/v1/iam/oauth/authorize",
  "token_endpoint": "https://iam.hanzo.ai/v1/iam/oauth/token",
  "userinfo_endpoint": "https://iam.hanzo.ai/v1/iam/oauth/userinfo",
  "jwks_uri": "https://iam.hanzo.ai/v1/iam/.well-known/jwks",
  "end_session_endpoint": "https://iam.hanzo.ai/v1/iam/oauth/logout",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token",
    "client_credentials", "password",
    "urn:ietf:params:oauth:grant-type:token-exchange",
    "urn:ietf:params:oauth:grant-type:device_code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "none"]
}

SDK Integration

The client contract is HIP-0111. JS/TS applications integrate only through @hanzo/iam; Go services use iamsdk. No application writes an OIDC path string.

Go SDK
import "github.com/hanzoai/iam/iamsdk"

func init() {
    iamsdk.InitConfig(
        "https://iam.hanzo.ai",       // IAM endpoint
        "hanzo-app-client-id",        // Client ID
        "client-secret-here",         // Client secret
        "cert-hanzo",                 // Certificate name
        "hanzo",                      // Organization
        "app-hanzo",                  // Application
    )
}

// Validate a JWT access token
func validateToken(token string) (*iamsdk.Claims, error) {
    claims, err := iamsdk.ParseJwtToken(token)
    if err != nil {
        return nil, fmt.Errorf("invalid token: %w", err)
    }
    return claims, nil
}

// Get user info from token
func getUserInfo(token string) (*iamsdk.User, error) {
    return iamsdk.GetUserByAccessToken(token)
}
JavaScript / TypeScript SDK (@hanzo/iam)

Server-side token validation:

import { validateToken } from "@hanzo/iam/server";

const result = await validateToken(accessToken, {
  serverUrl: "https://iam.hanzo.ai",
  clientId: "hanzo-app-client-id",
});
if (result.ok) {
  const { userId, email, owner } = result; // owner = org slug; scope queries to it
}

Framework providers (@hanzo/iam/betterauth, @hanzo/iam/nextauth), the React SPA client (@hanzo/iam/react, @hanzo/iam/browser), and Passport (@hanzo/iam/passport) are specified in HIP-0111.

Implementation

Deployment Shapes

IAM ships two ways, one store either way. Standalone, the binary serves the brand identity domains behind the platform ingress, opening iam.db at the path --db names. Grafted, hanzoai/cloud composes the whole IAM surface in-process — host.Use(iamserver.NewApp(db)), so cloud's router learns IAM's route patterns AND its op registry while IAM's own router keeps IAM's behaviour — and opens the same file under its data directory (cloud apps/iam/iam.go:13-18, :36-48). A store that will not open is handed to IAM as nil and every identity request answers 503 while co-resident subsystems stay up; the degrade never moves the route table (apps/iam/iam.go:56-69).

Production Configuration

Key configuration facts (the v1 config file and its knobs — enableErrorMask, logPostOnly, initDataNewOnly, kmsUrl — retired with that line; v2 is configured by flags and environment):

  • --db names the one store; grafted, cloud opens the same file under its data directory.
  • --init-data seeds on boot, new-only, with ${VAR} substituted from the environment (iam main.go:88).
  • Bootstrap fails closed: the operator provisioning routes authenticate a service token from the first non-empty of HANZO_API_KEY / KMS_SERVICE_TOKEN / IAM_SERVICE_TOKEN; unset means no bootstrap (internal/bootstrap/bootstrap.go:11-16).

The Entities

Every entity is addressed (owner, name) — the owner is the organization, and that pair is the tenancy key on every row: organizations, users, applications, tokens, sessions, signing certs, identity providers, roles and permissions. (Earlier revisions described the schema as XORM auto-migration over SQL/MySQL and listed a transaction table; all three belonged to the retired v1 line — the store is hanzoai/orm over the one SQLite file above, and there is no transaction entity.)

Secrets Management

IAM integrates with Hanzo KMS (HIP-27) for secret resolution. Configuration files and init_data.json use ${VARIABLE} placeholders:

{
  "clientSecret": "${IAM_APP_HANZO_CLIENT_SECRET}"
}

The placeholders resolve from the process environment at seed time (iam internal/seed/seed.go:54-56); the environment itself is KMS-synced by the deployment, so client secrets and signing keys never appear in Git, Docker images, or config files. (The v1 line fetched from a kmsUrl at startup via resolveSecrets(); v2 carries no runtime KMS client — the sync happens outside the process.)

Standards Compliance

Standards Implemented

StandardStatusNotes
RFC 6749 (OAuth 2.0)FullAuthorization Code + PKCE; client_secret_basic
RFC 7636 (PKCE)FullS256 only
OIDC Core 1.0FullDiscovery, UserInfo, ID Tokens
OIDC Discovery 1.0Full/.well-known/openid-configuration (host-relative)
OIDC RP-Initiated LogoutFull/v1/iam/oauth/logout
RFC 7517 (JWK)Full/v1/iam/.well-known/jwks
RFC 7519 (JWT)FullRS256 signing

Custom Login UI

The hanzo/id repository provides a forkable, white-label Next.js login UI that serves as the frontend for all identity domains. It includes:

  • OIDC discovery rewriting: serves .well-known host-relative to the tenant domain
  • Multi-tenant detection: hostname-based tenant resolution (per-brand origin)
  • PKCE support: built-in S256 code challenge generation and verification
  • White-label forkable: fork to luxfi/id, zoofdn/id, etc. for org-specific branding

SDK Compliance

The client contract is HIP-0111. JS/TS uses @hanzo/iam; Go uses iamsdk. All hit the canonical /v1/iam/oauth/* endpoints.

SDKPackageAuthorizeToken
JS/TS@hanzo/iam/v1/iam/oauth/authorize/v1/iam/oauth/token
Gogithub.com/hanzoai/iam/iamsdk/v1/iam/oauth/authorize/v1/iam/oauth/token

No Backward Compatibility

There are no legacy paths. /oauth/*, /api/login/oauth/*, and /api/-prefixed auth paths are not served and not supported. The OIDC discovery document returns only the canonical /v1/iam/oauth/* endpoints.

The Capability Contract

What HIP-0139 §6 asks of every capability, answered for iam:

  • Addresses. Everything is under /v1/iam, plus the two families a protocol fixes at other roots: /.well-known/* (RFC 8615 — OIDC discovery, OAuth server metadata, JWKS) and /login/oauth/*, the browser authorize surface the /v1/iam/oauth/authorize 302 targets (cloud manifest/apps.go:70). Every operation is typed through IAM's own op registry, which the graft composes rather than wraps (apps/iam/iam.go:19-27).
  • Tenancy. IAM is the issuer, so it is the one capability whose tenant does not arrive as another service's claim: the organization is the owner half of every entity key, resolved from the application the client authenticates as (or, for a direct login, the payload's application + organization, which must match), and each brand emits its own issuer from the request Host. A request that resolves no organization is refused, not defaulted.
  • Meter. It is free, said in those words: no meter, no debit through any plane (cloud plugin/iam/main.go, Price: cloud.Free). Identity is what the paid planes charge AGAINST, not a thing charged for.
  • Events. It publishes none — a customer's webhooks receive nothing from iam. Authentication events go to the audit log (below), not the bus.
  • Observability. Nothing beyond the request span every route already gets; the audit trail of authentication events is IAM's own store, read through /v1/iam/audit-logs.
  • Stage. ga.
  • Upstreams. None survive in HEAD. The v1 line was a fork (Beego/xorm lineage, retired to hanzoai/iam-v1); v2 is the clean-room rewrite this HIP's opening paragraph describes, and cloud's graph carries no v1 module (apps/iam/iam.go:8-13).
  • Attacker. The Security Considerations below are that analysis: the wrong implementation here is an estate-wide credential mint.

Security Considerations

Authentication Security

  • PKCE required: All public clients (SPAs, mobile apps) MUST use PKCE (RFC 7636) with S256 challenge method. Authorization code interception is the most common OAuth attack vector; PKCE eliminates it.
  • Token rotation: Refresh tokens are rotated on use. The previous refresh token is invalidated when a new one is issued. This limits the window of a leaked refresh token.
  • Password hashing: argon2id with per-org salt configuration. argon2id is the winner of the Password Hashing Competition and is resistant to both GPU and side-channel attacks.
  • WebAuthn: Enabled on all applications for phishing-resistant second-factor authentication.

Session Security

  • Session lifetime: the portal session TTL is 14 days, matching the refresh window (iam internal/sessions/resolve.go:17-19). Logout revokes the sid server-side AND expires the cookie, so a copy of the cookie taken before logout does not still resolve.
  • Secure cookies: sessions use HttpOnly, SameSite=Lax cookies (internal/oidc/challenge.go:124, internal/sessions/cookie.go).
  • Store-backed sessions: sessions are rows in the one identity store. (The v1 line held them in a KV side-store; there is no session cache beside iam.db any more.)

Network Security

  • TLS everywhere: Traefik terminates TLS with Let's Encrypt certificates. HTTP is redirected to HTTPS. No plaintext traffic.
  • CORS: only a registered browser client's origin is admitted; a reverse proxy MUST NOT append CORS headers of its own beside it (iam internal/cors/cors.go).
  • Signin throttle: failed signins are throttled per organization or per application — failedSigninLimit and failedSigninFrozenTime, clamped to safe bounds before persistence; zero inherits the application default (pkg/schema/organization.go:84-88).
  • Health endpoint isolation: the liveness probe is unauthenticated (required for load balancer probes) but returns only a boolean status, leaking no internal state.

Operational Security

  • No seeded admin: init_data.json seeds no user at all (iam internal/seed/seed.go:28-29), so there is no default admin password to rotate. Admin accounts arrive only through the operator bootstrap upsert, under the service token.
  • Audit logging: authentication events are rows in IAM's own store, read back through /v1/iam/audit-logs.

Authentication vs Authorization (AuthN vs AuthZ)

IAM handles both authentication (identity verification) and authorization (access control), but they are distinct concerns:

Authentication (AuthN) — "Who are you?"

  • OAuth 2.0 flows (authorization code + PKCE, client credentials, device code)
  • Password login with argon2id hashing
  • Social login (GitHub, Google, etc.) via identity providers
  • WebAuthn / FIDO2 for phishing-resistant MFA
  • Session management (store-backed; see Session Security)

Authorization (AuthZ) — "What can you do?"

  • OAuth scopes: Applications request scopes (openid, profile, email, custom). IAM validates requested scopes against the application's allowed scope set and returns invalid_scope per RFC 6749 §4.1.2.1 if the client requests scopes not configured for its application.
  • RBAC roles and permissions: IAM supports role-based access control. Roles are collections of permissions; users are assigned roles per-organization. The permission and role tables enforce this.
  • Organization isolation: Users can be members of multiple organizations (hanzo, lux, zoo, pars, adnexus) but each session is scoped to one organization context. Cross-org access requires switching context.
  • Application-level isolation: Each OAuth application has its own client credentials, redirect URIs, grant types, and scopes. A token issued for app-console cannot be used at app-commerce (different aud claim).
  • Admin vs normal user: The isAdmin flag on the user entity grants full API access within the organization. Non-admin users are restricted to self-service operations.
  • Spend is not a claim: whether a caller may spend is the finance ledger's answer, computed by the one predicate in hanzoai/cloud (spend.go) — never a balance field read off a token IAM signed.

The key design principle: IAM authenticates users and issues scoped tokens. Services authorize requests by validating token claims. IAM does not make fine-grained authorization decisions for downstream services — it provides the identity and claims that services use to make their own authorization decisions.

Four surfaces

SurfaceReaches this capability asCoverage
RESTiam at its own prefix150 operations
CLIhanzo iam …150 of 150
SDKIamApi in every published client135 of 150 — the clients are generated at their own release
MCPtool iam on https://api.hanzo.ai/v1/mcp75 operations, 21 under the document's own id — ask describe for the rest

Quickstart

export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys

Then the first call — a read that needs nothing but the key. GET /v1/iam/keys, operation get_iam_keys:

hanzo iam keys list

Answers 200 with object — ok.

Endpoints

EndpointWhat it does
GET /v1/iam/.well-known/jwksPublishes the public keys that verify the tokens issued here — the one URL you point a service at so it can check a token itself, offline, without…
GET /v1/iam/.well-known/oauth-authorization-serverReturns the OpenID Connect discovery document — the one URL you point a standards-compliant client at so it can find every other endpoint on its own,…
GET /v1/iam/.well-known/openid-configurationReturns the OpenID Connect discovery document — the one URL you point a standards-compliant client at so it can find every other endpoint on its own,…
GET /v1/iam/accountReturns the signed-in person's own account and the organization they belong to — what a console reads to draw the account menu.
PUT /v1/iam/accountSaves the calling person's own profile — the name they are shown by, their picture, a line about themselves and a link.
POST /v1/iam/admin/applications/upsertCreates an application or updates it in place, so a deployment can declare the applications it needs and run the same declaration on every…
POST /v1/iam/admin/provisionSets up an account on someone's behalf — the same onboarding a person gets themselves, driven by one of your own services instead of by them.
POST /v1/iam/admin/users/upsertCreates a person or updates them in place, so a deployment can declare the accounts it needs and re-run that declaration safely.
GET /v1/iam/applications/{owner}/{name}Returns one application: its sign-in methods, its allowed redirect URIs and the client credentials your integration authenticates with.
PUT /v1/iam/applications/{owner}/{name}Changes an application's display, its sign-in methods and the redirect URIs it may return to — the call that makes login work from a new host.
DELETE /v1/iam/applications/{owner}/{name}Removes an application.
GET /v1/iam/applicationsReturns the applications in one organization, newest first — each product or site your people sign in to, with the sign-in methods and redirect URIs…
POST /v1/iam/applicationsRegisters an application in your organization — one product or site your people sign in to, with its own client credentials, sign-in methods and…
POST /v1/iam/assumeSteps a platform operator into an organization: it returns their own access token re-scoped to that tenant, so they see what the tenant sees.
GET /v1/iam/audit-logs/{owner}/{name}Returns one audit entry in full: the action, the person or key behind it, and the request it came in on.
PUT /v1/iam/audit-logs/{owner}/{name}Corrects an audit entry.
DELETE /v1/iam/audit-logs/{owner}/{name}Removes an audit entry.
GET /v1/iam/audit-logsReturns your organization's audit trail, newest first — who did what, when, and from where.
POST /v1/iam/audit-logsRecords an audit entry, so activity from your own systems lands in the same trail as everything the Hanzo Cloud records for you.
GET /v1/iam/auth/applicationReturns everything a login screen needs to draw itself for one application: its branding, and each sign-in method it offers with the provider details…
GET /v1/iam/auth/methodsReturns the sign-in methods one application actually has switched on, so a login screen can render the right buttons for it without you hard-coding a…
GET /v1/iam/certs/{owner}/{name}Returns one signing certificate — its algorithm, its validity window and its public half.
PUT /v1/iam/certs/{owner}/{name}Changes a signing certificate's settings.
DELETE /v1/iam/certs/{owner}/{name}Removes a signing certificate.
GET /v1/iam/certsReturns your organization's signing certificates, newest first — the keys the tokens your applications verify are signed with.
POST /v1/iam/certsAdds a signing certificate your applications can verify tokens against — the call you make to stage the next one before a rotation.
GET /v1/iam/consentReturns the calling person's own privacy and communication choices.
PUT /v1/iam/consentRecords the calling person's privacy and communication choices.
POST /v1/iam/delete-membershipTakes away a person's or an application's right to act in an organization.
GET /v1/iam/invitations/{owner}/{name}Returns one invitation: who it is for, what it grants on acceptance, and when it expires.
PUT /v1/iam/invitations/{owner}/{name}Changes an invitation's terms — the role it grants, how many may redeem it, or when it expires.
DELETE /v1/iam/invitations/{owner}/{name}Withdraws an invitation.
GET /v1/iam/invitationsReturns your organization's invitations, newest first — who has been asked to join, on what terms, and how many seats each invitation still has left.
POST /v1/iam/invitationsIssues an invitation to join your organization — the code or link a new member redeems, with the role they arrive holding and the date it stops…
GET /v1/iam/keys/{owner}/{name}Returns one API key: what it is called, what it may reach, and when it was issued.
PUT /v1/iam/keys/{owner}/{name}Changes what a key is called or what it may reach.
DELETE /v1/iam/keys/{owner}/{name}Revokes an API key.
GET /v1/iam/keys/orgResolve a PUBLISHABLE key to the organization that owns it
GET /v1/iam/keys/principalResolve a SECRET key to the principal it authenticates
GET /v1/iam/keysReturns your organization's API keys, newest first — what each is called, what it may reach, and its publishable half.
POST /v1/iam/keysIssues an API key.
POST /v1/iam/linkStarts connecting another sign-in identity to the account you are already signed in as.
GET /v1/iam/linked-accountsReturns the sign-in identities linked to the calling person's account — every provider they can currently sign in with.
POST /v1/iam/loginSigns a person in with the credential they typed, and — when the request is part of an OAuth flow — hands back the one-time code that finishes it.
GET /v1/iam/membershipsAnswers either question about who belongs where: which organizations one person can act in, or who can act in one organization.
POST /v1/iam/membershipsLets a person or an application act in an organization.
POST /v1/iam/mfa/preferredPicks which second factor an account is asked for first when it has more than one.
POST /v1/iam/mfa/setup/enableFinishes the enrolment: from here the account's sign-ins ask for this factor.
POST /v1/iam/mfa/setup/initiateStarts enrolling a factor and hands over whatever the person needs to prove they hold it: app a fresh secret and the otpauth:// URL to render as a QR…
DELETE /v1/iam/mfaTurns a factor off, so sign-in stops asking for it.
GET /v1/iam/oauth/authorizeStarts a sign-in — the address you send a browser to, and the beginning of every OAuth and OpenID Connect flow.
POST /v1/iam/oauth/authorizeStarts a sign-in — the address you send a browser to, and the beginning of every OAuth and OpenID Connect flow.
GET /v1/iam/oauth/callbackCompletes the round-trip: it resolves and burns the single-use transaction (checking expiry + browser binding), exchanges and verifies the IdP…
POST /v1/iam/oauth/device/infoAnswers "what am I approving?" for a pending device code.
POST /v1/iam/oauth/deviceStarts a sign-in on a device with no browser and no keyboard — a TV, a CLI, a headless box.
POST /v1/iam/oauth/federation/mfaCompletes a sign-in that came in through another identity provider and still owes a second factor.
POST /v1/iam/oauth/introspectAnswers whether an access token is still good, and what it is good for — the check a resource server of yours makes before honouring a token it did…
GET /v1/iam/oauth/logoutEnds a sign-in and sends the browser somewhere sensible.
POST /v1/iam/oauth/logoutEnds a sign-in and sends the browser somewhere sensible.
POST /v1/iam/oauth/revokeRetires a token before it expires — what you call when someone signs out or a credential may have leaked.
POST /v1/iam/oauth/tokenExchanges what your application is holding for the tokens it needs — the one-time code from a finished sign-in, a refresh token, or your own client…
GET /v1/iam/oauth/userinfoReturns the profile claims for whoever the access token belongs to — the standard OpenID Connect way to find out who is calling you without your…
POST /v1/iam/oauth/userinfoReturns the profile claims for whoever the access token belongs to — the standard OpenID Connect way to find out who is calling you without your…
POST /v1/iam/onboardFinishes setting up the account of whoever is calling — it creates their organization if they have none and puts them in it, so a person who has just…
GET /v1/iam/organizations/{owner}/{name}Returns one organization: its display, its defaults and the sign-in rules everyone in it inherits.
PUT /v1/iam/organizations/{owner}/{name}Changes an organization's display, its defaults and the sign-in rules everyone in it inherits.
DELETE /v1/iam/organizations/{owner}/{name}Removes an organization and everything named inside it.
POST /v1/iam/organizations/avatarChanges how an organization appears across Hanzo: the square mark beside its name, as an uploaded image or as a single emoji.
GET /v1/iam/organizationsReturns the organizations you can act in, the ones you belong to first and the rest after, newest first, narrowed by an optional query against the…
POST /v1/iam/organizationsMakes a new organization — the account your users, applications, roles, projects and workspaces are all named inside.
PUT /v1/iam/passwordReplaces the calling person's password.
GET /v1/iam/permissions/{owner}/{name}Returns one permission: who it grants to, what it allows, and the resources it covers.
PUT /v1/iam/permissions/{owner}/{name}Changes who a permission grants to, what it allows, or the resources it covers.
DELETE /v1/iam/permissions/{owner}/{name}Revokes a permission.
GET /v1/iam/permissionsReturns the permissions in one organization, newest first — each one a grant saying which people or roles may do what, and to which resources.
POST /v1/iam/permissionsGrants a permission — the call that gives a person or a role the ability to do something.
POST /v1/iam/preferencesSaves the calling person's own settings and returns the full set afterwards.
GET /v1/iam/projects/{owner}/{name}Returns one project: what it is called and how it is set up.
PUT /v1/iam/projects/{owner}/{name}Changes a project's settings.
DELETE /v1/iam/projects/{owner}/{name}Removes a project.
GET /v1/iam/projectsReturns your organization's projects, newest first — the scope people pick between when their work is separated by product or client rather than by…
POST /v1/iam/projectsMakes a project inside your organization — the scope people pick between when their work is separated by product or client rather than by team.
GET /v1/iam/providers/{owner}/{name}Returns one provider: what it connects to and how it is configured.
PUT /v1/iam/providers/{owner}/{name}Changes a provider's settings or rotates the credentials it holds.
DELETE /v1/iam/providers/{owner}/{name}Removes a provider.
GET /v1/iam/providersReturns your organization's providers, newest first — the identity providers your people sign in with, and the senders and connectors your…
POST /v1/iam/providersAdds an identity provider your people can sign in with, or a service your applications send through — a social or enterprise login, an email or SMS…
GET /v1/iam/registry/jwksPublishes the public key your registry uses to verify the tokens issued above — the one URL to configure so the registry trusts logins without…
GET /v1/iam/registry/tokenSigns a container client in to your registry.
POST /v1/iam/registry/tokenSigns a container client in to your registry.
POST /v1/iam/releaseSteps a platform operator back out: it returns their own access token with no organization assumed, which is the credential they had before they…
GET /v1/iam/roles/{owner}/{name}Returns one role: who is in it, and the roles it includes.
PUT /v1/iam/roles/{owner}/{name}Changes who is in a role, or which roles it includes.
DELETE /v1/iam/roles/{owner}/{name}Removes a role.
GET /v1/iam/rolesReturns your organization's roles, newest first — each a named group of people that permissions are granted to.
POST /v1/iam/rolesMakes a role — a named group of people that permissions are granted to.
GET /v1/iam/scim/v2/ResourceTypes/{name}Returns one provisionable record kind in full.
GET /v1/iam/scim/v2/ResourceTypesReturns the kinds of record this directory provisions and the address of each, so your identity provider discovers them rather than having them…
GET /v1/iam/scim/v2/Schemas/{id}Returns one attribute definition in full.
GET /v1/iam/scim/v2/SchemasReturns the attribute definitions this directory understands, so your identity provider knows which fields it may send and what they mean before it…
GET /v1/iam/scim/v2/ServiceProviderConfigTells your identity provider which parts of SCIM this directory supports, so it configures itself instead of you filling in a form.
GET /v1/iam/scim/v2/Users/{owner}/{name}Returns one person in the standard SCIM shape.
PUT /v1/iam/scim/v2/Users/{owner}/{name}Overwrites a person's SCIM attributes with what your identity provider sends — how a change made there lands here.
PATCH /v1/iam/scim/v2/Users/{owner}/{name}Applies a partial change from your identity provider — one attribute moved, not the whole record resent.
DELETE /v1/iam/scim/v2/Users/{owner}/{name}Deprovisions a person — how removing someone in your identity provider removes their access here.
GET /v1/iam/scim/v2/UsersReturns the people in your organization to your identity provider, in the standard SCIM shape, so an IdP can reconcile its directory against ours.
POST /v1/iam/scim/v2/UsersProvisions a person from your identity provider — how a new hire gets an account here automatically when they are added over there.
POST /v1/iam/service-accounts/{name}/keysServes POST /v1/iam/service-accounts/:name/keys: mint a fresh key, invalidating the prior one, and return the new raw secret exactly once.
DELETE /v1/iam/service-accounts/{name}Serves DELETE /v1/iam/service-accounts/:name.
GET /v1/iam/service-accountsReturns your organization's service accounts — what each is called and when it was created.
POST /v1/iam/service-accountsMakes a service account — an identity for a program rather than a person, for a script, a bot or a deployment that has to authenticate on its own.
GET /v1/iam/sessions/{owner}/{name}/{application}Returns one person's session in one application — when it began and which browsers or devices are still carrying it.
PUT /v1/iam/sessions/{owner}/{name}/{application}Replaces the set of browsers a session covers — signing out the ones you leave off while the session itself stays live.
DELETE /v1/iam/sessions/{owner}/{name}/{application}Signs a person out of one application — the session ends and every browser carrying it stops being authenticated.
GET /v1/iam/sessionsReturns who is currently signed in to your organization, newest first, and can be narrowed to one person or one application.
POST /v1/iam/sessionsRecords a sign-in.
POST /v1/iam/signinCompletes a sign-in: it exchanges the one-time code your application was handed at the end of the login flow for a live session, and returns the…
POST /v1/iam/signupCreates an account from the sign-up form and applies the application's own sign-up rules — whether self-service registration is open at all, and…
GET /v1/iam/tokens/{owner}/{name}Returns one access token: who and what it was issued to, and when it expires.
PUT /v1/iam/tokens/{owner}/{name}Changes an access token's scope or expiry.
DELETE /v1/iam/tokens/{owner}/{name}Revokes an access token.
POST /v1/iam/tokens/issueMints an access token for the ?id=&lt;owner&gt;/&lt;name&gt; target user (optional ?aud= resource, RFC 8707), issued by the authenticated + allow-listed…
GET /v1/iam/tokensReturns the access tokens issued in your organization, newest first, and can be narrowed to one organization.
POST /v1/iam/tokensRecords an access token — the credential an application or integration presents on a caller's behalf.
POST /v1/iam/unlinkDisconnects one sign-in identity from an account, so that provider can no longer be used to sign in as that person.
POST /v1/iam/users/{owner}/{name}/keys(re)generates the target user's key of the requested TYPE and returns it once, over the shared authorizeMinter + mintTarget seam.
DELETE /v1/iam/users/{owner}/{name}/keysClears the target user's key of the requested TYPE (immediate revoke).
GET /v1/iam/users/{owner}/{name}Returns one person in your organization, addressed by their username or by their email address.
PUT /v1/iam/users/{owner}/{name}Changes a person's profile, their roles, or the credentials they sign in with.
DELETE /v1/iam/users/{owner}/{name}Removes a person from your organization.
GET /v1/iam/usersReturns a page of the people in your organization, with the total so you can page through the rest.
POST /v1/iam/usersAdds a person to your organization.
POST /v1/iam/verification-codesValidates the request and asks otp to get a code to the person.
GET /v1/iam/web3/nonceStarts a wallet sign-in: it returns a one-time challenge for the wallet to sign.
POST /v1/iam/web3/verifyCompletes a wallet sign-in: it verifies the signed challenge and, if it holds, signs the wallet's owner in.
GET /v1/iam/webauthn-credentials/{owner}/{name}Returns one passkey or security key: whose it is, what device it lives on, and when it was registered.
PUT /v1/iam/webauthn-credentials/{owner}/{name}Renames a registered passkey or security key, so a person can tell their devices apart.
DELETE /v1/iam/webauthn-credentials/{owner}/{name}Removes a passkey or security key — what you call when a device is lost.
GET /v1/iam/webauthn-credentialsReturns the passkeys and security keys registered to one person, newest first — which device each lives on and when it was registered.
POST /v1/iam/webauthn-credentialsRegisters a passkey or security key for a person, so they can sign in with their device instead of a password.
GET /v1/iam/webauthn/signin/beginStarts a passkey sign-in: it returns the challenge the person's authenticator signs.
POST /v1/iam/webauthn/signin/finishVerifies the signed challenge and signs the person in.
GET /v1/iam/webauthn/signup/beginStarts enrolling a passkey for the signed-in person: it returns the options their browser hands to the authenticator.
POST /v1/iam/webauthn/signup/finishVerifies the newly created passkey and stores it, so the person can sign in with their device from then on.
GET /v1/iam/whoamiTells you who the current caller is — the lightweight check a page makes on load to decide whether to render signed-in or signed-out.
GET /v1/iam/workspaces/{owner}/{name}Returns one workspace: what it is called and how it is set up.
PUT /v1/iam/workspaces/{owner}/{name}Changes a workspace's settings.
DELETE /v1/iam/workspaces/{owner}/{name}Removes a workspace.
GET /v1/iam/workspacesReturns your organization's workspaces, newest first — the scope a team works in, alongside projects rather than instead of them.
POST /v1/iam/workspacesMakes a workspace inside your organization — the scope a team works in, alongside projects rather than instead of them.

All Hanzo APIs · Interactive reference

How is this guide?