# Captable

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

> Your cap table: stakeholders, share classes, grants, SAFEs, rounds, and who owns what.

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

Your cap table: stakeholders, share classes, grants, SAFEs, rounds, and who owns what.

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

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

## Specification

> **HIP-1111 · Captable — Who Owns What** — Draft · [read the specification →](https://github.com/hanzoai/hips/blob/7ed737180339cd05b5f2935eafe7f7b83d2449a2/HIPs/hip-1111-captable.md)

`/v1/captable` is a company's cap table: stakeholders, share classes, share
certificates and transfers, option grants and equity plans, SAFEs and
convertible notes, priced rounds and their investments, and the summary that
totals outstanding and fully-diluted ownership from them. It is implemented in
`hanzoai/cloud` `apps/captable`. This HIP states the design worth holding to:
the business logic is a ported bundle that carries no storage, the Go host is
storage that carries no business logic, and each tenant's table is its own
database file.

### Motivation

The predecessor was a standalone application — Next.js, Prisma, Postgres — that
duplicated the platform's identity, storage and deployment for one vertical.
The fold retires that deployment entirely (`apps/captable/captable.go:28-33`):
cloud's per-tenant store is authoritative from the first write, with no data to
migrate and no second pod to keep honest.

### Specification

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

#### §1 Logic and storage are two artifacts

The cap-table logic — validation, conversion math, round mechanics — lives in a
self-contained bundle, `github.com/hanzoai/captable` (v1.0.0 in `go.mod:140`),
executed by the reusable goja host (`apps/goja`, over `dop251/goja`, MIT). The
Go host injects exactly three primitives — `__db`, `__newId`, `__now` — and
gives each request one SQLite transaction (`apps/captable/captable.go:9-22`).
The bundle MUST NOT reach storage except through them, and the host MUST NOT
reimplement a rule the bundle carries: one owner per concern, so the math
cannot drift between two copies.

#### §2 The store

One SQLite file per tenant, selected by the validated org, under the host's
data directory. The org selects the file AND scopes every row
(`apps/captable/captable.go:24-26`): a cross-tenant read requires both a wrong
file and a wrong predicate, which is the point of paying for two mechanisms.
The per-tenant schema is seeded on first touch (`apps/captable/schema.go:224`).

#### §3 The addresses

Everything is under `/v1/captable`: `company`, `stakeholders`, `classes`,
`shares` (and `shares/transfer`), `plans`, `options`, `safes`, `convertibles`,
`rounds` (with `close` and `investments`), `investments`, and `summary` —
collection and record routes composed on one group from the single prefix.
Operations are typed through the registry; the wire tests
(`apps/captable/typed_test.go`, `bodyless_test.go`) hold the surface to it.

#### §4 The in-process seam

`apps/captable/facade.go` lets a sibling subsystem — company formation, import
and fundraising flows — write to a tenant's cap table without an HTTP hop, by
dispatching the same bundle routes the handlers do. The caller MUST pass an
already-validated org (`apps/captable/facade.go:15-19`); the facade grants no
authority the HTTP path does not, it only removes the hop.

#### §5 Tenancy, money, events, telemetry, stage

Every route resolves the org from the validated principal (`principal.Org` /
`principal.Acting`, HIP-0026), never a client header
(`apps/captable/captable.go:359-361`); no principal, no answer. The capability
is free (`plugin/captable/main.go:21`, `cloud.Free`). It publishes no events on
the bus. It emits nothing to observability beyond the request span. Its stage
is `beta`: a vertical application, not the agentic-OS core.

#### §6 Upstreams

`dop251/goja` (MIT) is embedded as the bundle interpreter, through the shared
`apps/goja` host. `hanzoai/captable` is our own module, a port of the retired
application's tRPC logic. Storage is the `hanzoai/sqlite` facade like every
store in the binary. Nothing else is forked or mirrored.

### Rationale

The alternative to the bundle-plus-host split is a rewrite of the cap-table
math in Go. That is a second implementation of conversion and dilution rules to
keep in agreement with the one that was already trusted, and cap-table math is
exactly where a quiet divergence costs the most. Porting the logic whole and
giving it persistence keeps one implementation; the goja host is the price, and
it is shared with esign and dataroom rather than paid three times
(`apps/captable/captable.go:14-16`).

### Security Considerations

A cap table is the ownership record of a company: who holds what, at what
price, under what terms. A wrong implementation leaks a competitor's round to a
tenant, or worse, lets one tenant write another's ledger — a forged transfer is
a forged ownership claim. Tenancy is therefore physical (a file per tenant)
plus scoped (org on every row), and the org is never an input on any HTTP
route. The facade is the one path that takes org as a parameter, which is why
its contract requires the caller to have validated it first; a caller that
passes an unvalidated org has recreated the client-supplied-header hole
in-process.

## Four surfaces

| Surface  | Reaches this capability as                       | Coverage                                                                   |
| -------- | ------------------------------------------------ | -------------------------------------------------------------------------- |
| **REST** | `captable` at its own prefix                     | 31 operations                                                              |
| **CLI**  | `hanzo captable …`                               | 31 of 31                                                                   |
| **SDK**  | `CaptableApi` in every published client          | 31 methods                                                                 |
| **MCP**  | tool `captable` on `https://api.hanzo.ai/v1/mcp` | 30 operations, 2 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/captable/plans`, operation `get_captable_plans`:

**CLI**

```bash
hanzo captable plans get
```

**SDK**

**.ts**

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

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

**.py**

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

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

**.go**

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

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

**.rs**

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

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

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

**.java**

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

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

var result = new CaptableApi(client).getCaptablePlans();
```

**HTTP**

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

**MCP**

MCP reaches **captable** through the `captable` tool, which names its 30 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": "list_captable_classes"
         }
       }
     }'
