Files
cloud/plugin/framework/openapi.json
hanzo-dev e6ae1244a7 describe: every operation the fleet publishes now says what it does
1465 published operations, 797 described. The other 668 offered an operationId and
nothing else — a generated SDK method with no docstring, a spec-derived CLI command
with no help text, an MCP tool an agent cannot choose between. Now 1491 of 1491.

The gap was structural, not neglect. Almost every one of them was an UNTYPED route:
a proxy to a vendored module, an SSE stream, a WebSocket upgrade, a byte upload, an
All() wildcard, or a surface owned by another repo entirely. None has a handler doc
comment in this tree for zipdoc to lift, which is exactly why 47 apps carried no
zipdoc directive — adding one would have produced an empty file. The seam they
needed existed and had one caller; it now has 523.

Three surfaces had no seam at all and would have been left behind:

  - metrics and licensing are vendored modules that deliberately do not import
    cloud, so their prose lands at cloud's OWN wire fact in build.go;
  - authz is a leaf forbidden from importing cloud, and its handlers are untyped
    closures in another module — both seams shut — so its prose lands in
    plugin/authz/main.go, the file whose own doc says it is where "cloud's plugin
    contract bends to the leaf."

Every sentence was read off the handler, and reading 668 handlers is most of what
this cost. It found ten defects, filed as #376 — two of them money: gpu-charge is
not idempotent, and the finance ledger's peer path emits a vocabulary its reader
does not classify, so credits render empty and deposits sign negative, with the test
green on both paths because it only exercises the S2S mock. None is fixed here.
Describing is not repairing, and a description that flattered the code would have
been worth less than the silence it replaced — so where a route is broken, the prose
says what it actually does.

Three tests used "has prose" as a proxy for "is a typed op". That equivalence held
while prose could only arrive by lifting a typed op's comment, and Describe breaks
it by design — so each of those tests forbade precisely what the seam exists to do.
They now read zip's own registry and assert something stronger: every operation is
either a typed op with lifted prose or a recorded raw address with declared prose,
and either way it carries prose. apps/exec's is a gate over all 56 of its ops, which
matters most on a pure-proxy surface, where the description IS the product surface.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-07-31 10:00:47 -07:00

929 lines
32 KiB
JSON

