Company
Package company is incorporation end to end: pick a structure, add founders, pay, file, and e-sign.
Package company is incorporation end to end: pick a structure, add founders, pay, file, and e-sign.
| Base URL | https://api.hanzo.ai |
| Operations | 23 |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Specification
HIP-1312 · Company — The Formation Machine — Draft · read the specification →
/v1/company brings a legal entity into existence and records who owns it:
choose a structure, add founders, clear identity, pay the formation fee,
generate documents, route signature, anchor the equity genesis. The
implementation is hanzoai/cloud apps/company.
The capability is a guarded state machine — a closed table of stages and the
edges between them (apps/company/machine.go) — plus provider seams that reach
the outside world (apps/company/providers.go). HIP-0903 argues why a firm
should run this way; this is the contract the surface answers to.
Motivation
Formation is a sequence where every step depends on a fact an earlier one established, and most of those facts are legal rather than technical: money must not move before identity clears, and an entity must not be reported as formed before it is.
Enumerating what a caller may do — a permission list — fails in the direction that matters here. A list has gaps, and a gap is a company incorporated without a verified person behind it. A closed edge table has none: the illegal move is not forbidden, it is absent.
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
§1 The machine
Two paths reach the terminal stage, every edge carrying a guard
(apps/company/machine.go): incorporate — structure → founders → payment → documents → esign → genesis → company, guarded by structure chosen, identity
verified, fee paid, documents generated, signature complete, genesis recorded;
and import — structure → import → company, for an entity that already
exists and brings its own cap table and documents.
The table MUST stay closed: an edge that is not listed does not exist, so no request shape, argument or ordering advances a formation past a guard. A new step is a new edge with a new guard in the table, never a condition in a handler.
§2 The addresses
Twenty-one paths, every one under /v1/company (manifest/apps.go:433,
plugin/company/openapi.json): the machine's edges (/structure, /founders,
/kyc, /kyc/decision, /kyc/refresh, /payment, /documents, /esign,
/esign/complete, /genesis, /advance, /skip), the import pair, the
fundraise trio, the platform book (/register, /register/summary, /review)
and the GET|POST /v1/company root.
Twenty operations are typed. Two are declared with prose beside the route and
held to that count by a test (apps/company/typed_wire_test.go): the deck
upload takes a PDF as the raw request body, which has no value to name, and
POST /payment answers denial with the fleet's nested error body, which a typed
operation cannot emit. Both declare their bodies through openapi.Register, so
neither publishes an address nobody can explain.
§3 The store, and the tenant
One SQLite file in the system namespace — company — holds each org's formation
as a single row keyed by the org, at most one formation per org
(apps/company/store.go:22-28). Documents, cap-table rows and signing material
are NOT here: they live with dataroom, captable and kms behind the
provider seams (apps/company/providers.go), and a formation row that also held
them would make this capability the second owner of three other stores.
The org is principal.Org, the validated IAM owner claim (HIP-0026), on every
read and write; a caller with no validated principal is refused.
Two surfaces are SuperAdmin's and cross-tenant by nature: the platform book
(/register, /register/summary, /review) and the identity decision
(/kyc/decision). Hanzo forms the entity, so Hanzo carries the formation
identity obligation, and discharging it is the platform's own act rather than a
predicate over the customer's. The two planes MUST stay orthogonal — the
SuperAdmin operation writes a fact, the machine's guard reads it, neither calls
the other — and a reviewer's confirmation MUST be recorded as its own distinct
value, never as a provider verification. "A human confirmed this" and "a
provider verified this" stay different facts to an auditor forever. Every
privileged decision carries the deciding reviewer.
§4 The genesis anchor
The founding allocation — entity plus founders, ordered deterministically — is
hashed to a keccak root and committed to the Hanzo L1 (chain 36963) by a
KMS-signed transaction (apps/company/genesis.go). The chain is the source of
truth, so a holder of the allocation recomputes the root without trusting this
platform. When the RPC or the signer is unconfigured, the root MUST be returned
with an honest pending status: a transaction hash is never fabricated and
formation is never blocked on an unreachable chain.
§5 Price, events, emission, stage, upstream
The capability is metered: Price: cloud.Metered
(plugin/company/main.go:27; spend.go:294) — the surface moves a four-figure
sum, so the edge requires standing. The one charge is the one-time formation fee —
99900 cents (formationFeeCents, apps/company/providers.go:146; operator
override CLOUD_COMPANY_FEE_CENTS, apps/company/company.go:290-297) — taken
at POST /v1/company/payment through the charge seam onto the org's own ledger.
Insufficient balance is 402 and an unreachable balance is 503; neither advances
the stage, because the paid guard reads the receipt rather than the attempt
(apps/company/machine.go:332-335).
It publishes no events on the bus, so a customer's webhooks (HIP-1310) receive
nothing from it — provider completion arrives INBOUND at /esign/complete and
/kyc/decision, it is not emitted. It emits nothing to observability beyond the
request span every route gets.
The stage is beta (HIP-0139 §8): a vertical application rather than core, so
an org reaches it by the company flag. The manifest row declares it
(manifest/apps.go:433, Stage: Beta). It derives from no OSS upstream — the
identity and filing providers are hand-written clients behind the seams
(apps/company/providers.go, apps/company/filing.go).
Rationale
The alternative to the closed edge table is scopes: enumerate what each caller may invoke and check the enumeration in every handler. It costs one gap to be wrong, the gaps are invisible until exploited, and each new stage multiplies the checks that must agree. The table has one place to be right.
The alternative to the platform book is per-tenant records only, which cannot answer what the obligation actually poses: how many formations await review, and which have waited longest. That question is cross-tenant because the duty is.
Security Considerations
The wrong implementation hands an attacker one of three things. Another org's formation row is its founders' identity documents and personal data — the most sensitive record this platform holds outside secrets. A decision operation reachable by a non-SuperAdmin is laundered identity approval: a self-declared founder who then clears the guard. And an edge table with one extra entry is a company formed without payment or without a verified person behind it — a real entity, in a real jurisdiction, traceable to us. The anchor adds a fourth: a fabricated transaction hash is a false claim of public verifiability, worse than no claim, because a reader who checks the chain and finds nothing has already relied on it.
Four surfaces
| Surface | Reaches this capability as | Coverage |
|---|---|---|
| REST | company at its own prefix | 23 operations |
| CLI | hanzo company … | 22 of 23 — the CLI pins the document on its own clock |
| SDK | CompanyApi in every published client | 22 of 23 — the clients are generated at their own release |
| MCP | tool company on https://api.hanzo.ai/v1/mcp | 22 operations, 4 under the document's own id — ask describe for the rest |
Quickstart
export HANZO_API_KEY=sk-... # console.hanzo.ai → API keysThen the first call — a read that needs nothing but the key. GET /v1/company, operation get_company:
hanzo company getimport { Configuration, CompanyApi } from 'hanzoai';
const api = new CompanyApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getCompany();from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import CompanyApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = CompanyApi(client).get_company()cfg := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.CompanyAPI.GetCompany(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::apis::{configuration::Configuration, company_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = company_api::get_company(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.CompanyApi;
ApiClient client = new ApiClient();
client.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new CompanyApi(client).getCompany();curl https://api.hanzo.ai/v1/company \
-H "Authorization: Bearer $HANZO_API_KEY"Tool company, op get_company — POST the JSON-RPC envelope to https://api.hanzo.ai/v1/mcp.
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "company",
"arguments": {
"op": "get_company",
"input": {}
}
}
}'Answers 200 with object — ok.
Endpoints
| Endpoint | What it does |
|---|---|
POST /v1/company/advance | Advance runs the ONE guarded transition of the formation machine. |
POST /v1/company/documents | Renders the formation documents for the chosen structure and jurisdiction, ingests each into the org's data room, and submits the state filing… |
POST /v1/company/esign/complete | Records whether the formation documents have been signed. |
POST /v1/company/esign | Sends the generated formation documents for signature by every founder and records the provider's reference on the formation. |
POST /v1/company/founders | Replaces the formation's founders. |
POST /v1/company/fundraise/deck | Share a pitch deck in the org's data room |
POST /v1/company/fundraise/round | Records a fundraising round on the org's canonical cap table. |
POST /v1/company/fundraise/safe | Raises an e-signature request over documents already in the org's data room — a SAFE, a convertible note, or any other fundraising paper. |
POST /v1/company/genesis | Seeds the canonical cap table with the founding allocation (stakeholders, a common share class, issued shares) and anchors the deterministic… |
POST /v1/company/import/captable | Reads an existing company's cap table from a Google Sheet and adds its stakeholders to the canonical cap table. |
POST /v1/company/import/documents | Ingests an existing company's corporate documents from a Google Drive folder into the org's data room. |
POST /v1/company/kyc/decision | DecideKYC records a privileged reviewer's MANUAL decision on a founder's KYC — the human-in-the-loop path, and the ONLY route to a pass when no real… |
POST /v1/company/kyc/refresh | RefreshKYC reconciles each pending founder's KYC with the WIRED provider — the PULL path to a provider-reported terminal status. |
POST /v1/company/kyc | StartKYC opens an identity-verification session for every founder with the wired provider and records each session's reference on the formation. |
POST /v1/company/payment | Charges the caller's own org the one-time Hanzo Company formation fee. |
GET /v1/company/register/summary | Counts the platform's formations by stage — the register's shape in one read, so a queue that is growing is visible as a number rather than inferred… |
GET /v1/company/register | Returns the platform's whole formation register, newest activity first — every org's formation, not the caller's. |
GET /v1/company/review | Reports the founders whose KYC is not yet settled, oldest formation first, so the queue drains in the order founders have been waiting. |
POST /v1/company/skip | Skip marks the org as already incorporated and moves it onto the import path, so an existing company brings its documents and cap table in instead of… |
PUT /v1/company/structure | Records the entity kind, the state of formation and the proposed name. |
POST /v1/company/tariff | Itemises what a formation costs before anyone commits to it. |
GET /v1/company | Get returns the caller org's formation and the stages reachable from it, or 404 when the org has not begun one. |
POST /v1/company | Begin starts the org's one formation and returns it with the stages reachable from it. |
How is this guide?