```

Answers `200` with `object` — ok.

## Endpoints

| Endpoint                                                                                                                          | What it does                                                                                                                       |
| --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [`PATCH /v1/captable/classes/{id}`](https://docs.hanzo.ai/docs/openapi/captable/patch-captable-classes-by-id)                     | Replaces one share class's terms.                                                                                                  |
| [`GET /v1/captable/classes`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-classes)                                    | Returns the caller org's share classes, in creation order.                                                                         |
| [`POST /v1/captable/classes`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-classes)                                  | Defines a new class of shares.                                                                                                     |
| [`GET /v1/captable/company`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-company)                                    | Returns the caller org's cap-table company record.                                                                                 |
| [`PUT /v1/captable/company`](https://docs.hanzo.ai/docs/openapi/captable/put-captable-company)                                    | Sets the caller org's company name and incorporation details.                                                                      |
| [`DELETE /v1/captable/convertibles/{id}`](https://docs.hanzo.ai/docs/openapi/captable/delete-captable-convertibles-by-id)         | Removes one of the caller org's convertible notes, taking its principal out of the cap table's unconverted-instrument totals.      |
| [`GET /v1/captable/convertibles`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-convertibles)                          | Returns the caller org's convertible notes, newest first.                                                                          |
| [`POST /v1/captable/convertibles`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-convertibles)                        | Records a convertible note.                                                                                                        |
| [`GET /v1/captable/investments`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-investments)                            | Returns the caller org's investments, newest first.                                                                                |
| [`DELETE /v1/captable/options/{id}`](https://docs.hanzo.ai/docs/openapi/captable/delete-captable-options-by-id)                   | Removes one of the caller org's option grants, taking its shares out of the cap table's granted-options and fully-diluted counts.  |
| [`GET /v1/captable/options`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-options)                                    | Returns the caller org's option grants, newest first.                                                                              |
| [`POST /v1/captable/options`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-options)                                  | Grants options to a stakeholder from an equity plan.                                                                               |
| [`GET /v1/captable/plans`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-plans)                                        | Returns the caller org's equity plans, newest first.                                                                               |
| [`POST /v1/captable/plans`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-plans)                                      | Opens an equity plan that options are granted from.                                                                                |
| [`POST /v1/captable/rounds/{id}/close`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-rounds-by-id-close)             | Closes one of the caller org's fundraising rounds, recording the close date and moving its status to CLOSED.                       |
| [`POST /v1/captable/rounds/{id}/investments`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-rounds-by-id-investments) | Records one investor's money into an open round.                                                                                   |
| [`GET /v1/captable/rounds/{id}`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-rounds-by-id)                           | Returns one of the caller org's fundraising rounds together with every investment written into it, oldest first.                   |
| [`GET /v1/captable/rounds`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-rounds)                                      | Returns the caller org's fundraising rounds, newest first.                                                                         |
| [`POST /v1/captable/rounds`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-rounds)                                    | Opens a priced round that investments can be added to.                                                                             |
| [`DELETE /v1/captable/safes/{id}`](https://docs.hanzo.ai/docs/openapi/captable/delete-captable-safes-by-id)                       | Removes one of the caller org's SAFEs, taking its capital out of the cap table's unconverted-instrument totals.                    |
| [`GET /v1/captable/safes`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-safes)                                        | Returns the caller org's SAFEs, newest first.                                                                                      |
| [`POST /v1/captable/safes`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-safes)                                      | Records a SAFE — a simple agreement for future equity.                                                                             |
| [`DELETE /v1/captable/shares/{id}`](https://docs.hanzo.ai/docs/openapi/captable/delete-captable-shares-by-id)                     | Removes one of the caller org's share certificates, taking its shares out of the cap table's outstanding and fully-diluted counts. |
| [`POST /v1/captable/shares/transfer`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-shares-transfer)                  | Moves shares from one stakeholder to another.                                                                                      |
| [`GET /v1/captable/shares`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-shares)                                      | Returns the caller org's share certificates, newest first.                                                                         |
| [`POST /v1/captable/shares`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-shares)                                    | Issues a share certificate to a stakeholder.                                                                                       |
| [`PATCH /v1/captable/stakeholders/{id}`](https://docs.hanzo.ai/docs/openapi/captable/patch-captable-stakeholders-by-id)           | Changes one of the caller org's stakeholders.                                                                                      |
| [`DELETE /v1/captable/stakeholders/{id}`](https://docs.hanzo.ai/docs/openapi/captable/delete-captable-stakeholders-by-id)         | Removes one of the caller org's stakeholders.                                                                                      |
| [`GET /v1/captable/stakeholders`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-stakeholders)                          | Returns the caller org's stakeholders, newest first.                                                                               |
| [`POST /v1/captable/stakeholders`](https://docs.hanzo.ai/docs/openapi/captable/post-captable-stakeholders)                        | Add stakeholders to the cap table                                                                                                  |
| [`GET /v1/captable/summary`](https://docs.hanzo.ai/docs/openapi/captable/get-captable-summary)                                    | Computes the caller org's cap table.                                                                                               |

---

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