API Design — the contract IS the spec
Contract-first: write the machine-readable contract, review it like code, implement to it. Never code-first with docs generated after.
Style selection (decision table — pick ONE, record an ADR)
| Use case |
Style |
| Public/partner API, resource-shaped, broad client base |
REST + OpenAPI 3.1 |
| Product frontend with many views over the same graph, client-driven field selection |
GraphQL + SDL |
| Internal service-to-service, low latency, strong typing across languages |
gRPC + proto3 |
Ambiguous → one question to the human with the trade-off, never two styles at once without justification.
REST rules (OpenAPI 3.1)
- Contract file in-repo:
api/openapi.yaml (or the repo's existing convention). Reviewed in the same PR discipline as code.
- Resources are plural nouns; no verbs in paths (
POST /orders, not /createOrder). Consistent casing (pick kebab or snake for paths, camel for JSON; record it).
- Methods carry semantics: GET safe, PUT/DELETE idempotent, POST for creation/actions. Idempotency keys for payment-shaped POSTs.
- Errors: RFC 9457
application/problem+json — every operation lists its error responses in the contract. No bare 500s as design.
- Pagination: cursor-based by default; offset only with a written justification (deep-page cost). Filtering/sorting as documented query params.
- Versioning: choose URL prefix (
/v1) or header once, record the ADR, never mix.
- AuthN/authZ in the contract: securitySchemes + per-operation scopes/roles. An operation without a declared auth requirement is a finding, not an oversight.
GraphQL rules (SDL)
- Schema file in-repo. Nullability is design: non-null by intent, not by default.
- Typed errors (union results or errors interface) over throwing strings.
- Pagination: Relay-style connections. N+1: name the dataloader plan at design time.
- Depth/complexity limits stated in the contract docs (abuse surface).
gRPC rules (proto3)
- Proto files versioned in-repo; packages versioned (
v1).
- Field numbers are forever: reserve removed numbers, never reuse.
- Deadlines/timeouts and idempotency noted per RPC; streaming only with a stated reason.
Verification hook (Phase 6)
The implementation must match the contract: routes/fields/status codes vs the contract file (drift check — route list diff, schema validation of real responses where the test suite allows). Contract drift found in verify = the run is not done.
1---2name: api-design3description: API Design — the contract IS the spec4---56# API Design — the contract IS the spec78Contract-first: write the machine-readable contract, review it like code, implement to it. Never code-first with docs generated after.910## Style selection (decision table — pick ONE, record an ADR)1112| Use case | Style |13|---|---|14| Public/partner API, resource-shaped, broad client base | REST + OpenAPI 3.1 |15| Product frontend with many views over the same graph, client-driven field selection | GraphQL + SDL |16| Internal service-to-service, low latency, strong typing across languages | gRPC + proto3 |1718Ambiguous → one question to the human with the trade-off, never two styles at once without justification.1920## REST rules (OpenAPI 3.1)2122- Contract file in-repo: `api/openapi.yaml` (or the repo's existing convention). Reviewed in the same PR discipline as code.23- Resources are plural nouns; no verbs in paths (`POST /orders`, not `/createOrder`). Consistent casing (pick kebab or snake for paths, camel for JSON; record it).24- Methods carry semantics: GET safe, PUT/DELETE idempotent, POST for creation/actions. Idempotency keys for payment-shaped POSTs.25- **Errors:** RFC 9457 `application/problem+json` — every operation lists its error responses in the contract. No bare 500s as design.26- **Pagination:** cursor-based by default; offset only with a written justification (deep-page cost). Filtering/sorting as documented query params.27- **Versioning:** choose URL prefix (`/v1`) or header once, record the ADR, never mix.28- **AuthN/authZ in the contract:** securitySchemes + per-operation scopes/roles. An operation without a declared auth requirement is a finding, not an oversight.2930## GraphQL rules (SDL)3132- Schema file in-repo. Nullability is design: non-null by intent, not by default.33- Typed errors (union results or errors interface) over throwing strings.34- Pagination: Relay-style connections. N+1: name the dataloader plan at design time.35- Depth/complexity limits stated in the contract docs (abuse surface).3637## gRPC rules (proto3)3839- Proto files versioned in-repo; packages versioned (`v1`).40- Field numbers are forever: reserve removed numbers, never reuse.41- Deadlines/timeouts and idempotency noted per RPC; streaming only with a stated reason.4243## Verification hook (Phase 6)4445The implementation must match the contract: routes/fields/status codes vs the contract file (drift check — route list diff, schema validation of real responses where the test suite allows). Contract drift found in verify = the run is not done.