# Framework

URL: https://docs.hanzo.ai/docs/openapi/framework

> Document types you define: describe a record once, then create, list, submit and cancel documents against it.

Also for this capability: **API** · [CLI](https://docs.hanzo.ai/docs/cli/framework) · [MCP](https://docs.hanzo.ai/docs/mcp-tools/framework) · [SDKs](https://docs.hanzo.ai/docs/sdks)

Document types you define: describe a record once, then create, list, submit and cancel documents against it.

> [All API references →](https://docs.hanzo.ai/docs/openapi) · [Get started →](https://docs.hanzo.ai/docs/guides)

|                |                                        |
| -------------- | -------------------------------------- |
| **Base URL**   | `https://api.hanzo.ai`                 |
| **Operations** | 16                                     |
| **Auth**       | `Authorization: Bearer $HANZO_API_KEY` |

## Specification

> **HIP-1126 · Framework — The DocType Engine** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1126-framework-doctype-engine.md)

`/v1/framework` is document types an org defines: describe a record once — its
fields, naming rule, lifecycle, and which role may do what — then create, list,
submit and cancel documents against it. The engine is
`github.com/hanzoai/framework`, built on the metadata model
`github.com/hanzoai/doctype`; neither knows what HTTP is, and the cloud
subsystem at `apps/framework` is the adapter — the "place" in the engine's
layering (`apps/framework/framework.go:6-14`). This HIP states the store the
place opens, how a request becomes an engine caller, and the one surface that
cannot carry a schema.

### Motivation

Several application lanes — cms, erp, help, knowledge, content, guide — each
need "a record type with permissions and a lifecycle". Building that per lane
is the same engine five times, drifting. The adapter re-exports the engine
vocabulary so every lane keeps one import and compiles unchanged
(`apps/framework/framework.go:22-24`), and the engine enforces permissions
itself, so there is no authorization logic here — a second copy would be a
second answer (`apps/framework/framework.go:29-31`).

### Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted
as in RFC 2119.

#### §1 The store

framework owns one store: the engine's single database, opened by cloud's
storage policy — `sqlpool.Open("framework", DataDir)`, encrypted at rest under
a KMS-held master key (`apps/framework/framework.go:73-85`). Cloud names the
file rather than accepting a path from the library, because a name is what
keys an encrypted file and a handed-down path cannot be. The rows inside are
per-org — every DocType and document belongs to the caller's org — with
isolation enforced by the engine's own permission calculus.

#### §2 The addresses

Every route is under `/v1/framework` (`manifest/apps.go:180`). DocType
definition, roles, modules, the summary, document reads, submit and cancel are
typed operations; the DELETEs answer no body and carry no response schema. The
two document writes — `POST /v1/framework/{doctype}` and
`PUT /v1/framework/{doctype}/{name}` — are raw handlers and MUST stay raw until
the registry can carry them honestly: their request body is the document's own
field data, a flat object whose properties the DocType defines at run time, and
no Go struct both accepts that body verbatim and describes it. A reflected
schema would name the two path segments and nothing else — an SDK method that
cannot send a document. The three registry properties required to convert them
are enumerated at the registration, and a test holds the refusal until all
three exist (`apps/framework/framework.go:130-147`).

#### §3 Tenancy

The bridge parks the validated identity facts on the subtree before any route
runs (`apps/framework/framework.go:107`), and each operation turns the
validated principal's org into an engine Caller
(`apps/framework/framework.go:249`, HIP-0026); what the identity boundary
refuses, this surface refuses. On a fresh org the first caller to administer
DocTypes is seeded as its System Manager, after which only a System Manager or
a platform admin may define (`apps/framework/framework.go:395-399`) — the
engine's calculus, not the adapter's.

#### §4 Money, events, telemetry

framework is free, in those words (`plugin/framework/main.go:21`, `cloud.Free`;
not in `spend.go:275`). It publishes no events on the bus, and emits nothing to
observability beyond the request span every route gets.

#### §5 Stage

framework is `beta` — the manifest row declares `Stage: Beta`
(`manifest/apps.go:180`), so it is reached by flag while the raw document
writes in §2 wait on the registry properties that can type them. It is the
data core the application lanes stand on, so `ga` is where it is headed; the
promotion is the one manifest edit HIP-0139 §8.4 names.

#### §6 Upstream

framework derives from no third-party code. Its engine and metadata model are
the Hanzo modules `hanzoai/framework` v0.1.0 and `hanzoai/doctype` v0.1.0,
which this package adapts and re-exports.

### Rationale

Value / engine / place, rather than one HTTP-aware engine, is what keeps the
permission calculus testable without a server and reusable by lanes that are
not HTTP at all. The cost is an adapter whose whole job is translation —
principal to Caller, engine Code to HTTP status — and that cost is paid once
here instead of once per lane.

### Security Considerations

The wrong implementation answers with another org's records or lets a
non-manager redefine a type out from under its documents. Both gates are the
engine's: the adapter contributes only the validated org, never a
client-supplied one, and deliberately holds no authorization logic that could
disagree with the engine's answer. The store is encrypted at rest, so a copy
of the deployment's data directory does not yield the documents without the
key service.

## Four surfaces

| Surface  | Reaches this capability as                        | Coverage                                                                   |
| -------- | ------------------------------------------------- | -------------------------------------------------------------------------- |
| **REST** | `framework` at its own prefix                     | 16 operations                                                              |
| **CLI**  | `hanzo framework …`                               | 16 of 16                                                                   |
| **SDK**  | `FrameworkApi` in every published client          | 16 methods                                                                 |
| **MCP**  | tool `framework` on `https://api.hanzo.ai/v1/mcp` | 14 operations, 3 under the document's own id — ask `describe` for the rest |

## Quickstart

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

Then the first call — a read that needs nothing but the key. `GET /v1/framework/modules`, operation `get_framework_modules`:

**CLI**

```bash
hanzo framework modules list
```

**SDK**

**.ts**

```ts
import { Configuration, FrameworkApi } from 'hanzoai';

const api = new FrameworkApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getFrameworkModules();
```

**.py**

```python
from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import FrameworkApi

client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = FrameworkApi(client).get_framework_modules()
```

**.go**

```go
cfg := hanzoai.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := hanzoai.NewAPIClient(cfg)

resp, _, err := client.FrameworkAPI.GetFrameworkModules(context.Background()).Execute()
if err != nil {
	return err
}
```

**.rs**

```rust
use hanzo_client::apis::{configuration::Configuration, framework_api};

let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();

let result = framework_api::get_framework_modules(&cfg, Default::default()).await?;
```

**.java**

```java
import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.FrameworkApi;

ApiClient client = new ApiClient();
client.setBearerToken(System.getenv("HANZO_API_KEY"));

var result = new FrameworkApi(client).getFrameworkModules();
```

**HTTP**

```bash
curl https://api.hanzo.ai/v1/framework/modules \
  -H "Authorization: Bearer $HANZO_API_KEY"
```

**MCP**

MCP reaches **framework** through the `framework` tool, which names its 14 operations with its own verbs — this one among them, under a name only MCP declares. `describe` explains any of them:

```bash
curl -X POST https://api.hanzo.ai/v1/mcp \
  -H "Content-Type: application/json" \
  -d '{
       "jsonrpc": "2.0",
       "id": 1,
       "method": "tools/call",
       "params": {
         "name": "describe",
         "arguments": {
           "op": "get_framework_by_doctype"
         }
       }
     }'
```

Answers `200` with `object` — ok.

## Endpoints

| Endpoint                                                                                                                               | What it does                                                                                                                                                                               |
| -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`POST /v1/framework/{doctype}/{name}/cancel`](https://docs.hanzo.ai/docs/openapi/framework/post-framework-by-doctype-by-name-cancel)  | Moves a submitted document to cancelled (docstatus 1 → 2) after its on\_cancel hooks agree.                                                                                                |
| [`POST /v1/framework/{doctype}/{name}/submit`](https://docs.hanzo.ai/docs/openapi/framework/post-framework-by-doctype-by-name-submit)  | Moves a draft to submitted (docstatus 0 → 1) after its on\_submit hooks agree.                                                                                                             |
| [`GET /v1/framework/{doctype}/{name}`](https://docs.hanzo.ai/docs/openapi/framework/get-framework-by-doctype-by-name)                  | Returns one document by name, with Password fields redacted.                                                                                                                               |
| [`PUT /v1/framework/{doctype}/{name}`](https://docs.hanzo.ai/docs/openapi/framework/put-framework-by-doctype-by-name)                  | Replace a draft document's field data wholesale.                                                                                                                                           |
| [`DELETE /v1/framework/{doctype}/{name}`](https://docs.hanzo.ai/docs/openapi/framework/delete-framework-by-doctype-by-name)            | Removes one document, after its on\_trash hooks agree.                                                                                                                                     |
| [`GET /v1/framework/{doctype}`](https://docs.hanzo.ai/docs/openapi/framework/get-framework-by-doctype)                                 | Returns the caller org's documents of one DocType, filtered, ordered and projected by the query.                                                                                           |
| [`POST /v1/framework/{doctype}`](https://docs.hanzo.ai/docs/openapi/framework/post-framework-by-doctype)                               | Create one document of a DocType, from that DocType's own fields.                                                                                                                          |
| [`GET /v1/framework/doctypes/{name}`](https://docs.hanzo.ai/docs/openapi/framework/get-framework-doctypes-by-name)                     | Returns one DocType definition — its fields, naming rule, permissions and lifecycle flags.                                                                                                 |
| [`PUT /v1/framework/doctypes/{name}`](https://docs.hanzo.ai/docs/openapi/framework/put-framework-doctypes-by-name)                     | Replaces a DocType definition wholesale (PUT semantics): the stored definition becomes the body.                                                                                           |
| [`DELETE /v1/framework/doctypes/{name}`](https://docs.hanzo.ai/docs/openapi/framework/delete-framework-doctypes-by-name)               | Removes a DocType and every document stored under it.                                                                                                                                      |
| [`GET /v1/framework/doctypes`](https://docs.hanzo.ai/docs/openapi/framework/get-framework-doctypes)                                    | Returns every DocType defined in the caller's org.                                                                                                                                         |
| [`POST /v1/framework/doctypes`](https://docs.hanzo.ai/docs/openapi/framework/post-framework-doctypes)                                  | 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. |
| [`POST /v1/framework/modules/{module}/install`](https://docs.hanzo.ai/docs/openapi/framework/post-framework-modules-by-module-install) | Creates an app lane's DocTypes in the caller's org.                                                                                                                                        |
| [`GET /v1/framework/modules/{module}`](https://docs.hanzo.ai/docs/openapi/framework/get-framework-modules-by-module)                   | 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.                                                     |
| [`GET /v1/framework/modules`](https://docs.hanzo.ai/docs/openapi/framework/get-framework-modules)                                      | Returns every app lane compiled into this deployment and the DocTypes each one installs.                                                                                                   |
| [`GET /v1/framework/summary`](https://docs.hanzo.ai/docs/openapi/framework/get-framework-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.                                                 |

---

[All Hanzo APIs](https://docs.hanzo.ai/docs/openapi) · [Interactive reference](https://docs.hanzo.ai/docs/openapi/reference)
