Process
API review checklist
The ~80-item mechanical checklist every new or modified endpoint passes before merging. Consistency, semantics, security, performance, DX. Reviewers in the API Council walk this list.
Last updated
This checklist is what the API Council walks during gate 1 (spec draft review) and gate 2 (Council weekly forum). It is also the canonical reviewer cheat-sheet on every spec PR.
Items are grouped by concern. Each item references the principle, primitive, or runbook it enforces.
Consistency (shape, naming, structure)
- URL is resource-oriented, not RPC-verb-oriented. Verbs appear only as terminal path segments for actions that genuinely cannot be expressed as state.
- Path uses
snake_case(e.g./v1/share_classes, not/v1/shareClasses). - Resource name is plural (
/v1/entities, not/v1/entity). - Sub-resource paths nest correctly (
/v1/entities/{id}/grants/{grant_id}, not/v1/grants?entity_id=...for an owned sub-resource). - Typed ID prefix declared via
x-matter-id-prefix. Lives inpackages/resource-ids/src/index.ts. - JSON shapes use
snake_caseconsistently. - List response envelope is
{ object: "list", data, has_more, url, next_cursor? }— same shape across every list endpoint. - Cursor pagination with
limit(default 10, max 100),starting_after,ending_before. Never offset-based. - Custom-action verb (if any) reads naturally as an imperative —
dissolve,revoke,accelerate. Not setter-like (setStatus). - Idempotent verbs (GET, PUT, DELETE) are idempotent by HTTP definition. POST mutations require
Idempotency-Key.
Semantics (state, transitions, contracts)
- State machine (if applicable) is exhaustively typed. No string-valued status fields.
- Invalid state transitions return 409 with
code: state_transition_invalid. - Pre-conditions documented in the operation description (e.g. "Requires entity in
activestate"). - Authorising Resolution required where applicable (
authorizing_resolution_idis mandatory for cap-table mutations). - Optimistic concurrency version column on TOCTOU-exposed resources. Conflicting writes return 409.
- Composite atomic flows are sagas with typed compensation. Documented in the spec as such.
- Dry-run supported on every mutation via
?dry_run=true. Returns a structuredExecutionPlanand the would-be resource. - Async-by-default for any operation with provider I/O. Returns 202 +
Request. - Synchronous reads route through the consistency middleware. Operation declares
x-matter-consistency: strict | eventualor accepts the operation-class default. - Webhook event declared for every state transition. Event type follows the
<resource>.<verb>convention.
Error handling
- Error responses are
application/problem+json(RFC 7807). Neverapplication/jsonfor errors. codeis kebab-case, prefixed by family (auth_,scope_,validation_,state_,not_found_,op_,infra_).typepoints to an existing docs page atapps/docs/content/docs/errors/<code>.mdx. CI gate enforces.detailis actionable. Includes field path, value seen, expected shape, "did you mean" where applicable.instanceis a per-request URN (urn:matter:request:<request_id>).- No PII in any error field —
redact()is called at construction. - Cross-tenant lookups return 404 (
not_found_in_modeornot_found_resource), never 403. Do not leak existence.
Security
x-matter-authdeclared if the operation deviates from the default Bearer contract (e.g., requires fresh OAuth, requires signing session, requires step-up).x-matter-mcpdeclared withmin_tier,read_only,idempotent,workflow, and (if sensitive)never_expose: true.- Scope policy derived from
x-matter-mcp.min_tier+x-matter-authvia codegen. - Rate limit declared via
x-matter-rate-limit(per-tier). - PII fields marked
x-matter-pii: true. - Encrypted fields marked
x-matter-encrypted: true(mandatory for Highly Restricted; optional for Restricted). - Classification declared via
x-matter-classification: public | confidential | restricted | highly_restricted. - Audit-on-read triggers automatically for Highly Restricted fields. No manual wiring required.
- Webhook payload does not contain Highly Restricted fields in fat or thin form (per data classification rules).
- SSRF defense if the endpoint accepts a customer-supplied URL — validated against the allowlist.
- Prompt-injection defense if the endpoint feeds untrusted input into an AI call (input length cap, structural validation, output schema validation, sandboxed prompt template, no tool-calling).
Performance
- SLO class declared via
x-matter-slo.class(one of nine classes). - SLO override declared if the operation needs tighter / looser budget than its class default.
- Cost budget declared via
x-matter-cost-budget. - Cache layer declared via
x-matter-cache-layer(none | request | redis | edge | snapshot). - Cache target declared via
x-matter-cache-targetfor cached operations. - Expand depth declared via
x-matter-expand-max-depth(default 4). - N+1 audit — for expand fields, hydrator is batched. No per-row lookup.
- Consistency contract explicit per operation.
Async and webhooks
- 202 path returns
Requestresource with typed status andnext_actions[]. - Saga for any composite atomic flow. Typed compensation handlers. TLA+ spec for high-stakes sagas.
- Webhook events emitted in same transaction as resource + audit write.
- Event versioning — every Event carries
apiVersion. Transformers convert to older shapes on delivery. - 30-day replay window — re-deliverable via
POST /v1/events/{id}/redeliverwithin 30 days.
DX (developer experience)
x-matter-explaineron every non-trivial property. Long-form educational context, not the one-liner that's indescription.- Operation
summary≤ 80 chars; reads as a sentence ("Create an entity"). - Operation
description≥ 80 chars but ≤ 600; states what + how + when. No competitor comparisons. - Examples present in request body and 2xx + 4xx response.
- Code samples auto-generated by
apps/docs/scripts/generate-code-samples.ts. - MCP tool description quality bar — lifecycle phase, min tier, one-line example payload in the description.
- SDK example committed alongside the SDK source for the canonical use case.
Maturity and deprecation
x-matter-maturitydeclared. Defaultpreviewfor new endpoints.- Deprecation plan if the endpoint replaces an existing one.
Sunsetdate set; migration guide drafted. Deprecation+Sunset+Linkheaders automatic on Deprecated endpoints (no handler code).- Personalised emails to active users of deprecated endpoints (P11.33).
- Sunset behaviour — endpoint returns 410 Gone with
code: op_endpoint_sunset+ migration guide link.
Internationalisation
- Error messages localised via the i18n codegen (
x-matter-i18n-keyon the error code). - Multi-currency fields carry an explicit
currency(ISO 4217). Customer-supplied value validated. - Multi-language
Accept-Languagehonored where the response is customer-facing. - Date handling uses ISO 8601 UTC in storage; locale-rendered in response with
Matter-Localeheader. - Address handling uses CLDR-compliant address structure where applicable.
- Unicode normalisation (NFC) on every text-equality boundary (lookup, search, deterministic IDs).
Operational
- Runbook linked from any high-stakes endpoint.
- Alert typed in
packages/observability/src/alerts.tsif the endpoint has a unique failure mode. - Cost budget enforced in CI under load test.
- Tenant-isolation test added to
apps/api/__tests__/tenant-isolation/<phase>/. - Per-mode parity — live + sandbox + test all covered in integration test.
- Cross-version compat test — current minor + previous minor both validated.
- Mock-vs-real parity gate — before retiring the catch-all mock for this operation, real handler response is shape-compatible with the mock's.
Documentation
- Spec PR + docs PR can be the same PR (auto-generation from spec). Hand-written narrative goes in
apps/docs/content/docs/concepts/orapps/docs/content/docs/cookbook/as appropriate. - Changelog entry auto-generated via oasdiff (P11.15).
- Breaking changes blocked within minor — oasdiff CI fails if a breaking change is introduced without bumping the minor.
Sign-off
- API Council approval recorded in council minutes for non-trivial endpoints. Bounded-context owner approval required for any endpoint that crosses or modifies the context's invariants.
This list is exhaustive and intentional. If an item feels heavy, it is because the platform-level cost of getting that item wrong is heavy. Reviewers walking this list are doing the work of preventing 12 future engineers from each independently rediscovering the same lesson.
See also
- API Council — the forum that walks this list.
- API design philosophy — the ten principles this list enforces.
- Bounded contexts — context owners review changes that touch their context.