{
"openapi": "3.1.0",
"info": {
"title": "Hanzo Cloud API",
"description": "Package framework is document types you define: describe a record once, then create, list, submit and cancel documents against it.",
"version": "v1"
},
"servers": [
{
"url": "https://api.hanzo.ai"
}
],
"tags": [
{
"name": "framework"
}
],
"paths": {
"/v1/framework/doctypes": {
"get": {
"operationId": "get_v1_framework_doctypes",
"summary": "Returns every DocType defined in the caller's org.",
"description": "Returns every DocType defined in the caller's org. Another\ntenant's definitions are never included: the org is part of the store key.",
"tags": [
"framework"
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/docTypeList"
}
}
},
"description": "ok"
}
}
},
"post": {
"operationId": "post_v1_framework_doctypes",
"summary": "Defines a DocType in the caller's org: the metadata that gives a document surface its fields, its naming rule, whether it has a submit/cancel lifecycle, and which role may do what to it.",
"description": "Defines a DocType in the caller's org: the metadata that gives a\ndocument surface its fields, its naming rule, whether it has a submit/cancel\nlifecycle, and which role may do what to it. Manager-only — on a fresh org the\nfirst caller to administer it is seeded as its System Manager, after which\nonly a System Manager (or a platform admin) may define. Answers 201.",
"tags": [
"framework"
],
"requestBody": {
"content": {
"application/json": {
"example": {
"autoname": "TASK-.#####",
"fields": [
{
"fieldname": "subject",
"fieldtype": "Data",
"reqd": true
}
],
"name": "Task"
},
"schema": {
"$ref": "#/components/schemas/DocType"
}
}
},
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DocType"
}
}
},
"description": "created"
}
}
}
},
"/v1/framework/doctypes/{name}": {
"delete": {
"operationId": "delete_v1_framework_doctypes_name",
"summary": "Removes a DocType and every document stored under it.",
"description": "Removes a DocType and every document stored under it. The\ndefinition and its data go together — a document with no schema can be neither\nvalidated nor read back — so there is no undo. Manager-only. Answers 204.",
"tags": [
"framework"
],
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"description": "Name is the DocType's name, from the path. A name containing a space\n(\"Sales Invoice\") arrives percent-encoded and is decoded before it is\nmatched against the stored one.",
"schema": {
"type": "string"
},
"example": "Task"
}
],
"responses": {
"204": {
"description": "no content"
}
}
},
"get": {
"operationId": "get_v1_framework_doctypes_name",
"summary": "Returns one DocType definition — its fields, naming rule, permissions and lifecycle flags.",
"description": "Returns one DocType definition — its fields, naming rule,\npermissions and lifecycle flags. Scoped to the caller's org, so another\ntenant's DocType of the same name is simply not found.",
"tags": [
"framework"
],
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"description": "Name is the DocType's name, from the path. A name containing a space\n(\"Sales Invoice\") arrives percent-encoded and is decoded before it is\nmatched against the stored one.",
"schema": {
"type": "string"
},
"example": "Task"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DocType"
}
}
},
"description": "ok"
}
}
},
"put": {
"operationId": "put_v1_framework_doctypes_name",
"summary": "Replaces a DocType definition wholesale (PUT semantics): the stored definition becomes the body.",
"description": "Replaces a DocType definition wholesale (PUT semantics): the\nstored definition becomes the body. The name in the URL is authoritative over\nthe body's, and documents already stored under the DocType are left intact.\nManager-only.",
"tags": [
"framework"
],
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"example": "Task"
}
],
"requestBody": {
"content": {
"application/json": {
"example": {
"fields": [
{
"fieldname": "subject",
"fieldtype": "Data"
}
],
"name": "Task"
},
"schema": {
"$ref": "#/components/schemas/DocType"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DocType"
}
}
},
"description": "ok"
}
}
}
},
"/v1/framework/modules": {
"get": {
"operationId": "get_v1_framework_modules",
"summary": "Returns every app lane compiled into this deployment and the DocTypes each one installs.",
"description": "Returns every app lane compiled into this deployment and the\nDocTypes each one installs. It describes the BINARY, not the org: what a given\norg has actually installed is the per-module state below.",
"tags": [
"framework"
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/moduleList"
}
}
},
"description": "ok"
}
}
}
},
"/v1/framework/modules/{module}": {
"get": {
"operationId": "get_v1_framework_modules_module",
"summary": "Returns one app lane's install state for the caller's org: the DocTypes the lane declares, and which of them already exist in the org.",
"description": "Returns one app lane's install state for the caller's org: the\nDocTypes the lane declares, and which of them already exist in the org. That\nis the honest \"set up\" versus \"installed\" answer a console renders.",
"tags": [
"framework"
],
"parameters": [
{
"name": "module",
"in": "path",
"required": true,
"description": "Module is the lane's registered name (\"cms\", \"erp\"), from the path.",
"schema": {
"type": "string"
},
"example": "cms"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ModuleState"
}
}
},
"description": "ok"
}
}
}
},
"/v1/framework/modules/{module}/install": {
"post": {
"operationId": "post_v1_framework_modules_module_install",
"summary": "Creates an app lane's DocTypes in the caller's org.",
"description": "Creates an app lane's DocTypes in the caller's org. Idempotent\nand create-if-absent: a DocType the org already has is reported as existing\nand never replaced, so re-installing cannot clobber a definition the org has\nsince edited. Manager-only.",
"tags": [
"framework"
],
"parameters": [
{
"name": "module",
"in": "path",
"required": true,
"description": "Module is the lane's registered name (\"cms\", \"erp\"), from the path.",
"schema": {
"type": "string"
},
"example": "cms"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Install"
}
}
},
"description": "ok"
}
}
}
},
"/v1/framework/roles": {
"get": {
"operationId": "get_v1_framework_roles",
"summary": "Returns every (user, role) assignment in the caller's org.",
"description": "Returns every (user, role) assignment in the caller's org. Roles are\nwhat DocType permissions are written against, so this is the grant table the\npermission calculus resolves a member's rights from.",
"tags": [
"framework"
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/roleList"
}
}
},
"description": "ok"
}
}
},
"post": {
"operationId": "post_v1_framework_roles",
"summary": "Grants one user one role in the caller's org — how a member gains rights on a DocType, since permissions name roles and never users.",
"description": "Grants one user one role in the caller's org — how a member gains\nrights on a DocType, since permissions name roles and never users.\nManager-only. Answers 201.",
"tags": [
"framework"
],
"requestBody": {
"content": {
"application/json": {
"example": {
"role": "System Manager",
"user": "u_alice"
},
"schema": {
"$ref": "#/components/schemas/Role"
}
}
},
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Role"
}
}
},
"description": "created"
}
}
}
},
"/v1/framework/roles/{user}/{role}": {
"delete": {
"operationId": "delete_v1_framework_roles_user_role",
"summary": "Removes one (user, role) grant in the caller's org.",
"description": "Removes one (user, role) grant in the caller's org. Manager-only.\nAnswers 204; a grant that does not exist is not found.",
"tags": [
"framework"
],
"parameters": [
{
"name": "user",
"in": "path",
"required": true,
"description": "User is the assignee whose grant is being revoked, from the path.",
"schema": {
"type": "string"
},
"example": "u_alice"
},
{
"name": "role",
"in": "path",
"required": true,
"description": "Role is the role to revoke, from the path. A role name containing a space\n(\"System Manager\") arrives percent-encoded and is decoded before it is\nmatched against the stored assignment.",
"schema": {
"type": "string"
},
"example": "System Manager"
}
],
"responses": {
"204": {
"description": "no content"
}
}
}
},
"/v1/framework/summary": {
"get": {
"operationId": "get_v1_framework_summary",
"summary": "Reports how much of the DocType surface the caller's org uses: how many DocTypes it has defined, and how many documents exist across them.",
"description": "Reports how much of the DocType surface the caller's org uses: how\nmany DocTypes it has defined, and how many documents exist across them.",
"tags": [
"framework"
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/summaryView"
}
}
},
"description": "ok"
}
}
}
},
"/v1/framework/{doctype}": {
"get": {
"operationId": "get_v1_framework_doctype",
"summary": "Returns the caller org's documents of one DocType, filtered, ordered and projected by the query.",
"description": "Returns the caller org's documents of one DocType, filtered,\nordered and projected by the query. The DocType is resolved FIRST — through\nthe same permission gate the list itself uses — because the query is validated\nagainst its schema: a filter, sort or field name the DocType does not declare\nis refused rather than reaching the store.",
"tags": [
"framework"
],
"parameters": [
{
"name": "doctype",
"in": "path",
"required": true,
"description": "DocType is the DocType to list, from the path.",
"schema": {
"type": "string"
},
"example": "Task"
},
{
"name": "filters",
"in": "query",
"required": false,
"description": "Filters is a JSON object of equality matches, e.g. {\"priority\":\"High\"}.\nEvery key must be a field the DocType declares (or the managed name /\ndocstatus); an undeclared one is refused rather than silently ignored.",
"schema": {
"type": "string"
},
"example": "{\"priority\":\"High\"}"
},
{
"name": "fields",
"in": "query",
"required": false,
"description": "Fields projects the response to a subset — a JSON array [\"a\",\"b\"] or a\ncomma list \"a,b\". The envelope keys are always returned.",
"schema": {
"type": "string"
}
},
{
"name": "order_by",
"in": "query",
"required": false,
"description": "OrderBy is \"\u003cfield\u003e [asc|desc]\". Empty means most-recently-updated first.",
"schema": {
"type": "string"
},
"example": "estimate asc"
},
{
"name": "limit",
"in": "query",
"required": false,
"description": "Limit caps the rows returned. Anything that is not a positive integer\nleaves the engine's default in place.",
"schema": {
"type": "string"
},
"example": "20"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/documentList"
}
}
},
"description": "ok"
}
}
},
"post": {
"operationId": "post_v1_framework_by_doctype",
"summary": "Create one document of a DocType, from that DocType's own fields.",
"description": "The body is the DOCUMENT'S field data: a flat JSON object whose properties are the fieldnames the DocType declares, not a fixed envelope. That is why this operation publishes no request schema — the shape is metadata the DocType defines at run time, and no Go struct both accepts it verbatim and describes it, so nothing is asserted rather than something false.\n\nThe engine validates and coerces every field against the DocType, runs the before_insert and before_save hooks (either may reject the write), stores the document, then runs the after hooks. It answers 201 with the stored document: the field data plus the managed envelope — `name`, `doctype`, `docstatus`, `createdAt`, `updatedAt`. A Password field comes back as a fixed redaction marker and is dropped when empty; its stored value is never returned by this or any other read on this surface.\n\n`name` in the body is the REQUESTED DOCUMENT NAME, not a data field. A DocType with an autoname rule names the document itself and ignores it; a prompt-named DocType takes it. This collision is also why the two path segments cannot be folded into the body, and so why the route stays untyped.\n\nScoped to the org of the validated principal, and the engine's own permission calculus decides the rest: the caller needs create rights on this DocType through a role it holds, or a platform admin bit. A caller with no validated principal reaches the engine as the zero Caller and is refused before any store is opened — a forged org header alone buys nothing.\n\nA DocType declared Single has exactly ONE document per org, so this writes that one instance instead of adding a row. The body is size-bounded by the engine, the same bound on every host.",
"tags": [
"framework"
],
"parameters": [
{
"name": "doctype",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
]
}
},
"/v1/framework/{doctype}/{name}": {
"delete": {
"operationId": "delete_v1_framework_doctype_name",
"summary": "Removes one document, after its on_trash hooks agree.",
"description": "Removes one document, after its on_trash hooks agree. A\nSUBMITTED document cannot be deleted — cancel it first. Answers 204.",
"tags": [
"framework"
],
"parameters": [
{
"name": "doctype",
"in": "path",
"required": true,
"description": "DocType is the document's DocType, from the path.",
"schema": {
"type": "string"
},
"example": "Task"
},
{
"name": "name",
"in": "path",
"required": true,
"description": "Name is the document's name — its key within the DocType — from the path.\nA name containing a space arrives percent-encoded and is decoded before it\nis matched against the stored one.",
"schema": {
"type": "string"
},
"example": "TASK-00001"
}
],
"responses": {
"204": {
"description": "no content"
}
}
},
"get": {
"operationId": "get_v1_framework_doctype_name",
"summary": "Returns one document by name, with Password fields redacted.",
"description": "Returns one document by name, with Password fields redacted.",
"tags": [
"framework"
],
"parameters": [
{
"name": "doctype",
"in": "path",
"required": true,
"description": "DocType is the document's DocType, from the path.",
"schema": {
"type": "string"
},
"example": "Task"
},
{
"name": "name",
"in": "path",
"required": true,
"description": "Name is the document's name — its key within the DocType — from the path.\nA name containing a space arrives percent-encoded and is decoded before it\nis matched against the stored one.",
"schema": {
"type": "string"
},
"example": "TASK-00001"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"additionalProperties": {
"type": "object"
},
"type": "object"
}
}
},
"description": "ok"
}
}
},
"put": {
"operationId": "put_v1_framework_by_doctype_by_name",
"summary": "Replace a draft document's field data wholesale.",
"description": "PUT semantics: the stored field data BECOMES the body, so a field the body omits is not left at its previous value. The body is the document's own field data — the same metadata-defined open object the create takes, and the same reason this operation publishes no request schema.\n\nOnly a DRAFT can be edited. A document that has been submitted or cancelled is immutable and the write is refused as a conflict, so the submit lifecycle cannot be bypassed by a plain update — cancel it first, and note that a cancelled document can be deleted but never re-submitted or re-edited. The engine validates the new data against the DocType, runs before_save (which may reject), writes, then runs the after hooks, and answers 200 with the stored document plus its managed envelope, Password fields redacted.\n\nThe document name in the path is percent-decoded before it is matched, so a name containing a space is addressed as it is stored. An unknown DocType or document is not found, and the same answer covers a document that exists in another tenant: the org comes from the validated principal and is part of the store key, so a caller cannot learn that another org's document exists. Write rights on the DocType are required, decided by the engine's permission calculus.\n\nFor a Single DocType the path name is ignored — there is one instance per org and this writes it.",
"tags": [
"framework"
],
"parameters": [
{
"name": "doctype",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
]
}
},
"/v1/framework/{doctype}/{name}/cancel": {
"post": {
"operationId": "post_v1_framework_doctype_name_cancel",
"summary": "Moves a submitted document to cancelled (docstatus 1 → 2) after its on_cancel hooks agree.",
"description": "Moves a submitted document to cancelled (docstatus 1 → 2) after\nits on_cancel hooks agree. Cancelling is terminal — a cancelled document\ncannot be re-submitted — but it CAN then be deleted.",
"tags": [
"framework"
],
"parameters": [
{
"name": "doctype",
"in": "path",
"required": true,
"description": "DocType is the document's DocType, from the path.",
"schema": {
"type": "string"
},
"example": "Task"
},
{
"name": "name",
"in": "path",
"required": true,
"description": "Name is the document's name — its key within the DocType — from the path.\nA name containing a space arrives percent-encoded and is decoded before it\nis matched against the stored one.",
"schema": {
"type": "string"
},
"example": "TASK-00001"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"additionalProperties": {
"type": "object"
},
"type": "object"
}
}
},
"description": "ok"
}
}
}
},
"/v1/framework/{doctype}/{name}/submit": {
"post": {
"operationId": "post_v1_framework_doctype_name_submit",
"summary": "Moves a draft to submitted (docstatus 0 → 1) after its on_submit hooks agree.",
"description": "Moves a draft to submitted (docstatus 0 → 1) after its\non_submit hooks agree. A submitted document is IMMUTABLE: further writes and\ndeletes are refused until it is cancelled. Only a submittable DocType has this\nlifecycle; any other docstatus is an illegal transition.",
"tags": [
"framework"
],
"parameters": [
{
"name": "doctype",
"in": "path",
"required": true,
"description": "DocType is the document's DocType, from the path.",
"schema": {
"type": "string"
},
"example": "Task"
},
{
"name": "name",
"in": "path",
"required": true,
"description": "Name is the document's name — its key within the DocType — from the path.\nA name containing a space arrives percent-encoded and is decoded before it\nis matched against the stored one.",
"schema": {
"type": "string"
},
"example": "TASK-00001"
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"additionalProperties": {
"type": "object"
},
"type": "object"
}
}
},
"description": "ok"
}
}
}
}
},
"components": {
"schemas": {
"DocField": {
"properties": {
"default": {
"type": "string"
},
"fetchFrom": {
"type": "string"
},
"fieldname": {
"type": "string"
},
"fieldtype": {
"type": "string"
},
"hidden": {
"type": "boolean"
},
"inListView": {
"type": "boolean"
},
"label": {
"type": "string"
},
"options": {
"type": "string"
},
"readOnly": {
"type": "boolean"
},
"reqd": {
"type": "boolean"
},
"unique": {
"type": "boolean"
}
},
"type": "object"
},
"DocPerm": {
"properties": {
"cancel": {
"type": "boolean"
},
"create": {
"type": "boolean"
},
"delete": {
"type": "boolean"
},
"read": {
"type": "boolean"
},
"role": {
"type": "string"
},
"submit": {
"type": "boolean"
},
"write": {
"type": "boolean"
}
},
"type": "object"
},
"DocType": {
"properties": {
"autoname": {
"type": "string"
},
"createdAt": {
"type": "integer"
},
"fields": {
"items": {
"$ref": "#/components/schemas/DocField"
},
"type": "array"
},
"isSingle": {
"type": "boolean"
},
"isSubmittable": {
"type": "boolean"
},
"module": {
"type": "string"
},
"name": {
"type": "string"
},
"permissions": {
"items": {
"$ref": "#/components/schemas/DocPerm"
},
"type": "array"
},
"titleField": {
"type": "string"
},
"updatedAt": {
"type": "integer"
}
},
"type": "object"
},
"Install": {
"properties": {
"created": {
"items": {
"type": "string"
},
"type": "array"
},
"existing": {
"items": {
"type": "string"
},
"type": "array"
},
"module": {
"type": "string"
}
},
"type": "object"
},
"ModuleInfo": {
"properties": {
"doctypes": {
"items": {
"type": "string"
},
"type": "array"
},
"module": {
"type": "string"
}
},
"type": "object"
},
"ModuleState": {
"properties": {
"doctypes": {
"items": {
"type": "string"
},
"type": "array"
},
"installed": {
"items": {
"type": "string"
},
"type": "array"
},
"module": {
"type": "string"
}
},
"type": "object"
},
"Role": {
"properties": {
"role": {
"type": "string"
},
"user": {
"type": "string"
}
},
"type": "object"
},
"docTypeList": {
"properties": {
"data": {
"description": "Data is every DocType defined in the caller's org.",
"items": {
"$ref": "#/components/schemas/DocType"
},
"type": "array"
}
},
"type": "object"
},
"documentList": {
"properties": {
"data": {
"description": "Data is the matching documents, newest-updated first unless order_by said\notherwise, each projected to the requested fields plus the envelope keys.",
"items": {
"additionalProperties": {
"type": "object"
},
"type": "object"
},
"type": "array"
}
},
"type": "object"
},
"moduleList": {
"properties": {
"data": {
"description": "Data is every module compiled into this binary, with the DocTypes it installs.",
"items": {
"$ref": "#/components/schemas/ModuleInfo"
},
"type": "array"
}
},
"type": "object"
},
"roleList": {
"properties": {
"data": {
"description": "Data is every (user, role) assignment in the caller's org.",
"items": {
"$ref": "#/components/schemas/Role"
},
"type": "array"
}
},
"type": "object"
},
"summaryView": {
"properties": {
"doctypes": {
"description": "DocTypes is how many DocTypes the org has defined.",
"type": "integer"
},
"documents": {
"description": "Documents is how many documents exist across them.",
"type": "integer"
}
},
"type": "object"
}
}
}
}