Use when architecture boundaries are already defined and the team must specify stable API contracts between components or for external consumers, especially when backward compatibility, brownfield coexistence, authorization rules, and error semantics must be made explicit before implementation.
APIs are contracts. Design them as carefully as you would a legal agreement -- they're hard to change once published.
API design defines how system components communicate. A well-designed API is intuitive, consistent, and evolvable. A poorly designed API creates coupling, breaks clients, and generates support burden for years.
In a lifecycle-aware system, API design must preserve upstream architecture boundaries and unresolved questions. Do not smuggle rollout plans, migration choreography, or internal data-model assumptions into the contract unless they are true contract requirements.
| Style | Best For | Trade-offs |
|---|---|---|
| REST | CRUD resources, public APIs | Simple, cacheable, but over/under-fetching |
| GraphQL | Complex data graphs, mobile clients | Flexible queries, but complex caching |
| gRPC | Service-to-service, high performance | Fast, typed, but not browser-native |
| Event-driven | Async workflows, decoupling | Loose coupling, but eventual consistency |
Choose style from the architecture and consumer boundary, not from fashion. If the architecture leaves a timing or consistency question unresolved, preserve that uncertainty in the contract assumptions instead of collapsing it into a premature transport decision.
Map domain entities to API resources. Use nouns, not verbs:
GET /orders not GET /getOrdersPOST /orders/{id}/cancel for actions on resourcesFor brownfield work, explicitly separate:
For each resource: which CRUD operations? What request/response schemas? What status codes?
Document contract boundaries explicitly:
Choose a strategy before the first release:
/v1/orders (simple, explicit)Accept: application/vnd.api+json;version=1 (clean URLs)For brownfield modernization, include backward-compatibility rules between legacy and new surfaces. If coexistence exists, define what callers can rely on during the coexistence window without committing to migration choreography.
Consistent error responses across all endpoints:
{
"error": { "code": "VALIDATION_ERROR", "message": "Human-readable message", "details": [...] }
}
Write the contract specification before implementation. Use OpenAPI for REST, protobuf for gRPC. This enables contract testing and client code generation.
If architecture or requirements still leave uncertainty around:
record those as explicit contract assumptions, deferred fields, or open questions. Do not silently hard-code them into endpoint behavior.
skills/.curatedskills/02-architecture/api-design/SKILL.mdnpx skills add/update compatibility.betabeta