API Contract (OpenAPI)
Overview
The OpenAPI 3.1 specification at /openapi.yaml is the single source of truth for all API types and endpoints.
Bi-Directional Codegen
TypeScript (Frontend)
cd packages/api-client
pnpm generateGenerates TypeScript types and a type-safe openapi-fetch client.
Go (Backend)
The Go handlers are manually implemented to match the contract, but request/response types can be validated against the OpenAPI spec.
Development Workflow
- Modify
/openapi.yamlwith new endpoints, schemas, request/response bodies - Run codegen to update TypeScript types
- Implement Go handlers matching the contract
- Build Next.js Server Actions proxying the new endpoints
- Test end-to-end
Key Rules
- Never hand-write API types or fetchers
- Every feature starts with an OpenAPI contract change
- The contract must be reviewed and approved before implementation begins
Exposure and the public API
Every operation is internal unless openapi.yaml says otherwise:
x-api-exposure: beta | stableopens an operation to access tokens. Absent means internal.x-api-since: "YYYY-MM-DD"is required on an exposed operation.x-api-sunset(a date) marks one for retirement.x-api-sensitivity: personal_contact | personal_diversity | financial_rate | secretsits on a response property. Tokens without the matching scope getnull(contact, diversity), rates are alwaysnull, and secrets are removed.
The x-* keys are a closed list per location. A misspelled key fails pnpm api:codegen and CI.
pnpm api:codegen runs apigen, which writes the public spec (openapi.public.yaml), the route table the API enforces (backend/pkg/apimap/apimap.generated.go), the stable-shape manifest (api.frozen.txt), the API reference and llms.txt. CI regenerates them and fails on any difference.
Gates
| Gate | Fails when |
|---|---|
| G1 | a generated output is out of date |
| G2 | an exposed operation has no permission key with a token scope, no 2xx schema, a non-token security scheme, a financial, destructive or external risk, or sits on a contract-truth ratchet |
| G3 | a stable operation’s shape breaks callers |
| G4 | an exposed response has an unclassified personal-looking field or an open object |
| G5 | an exposed write has no cross-org test |
| G6 | an exposed write could skip the Idempotency-Key check |
| G7 | an exposed operation names an entity with no ontology card |
| G10 | an exposed operation is not served, or the server serves a route the contract does not declare (beyond the list) |