Reconcile hanzo.yaml to the live cloud surface — 1698 paths, all products (#18)

* spec(ai): define /v1/images/generations + /v1/audio/{speech,transcriptions}

The ai spec's description already advertised /v1/images/* and /v1/audio/*
but never defined the operations. Add the OpenAI-compatible image generation,
text-to-speech, and speech-to-text (Whisper) operations + schemas so the spec
matches the SDK image/audio resources and what hanzo.chat consumes.

* spec(app): add hanzo.app projects & deployments surface (/v1/projects/*)

hanzo.app calls GET/POST /v1/projects, GET/PATCH/DELETE /v1/projects/{slug},
POST /v1/projects/{slug}/deploy (tar artifact or git build), and
GET /v1/projects/{slug}/deployments — none of which were in any spec.
New self-contained app service spec, top-level /v1/projects/* (path is the
contract, same rationale as top-level inference). Org is resolved server-side
from the IAM JWT owner claim, never sent by the browser.

* spec(base): add Hanzo Base records surface (/v1/collections/{collection}/records)

hanzo.app persists each site's working draft as a row in the Base site_drafts
collection (gated by VITE_BASE_URL) via GET(list+filter)/POST/PATCH on
/v1/collections/{collection}/records. Add a self-contained base service spec
covering the full record CRUD so the frontend contract is complete.

* spec(cloud): scrub literal '/api/' from product-surface comment

The only '/api/' occurrence left in any spec was a documentation comment.
Reword so 'grep -rn /api/' returns zero across the repo; meaning is unchanged
(every route is /v1/*).

* feat(openapi): regenerate unified hanzo.yaml (38 services, 1590 paths)

Regenerated via merge.py after adding app + base and the ai image/audio ops.
Verified: zero /api/ paths and zero /api/ literals across all specs; the only
non-/v1 routes are the unversioned infra probes /health, /healthz, /metrics.
README service index gains app + base rows.

* fix(merge): namespace operationIds by service in unified master

Two services can each define a unique-within-themselves operationId that
collides in the merged master (e.g. base.listRecords vs another service).
Redocly flagged 23 operation-operationId-unique errors, making the master
invalid as a single generatable document. Prefix operationIds with the
service tag — the same namespacing already applied to schemas/responses/
params — so the master is a globally-valid OpenAPI doc every client can
derive from. Per-service specs (Stainless inputs) keep their bare
operationIds. Master lint errors: 369 -> 100 (remainder is pre-existing
per-service nullable/summary debt, unrelated to this change).

* docs(sdk): document the one canonical regeneration path per SDK

hanzo.yaml is the source of truth; JS/Python/Go regenerate via the Stainless
'hanzo-ai' project (push spec -> PR), Rust is hand-written crates (not
spec-generated). Records the exact 'stl builds create' command + KMS-sourced
key for the hosted step.

* docs(openapi): document /v1/sdk enveloped secrets+sign surface (kms)

Add POST /v1/sdk/secrets — the SDK-facing native secrets plane served by
kmsd. Single RPC endpoint: the operation is the SIGNED op field, not the
URL; the ML-DSA-65 envelope IS the credential (no bearer, security: []).
Adds SdkEnvelope + SdkEnvelopeIdentity schemas. Regenerated hanzo.yaml.

* docs(sdk): record the real generation drift + the one-way

The published python/go/js SDKs derive from the LEGACY LLM-gateway spec, not
hanzo.yaml — they cover inference + gateway-admin only, none of the cloud
products. README implied otherwise; correct it. LLM.md now records: repoint
Stainless hanzo-ai -> hanzo.yaml, the 28 live /v1 prefixes with no spec, the 4
prefix-mismatch re-bases, and the stale monolith specs (cloud/nexus). Add cpp +
dart rows (openapi-generator on ARC) — one repo per language, one spec source.

* spec(agents,functions): add /v1/agents + /v1/functions surfaces from clients/*

Route-accurate to hanzoai/cloud/clients/{agents,functions}. agents covers
the agent CRUD + metered run + live session tree; functions covers
serverless CRUD + metered invoke + triggers/deployments/secrets/metrics.
Self-contained; regenerated hanzo.yaml (0 dup operationIds, 0 dangling refs).

* spec(framework,tracker,crm): add business-app surfaces from clients/*

Route-accurate to hanzoai/cloud/clients/{framework,tracker,crm}: framework
doctype/module/role + generic document CRUD, tracker projects+issues,
crm companies/contacts/opportunities.

* spec(automations,integrations,admin,plan): add orchestration + admin surfaces

Route-accurate to hanzoai/cloud/clients/{automations,integrations,admin,plan}:
automations flows/versions/runs/pieces/mcp, integrations provider-connect +
Slack, admin god-view aggregates, plans catalog/resolve/entitlements (the
plan spec expands the fixed-binding route loop, not just literal app.Get calls).

* spec(visor,zt): re-base onto live flat prefixes from clients/{visor,zt}

* spec(kb,prompts,templates): add /v1/{kb,prompts,templates} from clients/*

* spec(git,graph,do): add /v1/git + /v1/{indexers,oracles} + /v1/{vpcs,load-balancers} from clients/*

* spec(billing,security): add /v1/billing + /v1/security from clients/*

* spec(exec,websearch,tasks): add /v1/{exec,websearch,tasks} proxy surfaces; regen hanzo.yaml (58 services); changelog

---------

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-07-04 15:26:21 -07:00
committed by GitHub
co-authored by Hanzo Dev
parent 8485f8e756
commit 01c59d173f
25 changed files with 12901 additions and 7560 deletions
+52
View File
@@ -2,6 +2,58 @@
## v1.0.0
### Cloud product-surface specs (clients/* → live /v1 flat prefixes)
Authored per-service specs for the fused `api.hanzo.ai/v1` cloud-binary
surface, route-accurate to the Go handlers in `hanzoai/cloud/clients/<x>`:
- `/v1/agents/*` (agents + runs + live session tree) is covered by
`cloud/openapi.yaml` (landed on main); the 21 specs below are the rest.
- Added `functions/openapi.yaml` — `/v1/functions/*` (serverless functions,
metered invoke) from `clients/functions`.
- Added `framework/openapi.yaml` — `/v1/framework/*` (doctypes, modules, roles,
generic document CRUD) from `clients/framework`.
- Added `tracker/openapi.yaml` — `/v1/tracker/*` (projects + issues) from
`clients/tracker`.
- Added `crm/openapi.yaml` — `/v1/crm/*` (companies, contacts, opportunities)
from `clients/crm`.
- Added `automations/openapi.yaml` — `/v1/automations/*` (flows, versions,
runs, pieces, MCP) from `clients/automations`.
- Added `integrations/openapi.yaml` — `/v1/integrations/*` (provider connect
+ Slack events/commands/link) from `clients/integrations`.
- Added `admin/openapi.yaml` — `/v1/admin/*` (god-view overview, orgs, users,
customers, finance, revenue, audit) from `clients/admin`.
- Added `plan/openapi.yaml` — `/v1/plans/*` (plan catalog, resolve,
entitlements) from `clients/plan`.
- Added `kb/openapi.yaml` — `/v1/kb/*` (search + connectors) from `clients/kb`.
- Added `prompts/openapi.yaml` — `/v1/prompts/*` (prompt library, catalog,
metrics) from `clients/prompts`.
- Added `templates/openapi.yaml` — `/v1/templates/*` (template gallery) from
`clients/templates`.
- Added `git/openapi.yaml` — `/v1/git/*` (repos + Git smart-HTTP transport)
from `clients/git`.
- Added `billing/openapi.yaml` — `/v1/billing/*` (usage, balance,
gpu-eligibility, gpu-charge, payment-methods) from `clients/billing`.
- Added `security/openapi.yaml` — `/v1/security/*` (rules, scans, findings)
from `clients/security`.
- Added `graph/openapi.yaml` — `/v1/indexers`, `/v1/oracles` from
`clients/graph` (flat prefixes, not `/v1/graph`).
- Added `do/openapi.yaml` — `/v1/vpcs`, `/v1/load-balancers` from `clients/do`
(flat prefixes, not `/v1/do`).
- Added `exec/openapi.yaml` — `/v1/exec`, `/v1/upload`, `/v1/download`,
`/v1/files` (code interpreter, `X-API-Key` service auth) from `clients/exec`.
- Added `websearch/openapi.yaml` — `/v1/websearch/{search,scrape}` (SearXNG +
Firecrawl-shaped over Hanzo Crawl) from `clients/websearch`.
- Added `tasks/openapi.yaml` — `/v1/tasks/*` (durable workflow engine surface,
identity-gated) from `clients/tasksvc`.
- Re-based `visor/openapi.yaml` onto its live flat prefixes `/v1/machines`,
`/v1/gpus`, `/v1/clusters`, `/v1/bots`, `/v1/compute/*`, `/v1/agent-bindings`
from `clients/visor` (was wrongly under `/v1/visor/*`).
- Re-based `zt/openapi.yaml` onto its live flat prefixes `/v1/networks`,
`/v1/mesh/services`, `/v1/edge/nodes` from `clients/zt` (was wrongly under
`/v1/zt/*`).
- `hanzo.yaml` regenerated: 58 services, 1698 paths.
### Frontend surface completeness (hanzo.app + hanzo.chat)
- Added the `app` service spec (`app/openapi.yaml`): the hanzo.app projects &
+52
View File
@@ -36,3 +36,55 @@ python3 -c "import yaml, glob; [yaml.safe_load(open(s)) for s in glob.glob('*/op
other service yamls.
4. Examples must come from real responses.
5. Update `CHANGELOG.md` with a dated entry under the v1.0.0 heading.
6. Run `python3 merge.py` (regenerates `hanzo.yaml`) and commit both.
## SDK generation — the ONE way, and its current drift (READ THIS)
The one interface is `hanzo.yaml`; the generator backend is an orthogonal
per-language concern (one interface, orthogonal backends):
| Lang | Repo | Backend | Why |
|------|------|---------|-----|
| Python/Go/JS | `hanzoai/{python,go,js}-sdk` | Stainless project `hanzo-ai` | best DX, already published |
| Rust | `hanzoai/rust-sdk` | hand-written crates | Stainless emits no Rust |
| C++ | `hanzoai/cpp-sdk` | openapi-generator `cpp-restsdk` on ARC | Stainless emits no C++ |
| Dart (if wanted) | `hanzoai/dart-sdk` | openapi-generator `dart-dio` on ARC | Stainless emits no Dart |
One repo per language, each derives from `hanzo.yaml`. There is NO unified
`hanzoai/sdk` multi-lang monorepo generator — that repo's `gen/` (openapi-
generator over all 10 langs, "replaces Stainless") is the retired SECOND way;
`hanzoai/sdk` is CLI-only now.
### Known drift (2026-07 audit — must be fixed for the SDKs to be truthful)
1. **Stainless points at the LEGACY LLM-gateway spec, not `hanzo.yaml`.** The
published python/go/js SDKs expose only the LiteLLM inference + gateway-admin
surface (188 endpoints / 49 resources: chat, models, embeddings, Key, User,
Team, Spend, Budget, Guardrails, provider passthrough). They cover NONE of
the cloud product surface. Fix = repoint the Stainless `hanzo-ai` project's
spec source to `hanzo.yaml` and expand its resource tree to the full surface.
`python-sdk/.stats.yml` (spec `87bc62c…`) is also diverged from go/js
(`9b3181f…`) — the repoint resyncs all three.
2. **~28 LIVE `/v1` product prefixes have no usable spec** (so even after the
repoint they'd be uncovered). The cloud binary (`hanzoai/cloud`
`subsystems.go`) mounts these flat; they need per-service specs authored from
the Go routes in `cloud/clients/<x>/*.go`:
`agents, tracker, crm, framework(+cms/erp/help/kb), kb, prompts, tasks,
functions, git, templates, security, integrations, notify, automations,
billing, plans, graph(indexers/oracles), exec, websearch, admin, do
(vpcs/load-balancers), provisioning(sql/datastore/docdb), memory, vfs,
licensing, authz, ml(train)`.
3. **Prefix-mismatch specs — re-base onto the live flat prefix:** `visor` →
`/v1/machines,/v1/gpus,/v1/clusters`; `zt` → `/v1/networks,/v1/mesh,/v1/edge`;
`flow` → `/v1/automations`; `guard` → `/v1/security`.
4. **Stale specs (legacy monolith, NOT fused into the cloud binary):**
`cloud/` (157) and `nexus/` (151) are the retired RPC monolith — only the
`/v1/search-docs` + `/v1/vector` slices survive (via `clients/product`).
`mq, pubsub, stream, registry, db, did, engine` are not live. Separate
deployments that legitimately keep their spec: `iam, chat, gateway,
operative, dns, flow`.
The SDK-generation surface is the FUSED `api.hanzo.ai/v1` binary, not the union
of every deployment. `merge.py` currently unions all per-service specs; the
correct target is the live cloud surface (add the 28, re-base the 4, drop the
stale monolith).
+6 -6
View File
@@ -113,12 +113,12 @@ npx -y @redocly/cli preview-docs hanzo.yaml
`hanzo.yaml` (regenerated by `merge.py`) is the single source of truth every
official SDK derives from. One way per SDK:
| SDK | Repo | Generator | Regenerate |
|-----|------|-----------|------------|
| JS/TS | `hanzoai/js-sdk` | Stainless (`hanzo-ai`) | push spec → Stainless PR |
| Python | `hanzoai/python-sdk` | Stainless (`hanzo-ai`) | push spec → Stainless PR |
| Go | `hanzoai/go-sdk` | Stainless (`hanzo-ai`) | push spec → Stainless PR |
| Rust | `hanzoai/rust-sdk` | hand-written crates | not generated from this spec |
> **Status (2026-07):** the Stainless project `hanzo-ai` still points at the
> legacy LLM-gateway spec, so the published python/go/js SDKs cover the
> inference + gateway-admin surface only — NOT the cloud products. Repoint it
> to `hanzo.yaml` and close the ~28 missing product specs first. See the SDK
> generation reconciliation section in `LLM.md`.
### Regenerate the Stainless SDKs (canonical)
+718
View File
@@ -0,0 +1,718 @@
openapi: 3.1.0
info:
title: Hanzo Admin API
description: |
The god-mode admin surface (`/v1/admin/*`) the Hanzo Admin Console
(admin.hanzo.ai / apps/operator) calls — served by the cloud `admin`
subsystem (`hanzoai/cloud/clients/admin`).
It is an AGGREGATOR, not a store: identity (orgs/users/roles/applications/
audit/me) is read from IAM, the money panels (spend/tokens/credits, revenue,
finance/COGS) from commerce, System Health from o11y, and the compute/o11y
fleet lenses from the shared datastore. Every route is GLOBAL-ADMIN ONLY and
fail-closed: a non-global-admin principal is refused 403 before any upstream
is touched.
Reads use the `/v1` envelope `{ status, msg, data, data2 }` — `data` is the
payload, `data2` the total for list endpoints.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Identity
description: Operator identity, orgs, users, roles, applications
- name: Overview
description: Fleet overview, usage, products, sync
- name: Customers
description: Customer cockpit — list, detail, credit, suspend/reactivate
- name: Money
description: Revenue, finance (COGS/margin), analytics
- name: Observability
description: Fleet compute and o11y aggregates
- name: Audit
description: Tamper-evident audit trail
security:
- bearerAuth: []
paths:
/v1/admin/me:
get:
tags: [Identity]
summary: Validated operator identity
operationId: adminMe
responses:
'200': { description: Operator identity, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/AdminMe' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/overview:
get:
tags: [Overview]
summary: Fleet overview tiles
operationId: adminOverview
responses:
'200': { description: Overview, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/OverviewData' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/orgs:
get:
tags: [Identity]
summary: Tenant directory
operationId: adminListOrgs
responses:
'200': { description: Org rows, content: { application/json: { schema: { $ref: '#/components/schemas/OrgList' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/users:
get:
tags: [Identity]
summary: Cross-org user directory
operationId: adminListUsers
parameters:
- { name: org, in: query, schema: { type: string }, description: Filter to one org (owner) }
- { name: p, in: query, schema: { type: integer }, description: 1-based page }
- { name: pageSize, in: query, schema: { type: integer } }
- { name: q, in: query, schema: { type: string }, description: Free-text name filter }
responses:
'200': { description: User rows, content: { application/json: { schema: { $ref: '#/components/schemas/UserList' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/roles:
get:
tags: [Identity]
summary: IAM roles (verbatim passthrough)
operationId: adminListRoles
parameters:
- { name: owner, in: query, schema: { type: string } }
- { name: p, in: query, schema: { type: integer } }
- { name: pageSize, in: query, schema: { type: integer } }
responses:
'200': { description: Role rows (raw IAM shape), content: { application/json: { schema: { $ref: '#/components/schemas/RawList' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/applications:
get:
tags: [Identity]
summary: IAM applications (verbatim passthrough)
operationId: adminListApplications
parameters:
- { name: owner, in: query, schema: { type: string } }
- { name: p, in: query, schema: { type: integer } }
- { name: pageSize, in: query, schema: { type: integer } }
responses:
'200': { description: Application rows (raw IAM shape), content: { application/json: { schema: { $ref: '#/components/schemas/RawList' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/audit:
get:
tags: [Audit]
summary: Query the tamper-evident audit trail
operationId: adminListAudit
parameters:
- { name: org, in: query, schema: { type: string } }
- { name: sub, in: query, schema: { type: string } }
- { name: action, in: query, schema: { type: string } }
- { name: resource, in: query, schema: { type: string } }
- { name: result, in: query, schema: { type: string } }
- { name: since, in: query, schema: { type: string, format: date-time } }
- { name: until, in: query, schema: { type: string, format: date-time } }
- { name: pageSize, in: query, schema: { type: integer, default: 100 } }
- { name: p, in: query, schema: { type: integer, default: 1 } }
responses:
'200': { description: Audit rows, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { type: array, items: { $ref: '#/components/schemas/AuditRow' } }, data2: { type: integer }, integrity: { $ref: '#/components/schemas/AuditIntegrity' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/audit/verify:
get:
tags: [Audit]
summary: Verify audit-chain integrity
operationId: adminVerifyAudit
responses:
'200': { description: Integrity result, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/AuditIntegrity' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/usage:
get:
tags: [Overview]
summary: Fleet usage roll-up
operationId: adminUsage
parameters:
- { name: org, in: query, schema: { type: string }, description: Scope to one org (default fleet) }
responses:
'200': { description: Usage data, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/UsageData' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/products:
get:
tags: [Overview]
summary: Product / workload registry
operationId: adminProducts
responses:
'200': { description: Product rows, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { type: array, items: { $ref: '#/components/schemas/ProductRow' } }, data2: { type: integer } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/finance:
get:
tags: [Money]
summary: COGS / gross-margin / runway dashboard
operationId: adminFinance
responses:
'200': { description: Finance data, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/FinanceData' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/compute:
get:
tags: [Observability]
summary: Cross-tenant compute analytics
operationId: adminCompute
parameters:
- { name: kind, in: query, schema: { type: string }, description: 'bot | machine | cluster | nodepool | container | function | …' }
- { name: org, in: query, schema: { type: string } }
- { name: range, in: query, schema: { type: string, enum: [24h, 7d, 30d], default: 30d } }
responses:
'200': { description: Compute leaves, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { type: array, items: { $ref: '#/components/schemas/ComputeLeaf' } }, data2: { type: integer } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/o11y:
get:
tags: [Observability]
summary: Fleet-wide observability board
operationId: adminO11y
parameters:
- { name: range, in: query, schema: { type: string, enum: [24h, 7d, 30d], default: 30d } }
responses:
'200': { description: Fleet o11y, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/O11yGlobal' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/sync:
post:
tags: [Overview]
summary: Trigger a fleet re-read
operationId: adminSync
responses:
'200': { description: Started, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { type: object, properties: { started: { type: boolean } } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/customers:
get:
tags: [Customers]
summary: Fleet customer list
operationId: adminListCustomers
responses:
'200': { description: Customer rows, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { type: array, items: { $ref: '#/components/schemas/CustomerRow' } }, data2: { type: integer } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/customers/{org}:
get:
tags: [Customers]
summary: One customer's detail
operationId: adminCustomerDetail
parameters:
- { name: org, in: path, required: true, schema: { type: string } }
responses:
'200': { description: Customer detail, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/CustomerDetail' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Customer not found }
/v1/admin/customers/{org}/credit:
post:
tags: [Customers]
summary: Grant credit (commerce deposit)
operationId: adminGrantCredit
parameters:
- { name: org, in: path, required: true, schema: { type: string } }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreditRequest' }
responses:
'200': { description: Grant result, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/CreditResult' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Customer not found }
/v1/admin/customers/{org}/suspend:
post:
tags: [Customers]
summary: Suspend a customer (IAM isForbidden=true)
operationId: adminSuspendCustomer
parameters:
- { name: org, in: path, required: true, schema: { type: string } }
responses:
'200': { description: Suspend result, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/AccessResult' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Customer not found }
/v1/admin/customers/{org}/reactivate:
post:
tags: [Customers]
summary: Reactivate a customer (IAM isForbidden=false)
operationId: adminReactivateCustomer
parameters:
- { name: org, in: path, required: true, schema: { type: string } }
responses:
'200': { description: Reactivate result, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/AccessResult' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Customer not found }
/v1/admin/revenue:
get:
tags: [Money]
summary: Fleet revenue aggregate
operationId: adminRevenue
responses:
'200': { description: Revenue data, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/RevenueData' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/admin/analytics:
get:
tags: [Money]
summary: Native SaaS analytics (retention/growth/churn)
operationId: adminAnalytics
parameters:
- { name: range, in: query, schema: { type: string, enum: [7d, 30d, 90d, all], default: 30d } }
responses:
'200': { description: Analytics data, content: { application/json: { schema: { type: object, properties: { status: { type: string }, msg: { type: string }, data: { $ref: '#/components/schemas/AnalyticsData' } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
RawList:
type: object
description: Verbatim IAM list envelope — `data` carries the raw upstream rows.
properties:
status: { type: string }
msg: { type: string }
data: { type: array, items: { type: object, additionalProperties: true } }
data2: { type: integer }
AdminMe:
type: object
properties:
owner: { type: string }
name: { type: string }
email: { type: string }
displayName: { type: string }
isGlobalAdmin: { type: boolean }
SourceStatus:
type: object
properties:
name: { type: string }
ok: { type: boolean }
rows: { type: integer }
error: { type: string }
at: { type: string }
OverviewData:
type: object
properties:
orgs: { type: integer }
users: { type: integer }
products: { type: integer }
activeProducts: { type: integer }
drift: { type: integer }
spendCents30d: { type: integer, format: int64 }
tokens30d: { type: integer, format: int64 }
creditsCents: { type: integer, format: int64 }
lastSync: { type: string }
sources: { type: array, items: { $ref: '#/components/schemas/SourceStatus' } }
OrgRow:
type: object
properties:
org: { type: string }
display: { type: string }
users: { type: integer }
products: { type: integer }
spendCents: { type: integer, format: int64 }
creditsCents: { type: integer, format: int64 }
tokens: { type: integer, format: int64 }
created: { type: string }
OrgList:
type: object
properties:
status: { type: string }
msg: { type: string }
data: { type: array, items: { $ref: '#/components/schemas/OrgRow' } }
data2: { type: integer }
OperatorUser:
type: object
properties:
owner: { type: string }
name: { type: string }
email: { type: string }
displayName: { type: string }
isAdmin: { type: boolean }
isGlobalAdmin: { type: boolean }
tag: { type: string }
created: { type: string }
lastSignin: { type: string }
forbidden: { type: boolean }
UserList:
type: object
properties:
status: { type: string }
msg: { type: string }
data: { type: array, items: { $ref: '#/components/schemas/OperatorUser' } }
data2: { type: integer }
UsageTotals:
type: object
properties:
spendCents: { type: integer, format: int64 }
tokens: { type: integer, format: int64 }
requests: { type: integer, format: int64 }
UsagePoint:
type: object
properties:
date: { type: string }
spendCents: { type: integer, format: int64 }
tokens: { type: integer, format: int64 }
requests: { type: integer, format: int64 }
UsageByProduct:
type: object
properties:
product: { type: string }
spendCents: { type: integer, format: int64 }
tokens: { type: integer, format: int64 }
UsageData:
type: object
properties:
totals: { $ref: '#/components/schemas/UsageTotals' }
series: { type: array, items: { $ref: '#/components/schemas/UsagePoint' } }
byProduct: { type: array, items: { $ref: '#/components/schemas/UsageByProduct' } }
ProductRow:
type: object
properties:
name: { type: string }
kind: { type: string }
org: { type: string }
cluster: { type: string }
declaredTag: { type: string }
runningTag: { type: string }
health: { type: string }
drift: { type: boolean }
updated: { type: string }
CustomerRow:
type: object
properties:
org: { type: string }
display: { type: string }
ownerEmail: { type: string }
plan: { type: string }
status: { type: string, enum: [active, suspended] }
users: { type: integer }
balanceCents: { type: integer, format: int64 }
spendCents: { type: integer, format: int64 }
mrrCents: { type: integer, format: int64 }
created: { type: string }
lastActive: { type: string }
CustomerUser:
type: object
properties:
name: { type: string }
email: { type: string }
isAdmin: { type: boolean }
forbidden: { type: boolean }
hasApiKey: { type: boolean }
lastSignin: { type: string }
created: { type: string }
CustomerTxn:
type: object
properties:
id: { type: string }
type: { type: string, enum: [deposit, withdraw] }
cents: { type: integer, format: int64 }
currency: { type: string }
notes: { type: string }
time: { type: string }
CustomerDetail:
type: object
properties:
org: { type: string }
display: { type: string }
ownerEmail: { type: string }
plan: { type: string }
status: { type: string }
created: { type: string }
balanceCents: { type: integer, format: int64 }
spendCents: { type: integer, format: int64 }
mrrCents: { type: integer, format: int64 }
apiKeys: { type: integer }
users: { type: array, items: { $ref: '#/components/schemas/CustomerUser' } }
transactions: { type: array, items: { $ref: '#/components/schemas/CustomerTxn' } }
CreditRequest:
type: object
required: [amountCents]
properties:
amountCents: { type: integer, format: int64, description: Positive credit to add (capped at 10,000,000 cents per grant) }
currency: { type: string, default: usd }
reason: { type: string, description: Operator justification (recorded in the audit trail) }
CreditResult:
type: object
properties:
org: { type: string }
grantedCents: { type: integer, format: int64 }
currency: { type: string }
balanceCents: { type: integer, format: int64 }
transactionId: { type: string }
AccessResult:
type: object
properties:
org: { type: string }
suspended: { type: boolean }
affected: { type: array, items: { type: string } }
failed: { type: array, items: { type: string } }
VendorCost:
type: object
properties:
vendor: { type: string }
service: { type: string }
amountCents: { type: integer, format: int64 }
source: { type: string, enum: [actual, estimated] }
note: { type: string }
DoHistoryPoint:
type: object
properties:
date: { type: string }
amountCents: { type: integer, format: int64 }
type: { type: string }
description: { type: string }
DoCost:
type: object
properties:
configured: { type: boolean }
error: { type: string }
creditRemainingCents: { type: integer, format: int64 }
monthToDateSpendCents: { type: integer, format: int64 }
avgDailyBurnCents: { type: integer, format: int64 }
accountBalanceCents: { type: integer, format: int64 }
generatedAt: { type: string }
history: { type: array, items: { $ref: '#/components/schemas/DoHistoryPoint' } }
FinanceCost:
type: object
properties:
configured: { type: boolean }
error: { type: string }
period: { type: string }
totalCents: { type: integer, format: int64 }
vendors: { type: array, items: { $ref: '#/components/schemas/VendorCost' } }
digitalocean: { $ref: '#/components/schemas/DoCost' }
FinanceRevenue:
type: object
properties:
configured: { type: boolean }
totalRevenueCents: { type: integer, format: int64 }
mrrCents: { type: integer, format: int64 }
creditsConsumedCents: { type: integer, format: int64 }
FinanceDerived:
type: object
properties:
grossMarginCents: { type: integer, format: int64 }
grossMarginPct: { type: number, format: double }
runwayDays: { type: number, format: double, nullable: true }
profitable: { type: boolean }
FinanceData:
type: object
properties:
cost: { $ref: '#/components/schemas/FinanceCost' }
revenue: { $ref: '#/components/schemas/FinanceRevenue' }
derived: { $ref: '#/components/schemas/FinanceDerived' }
generatedAt: { type: string }
sources: { type: array, items: { $ref: '#/components/schemas/SourceStatus' } }
SeriesPoint:
type: object
properties:
t: { type: string }
value: { type: integer, format: int64 }
RevenueCustomer:
type: object
properties:
org: { type: string }
display: { type: string }
plan: { type: string }
balanceCents: { type: integer, format: int64 }
spendCents: { type: integer, format: int64 }
mrrCents: { type: integer, format: int64 }
RevenueData:
type: object
properties:
totalBalancesCents: { type: integer, format: int64 }
totalSpendCents: { type: integer, format: int64 }
mrrCents: { type: integer, format: int64 }
customers: { type: integer }
payingCustomers: { type: integer }
arpuCents: { type: integer, format: int64 }
perCustomer: { type: array, items: { $ref: '#/components/schemas/RevenueCustomer' } }
spendTrend: { type: array, items: { $ref: '#/components/schemas/SeriesPoint' } }
generatedAt: { type: string }
sources: { type: array, items: { $ref: '#/components/schemas/SourceStatus' } }
RetentionCohort:
type: object
properties:
cohort: { type: string }
size: { type: integer }
values: { type: array, items: { type: number, format: double } }
RetentionGrid:
type: object
properties:
interval: { type: string }
periods: { type: integer }
cohorts: { type: array, items: { $ref: '#/components/schemas/RetentionCohort' } }
AnalyticsSlice:
type: object
properties:
label: { type: string }
value: { type: integer, format: int64 }
hint: { type: string }
AnalyticsData:
type: object
properties:
range: { type: string }
interval: { type: string }
generatedAt: { type: string }
signups: { type: array, items: { $ref: '#/components/schemas/SeriesPoint' } }
cumulativeCustomers: { type: array, items: { $ref: '#/components/schemas/SeriesPoint' } }
totalCustomers: { type: integer }
newCustomers: { type: integer }
growthRatePct: { type: number, format: double }
activeCustomers: { type: array, items: { $ref: '#/components/schemas/SeriesPoint' } }
dau: { type: integer }
wau: { type: integer }
mau: { type: integer }
retention: { $ref: '#/components/schemas/RetentionGrid' }
churn: { type: array, items: { $ref: '#/components/schemas/SeriesPoint' } }
churnRatePct: { type: number, format: double }
mrrCents: { type: integer, format: int64 }
revenue: { type: array, items: { $ref: '#/components/schemas/SeriesPoint' } }
arpuCents: { type: integer, format: int64 }
ltvCents: { type: integer, format: int64, nullable: true }
nrrPct: { type: number, format: double, nullable: true }
usage: { type: array, items: { $ref: '#/components/schemas/SeriesPoint' } }
topCustomers: { type: array, items: { $ref: '#/components/schemas/AnalyticsSlice' } }
computed: { type: object, additionalProperties: { type: boolean } }
sources: { type: array, items: { $ref: '#/components/schemas/SourceStatus' } }
ComputeLeaf:
type: object
properties:
org: { type: string }
app: { type: string }
project: { type: string }
kind: { type: string }
machines: { type: integer, format: int64 }
active: { type: integer, format: int64 }
spendCents: { type: integer, format: int64 }
lastTs: { type: string }
O11yTotals:
type: object
properties:
requests: { type: integer, format: int64 }
tokens: { type: integer, format: int64 }
promptTokens: { type: integer, format: int64 }
completionTokens: { type: integer, format: int64 }
costCents: { type: integer, format: int64 }
errors: { type: integer, format: int64 }
orgs: { type: integer, format: int64 }
models: { type: integer, format: int64 }
traceCount: { type: integer, format: int64 }
latencyP50Ms: { type: number, format: double }
latencyP95Ms: { type: number, format: double }
latencyP99Ms: { type: number, format: double }
traceErrorRate: { type: number, format: double }
services: { type: integer, format: int64 }
logVolume: { type: integer, format: int64 }
O11ySeries:
type: object
properties:
ts: { type: string }
requests: { type: integer, format: int64 }
tokens: { type: integer, format: int64 }
costCents: { type: integer, format: int64 }
errors: { type: integer, format: int64 }
O11yLogPoint:
type: object
properties:
ts: { type: string }
count: { type: integer, format: int64 }
O11yOrgStat:
type: object
properties:
org: { type: string }
requests: { type: integer, format: int64 }
tokens: { type: integer, format: int64 }
costCents: { type: integer, format: int64 }
O11yModelStat:
type: object
properties:
model: { type: string }
requests: { type: integer, format: int64 }
tokens: { type: integer, format: int64 }
costCents: { type: integer, format: int64 }
O11ySvcStat:
type: object
properties:
service: { type: string }
requests: { type: integer, format: int64 }
errorRate: { type: number, format: double }
latencyP95Ms: { type: number, format: double }
O11yLLM:
type: object
properties:
generations: { type: integer, format: int64 }
costUsd: { type: number, format: double }
O11yGlobal:
type: object
properties:
range: { type: string }
start: { type: string }
end: { type: string }
totals: { $ref: '#/components/schemas/O11yTotals' }
series: { type: array, items: { $ref: '#/components/schemas/O11ySeries' } }
logSeries: { type: array, items: { $ref: '#/components/schemas/O11yLogPoint' } }
topOrgs: { type: array, items: { $ref: '#/components/schemas/O11yOrgStat' } }
topModels: { type: array, items: { $ref: '#/components/schemas/O11yModelStat' } }
topServices: { type: array, items: { $ref: '#/components/schemas/O11ySvcStat' } }
llm: { $ref: '#/components/schemas/O11yLLM' }
AuditRow:
type: object
properties:
seq: { type: integer, format: int64 }
time: { type: string }
org: { type: string }
sub: { type: string }
email: { type: string }
action: { type: string }
resource: { type: string }
resourceId: { type: string }
method: { type: string }
path: { type: string }
result: { type: string }
status: { type: integer }
reason: { type: string }
sourceIp: { type: string }
userAgent: { type: string }
requestId: { type: string }
isAdmin: { type: boolean }
authMethod: { type: string }
hash: { type: string }
prevHash: { type: string }
AuditIntegrity:
type: object
description: Hash-chain verification summary.
properties:
ok: { type: boolean }
count: { type: integer, format: int64 }
head: { type: string }
brokenAt: { type: integer, format: int64 }
+414
View File
@@ -0,0 +1,414 @@
openapi: 3.1.0
info:
title: Hanzo Automations API
description: |
Connectors + Automations engine for Hanzo Cloud — the `/v1/automations/*`
surface served by the cloud `automations` subsystem
(`hanzoai/cloud/clients/automations`, HIP-0106). An org authors a
trigger→action flow tree, versions it, and runs it durably on the shared
in-process tasks engine; connector actions are also exposed as MCP tools at
`/v1/automations/mcp`. Every flow runs in the OWNER org's namespace; the org
is always server-derived from the validated principal, never a request body.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Pieces
description: The connector/piece catalogue
- name: Flows
description: Automation flows and their versions
- name: Runs
description: Durable flow runs
- name: MCP
description: JSON-RPC tool surface over connector actions
security:
- bearerAuth: []
paths:
/v1/automations/pieces:
get:
tags: [Pieces]
summary: List the connector/piece catalogue
operationId: listPieces
responses:
'200': { description: Catalogue, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/automations/flows:
get:
tags: [Flows]
summary: List flows
operationId: listFlows
parameters:
- { name: limit, in: query, schema: { type: integer, default: 200, maximum: 1000 } }
responses:
'200': { description: Flows, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Flow' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Flows]
summary: Create a flow (with an initial draft version)
operationId: createFlow
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateFlowRequest' }
responses:
'201': { description: Flow created, content: { application/json: { schema: { $ref: '#/components/schemas/PopulatedFlow' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'422': { description: Invalid trigger }
/v1/automations/flows/{id}:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
get:
tags: [Flows]
summary: Get a flow and its latest version
operationId: getFlow
responses:
'200': { description: Flow, content: { application/json: { schema: { $ref: '#/components/schemas/PopulatedFlow' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Flow not found }
patch:
tags: [Flows]
summary: Update flow metadata
operationId: updateFlow
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/PatchFlowRequest' }
responses:
'200': { description: Updated flow, content: { application/json: { schema: { $ref: '#/components/schemas/Flow' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Flow not found }
'422': { description: publishedVersionId must name a version of this flow }
delete:
tags: [Flows]
summary: Delete a flow (with its versions and runs)
operationId: deleteFlow
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Flow not found }
/v1/automations/flows/{id}/versions:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
get:
tags: [Flows]
summary: List a flow's versions
operationId: listVersions
parameters:
- { name: limit, in: query, schema: { type: integer, default: 200, maximum: 1000 } }
responses:
'200': { description: Versions, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/FlowVersion' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Flows]
summary: Create a draft version
operationId: createVersion
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateVersionRequest' }
responses:
'201': { description: Version created, content: { application/json: { schema: { $ref: '#/components/schemas/FlowVersion' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Flow not found }
'422': { description: Invalid trigger }
/v1/automations/flows/{id}/operations:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
post:
tags: [Flows]
summary: Apply a flow operation to the latest version
operationId: applyOperation
description: A CHANGE_STATUS op enables/disables the flow (returns the Flow); every other op mutates the latest version's step tree (returns the FlowVersion).
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/FlowOperation' }
responses:
'200': { description: Updated version or flow, content: { application/json: { schema: { $ref: '#/components/schemas/FlowVersion' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Flow, version, or step not found }
'422': { description: Unsupported or invalid operation }
/v1/automations/flows/{id}/run:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
post:
tags: [Runs]
summary: Start a durable run of a flow's runnable version
operationId: runFlow
responses:
'201': { description: Run started, content: { application/json: { schema: { $ref: '#/components/schemas/FlowRun' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Flow not found or has no runnable version }
'429': { description: Too many concurrent automation requests for this org }
'503': { description: Automation engine not ready }
/v1/automations/flows/{id}/enable:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
post:
tags: [Flows]
summary: Enable a flow (POLLING triggers create a schedule)
operationId: enableFlow
responses:
'200': { description: Flow enabled, content: { application/json: { schema: { $ref: '#/components/schemas/Flow' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Flow not found }
'503': { description: Engine required for a POLLING enable and not ready }
/v1/automations/flows/{id}/disable:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
post:
tags: [Flows]
summary: Disable a flow (removes any POLLING schedule)
operationId: disableFlow
responses:
'200': { description: Flow disabled, content: { application/json: { schema: { $ref: '#/components/schemas/Flow' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Flow not found }
/v1/automations/runs:
get:
tags: [Runs]
summary: List runs
operationId: listRuns
parameters:
- { name: flowId, in: query, schema: { type: string } }
- { name: limit, in: query, schema: { type: integer, default: 200, maximum: 1000 } }
responses:
'200': { description: Runs, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/FlowRun' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/automations/runs/{id}:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
get:
tags: [Runs]
summary: Get a run (non-terminal status is refreshed from the engine)
operationId: getRun
responses:
'200': { description: Run, content: { application/json: { schema: { $ref: '#/components/schemas/FlowRun' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Run not found }
/v1/automations/runs/{id}/resume:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
post:
tags: [Runs]
summary: Resume a paused run
operationId: resumeRun
description: The JSON body (max 64 KiB) is delivered verbatim as the waitpoint's output.
requestBody:
required: false
content:
application/json:
schema: { description: Arbitrary JSON payload delivered to the paused waitpoint }
responses:
'200': { description: Resumed, content: { application/json: { schema: { type: object, properties: { resumed: { type: boolean } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Run not found }
'413': { description: Resume payload exceeds the 64 KiB limit }
'503': { description: Automation engine not ready }
/v1/automations/mcp:
post:
tags: [MCP]
summary: JSON-RPC 2.0 tool surface over connector actions
operationId: mcp
description: |
HIP-0300 JSON-RPC 2.0. Methods: `initialize`, `ping`, `tools/list`,
`tools/call`. Every connector action is a tool named `<connector>_<action>`.
JSON-RPC errors are returned with HTTP 200 and an `error` member.
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/McpRequest' }
responses:
'200': { description: JSON-RPC response, content: { application/json: { schema: { $ref: '#/components/schemas/McpResponse' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
Catalog:
type: object
properties:
pieceCount: { type: integer }
pieces: { type: array, items: { $ref: '#/components/schemas/PieceMetadata' } }
PieceMetadata:
type: object
properties:
name: { type: string }
displayName: { type: string }
description: { type: string }
logoUrl: { type: string }
version: { type: string }
categories: { type: array, items: { type: string } }
auth: { $ref: '#/components/schemas/PieceAuth' }
actions: { type: array, items: { $ref: '#/components/schemas/PieceAction' } }
triggers: { type: array, items: { $ref: '#/components/schemas/PieceTrigger' } }
PieceAuth:
type: object
properties:
type: { type: string, description: 'none | oauth2 | bot_token' }
required: { type: boolean }
PieceAction:
type: object
properties:
name: { type: string }
displayName: { type: string }
description: { type: string }
props: { type: array, items: { $ref: '#/components/schemas/PropSpec' } }
PieceTrigger:
type: object
properties:
name: { type: string }
displayName: { type: string }
description: { type: string }
strategy: { type: string }
props: { type: array, items: { $ref: '#/components/schemas/PropSpec' } }
PropSpec:
type: object
properties:
name: { type: string }
displayName: { type: string }
type: { type: string, enum: [string, number, boolean, object, array] }
required: { type: boolean }
description: { type: string }
Flow:
type: object
properties:
id: { type: string }
projectId: { type: string, description: The owner org (server-derived) }
externalId: { type: string }
folderId: { type: string }
status: { type: string, enum: [ENABLED, DISABLED] }
publishedVersionId: { type: string }
metadata: { description: Opaque builder metadata }
created: { type: integer, format: int64, description: Unix ms }
updated: { type: integer, format: int64, description: Unix ms }
PopulatedFlow:
allOf:
- { $ref: '#/components/schemas/Flow' }
- type: object
properties:
version: { $ref: '#/components/schemas/FlowVersion' }
FlowVersion:
type: object
properties:
id: { type: string }
flowId: { type: string }
displayName: { type: string }
trigger: { $ref: '#/components/schemas/FlowTrigger' }
valid: { type: boolean }
state: { type: string, enum: [DRAFT, LOCKED] }
schemaVersion: { type: string }
created: { type: integer, format: int64 }
updated: { type: integer, format: int64 }
StepSettings:
type: object
properties:
pieceName: { type: string }
pieceVersion: { type: string }
actionName: { type: string }
triggerName: { type: string }
input: { type: object, additionalProperties: true }
FlowTrigger:
type: object
properties:
name: { type: string }
type: { type: string, enum: [PIECE_TRIGGER, EMPTY] }
displayName: { type: string }
valid: { type: boolean }
strategy: { type: string, enum: [POLLING, WEBHOOK, APP_WEBHOOK, MANUAL] }
settings: { $ref: '#/components/schemas/StepSettings' }
nextAction: { $ref: '#/components/schemas/FlowAction' }
FlowAction:
type: object
properties:
name: { type: string }
type: { type: string, enum: [PIECE, CODE, ROUTER, LOOP_ON_ITEMS, BRANCH] }
displayName: { type: string }
valid: { type: boolean }
skip: { type: boolean }
settings: { $ref: '#/components/schemas/StepSettings' }
nextAction: { $ref: '#/components/schemas/FlowAction' }
CreateFlowRequest:
type: object
properties:
displayName: { type: string }
externalId: { type: string }
folderId: { type: string }
trigger: { $ref: '#/components/schemas/FlowTrigger' }
PatchFlowRequest:
type: object
properties:
folderId: { type: string }
externalId: { type: string }
publishedVersionId: { type: string }
metadata: { description: Opaque builder metadata }
CreateVersionRequest:
type: object
properties:
displayName: { type: string }
trigger: { $ref: '#/components/schemas/FlowTrigger' }
FlowOperation:
type: object
required: [type, request]
properties:
type: { type: string, enum: [ADD_ACTION, UPDATE_ACTION, DELETE_ACTION, MOVE_ACTION, UPDATE_TRIGGER, CHANGE_NAME, CHANGE_STATUS] }
request: { description: Per-type operation payload }
FlowRun:
type: object
properties:
id: { type: string }
flowId: { type: string }
flowVersionId: { type: string }
status: { type: string, enum: [RUNNING, SUCCEEDED, FAILED, PAUSED, QUEUED, CANCELED, TIMEOUT] }
startTime: { type: integer, format: int64 }
finishTime: { type: integer, format: int64 }
created: { type: integer, format: int64 }
updated: { type: integer, format: int64 }
McpRequest:
type: object
required: [jsonrpc, method]
properties:
jsonrpc: { type: string, example: '2.0' }
id: { description: Request id (string, number, or null) }
method: { type: string, enum: [initialize, ping, tools/list, tools/call] }
params: { description: Method params }
McpResponse:
type: object
properties:
jsonrpc: { type: string, example: '2.0' }
id: { description: Echoed request id }
result: { description: Method result (present on success) }
error: { type: object, properties: { code: { type: integer }, message: { type: string } } }
+161
View File
@@ -0,0 +1,161 @@
openapi: 3.1.0
info:
title: Hanzo Billing API
description: |
The customer-facing, org-scoped billing surface (`/v1/billing/*`) served by
the cloud `billing` subsystem (`hanzoai/cloud/clients/billing`). It is the
per-org READ twin of the admin god-view: a thin service-to-service proxy to
commerce, scoped to the caller's OWN org (the validated IAM owner claim). The
billing subject is pinned server-side — a client can never widen scope to
another tenant's wallet. Bodies + status codes are forwarded from commerce
verbatim (e.g. `402` on `gpu-charge` when a card is required or prepaid is
insufficient).
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Billing
description: Per-org usage, balance, GPU launch gate, and saved cards
security:
- bearerAuth: []
paths:
/v1/billing/usage:
get:
tags: [Billing]
summary: Per-request usage ledger
operationId: billingUsage
description: The raw per-request billing ledger (one row per billed call) for the caller's own org.
parameters:
- { name: start, in: query, schema: { type: string }, description: Optional server-side window start }
- { name: end, in: query, schema: { type: string }, description: Optional server-side window end }
responses:
'200': { description: Usage ledger, content: { application/json: { schema: { $ref: '#/components/schemas/UsageLedger' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/billing/balance:
get:
tags: [Billing]
summary: Prepaid credit balance
operationId: billingBalance
description: The org's prepaid credit balance in USD cents — the same wallet the gateway debits.
parameters:
- { name: currency, in: query, schema: { type: string }, description: Optional currency filter (default usd) }
responses:
'200': { description: Balance, content: { application/json: { schema: { $ref: '#/components/schemas/Balance' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/billing/gpu-eligibility:
get:
tags: [Billing]
summary: GPU launch eligibility gate
operationId: billingGpuEligibility
description: Read-only launch gate — reports prepaid availability and card-on-file so the launch UI can show the exact remedy.
parameters:
- { name: amountCents, in: query, schema: { type: integer, format: int64 }, description: Immediate charge to test against }
- { name: minPrepaidCents, in: query, schema: { type: integer, format: int64 }, description: 24h-minimum prepaid floor }
- { name: currency, in: query, schema: { type: string } }
responses:
'200': { description: Eligibility, content: { application/json: { schema: { $ref: '#/components/schemas/GpuEligibility' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/billing/gpu-charge:
post:
tags: [Billing]
summary: Prepay-only GPU charge
operationId: billingGpuCharge
description: |
The prepay-only, card-required GPU debit against the caller's own org
wallet. Commerce enforces both gates server-side; the subject is pinned
server-side, so only the charge params ride the body. Commerce's status is
forwarded verbatim.
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/GpuChargeRequest' }
responses:
'201': { description: Charge accepted, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'402': { description: 'Payment required — card_required or insufficient_prepaid', content: { application/json: { schema: { type: object, properties: { reason: { type: string, enum: [card_required, insufficient_prepaid] } }, additionalProperties: true } } } }
/v1/billing/payment-methods:
get:
tags: [Billing]
summary: Saved payment methods (masked)
operationId: billingPaymentMethods
description: The org's saved cards as commerce's masked descriptor (brand + last4 + expiry) — never a PAN/CVV/token.
responses:
'200': { description: Saved cards, content: { application/json: { schema: { $ref: '#/components/schemas/PaymentMethods' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
UsageRecord:
type: object
properties:
transactionId: { type: string }
amount: { type: integer, format: int64, description: Billed amount in USD cents }
metadata: { type: object, additionalProperties: true }
createdAt: { type: string }
additionalProperties: true
UsageLedger:
type: object
properties:
usage: { type: array, items: { $ref: '#/components/schemas/UsageRecord' } }
additionalProperties: true
Balance:
type: object
description: Prepaid wallet balance in USD cents.
properties:
balance: { type: integer, format: int64 }
holds: { type: integer, format: int64 }
available: { type: integer, format: int64 }
additionalProperties: true
GpuEligibility:
type: object
properties:
eligible: { type: boolean }
reason: { type: string }
prepaidAvailable: { type: integer, format: int64 }
cardOnFile: { type: boolean }
requiredCents: { type: integer, format: int64 }
additionalProperties: true
GpuChargeRequest:
type: object
properties:
amountCents: { type: integer, format: int64 }
currency: { type: string }
requestId: { type: string }
tag: { type: string }
PaymentMethod:
type: object
description: Masked card descriptor — brand + last4 + expiry only.
properties:
brand: { type: string }
last4: { type: string }
expMonth: { type: integer }
expYear: { type: integer }
additionalProperties: true
PaymentMethods:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/PaymentMethod' } }
additionalProperties: true
+298
View File
@@ -0,0 +1,298 @@
openapi: 3.1.0
info:
title: Hanzo CRM API
description: |
Per-org CRM for Hanzo Cloud — the `/v1/crm/*` surface served by the cloud
`crm` subsystem (`hanzoai/cloud/clients/crm`). A native-Go port of the
Twenty CRM core model (company / person / opportunity) on Base/SQLite, NOT a
proxy to a NestJS backend. Tenant isolation is enforced server-side on every
request from the validated bearer owner claim; one tenant can never read or
mutate another's records.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Companies
description: Account records (Twenty `company` standard object)
- name: Contacts
description: Person records (Twenty `person` standard object)
- name: Opportunities
description: Deal records (Twenty `opportunity` standard object)
- name: Summary
description: Per-org row counts
security:
- bearerAuth: []
paths:
/v1/crm/summary:
get:
tags: [Summary]
summary: Per-org row counts (companies / contacts / opportunities)
operationId: crmSummary
responses:
'200': { description: Counts, content: { application/json: { schema: { $ref: '#/components/schemas/Summary' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/crm/companies:
get:
tags: [Companies]
summary: List companies
operationId: listCompanies
parameters:
- { name: limit, in: query, schema: { type: integer, default: 200, maximum: 1000 } }
responses:
'200': { description: Companies, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Company' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Companies]
summary: Create a company
operationId: createCompany
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CompanyInput' }
responses:
'201': { description: Company created, content: { application/json: { schema: { $ref: '#/components/schemas/Company' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/crm/companies/{id}:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
get:
tags: [Companies]
summary: Company detail
operationId: getCompany
responses:
'200': { description: Company, content: { application/json: { schema: { $ref: '#/components/schemas/Company' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Company not found }
put:
tags: [Companies]
summary: Update a company
operationId: updateCompany
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CompanyInput' }
responses:
'200': { description: Company updated, content: { application/json: { schema: { $ref: '#/components/schemas/Company' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Company not found }
delete:
tags: [Companies]
summary: Delete a company (clears dangling contact/opportunity refs)
operationId: deleteCompany
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Company not found }
/v1/crm/contacts:
get:
tags: [Contacts]
summary: List contacts
operationId: listContacts
parameters:
- { name: companyId, in: query, schema: { type: string }, description: Filter to one company }
- { name: limit, in: query, schema: { type: integer, default: 200, maximum: 1000 } }
responses:
'200': { description: Contacts, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Contact' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Contacts]
summary: Create a contact
operationId: createContact
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ContactInput' }
responses:
'201': { description: Contact created, content: { application/json: { schema: { $ref: '#/components/schemas/Contact' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'422': { description: Referenced company not found in org }
/v1/crm/contacts/{id}:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
get:
tags: [Contacts]
summary: Contact detail
operationId: getContact
responses:
'200': { description: Contact, content: { application/json: { schema: { $ref: '#/components/schemas/Contact' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Contact not found }
put:
tags: [Contacts]
summary: Update a contact
operationId: updateContact
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ContactInput' }
responses:
'200': { description: Contact updated, content: { application/json: { schema: { $ref: '#/components/schemas/Contact' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Contact not found }
delete:
tags: [Contacts]
summary: Delete a contact (clears opportunity point-of-contact refs)
operationId: deleteContact
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Contact not found }
/v1/crm/opportunities:
get:
tags: [Opportunities]
summary: List opportunities
operationId: listOpportunities
parameters:
- { name: stage, in: query, schema: { $ref: '#/components/schemas/Stage' }, description: Filter to one pipeline stage }
- { name: limit, in: query, schema: { type: integer, default: 200, maximum: 1000 } }
responses:
'200': { description: Opportunities, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Opportunity' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Opportunities]
summary: Create an opportunity
operationId: createOpportunity
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/OpportunityInput' }
responses:
'201': { description: Opportunity created, content: { application/json: { schema: { $ref: '#/components/schemas/Opportunity' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'422': { description: Referenced company/contact not found in org }
/v1/crm/opportunities/{id}:
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
get:
tags: [Opportunities]
summary: Opportunity detail
operationId: getOpportunity
responses:
'200': { description: Opportunity, content: { application/json: { schema: { $ref: '#/components/schemas/Opportunity' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Opportunity not found }
put:
tags: [Opportunities]
summary: Update an opportunity
operationId: updateOpportunity
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/OpportunityInput' }
responses:
'200': { description: Opportunity updated, content: { application/json: { schema: { $ref: '#/components/schemas/Opportunity' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Opportunity not found }
delete:
tags: [Opportunities]
summary: Delete an opportunity
operationId: deleteOpportunity
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Opportunity not found }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
Stage:
type: string
description: Opportunity pipeline stage (default NEW).
enum: [NEW, SCREENING, MEETING, PROPOSAL, CUSTOMER]
Summary:
type: object
properties:
companies: { type: integer }
contacts: { type: integer }
opportunities: { type: integer }
CompanyInput:
type: object
required: [name]
properties:
name: { type: string }
domainName: { type: string }
employees: { type: integer, format: int64 }
city: { type: string }
country: { type: string }
arr: { type: integer, format: int64, description: Annual recurring revenue in minor units (cents) of currency }
currency: { type: string, default: USD }
idealCustomerProfile: { type: boolean }
linkedinLink: { type: string }
xLink: { type: string }
Company:
allOf:
- $ref: '#/components/schemas/CompanyInput'
- type: object
properties:
id: { type: string }
createdAt: { type: integer, format: int64, description: Unix seconds }
updatedAt: { type: integer, format: int64, description: Unix seconds }
ContactInput:
type: object
description: One of firstName, lastName, or email is required.
properties:
firstName: { type: string }
lastName: { type: string }
email: { type: string }
phone: { type: string }
jobTitle: { type: string }
city: { type: string }
companyId: { type: string, description: Optional in-org relation to a Company }
linkedinLink: { type: string }
xLink: { type: string }
Contact:
allOf:
- $ref: '#/components/schemas/ContactInput'
- type: object
properties:
id: { type: string }
createdAt: { type: integer, format: int64, description: Unix seconds }
updatedAt: { type: integer, format: int64, description: Unix seconds }
OpportunityInput:
type: object
required: [name]
properties:
name: { type: string }
amount: { type: integer, format: int64, description: Deal size in minor units (cents) of currency }
currency: { type: string, default: USD }
stage: { $ref: '#/components/schemas/Stage' }
closeDate: { type: integer, format: int64, description: Unix seconds (0 = unset) }
companyId: { type: string, description: Optional in-org relation to a Company }
pointOfContactId: { type: string, description: Optional in-org relation to a Contact }
Opportunity:
allOf:
- $ref: '#/components/schemas/OpportunityInput'
- type: object
properties:
id: { type: string }
createdAt: { type: integer, format: int64, description: Unix seconds }
updatedAt: { type: integer, format: int64, description: Unix seconds }
+184
View File
@@ -0,0 +1,184 @@
openapi: 3.1.0
info:
title: Hanzo DigitalOcean Infra API
description: |
DigitalOcean-native infra surface served by `hanzoai/cloud/clients/do`:
VPCs (`/v1/vpcs`) and Load Balancers (`/v1/load-balancers`), a thin
org-scoped facade over the `digitalocean/godo` SDK. DigitalOcean is a single
account, so the org boundary is enforced by this subsystem: a resource's
physical DO name is derived from the caller's validated org, LIST filters to
the caller's namespace, and a cross-tenant id is reported 404 (an
existence-oracle guard, never 403).
Fail-closed: absent `DO_API_TOKEN` the surface mounts but every op returns
503; it never fabricates a resource.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: VPCs
description: Virtual Private Cloud networks
- name: Load Balancers
description: Regional/global load balancers
security:
- bearerAuth: []
paths:
/v1/vpcs:
get:
tags: [VPCs]
summary: List the caller's VPCs
operationId: listVpcs
responses:
'200': { description: VPCs, content: { application/json: { schema: { type: object, properties: { vpcs: { type: array, items: { $ref: '#/components/schemas/Vpc' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'502': { description: DigitalOcean upstream error }
'503': { description: DigitalOcean not configured }
post:
tags: [VPCs]
summary: Create a VPC
operationId: createVpc
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/VpcCreate' }
responses:
'201': { description: VPC created, content: { application/json: { schema: { $ref: '#/components/schemas/Vpc' } } } }
'400': { description: Invalid name or missing region }
'401': { $ref: '#/components/responses/Unauthorized' }
'409': { description: A VPC with that name already exists }
'502': { description: DigitalOcean upstream error }
'503': { description: DigitalOcean not configured }
/v1/vpcs/{id}:
parameters:
- { name: id, in: path, required: true, schema: { type: string }, description: DO VPC id }
get:
tags: [VPCs]
summary: Get one VPC (owned)
operationId: getVpc
responses:
'200': { description: VPC, content: { application/json: { schema: { $ref: '#/components/schemas/Vpc' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: VPC not found }
'503': { description: DigitalOcean not configured }
delete:
tags: [VPCs]
summary: Delete one VPC (owned)
operationId: deleteVpc
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: VPC not found }
'503': { description: DigitalOcean not configured }
/v1/load-balancers:
get:
tags: [Load Balancers]
summary: List the caller's load balancers
operationId: listLoadBalancers
responses:
'200': { description: Load balancers, content: { application/json: { schema: { type: object, properties: { loadBalancers: { type: array, items: { $ref: '#/components/schemas/LoadBalancer' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'502': { description: DigitalOcean upstream error }
'503': { description: DigitalOcean not configured }
post:
tags: [Load Balancers]
summary: Create a load balancer
operationId: createLoadBalancer
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/LoadBalancerCreate' }
responses:
'201': { description: Load balancer created, content: { application/json: { schema: { $ref: '#/components/schemas/LoadBalancer' } } } }
'400': { description: Invalid name or missing region }
'401': { $ref: '#/components/responses/Unauthorized' }
'409': { description: A load balancer with that name already exists }
'502': { description: DigitalOcean upstream error }
'503': { description: DigitalOcean not configured }
/v1/load-balancers/{id}:
parameters:
- { name: id, in: path, required: true, schema: { type: string }, description: DO load balancer id }
get:
tags: [Load Balancers]
summary: Get one load balancer (owned)
operationId: getLoadBalancer
responses:
'200': { description: Load balancer, content: { application/json: { schema: { $ref: '#/components/schemas/LoadBalancer' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Load balancer not found }
'503': { description: DigitalOcean not configured }
delete:
tags: [Load Balancers]
summary: Delete one load balancer (owned)
operationId: deleteLoadBalancer
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Load balancer not found }
'503': { description: DigitalOcean not configured }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
VpcCreate:
type: object
required: [name, region]
properties:
name: { type: string, description: 'Friendly name; must match ^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$' }
region: { type: string, description: DO region slug }
ip_range: { type: string, description: 'CIDR (empty → DO auto-assigns)' }
Vpc:
type: object
properties:
id: { type: string }
name: { type: string }
cidr: { type: string }
region: { type: string }
subnets: { type: array, items: { type: string } }
status: { type: string, example: active }
ForwardingRule:
type: object
properties:
entry_protocol: { type: string }
entry_port: { type: integer }
target_protocol: { type: string }
target_port: { type: integer }
LoadBalancerCreate:
type: object
required: [name, region]
properties:
name: { type: string, description: 'Friendly name; must match ^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$' }
region: { type: string, description: DO region slug }
type: { type: string, description: 'empty → DO default (REGIONAL)' }
size: { type: string, description: DO size slug }
forwarding_rules: { type: array, items: { $ref: '#/components/schemas/ForwardingRule' }, description: 'empty → default http 80→80' }
LoadBalancer:
type: object
properties:
id: { type: string }
name: { type: string }
type: { type: string }
targets: { type: integer, description: Count of attached backend droplets }
ip: { type: string }
status: { type: string }
+146
View File
@@ -0,0 +1,146 @@
openapi: 3.1.0
info:
title: Hanzo Exec API
description: |
Code Interpreter ("Run Code") surface on the unified cloud-api /v1 plane
(`hanzoai/cloud/clients/exec`), per HIP-0106. A thin reverse proxy that
forwards each request UNCHANGED to a sandboxed executor upstream — no code
runs in the cloud binary. The contract is the LibreChat code-interpreter
contract: POST `{lang, code, files?}` to `/v1/exec`, with sibling paths for
programmatic execution, uploads, downloads and per-session file listing.
AUTH: this surface is called server-side by the chat node process with a
shared service key on the `X-API-Key` header (NOT a user JWT — the gateway
bypasses these paths). An unset key fails closed (503); a wrong key is 401.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Exec
description: Sandboxed code execution
- name: Files
description: Session file upload / download / listing
security:
- serviceKey: []
paths:
/v1/exec:
post:
tags: [Exec]
summary: Execute code in a sandboxed session
operationId: execCode
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ExecRequest' }
responses:
'200': { description: Execution result, content: { application/json: { schema: { $ref: '#/components/schemas/ExecResult' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'503': { $ref: '#/components/responses/NotConfigured' }
/v1/exec/programmatic:
post:
tags: [Exec]
summary: Programmatic code execution (sibling of /v1/exec, same executor contract)
operationId: execProgrammatic
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ExecRequest' }
responses:
'200': { description: Execution result, content: { application/json: { schema: { $ref: '#/components/schemas/ExecResult' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'503': { $ref: '#/components/responses/NotConfigured' }
/v1/upload:
post:
tags: [Files]
summary: Upload a file into a session
operationId: uploadFile
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file: { type: string, format: binary }
session_id: { type: string }
responses:
'200': { description: Uploaded, content: { application/json: { schema: { type: object, properties: { session_id: { type: string }, files: { type: array, items: { $ref: '#/components/schemas/ExecFile' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'503': { $ref: '#/components/responses/NotConfigured' }
/v1/download/{id}:
get:
tags: [Files]
summary: Download a produced file by id
operationId: downloadFile
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
responses:
'200': { description: File contents, content: { application/octet-stream: { schema: { type: string, format: binary } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'503': { $ref: '#/components/responses/NotConfigured' }
/v1/files/{session_id}:
get:
tags: [Files]
summary: List the files produced in a session
operationId: listSessionFiles
parameters:
- { name: session_id, in: path, required: true, schema: { type: string } }
responses:
'200': { description: Files, content: { application/json: { schema: { type: object, properties: { files: { type: array, items: { $ref: '#/components/schemas/ExecFile' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'503': { $ref: '#/components/responses/NotConfigured' }
components:
securitySchemes:
serviceKey:
type: apiKey
in: header
name: X-API-Key
description: Shared service key (KMS-sourced) presented server-side by the chat node process. Not a user JWT.
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: integer }, error: { type: string } } }
NotConfigured:
description: Code execution not configured on this deployment (service key unset — fail-closed)
content:
application/json:
schema: { type: object, properties: { status: { type: integer }, error: { type: string } } }
schemas:
ExecRequest:
type: object
required: [lang, code]
properties:
lang: { type: string, description: Language / runtime (e.g. python, node) }
code: { type: string }
files: { type: array, description: Optional input files, items: { $ref: '#/components/schemas/ExecFile' } }
ExecFile:
type: object
properties:
name: { type: string }
ExecResult:
type: object
properties:
session_id: { type: string }
stdout: { type: string }
stderr: { type: string }
files: { type: array, items: { $ref: '#/components/schemas/ExecFile' } }
+392
View File
@@ -0,0 +1,392 @@
openapi: 3.1.0
info:
title: Hanzo Framework API
description: |
Metadata-driven DocType engine — the `/v1/framework/*` surface served by the
cloud `framework` subsystem (`hanzoai/cloud/clients/framework`). A native-Go,
Base/SQLite successor to Frappe's DocType/metadata core: define a DocType
(fields, permissions, lifecycle), then CRUD schemaless documents against it
through ONE generic surface. CMS content-types, ERP DocTypes, Helpdesk, and
the KB knowledge model are all "just DocTypes" installed as modules.
- Register a DocType and its fields, roles, and per-role permissions.
- Create/read/update/delete/submit/cancel documents of any DocType.
- Assign and revoke per-org roles; install app-lane module fixtures.
Everything is org-scoped: the org is the validated tenant, so one org's
schema and data are invisible to another.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: DocTypes
description: DocType registry (metadata definitions)
- name: Documents
description: Generic metadata-driven document CRUD + lifecycle
- name: Roles
description: Per-org role assignments
- name: Modules
description: App-lane module fixtures (install a content model into an org)
security:
- bearerAuth: []
paths:
/v1/framework/summary:
get:
tags: [DocTypes]
summary: Org summary (doctype + document counts)
operationId: frameworkSummary
responses:
'200':
description: Summary
content:
application/json:
schema: { type: object, properties: { doctypes: { type: integer }, documents: { type: integer } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/framework/doctypes:
get:
tags: [DocTypes]
summary: List DocType definitions
operationId: listDocTypes
responses:
'200': { description: DocType list, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/DocType' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [DocTypes]
summary: Define a DocType
operationId: createDocType
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/DocType' }
responses:
'201': { description: DocType created, content: { application/json: { schema: { $ref: '#/components/schemas/DocType' } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/framework/doctypes/{name}:
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
get:
tags: [DocTypes]
summary: Get a DocType definition
operationId: getDocType
responses:
'200': { description: DocType, content: { application/json: { schema: { $ref: '#/components/schemas/DocType' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
put:
tags: [DocTypes]
summary: Replace a DocType definition
operationId: replaceDocType
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/DocType' }
responses:
'200': { description: DocType replaced, content: { application/json: { schema: { $ref: '#/components/schemas/DocType' } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
tags: [DocTypes]
summary: Delete a DocType (and its documents)
operationId: deleteDocType
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/framework/roles:
get:
tags: [Roles]
summary: List per-org role assignments
operationId: listRoles
responses:
'200': { description: Role assignments, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Role' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Roles]
summary: Assign a role to a user
operationId: assignRole
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/Role' }
responses:
'201': { description: Role assigned, content: { application/json: { schema: { $ref: '#/components/schemas/Role' } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/framework/roles/{user}/{role}:
parameters:
- { name: user, in: path, required: true, schema: { type: string } }
- { name: role, in: path, required: true, schema: { type: string } }
delete:
tags: [Roles]
summary: Revoke a role from a user
operationId: revokeRole
responses:
'204': { description: Revoked }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/framework/modules:
get:
tags: [Modules]
summary: List registered app-lane modules
operationId: listModules
responses:
'200':
description: Modules
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: object
properties:
module: { type: string }
doctypes: { type: array, items: { type: string } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/framework/modules/{module}:
parameters:
- { name: module, in: path, required: true, schema: { type: string } }
get:
tags: [Modules]
summary: Inspect a module and its install state in this org
operationId: getModule
responses:
'200':
description: Module detail
content:
application/json:
schema:
type: object
properties:
module: { type: string }
doctypes: { type: array, items: { type: string } }
installed: { type: array, items: { type: string } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/framework/modules/{module}/install:
parameters:
- { name: module, in: path, required: true, schema: { type: string } }
post:
tags: [Modules]
summary: Install a module's DocType fixtures into this org (idempotent)
operationId: installModule
responses:
'200':
description: Install result
content:
application/json:
schema:
type: object
properties:
module: { type: string }
created: { type: array, items: { type: string } }
existing: { type: array, items: { type: string } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/framework/{doctype}:
parameters:
- { name: doctype, in: path, required: true, schema: { type: string } }
get:
tags: [Documents]
summary: List documents of a DocType
operationId: listDocuments
parameters:
- { name: filters, in: query, schema: { type: string }, description: 'JSON object of field→value filters (declared fields, name, or docstatus)' }
- { name: fields, in: query, schema: { type: string }, description: 'Comma list or JSON array of field names to project' }
- { name: order_by, in: query, schema: { type: string }, description: 'field [asc|desc]' }
- { name: limit, in: query, schema: { type: integer, default: 100 } }
responses:
'200': { description: Documents, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Document' } } } } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
post:
tags: [Documents]
summary: Create a document
operationId: createDocument
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/Document' }
responses:
'201': { description: Document created, content: { application/json: { schema: { $ref: '#/components/schemas/Document' } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
/v1/framework/{doctype}/{name}:
parameters:
- { name: doctype, in: path, required: true, schema: { type: string } }
- { name: name, in: path, required: true, schema: { type: string } }
get:
tags: [Documents]
summary: Get a document
operationId: getDocument
responses:
'200': { description: Document, content: { application/json: { schema: { $ref: '#/components/schemas/Document' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
put:
tags: [Documents]
summary: Update a draft document
operationId: updateDocument
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/Document' }
responses:
'200': { description: Document updated, content: { application/json: { schema: { $ref: '#/components/schemas/Document' } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'409': { description: Document is not a draft }
delete:
tags: [Documents]
summary: Delete a document
operationId: deleteDocument
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'409': { description: Submitted document must be cancelled first }
/v1/framework/{doctype}/{name}/submit:
parameters:
- { name: doctype, in: path, required: true, schema: { type: string } }
- { name: name, in: path, required: true, schema: { type: string } }
post:
tags: [Documents]
summary: Submit a document (docstatus 0→1)
operationId: submitDocument
responses:
'200': { description: Document submitted, content: { application/json: { schema: { $ref: '#/components/schemas/Document' } } } }
'400': { description: DocType is not submittable }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'409': { description: Illegal docstatus transition }
/v1/framework/{doctype}/{name}/cancel:
parameters:
- { name: doctype, in: path, required: true, schema: { type: string } }
- { name: name, in: path, required: true, schema: { type: string } }
post:
tags: [Documents]
summary: Cancel a submitted document (docstatus 1→2)
operationId: cancelDocument
responses:
'200': { description: Document cancelled, content: { application/json: { schema: { $ref: '#/components/schemas/Document' } } } }
'400': { description: DocType is not submittable }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'409': { description: Illegal docstatus transition }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
BadRequest:
description: Invalid request
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
NotFound:
description: Resource not found
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
Fieldtype:
type: string
description: DocField type (closed set the engine validates).
enum: [Data, Int, Float, Currency, Check, Date, Datetime, Text, SmallText, LongText, RichText, Select, Link, Table, Attach, JSON, Password]
DocField:
type: object
required: [fieldname, fieldtype]
properties:
fieldname: { type: string, description: snake_case identifier }
fieldtype: { $ref: '#/components/schemas/Fieldtype' }
label: { type: string }
reqd: { type: boolean }
options: { type: string, description: 'Select → newline choices; Link → target DocType; Table → child DocType' }
default: { type: string }
unique: { type: boolean }
readOnly: { type: boolean }
hidden: { type: boolean }
inListView: { type: boolean }
fetchFrom: { type: string, description: '"link_field.source_field" — auto-populate from a linked document' }
DocPerm:
type: object
required: [role]
properties:
role: { type: string }
read: { type: boolean }
write: { type: boolean }
create: { type: boolean }
delete: { type: boolean }
submit: { type: boolean }
cancel: { type: boolean }
DocType:
type: object
required: [name, fields]
properties:
name: { type: string }
module: { type: string }
isSingle: { type: boolean, description: Exactly one document exists }
isSubmittable: { type: boolean, description: Has the submit/cancel lifecycle }
autoname: { type: string, description: '"" or hash → random id; "field:x"; "prompt"; or a series pattern e.g. "INV-.YYYY.-.#####"' }
titleField: { type: string }
fields: { type: array, items: { $ref: '#/components/schemas/DocField' } }
permissions: { type: array, items: { $ref: '#/components/schemas/DocPerm' } }
createdAt: { type: integer, format: int64 }
updatedAt: { type: integer, format: int64 }
Role:
type: object
required: [user, role]
properties:
user: { type: string }
role: { type: string }
Document:
type: object
description: |
A schemaless document. Field data is carried at the top level alongside the
envelope keys (name/doctype/docstatus/createdAt/updatedAt). Password fields
are redacted in responses. On write, only the field data is read.
additionalProperties: true
properties:
name: { type: string }
doctype: { type: string }
docstatus: { type: integer, enum: [0, 1, 2], description: '0=draft, 1=submitted, 2=cancelled' }
createdAt: { type: integer, format: int64 }
updatedAt: { type: integer, format: int64 }
+259
View File
@@ -0,0 +1,259 @@
openapi: 3.1.0
info:
title: Hanzo Functions API
description: |
Org-scoped serverless functions on the unified cloud binary — the
`/v1/functions/*` surface served by the cloud `functions` subsystem
(`hanzoai/cloud/clients/functions`). Define a function (runtime + code or
image + env), invoke it through the sandboxed code-execution runtime (each
invocation is real, metered compute against the org's balance), and read
its triggers, deployments, secrets, invocations, and metrics. Tenant
isolation is enforced server-side from the validated bearer `owner` claim
(HIP-0026).
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Functions
description: Function definitions and invocations
- name: Metrics
description: Invocation histograms and status breakdown
security:
- bearerAuth: []
paths:
/v1/functions:
get:
tags: [Functions]
summary: List functions
operationId: listFunctions
responses:
'200': { description: Functions, content: { application/json: { schema: { type: object, properties: { functions: { type: array, items: { $ref: '#/components/schemas/Function' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Functions]
summary: Create a function
operationId: createFunction
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateFunctionRequest' }
responses:
'201': { description: Function created, content: { application/json: { schema: { $ref: '#/components/schemas/Function' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/functions/metrics:
get:
tags: [Metrics]
summary: Invocation histogram + status breakdown
operationId: functionMetrics
parameters:
- { name: range, in: query, schema: { type: string, enum: ['24H', '7D', '30D'], default: '24H' } }
responses:
'200': { description: Metrics, content: { application/json: { schema: { $ref: '#/components/schemas/Metrics' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/functions/triggers:
get:
tags: [Functions]
summary: List triggers across functions
operationId: listTriggers
responses:
'200': { description: Triggers, content: { application/json: { schema: { type: object, properties: { triggers: { type: array, items: { $ref: '#/components/schemas/Trigger' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/functions/deployments:
get:
tags: [Functions]
summary: List deployed functions
operationId: listDeployments
responses:
'200': { description: Deployments, content: { application/json: { schema: { type: object, properties: { functions: { type: array, items: { $ref: '#/components/schemas/Function' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/functions/secrets:
get:
tags: [Functions]
summary: List function secrets (names only)
operationId: listFunctionSecrets
responses:
'200': { description: Secrets, content: { application/json: { schema: { type: object, properties: { secrets: { type: array, items: { $ref: '#/components/schemas/Secret' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/functions/{name}:
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
get:
tags: [Functions]
summary: Get a function (with triggers, recent invocations, secrets)
operationId: getFunction
responses:
'200': { description: Function detail, content: { application/json: { schema: { $ref: '#/components/schemas/FunctionDetail' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Function not found }
delete:
tags: [Functions]
summary: Delete a function
operationId: deleteFunction
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Function not found }
/v1/functions/{name}/invocations:
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
get:
tags: [Functions]
summary: List a function's invocations
operationId: listInvocations
parameters:
- { name: limit, in: query, schema: { type: integer } }
responses:
'200': { description: Invocations, content: { application/json: { schema: { type: object, properties: { invocations: { type: array, items: { $ref: '#/components/schemas/Invocation' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Function not found }
/v1/functions/{name}/logs:
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
get:
tags: [Functions]
summary: Get the latest invocation logs
operationId: getFunctionLogs
responses:
'200': { description: Logs, content: { application/json: { schema: { type: object, properties: { logs: { type: string } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Function not found }
/v1/functions/{name}/invoke:
parameters:
- { name: name, in: path, required: true, schema: { type: string } }
post:
tags: [Functions]
summary: Invoke a function (metered compute)
operationId: invokeFunction
requestBody:
required: false
content:
application/json:
schema: { $ref: '#/components/schemas/InvokeRequest' }
responses:
'200': { description: Invocation result, content: { application/json: { schema: { $ref: '#/components/schemas/Invocation' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'402': { description: Insufficient balance }
'404': { description: Function not found }
'502': { description: Sandbox execution failed (invocation recorded with error status), content: { application/json: { schema: { $ref: '#/components/schemas/Invocation' } } } }
'503': { description: Code execution runtime not configured }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
Function:
type: object
properties:
name: { type: string }
namespace: { type: string }
environment: { type: string }
status: { type: string }
image: { type: string }
endpoint: { type: string }
envCount: { type: integer }
timeoutSec: { type: integer }
memoryLimit: { type: string }
invocations7d: { type: integer, nullable: true }
successRate: { type: number, nullable: true }
avgDurationMs: { type: number, nullable: true }
errors7d: { type: integer, nullable: true }
createdAt: { type: string }
lastDeployedAt: { type: string }
FunctionDetail:
allOf:
- { $ref: '#/components/schemas/Function' }
- type: object
properties:
triggers: { type: array, items: { $ref: '#/components/schemas/Trigger' } }
recentInvocations: { type: array, items: { $ref: '#/components/schemas/Invocation' } }
secrets: { type: array, items: { type: string } }
CreateFunctionRequest:
type: object
required: [name]
properties:
name: { type: string }
environment: { type: string }
runtime: { type: string }
namespace: { type: string }
image: { type: string }
code: { type: string }
handler: { type: string }
timeoutSec: { type: integer }
memoryLimit: { type: string }
envNames: { type: array, items: { type: string } }
Trigger:
type: object
properties:
id: { type: string }
name: { type: string }
type: { type: string }
enabled: { type: boolean }
target: { type: string }
functionName: { type: string }
Invocation:
type: object
properties:
id: { type: string }
statusCode: { type: integer }
status: { type: string, enum: [ok, error] }
method: { type: string }
time: { type: string, description: RFC3339 UTC }
durationMs: { type: integer, format: int64 }
Secret:
type: object
properties:
name: { type: string }
namespace: { type: string }
mountedBy: { type: string }
InvokeRequest:
type: object
properties:
input: { type: string }
Metrics:
type: object
properties:
series:
type: array
items:
type: object
properties:
key: { type: string }
points:
type: array
items:
type: object
properties:
t: { type: string }
v: { type: integer }
status:
type: object
properties:
success: { type: integer }
timeout: { type: integer }
error: { type: integer }
costCents: { type: integer, format: int64, nullable: true }
+192
View File
@@ -0,0 +1,192 @@
openapi: 3.1.0
info:
title: Hanzo Git API
description: |
S3-backed Git hosting native to the Hanzo Cloud binary — the `/v1/git/*`
surface served by `hanzoai/cloud/clients/git`. A repo is the Git layer
(source code) scoped under an org (gateway-minted `X-Org-Id`, HIP-0026) and
an optional project sub-scope (`X-Project-Id`); tenancy is enforced on every
query so one tenant can never read, clone, push to, or delete another's.
The control plane (`/v1/git/repos`, `/v1/git/usage`) is JSON. The
smart-HTTP transport routes (`.../info/refs`, `.../git-upload-pack`,
`.../git-receive-pack`) speak the native Git wire protocol so
`git clone` / `git fetch` / `git push` work against
`https://api.hanzo.ai/v1/git/<org>/<repo>.git`.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Repos
description: Repository lifecycle (create, list, detail, delete)
- name: Usage
description: Per-repo + total storage bytes (metering)
- name: Smart HTTP
description: Native Git wire protocol (clone / fetch / push)
security:
- bearerAuth: []
paths:
/v1/git/repos:
post:
tags: [Repos]
summary: Create a bare repo
operationId: createRepo
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateRepo' }
responses:
'201': { description: Repo created, content: { application/json: { schema: { $ref: '#/components/schemas/Repo' } } } }
'400': { description: Invalid name/project/description }
'401': { $ref: '#/components/responses/Unauthorized' }
'409': { description: Repo name already exists in this scope }
get:
tags: [Repos]
summary: List the tenant's repos
operationId: listRepos
responses:
'200': { description: Repos, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Repo' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/git/usage:
get:
tags: [Usage]
summary: Per-repo + total storage bytes for the tenant
operationId: getGitUsage
responses:
'200': { description: Usage rollup, content: { application/json: { schema: { $ref: '#/components/schemas/Usage' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/git/repos/{name}:
parameters:
- { name: name, in: path, required: true, schema: { type: string }, description: 'Repo name (a trailing ".git" is stripped)' }
get:
tags: [Repos]
summary: Repo detail (branches + resolved HEAD)
operationId: getRepo
responses:
'200': { description: Repo, content: { application/json: { schema: { $ref: '#/components/schemas/Repo' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Repo not found }
delete:
tags: [Repos]
summary: Delete a repo and purge its storage
operationId: deleteRepo
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Repo not found }
/v1/git/{org}/{repo}/info/refs:
get:
tags: [Smart HTTP]
summary: Git smart-HTTP ref advertisement
description: The ref-advertisement phase. `?service=` selects git-upload-pack (fetch) or git-receive-pack (push).
operationId: gitInfoRefs
parameters:
- { name: org, in: path, required: true, schema: { type: string } }
- { name: repo, in: path, required: true, schema: { type: string } }
- { name: service, in: query, required: true, schema: { type: string, enum: [git-upload-pack, git-receive-pack] } }
responses:
'200':
description: pkt-line ref advertisement
content:
application/x-git-upload-pack-advertisement: { schema: { type: string, format: binary } }
application/x-git-receive-pack-advertisement: { schema: { type: string, format: binary } }
'400': { description: service must be git-upload-pack or git-receive-pack }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Repo not found }
/v1/git/{org}/{repo}/git-upload-pack:
post:
tags: [Smart HTTP]
summary: Git upload-pack (clone / fetch)
operationId: gitUploadPack
parameters:
- { name: org, in: path, required: true, schema: { type: string } }
- { name: repo, in: path, required: true, schema: { type: string } }
requestBody:
required: true
content:
application/x-git-upload-pack-request: { schema: { type: string, format: binary } }
responses:
'200': { description: Packfile result, content: { application/x-git-upload-pack-result: { schema: { type: string, format: binary } } } }
'400': { description: Malformed upload-pack request }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Repo not found }
'413': { description: Request body exceeds 256 MiB }
/v1/git/{org}/{repo}/git-receive-pack:
post:
tags: [Smart HTTP]
summary: Git receive-pack (push)
operationId: gitReceivePack
parameters:
- { name: org, in: path, required: true, schema: { type: string } }
- { name: repo, in: path, required: true, schema: { type: string } }
requestBody:
required: true
content:
application/x-git-receive-pack-request: { schema: { type: string, format: binary } }
responses:
'200': { description: report-status, content: { application/x-git-receive-pack-result: { schema: { type: string, format: binary } } } }
'400': { description: Malformed receive-pack request }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Repo not found }
'413': { description: Request body exceeds 256 MiB }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
CreateRepo:
type: object
required: [name]
properties:
name: { type: string, description: 'Repo name; must match ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$' }
project: { type: string, description: Optional project sub-scope (else the X-Project-Id header) }
description: { type: string, maxLength: 4096 }
Repo:
type: object
properties:
id: { type: string }
org: { type: string }
project: { type: string }
name: { type: string }
description: { type: string }
defaultBranch: { type: string, example: main }
branches: { type: array, items: { type: string } }
head: { type: string, description: Resolved HEAD commit hash }
cloneUrl: { type: string, format: uri }
sizeBytes: { type: integer, format: int64 }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
UsageRepo:
type: object
properties:
name: { type: string }
project: { type: string }
sizeBytes: { type: integer, format: int64 }
Usage:
type: object
properties:
org: { type: string }
totalBytes: { type: integer, format: int64 }
repos: { type: array, items: { $ref: '#/components/schemas/UsageRepo' } }
+87
View File
@@ -0,0 +1,87 @@
openapi: 3.1.0
info:
title: Hanzo Graph API
description: |
Chain-data surface served by `hanzoai/cloud/clients/graph`: the deployment's
blockchain indexing (`/v1/indexers`) and on-chain oracle feeds
(`/v1/oracles`), re-shaped as principal-gated REST off the unified cloud
binary. It fronts the Lux chain-data plane — `luxfi/indexer` (per-network
block/event indexer) and `luxfi/graph` (GraphQL oracle price-feed registry).
The subsystem owns no chain state; it is a thin translator that never
fabricates a row. An unreachable upstream surfaces an honest 502; a
reachable-but-empty upstream returns an empty list. Every route requires a
validated IAM principal (HIP-0026).
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Indexers
description: Per-network chain indexers + status
- name: Oracles
description: On-chain price/data oracle feeds
security:
- bearerAuth: []
paths:
/v1/indexers:
get:
tags: [Indexers]
summary: List the deployment's chain indexer(s)
operationId: listIndexers
responses:
'200': { description: Indexers, content: { application/json: { schema: { type: object, properties: { indexers: { type: array, items: { $ref: '#/components/schemas/Indexer' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'502': { description: Indexer upstream unreachable }
/v1/oracles:
get:
tags: [Oracles]
summary: List on-chain price/data oracles
operationId: listOracles
responses:
'200': { description: Oracles, content: { application/json: { schema: { type: object, properties: { oracles: { type: array, items: { $ref: '#/components/schemas/Oracle' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'502': { description: Graph upstream unreachable }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
Indexer:
type: object
description: 'One chain indexer row. `lag` is omitted when the chain HEAD is not derivable (never fabricated).'
properties:
id: { type: string }
chain: { type: string }
network: { type: string, description: mainnet | testnet | devnet }
height: { type: string, description: Latest indexed block height }
lag: { type: string }
status: { type: string, enum: [active, degraded] }
updatedAt: { type: string, format: date-time }
Oracle:
type: object
description: One on-chain price feed.
properties:
id: { type: string }
name: { type: string, description: Trading pair (e.g. LUX/USD) }
feed: { type: string }
value: { type: string, description: Feed price verbatim }
source: { type: string, example: O-Chain }
status: { type: string, enum: [active] }
updatedAt: { type: string, format: date-time }
+7976 -3665
View File
File diff suppressed because it is too large Load Diff
+217
View File
@@ -0,0 +1,217 @@
openapi: 3.1.0
info:
title: Hanzo Integrations API
description: |
Generic, provider-agnostic OAuth connector plane for Hanzo Cloud — the
`/v1/integrations/*` surface served by the cloud `integrations` subsystem
(`hanzoai/cloud/clients/integrations`). One framework, N providers: an org
connects a third-party account (Slack today; GitHub scaffolded), the callback
seals the resulting per-org tokens into KMS custody, and the store keeps only
non-secret connection metadata. Also hosts the Slack `@hanzo` agent front-door
(events + slash commands) and the per-user account-link browser flow — those
legs authenticate INSIDE the handler (HMAC-signed body / `__Host-` cookies +
state), not with a bearer token.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Providers
description: Connectable third-party providers and per-org connection status
- name: OAuth
description: OAuth connect/callback/disconnect flow
- name: Slack
description: Slack agent webhooks and per-user account link
security:
- bearerAuth: []
paths:
/v1/integrations:
get:
tags: [Providers]
summary: List providers with this org's connection status
operationId: listProviders
responses:
'200': { description: Providers, content: { application/json: { schema: { type: object, properties: { providers: { type: array, items: { $ref: '#/components/schemas/ProviderView' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/integrations/slack/events:
post:
tags: [Slack]
summary: Slack Events API webhook
operationId: slackEvents
description: Public at the JWT layer; authenticated inside the handler by HMAC-SHA256 over the raw body with the Slack signing secret. Handles the `url_verification` challenge and routes `app_mention`/`message` events to an on-behalf-of agent run.
security: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SlackEventEnvelope' }
responses:
'200': { description: Acknowledged (challenge echo, or empty ack) }
/v1/integrations/slack/commands:
post:
tags: [Slack]
summary: Slack slash command webhook
operationId: slackCommands
description: Public at the JWT layer; HMAC-verified inside the handler. Accepts the Slack `application/x-www-form-urlencoded` slash-command payload.
security: []
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
command: { type: string }
text: { type: string }
team_id: { type: string }
user_id: { type: string }
channel_id: { type: string }
response_url: { type: string }
responses:
'200': { description: Slash command acknowledged }
/v1/integrations/slack/link:
get:
tags: [Slack]
summary: Begin the per-user account link (leg 1 — Slack sign-in)
operationId: slackLinkStart
description: Sets a `__Host-` init cookie and redirects to Slack sign-in. State-authed (no bearer).
security: []
parameters:
- { name: state, in: query, required: true, schema: { type: string }, description: Signed, single-use link state }
responses:
'302': { description: Redirect to Slack sign-in }
'400': { description: Invalid or expired link }
'503': { description: Account linking not configured }
/v1/integrations/slack/link/slack:
get:
tags: [Slack]
summary: Slack sign-in callback (leg 2)
operationId: slackLinkSlack
description: Requires the leg-1 init cookie to match the sign-in state before exchanging the code; sets the `__Host-` link cookie and redirects to hanzo.id OIDC.
security: []
parameters:
- { name: code, in: query, schema: { type: string } }
- { name: state, in: query, schema: { type: string } }
- { name: error, in: query, schema: { type: string } }
responses:
'302': { description: Redirect to hanzo.id OIDC authorize }
'400': { description: Invalid state, session mismatch, or sign-in failed }
'503': { description: Account linking not configured }
/v1/integrations/slack/link/callback:
get:
tags: [Slack]
summary: hanzo.id OIDC callback — bind Slack↔Hanzo (leg 3)
operationId: slackLinkCallback
description: Reads the Slack-verified (team,user) from the `__Host-` link cookie, exchanges the OIDC code, and seals the refresh token into the workspace org's KMS namespace. Returns an HTML confirmation page.
security: []
parameters:
- { name: code, in: query, schema: { type: string } }
- { name: state, in: query, schema: { type: string } }
- { name: error, in: query, schema: { type: string } }
responses:
'200': { description: Account linked, content: { text/html: { schema: { type: string } } } }
'400': { description: Missing/mismatched state or workspace not connected }
'503': { description: Secret store unavailable or linking not configured }
/v1/integrations/{provider}:
parameters:
- { name: provider, in: path, required: true, schema: { type: string }, description: Provider slug (e.g. slack, github) }
get:
tags: [Providers]
summary: Get one provider with this org's connection status
operationId: getProvider
responses:
'200': { description: Provider, content: { application/json: { schema: { $ref: '#/components/schemas/ProviderView' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Unknown provider }
/v1/integrations/{provider}/connect:
parameters:
- { name: provider, in: path, required: true, schema: { type: string } }
post:
tags: [OAuth]
summary: Begin an OAuth flow (returns the provider authorize URL)
operationId: connectProvider
responses:
'200': { description: Authorize URL, content: { application/json: { schema: { type: object, properties: { authorizeUrl: { type: string } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Unknown provider }
'503': { description: Provider not configured, or KMS not ready }
/v1/integrations/{provider}/callback:
parameters:
- { name: provider, in: path, required: true, schema: { type: string } }
get:
tags: [OAuth]
summary: OAuth return — seal tokens and redirect to console
operationId: providerCallback
description: Public, state-authed. The org comes only from the signed, single-use state. Always 302s back to the console (success or labeled failure).
security: []
parameters:
- { name: state, in: query, required: true, schema: { type: string } }
- { name: code, in: query, schema: { type: string } }
- { name: error, in: query, schema: { type: string } }
responses:
'302': { description: Redirect to console (connected or error) }
/v1/integrations/{provider}/disconnect:
parameters:
- { name: provider, in: path, required: true, schema: { type: string } }
post:
tags: [OAuth]
summary: Revoke and forget an org's connection (idempotent)
operationId: disconnectProvider
responses:
'200': { description: Disconnected, content: { application/json: { schema: { type: object, properties: { disconnected: { type: boolean } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Unknown provider }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
ProviderView:
type: object
properties:
id: { type: string }
name: { type: string }
description: { type: string }
category: { type: string }
available: { type: boolean, description: App credentials configured on this deployment }
connected: { type: boolean, description: This org has a connection }
connection: { $ref: '#/components/schemas/ConnectionView' }
ConnectionView:
type: object
properties:
account: { type: string, description: Human account label (e.g. Slack team name) }
externalId: { type: string, description: Provider account id (e.g. Slack team id) }
scopes: { type: array, items: { type: string } }
connectedAt: { type: string, format: date-time }
SlackEventEnvelope:
type: object
description: Slack Events API outer envelope (subset).
properties:
type: { type: string, description: 'e.g. url_verification | event_callback' }
challenge: { type: string, description: Present on url_verification }
team_id: { type: string }
event: { type: object, additionalProperties: true }
+233
View File
@@ -0,0 +1,233 @@
openapi: 3.1.0
info:
title: Hanzo KB API
description: |
Knowledge Base retrieval + ingestion control-plane — the `/v1/kb/*` surface
served by the cloud `kb` subsystem (`hanzoai/cloud/clients/kb`). It is the thin
RAG + connector layer on top of the framework DocType store (KB content lives as
`kb-page`/`kb-memory`/`kb-source` documents at `/v1/framework/*`) and the per-org
vector index.
- Semantic search over the caller org's own knowledge namespace (the RAG entry
point an agent/chat calls) — human wiki + AI memory in one store.
- Per-org app connectors (GitHub/Slack/Google/Notion): OAuth in, sync external
documents INTO the same store + index.
Every route is org-scoped to the validated tenant — a caller can only search or
connect its own knowledge.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Search
description: Org-scoped semantic retrieval (RAG)
- name: Connectors
description: Per-org app connectors (OAuth ingestion)
security:
- bearerAuth: []
paths:
/v1/kb/search:
post:
tags: [Search]
summary: Semantic search over the org's knowledge
operationId: kbSearch
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SearchRequest' }
responses:
'200':
description: Hits (an unreachable/disabled index returns an empty degraded result, never a 5xx)
content:
application/json:
schema:
type: object
properties:
hits: { type: array, items: { $ref: '#/components/schemas/Hit' } }
degraded: { type: boolean, description: true when the index was unreachable and no context was retrieved }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/kb/connectors:
get:
tags: [Connectors]
summary: List this org's connectors and connection state
operationId: listConnectors
responses:
'200':
description: Connectors
content:
application/json:
schema: { type: object, properties: { connectors: { type: array, items: { $ref: '#/components/schemas/ConnectorState' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/kb/connectors/catalog:
get:
tags: [Connectors]
summary: List every connectable source (native + long-tail pieces)
operationId: listCatalog
responses:
'200':
description: Catalog
content:
application/json:
schema: { type: object, properties: { connectors: { type: array, items: { $ref: '#/components/schemas/CatalogEntry' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/kb/connectors/{provider}/connect:
parameters:
- { name: provider, in: path, required: true, schema: { type: string, enum: [github, slack, google, notion] } }
get:
tags: [Connectors]
summary: Begin an OAuth connection (returns the provider authorize URL)
operationId: connectStart
responses:
'200':
description: Authorize URL for the console to open
content:
application/json:
schema: { type: object, properties: { authorizeUrl: { type: string, format: uri } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'503': { description: Connector not configured on this deployment }
/v1/kb/connectors/{provider}/callback:
parameters:
- { name: provider, in: path, required: true, schema: { type: string, enum: [github, slack, google, notion] } }
get:
tags: [Connectors]
summary: OAuth callback (provider redirect — org recovered from signed state)
operationId: connectCallback
security: []
parameters:
- { name: code, in: query, schema: { type: string } }
- { name: state, in: query, schema: { type: string } }
- { name: error, in: query, schema: { type: string } }
responses:
'200':
description: Connected
content:
application/json:
schema:
type: object
properties:
provider: { type: string }
status: { type: string, example: connected }
account: { type: string }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { description: Invalid OAuth state }
'404': { $ref: '#/components/responses/NotFound' }
/v1/kb/connectors/{provider}/sync:
parameters:
- { name: provider, in: path, required: true, schema: { type: string, enum: [github, slack, google, notion] } }
post:
tags: [Connectors]
summary: Sync the provider's documents into this org's knowledge store
operationId: syncConnector
responses:
'200':
description: Sync result
content:
application/json:
schema:
type: object
properties:
provider: { type: string }
ingested: { type: integer }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
'502': { description: Sync failed }
/v1/kb/connectors/{provider}:
parameters:
- { name: provider, in: path, required: true, schema: { type: string, enum: [github, slack, google, notion] } }
delete:
tags: [Connectors]
summary: Disconnect a connector (tombstone token, purge its vector points)
operationId: disconnectConnector
responses:
'200':
description: Disconnected
content:
application/json:
schema:
type: object
properties:
provider: { type: string }
status: { type: string, example: disconnected }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { $ref: '#/components/responses/NotFound' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
BadRequest:
description: Invalid request
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
NotFound:
description: Resource not found
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
SearchRequest:
type: object
required: [query]
properties:
query: { type: string, description: Natural-language question }
limit: { type: integer, default: 10, maximum: 50 }
project: { type: string, description: Optional project scope }
doctypes:
type: array
items: { type: string, enum: [kb-page, kb-memory, kb-source] }
description: Optional restriction to a subset of indexed knowledge doctypes
Hit:
type: object
properties:
doctype: { type: string }
name: { type: string }
title: { type: string }
project: { type: string }
provider: { type: string }
url: { type: string }
score: { type: number, format: double }
ConnectorState:
type: object
properties:
provider: { type: string }
configured: { type: boolean, description: Deployment has OAuth credentials for it }
status: { type: string, enum: [disconnected, connected, syncing, error] }
docCount: { type: integer, description: Ingested kb-source documents from this provider }
kind: { type: string, enum: [native, piece] }
account: { type: string }
lastSync: { type: string }
error: { type: string }
CatalogEntry:
type: object
properties:
provider: { type: string }
displayName: { type: string }
description: { type: string }
kind: { type: string, enum: [native, piece] }
configured: { type: boolean }
+186
View File
@@ -0,0 +1,186 @@
openapi: 3.1.0
info:
title: Hanzo Plans API
description: |
The `@hanzo/plans` catalog mounted into the unified cloud binary under
`/v1/plans/*` by `hanzoai/cloud/clients/plan`. A thin Go wrapper loads the
`hanzoai/plans` bundle into a goja runtime and dispatches each route through
it — the entitlement transforms (fromLegacy / toLicenseFeatures /
resolvePlan) run as real JS, not a Go reimplementation.
The catalog is read-only public content. Every route reads the
gateway-minted identity (`X-Org-Id`, HIP-0026) as the tenant so a reseller
org sees its own catalog overrides; an anonymous or forged org falls back to
the public `hanzo` default. Responses are the bundle's catalog JSON.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Plans
description: Plan catalog + entitlement resolution
security:
- bearerAuth: []
paths:
/v1/plans/health:
get:
tags: [Plans]
summary: Health check
operationId: plansHealth
security: []
responses:
'200': { description: Service healthy, content: { application/json: { schema: { type: object, properties: { status: { type: string, example: ok }, service: { type: string, example: plans } } } } } }
/v1/plans:
get:
tags: [Plans]
summary: The full plan catalog
operationId: listPlans
responses:
'200': { description: Plan catalog, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/subscriptions:
get:
tags: [Plans]
summary: Subscription plans
operationId: listSubscriptionPlans
responses:
'200': { description: Subscription plans, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/cloud:
get:
tags: [Plans]
summary: Cloud plans
operationId: listCloudPlans
responses:
'200': { description: Cloud plans, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/blockchain:
get:
tags: [Plans]
summary: Blockchain plans
operationId: listBlockchainPlans
responses:
'200': { description: Blockchain plans, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/dns:
get:
tags: [Plans]
summary: DNS plans
operationId: listDnsPlans
responses:
'200': { description: DNS plans, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/gpu:
get:
tags: [Plans]
summary: GPU plans
operationId: listGpuPlans
responses:
'200': { description: GPU plans, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/regions:
get:
tags: [Plans]
summary: Regions
operationId: listPlanRegions
responses:
'200': { description: Regions, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/storage:
get:
tags: [Plans]
summary: Storage plans
operationId: listStoragePlans
responses:
'200': { description: Storage plans, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/tools:
get:
tags: [Plans]
summary: Tools catalog
operationId: listPlanTools
responses:
'200': { description: Tools, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/policy:
get:
tags: [Plans]
summary: Plan policy
operationId: getPlanPolicy
responses:
'200': { description: Policy, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/schema:
get:
tags: [Plans]
summary: Entitlement schema
operationId: getPlanSchema
responses:
'200': { description: Schema, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/vocab:
get:
tags: [Plans]
summary: Entitlement vocabulary
operationId: getPlanVocab
responses:
'200': { description: Vocabulary, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/resolve/{id}:
get:
tags: [Plans]
summary: Resolve a plan by id
operationId: resolvePlan
parameters:
- { name: id, in: path, required: true, schema: { type: string }, description: Plan id }
responses:
'200': { description: Resolved plan, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/plans/entitlements/{id}:
get:
tags: [Plans]
summary: Entitlements for a plan
operationId: getPlanEntitlements
parameters:
- { name: id, in: path, required: true, schema: { type: string }, description: Plan id }
responses:
'200': { description: Entitlements, content: { application/json: { schema: { $ref: '#/components/schemas/Catalog' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
Catalog:
type: object
description: Opaque plan-catalog JSON emitted by the @hanzo/plans bundle (shape varies per route).
additionalProperties: true
+156
View File
@@ -0,0 +1,156 @@
openapi: 3.1.0
info:
title: Hanzo Prompts API
description: |
Per-org, versioned prompt library for Hanzo Cloud — the `/v1/prompts`
surface served by the cloud `prompts` subsystem
(`hanzoai/cloud/clients/prompts`). Every prompt belongs to exactly one org
(the validated bearer owner claim, HIP-0026); tenant isolation is enforced on
every query. Creating a prompt whose name already exists appends a new
version — real, inspectable history. The store holds template text + taxonomy
only, never a secret.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Prompts
description: Named, versioned prompt templates
- name: Catalog
description: Read-only Hanzo starter prompt library
security:
- bearerAuth: []
paths:
/v1/prompts:
get:
tags: [Prompts]
summary: List current prompts for the org
operationId: listPrompts
responses:
'200': { description: Prompts, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/PromptMeta' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Prompts]
summary: Create a prompt or append a new version
operationId: createPrompt
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreatePrompt' }
responses:
'201': { description: Prompt detail (post-write), content: { application/json: { schema: { $ref: '#/components/schemas/PromptDetail' } } } }
'400': { description: Invalid name / content too large }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/prompts/metrics:
get:
tags: [Prompts]
summary: Real per-prompt statistics
operationId: promptMetrics
responses:
'200': { description: Metrics, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/PromptMetric' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/prompts/catalog:
get:
tags: [Catalog]
summary: Read-only starter prompt library
operationId: promptCatalog
responses:
'200': { description: Catalog, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/CatalogEntry' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/prompts/{name}:
parameters:
- { name: name, in: path, required: true, schema: { type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$' } }
get:
tags: [Prompts]
summary: Prompt detail + version history
operationId: getPrompt
responses:
'200': { description: Prompt detail, content: { application/json: { schema: { $ref: '#/components/schemas/PromptDetail' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Prompt not found }
delete:
tags: [Prompts]
summary: Delete a prompt and its version history
operationId: deletePrompt
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Prompt not found }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
CreatePrompt:
type: object
required: [name]
properties:
name: { type: string, description: 'Org-unique handle, ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ (not "metrics"/"new"/"catalog")' }
type: { type: string, default: text }
prompt: { type: string, description: Template body (max 64KiB) }
labels: { type: array, items: { type: string } }
tags: { type: array, items: { type: string } }
PromptMeta:
type: object
properties:
name: { type: string }
type: { type: string }
versions: { type: array, items: { type: integer }, description: Version numbers, newest first }
labels: { type: array, items: { type: string } }
tags: { type: array, items: { type: string } }
lastUpdatedAt: { type: string, format: date-time }
VersionMeta:
type: object
description: Version history metadata only — no per-version content.
properties:
version: { type: integer }
type: { type: string }
createdAt: { type: string, format: date-time }
PromptDetail:
type: object
properties:
name: { type: string }
type: { type: string }
prompt: { type: string, description: Current version content }
version: { type: integer, description: Current version number }
labels: { type: array, items: { type: string } }
tags: { type: array, items: { type: string } }
versionHistory: { type: array, items: { $ref: '#/components/schemas/VersionMeta' } }
createdAt: { type: string, format: date-time }
lastUpdatedAt: { type: string, format: date-time }
PromptMetric:
type: object
properties:
name: { type: string }
type: { type: string }
versions: { type: integer, description: True version count }
currentVersion: { type: integer }
createdAt: { type: string, format: date-time }
lastUpdatedAt: { type: string, format: date-time }
CatalogEntry:
type: object
properties:
name: { type: string }
type: { type: string }
prompt: { type: string }
tags: { type: array, items: { type: string } }
labels: { type: array, items: { type: string } }
+166
View File
@@ -0,0 +1,166 @@
openapi: 3.1.0
info:
title: Hanzo Security API
description: |
Secret / vulnerability scanning (`/v1/security/*`) served by the cloud
`security` subsystem (`hanzoai/cloud/clients/security`). Submit source files,
the engine runs its detection ruleset over them in memory (raw content is
never stored), and persists a per-org scan summary plus redacted findings.
Every route is tenant-scoped to the caller's validated org.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Scans
description: Submit and read scans
- name: Findings
description: Redacted findings
- name: Rules
description: Detection ruleset
security:
- bearerAuth: []
paths:
/v1/security/health:
get:
tags: [Rules]
summary: Health check
operationId: securityHealth
security: []
responses:
'200': { description: Service healthy, content: { application/json: { schema: { type: object, properties: { status: { type: string, example: ok }, rules: { type: integer } } } } } }
/v1/security/rules:
get:
tags: [Rules]
summary: List the detection ruleset
operationId: securityListRules
responses:
'200': { description: Rules, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Rule' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/security/scans:
post:
tags: [Scans]
summary: Submit a scan
operationId: securitySubmitScan
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ScanRequest' }
responses:
'201': { description: Scan summary, content: { application/json: { schema: { $ref: '#/components/schemas/Scan' } } } }
'400': { description: Missing files, too many files, or scan too large }
'401': { $ref: '#/components/responses/Unauthorized' }
get:
tags: [Scans]
summary: List scans
operationId: securityListScans
parameters:
- { name: limit, in: query, schema: { type: integer } }
responses:
'200': { description: Scans, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Scan' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/security/findings:
get:
tags: [Findings]
summary: List findings
operationId: securityListFindings
parameters:
- { name: scanId, in: query, schema: { type: string } }
- { name: minSeverity, in: query, schema: { type: string, enum: [critical, high, medium, low] } }
- { name: limit, in: query, schema: { type: integer } }
responses:
'200': { description: Findings, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Finding' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/security/scans/{id}:
get:
tags: [Scans]
summary: Get a scan with its findings
operationId: securityGetScan
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
responses:
'200': { description: Scan detail, content: { application/json: { schema: { type: object, properties: { scan: { $ref: '#/components/schemas/Scan' }, findings: { type: array, items: { $ref: '#/components/schemas/Finding' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Scan not found }
/v1/security/findings/{id}:
get:
tags: [Findings]
summary: Get a finding
operationId: securityGetFinding
parameters:
- { name: id, in: path, required: true, schema: { type: string } }
responses:
'200': { description: Finding, content: { application/json: { schema: { $ref: '#/components/schemas/Finding' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Finding not found }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
Rule:
type: object
description: One detection rule in the catalog.
properties:
ID: { type: string }
Name: { type: string }
Severity: { type: string, enum: [critical, high, medium, low] }
Description: { type: string }
FileInput:
type: object
required: [path, content]
properties:
path: { type: string }
content: { type: string }
ScanRequest:
type: object
required: [files]
properties:
project: { type: string, description: 'Optional sub-scope, ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$' }
files: { type: array, description: 'At least one file; max 500 files / 8 MiB total', items: { $ref: '#/components/schemas/FileInput' } }
Scan:
type: object
properties:
id: { type: string }
project: { type: string }
files: { type: integer }
findings: { type: integer }
critical: { type: integer }
high: { type: integer }
medium: { type: integer }
low: { type: integer }
createdAt: { type: integer, format: int64, description: Unix milliseconds }
Finding:
type: object
properties:
id: { type: string }
scanId: { type: string }
ruleId: { type: string }
ruleName: { type: string }
severity: { type: string, enum: [critical, high, medium, low] }
path: { type: string }
line: { type: integer }
preview: { type: string, description: Redacted preview — never the raw secret }
fingerprint: { type: string }
createdAt: { type: integer, format: int64, description: Unix milliseconds }
+137
View File
@@ -0,0 +1,137 @@
openapi: 3.1.0
info:
title: Hanzo Tasks API
description: |
Hanzo Tasks — the durable workflow/activity engine (event-sourced,
exactly-once, crash-recovering) mounted natively on the unified cloud binary
(`hanzoai/cloud/clients/tasksvc`), per HIP-0106. The subsystem mounts the ONE
shared in-process engine's HTTP surface under `/v1/tasks/*`; the Tasks
product reads the same durable state as the AI durable-ingest path.
Capability/probe routes (`/settings`, `/cluster`) are open; the data surface
(the engine JSON API, `/mcp`, `/events`) is identity-gated — a request
without a gateway-validated principal (X-User-Id) is refused (403). The
engine is resolved lazily, so requests before it is live return 503.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Tasks
description: Durable workflow/activity engine JSON API
- name: System
description: Liveness, settings and cluster probes
security:
- bearerAuth: []
paths:
/v1/tasks/health:
get:
tags: [System]
summary: Liveness probe
operationId: tasksHealth
security: []
responses:
'200': { description: Service healthy, content: { application/json: { schema: { type: object, properties: { status: { type: string, example: ok } } } } } }
/v1/tasks/settings:
get:
tags: [System]
summary: Capability flags (open bootstrap)
operationId: tasksSettings
security: []
responses:
'200': { description: Capability flags, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
/v1/tasks/cluster:
get:
tags: [System]
summary: Cluster status (open probe)
operationId: tasksCluster
security: []
responses:
'200': { description: Cluster status, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
/v1/tasks/cluster/health:
get:
tags: [System]
summary: Cluster health (open probe)
operationId: tasksClusterHealth
security: []
responses:
'200': { description: Cluster health, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
/v1/tasks/mcp:
post:
tags: [Tasks]
summary: Tasks MCP tool surface (JSON-RPC, identity-gated)
operationId: tasksMcp
requestBody:
required: true
content:
application/json:
schema: { type: object, additionalProperties: true, description: JSON-RPC 2.0 request }
responses:
'200': { description: JSON-RPC response, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
'403': { $ref: '#/components/responses/IdentityRequired' }
'503': { $ref: '#/components/responses/EngineNotReady' }
/v1/tasks/events:
get:
tags: [Tasks]
summary: Realtime event stream (SSE, identity-gated)
operationId: tasksEvents
responses:
'200': { description: Server-sent event stream, content: { text/event-stream: { schema: { type: string } } } }
'403': { $ref: '#/components/responses/IdentityRequired' }
'503': { $ref: '#/components/responses/EngineNotReady' }
/v1/tasks/{resource}:
get:
tags: [Tasks]
summary: Engine JSON API (namespaces, workflows, activities, …), identity-gated
operationId: tasksGet
parameters:
- { name: resource, in: path, required: true, schema: { type: string }, description: 'Engine resource path (e.g. namespaces, nexus)' }
responses:
'200': { description: Engine JSON, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
'403': { $ref: '#/components/responses/IdentityRequired' }
'503': { $ref: '#/components/responses/EngineNotReady' }
post:
tags: [Tasks]
summary: Engine JSON API write (identity-gated)
operationId: tasksPost
parameters:
- { name: resource, in: path, required: true, schema: { type: string } }
requestBody:
required: true
content:
application/json:
schema: { type: object, additionalProperties: true }
responses:
'200': { description: Engine JSON, content: { application/json: { schema: { type: object, additionalProperties: true } } } }
'403': { $ref: '#/components/responses/IdentityRequired' }
'503': { $ref: '#/components/responses/EngineNotReady' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key. The gateway mints X-User-Id/X-Org-Id from the validated token.
responses:
IdentityRequired:
description: Identity required — no gateway-validated principal on the request
content:
application/json:
schema: { type: object, properties: { error: { type: string }, code: { type: integer, example: 403 } } }
EngineNotReady:
description: Tasks engine not ready (resolved lazily; retry once it is wired)
content:
application/json:
schema: { type: object, properties: { error: { type: string }, code: { type: integer, example: 503 } } }
+75
View File
@@ -0,0 +1,75 @@
openapi: 3.1.0
info:
title: Hanzo Templates API
description: |
Read-only starter-kit gallery for Hanzo Cloud — the `/v1/templates` surface
served by the cloud `templates` subsystem
(`hanzoai/cloud/clients/templates`). Deployable app/site scaffolds (source of
truth: `hanzoai/gallery`), vendored so the unified cloud binary ships the
catalog with no external dependency. Reference content, not org data — a user
forks a template into a project via the deploy flow, not by mutating this
catalog.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Templates
description: Deployable starter-kit scaffolds
security:
- bearerAuth: []
paths:
/v1/templates:
get:
tags: [Templates]
summary: List the starter-kit catalog
operationId: listTemplates
responses:
'200': { description: Catalog, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Template' } } } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
/v1/templates/{slug}:
parameters:
- { name: slug, in: path, required: true, schema: { type: string } }
get:
tags: [Templates]
summary: One template by slug
operationId: getTemplate
responses:
'200': { description: Template, content: { application/json: { schema: { $ref: '#/components/schemas/Template' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Template not found }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
Template:
type: object
properties:
slug: { type: string }
title: { type: string }
category: { type: string }
description: { type: string }
framework: { type: string }
features: { type: array, items: { type: string } }
useCase: { type: string }
tier: { type: integer, description: Optional pricing tier }
rating: { type: number, format: double, description: Optional gallery rating }
source: { type: string, description: Live gallery fork/deploy URL }
preview: { type: string, description: Live gallery screenshot/preview URL }
+222
View File
@@ -0,0 +1,222 @@
openapi: 3.1.0
info:
title: Hanzo Tracker API
description: |
Native-Go, per-org issue tracker (projects + issues) for Hanzo Cloud —
the `/v1/tracker/*` surface served by the cloud `tracker` subsystem
(`hanzoai/cloud/clients/tracker`). Linear-style projects with an
org-unique uppercase key that prefixes every issue identifier
(`KEY-<number>`). Tenant isolation is enforced server-side on every
request from the validated bearer `owner` claim (HIP-0026) — never a
client-supplied header.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Projects
description: Org-scoped tracker projects (Linear-style teams)
- name: Issues
description: Work items inside a project
security:
- bearerAuth: []
paths:
/v1/tracker/projects:
get:
tags: [Projects]
summary: List projects
operationId: listProjects
responses:
'200': { description: Projects, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Project' } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [Projects]
summary: Create a project
operationId: createProject
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateProjectRequest' }
responses:
'201': { description: Project created, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'409': { description: Project key already exists in this org }
/v1/tracker/projects/{key}:
parameters:
- { name: key, in: path, required: true, schema: { type: string }, description: 'Project key (uppercase, ^[A-Z][A-Z0-9]{1,7}$)' }
get:
tags: [Projects]
summary: Get a project
operationId: getProject
responses:
'200': { description: Project, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Project not found }
patch:
tags: [Projects]
summary: Update a project
operationId: updateProject
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/UpdateProjectRequest' }
responses:
'200': { description: Project updated, content: { application/json: { schema: { $ref: '#/components/schemas/Project' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Project not found }
delete:
tags: [Projects]
summary: Delete a project and all its issues
operationId: deleteProject
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Project not found }
/v1/tracker/projects/{key}/issues:
parameters:
- { name: key, in: path, required: true, schema: { type: string }, description: Project key }
get:
tags: [Issues]
summary: List issues (board/list)
operationId: listIssues
parameters:
- { name: status, in: query, schema: { $ref: '#/components/schemas/IssueStatus' }, description: Filter to one board column }
responses:
'200': { description: Issues, content: { application/json: { schema: { type: array, items: { $ref: '#/components/schemas/Issue' } } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Project not found }
post:
tags: [Issues]
summary: Create an issue
operationId: createIssue
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CreateIssueRequest' }
responses:
'201': { description: Issue created, content: { application/json: { schema: { $ref: '#/components/schemas/Issue' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Project not found }
/v1/tracker/projects/{key}/issues/{num}:
parameters:
- { name: key, in: path, required: true, schema: { type: string }, description: Project key }
- { name: num, in: path, required: true, schema: { type: integer }, description: Per-project issue number }
get:
tags: [Issues]
summary: Get an issue
operationId: getIssue
responses:
'200': { description: Issue, content: { application/json: { schema: { $ref: '#/components/schemas/Issue' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Issue not found }
patch:
tags: [Issues]
summary: Update an issue
operationId: updateIssue
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/UpdateIssueRequest' }
responses:
'200': { description: Issue updated, content: { application/json: { schema: { $ref: '#/components/schemas/Issue' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Issue not found }
delete:
tags: [Issues]
summary: Delete an issue
operationId: deleteIssue
responses:
'204': { description: Deleted }
'401': { $ref: '#/components/responses/Unauthorized' }
'404': { description: Issue not found }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: string }, msg: { type: string } } }
schemas:
IssueStatus:
type: string
enum: [backlog, todo, in_progress, done, canceled]
description: Board column. Empty defaults to backlog.
IssuePriority:
type: string
enum: [none, urgent, high, medium, low]
description: Priority. Empty defaults to none.
Project:
type: object
properties:
id: { type: string }
org: { type: string }
key: { type: string }
name: { type: string }
description: { type: string }
createdAt: { type: integer, format: int64, description: Unix seconds }
updatedAt: { type: integer, format: int64, description: Unix seconds }
CreateProjectRequest:
type: object
required: [name]
properties:
key: { type: string, description: 'Optional; derived from name when omitted. ^[A-Z][A-Z0-9]{1,7}$' }
name: { type: string, maxLength: 256 }
description: { type: string }
UpdateProjectRequest:
type: object
properties:
name: { type: string, maxLength: 256 }
description: { type: string }
Issue:
type: object
properties:
id: { type: string }
identifier: { type: string, description: 'KEY-<number>, the human handle' }
projectKey: { type: string }
number: { type: integer }
title: { type: string }
description: { type: string }
status: { $ref: '#/components/schemas/IssueStatus' }
priority: { $ref: '#/components/schemas/IssuePriority' }
assignee: { type: string }
labels: { type: array, items: { type: string } }
createdAt: { type: integer, format: int64 }
updatedAt: { type: integer, format: int64 }
CreateIssueRequest:
type: object
required: [title]
properties:
title: { type: string, maxLength: 512 }
description: { type: string }
status: { $ref: '#/components/schemas/IssueStatus' }
priority: { $ref: '#/components/schemas/IssuePriority' }
assignee: { type: string }
labels: { type: array, items: { type: string } }
UpdateIssueRequest:
type: object
properties:
title: { type: string, maxLength: 512 }
description: { type: string }
status: { $ref: '#/components/schemas/IssueStatus' }
priority: { $ref: '#/components/schemas/IssuePriority' }
assignee: { type: string }
labels: { type: array, items: { type: string } }
+352 -1432
View File
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
openapi: 3.1.0
info:
title: Hanzo Web Search API
description: |
Hanzo-native Web Search + Scrape on the unified cloud-api /v1 plane
(`hanzoai/cloud/clients/websearch`), per HIP-0106 — so chat's web_search
agent tool runs entirely on Hanzo infrastructure with NO external SaaS
provider. Two contracts under `/v1/websearch`, backed by Hanzo's own
services: a SearXNG-shaped search proxied to a Hanzo-operated metasearch
instance, and a Firecrawl-shaped scrape backed by Hanzo Crawl.
AUTH: both surfaces require the shared service key (KMS-sourced) and fail
closed — search via the `X-API-Key` header, scrape via `Authorization:
Bearer`. An unset key returns 503; a missing/mismatched key returns 401.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Search
description: SearXNG-shaped metasearch
- name: Scrape
description: Firecrawl-shaped URL scrape over Hanzo Crawl
security:
- bearerAuth: []
paths:
/v1/websearch/search:
get:
tags: [Search]
summary: Search the web (SearXNG JSON contract, proxied verbatim)
operationId: webSearch
security:
- serviceKey: []
parameters:
- { name: q, in: query, required: true, schema: { type: string }, description: Search query }
- { name: format, in: query, required: false, schema: { type: string, enum: [json], default: json } }
responses:
'200': { description: Search results (SearXNG JSON), content: { application/json: { schema: { $ref: '#/components/schemas/SearchResponse' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'503': { $ref: '#/components/responses/NotConfigured' }
/v1/websearch/v1/scrape:
post:
tags: [Scrape]
summary: Scrape a URL to markdown (Firecrawl response shape)
operationId: webScrape
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ScrapeRequest' }
responses:
'200': { description: 'Scrape result (success may be false with an error; HTTP still 200)', content: { application/json: { schema: { $ref: '#/components/schemas/ScrapeResponse' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'503': { $ref: '#/components/responses/NotConfigured' }
/v1/websearch/scrape:
post:
tags: [Scrape]
summary: Scrape a URL to markdown (bare alias of /v1/websearch/v1/scrape)
operationId: webScrapeBare
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ScrapeRequest' }
responses:
'200': { description: Scrape result, content: { application/json: { schema: { $ref: '#/components/schemas/ScrapeResponse' } } } }
'401': { $ref: '#/components/responses/Unauthorized' }
'503': { $ref: '#/components/responses/NotConfigured' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'Shared service key presented as an Authorization Bearer header (scrape). Not a user JWT.'
serviceKey:
type: apiKey
in: header
name: X-API-Key
description: Shared service key presented on `X-API-Key` (search). Not a user JWT.
responses:
Unauthorized:
description: Missing or invalid credentials
content:
application/json:
schema: { type: object, properties: { status: { type: integer }, error: { type: string } } }
NotConfigured:
description: Web search not configured on this deployment (service key unset — fail-closed)
content:
application/json:
schema: { type: object, properties: { status: { type: integer }, error: { type: string } } }
schemas:
SearchResponse:
type: object
description: SearXNG JSON, forwarded verbatim.
properties:
results:
type: array
items:
type: object
properties:
url: { type: string }
title: { type: string }
content: { type: string }
img_src: { type: string }
ScrapeRequest:
type: object
required: [url]
properties:
url: { type: string }
ScrapeResponse:
type: object
properties:
success: { type: boolean }
data:
type: object
properties:
markdown: { type: string }
metadata: { type: object, additionalProperties: true }
error: { type: string }
+92 -2457
View File
File diff suppressed because it is too large Load Diff