Design a clean API
The API design standard uses RFC 2119 keywords: MUST/REQUIRED is non-negotiable, SHOULD/DEFAULT is the opinionated default you ship unless you record a deviation, and MAY is allowed. Design a surface in this order.
1. Contract-first: the schema is the source of truth
The contract artifact is authored before the implementation, and code is generated from - or verified against - it, never the reverse. OpenAPI 3.1 for REST, proto3 for gRPC, SDL for GraphQL, and JSON Schema per tool for MCP. The artifact lives in the repo and is the diff an auditor reads. Every surface a product exposes MUST also be reachable as MCP - the MCP projection is a first-class contract, not an afterthought. Contracts MUST be self-describing: descriptions on every field and method, examples on every operation, so an agent can discover a surface instead of regenerating it.
2. Model resources, then act on them
Default to a hierarchy of resources (nouns) acted on by a small set of standard methods; reach for an RPC-style verb only as a custom method. Paths alternate collection/id segments and express nesting explicitly, e.g. /v1/work-items/{work_item_id}/comments/{comment_id}. Naming has one canonical casing per place:
- URL collection segments: lower-kebab-case, plural (
/v1/work-items). - JSON/proto fields:
snake_case(display_name), so REST and gRPC never diverge in casing. - Enum values:
UPPER_SNAKE_CASEwith a*_UNSPECIFIED = 0zero value. - MCP tool names:
snake_caseverb_noun(create_work_item).
IDs are server-assigned, opaque, and sortable (ULID or UUIDv7); clients MUST NOT parse meaning out of them. List/Get/Create/Update/Delete/BatchGet cover CRUD - and nothing bespoke for it. Partial update via PATCH + a field mask is the default; a field absent from the mask MUST NOT be cleared. Non-CRUD actions (a state transition, a bulk job) become a custom method in colon notation over POST: POST /v1/work-items/{id}:archive.
3. The estate-wide invariants
Some rules are MUST and not deviable per repo. Get these right or the design is wrong:
- Errors are RFC 9457 typed problems on every surface -
application/problem+jsonwithtype,title,status,detail,instance, plus the Cogitave extensionscode(a stable error-code enum - the machine contract),trace_id,retryable, anderrors[]for field-level validation. Eachcodemaps to a canonical class and HTTP status. Raw panics MUST NOT cross the boundary. - Cursor pagination by default. Every
Listtakespage_size+page_tokenand returnsnext_page_token, with an enforced default and maximum page size (an unbounded list is a defect). The cursor is opaque and signed; offset/limit is a recorded deviation. - Idempotency on creating writes. Every non-idempotent write honors an
Idempotency-Key: a replay returns the original response; a reused key with a different fingerprint fails409.
4. One shape, and the deviation rule
A single concept MUST have one shape across REST, gRPC, GraphQL, and MCP - same fields, enums, code taxonomy, and pagination. Divergence is a lint failure, not a style choice; Spectral, buf, and schema linters gate it in CI. Deviating from a SHOULD/DEFAULT requires an Accepted ADR/RFC recording the reason, a compensating control, and an expiry date. The MUST invariants above are not deviable at all.