--- name: api-contract-design description: Use when designing or changing a REST, GraphQL, RPC, module, component, or service interface. Defines stable contracts, predictable errors, boundary validation, compatibility rules, and contract verification before implementation. license: MIT source: adapted from https://github.com/addyosmani/agent-skills/tree/main/skills/api-and-interface-design (MIT) --- # API Contract Design Design the contract before its implementation. Treat every observable public behavior as something a consumer may depend on, including error shapes, ordering, nullability, defaults, and timing-related semantics. ## Start from consumers and invariants 1. Identify every consumer and trust boundary. 2. Write representative requests/calls and exact successful and failing responses. 3. State invariants: identity, authorization, idempotency, ordering, pagination, concurrency, nullability, and retry behavior. 4. Define machine-readable input and output schemas before handler code. 5. Review the contract for misuse: make valid operations easy and invalid states difficult or impossible to express. Do not infer an API shape from its current implementation. Existing quirks may be accidental; record which behaviors are intentional compatibility promises. ## Keep semantics predictable - Use one naming convention across endpoints and fields. - Use one structured error envelope with a stable machine code, safe human message, and optional field details. Never expose stack traces or internals. - Distinguish authentication, authorization, missing resources, validation, conflicts, and server failures consistently. - Validate untrusted input and third-party responses at system boundaries. Once validated, let internal typed code rely on the contract instead of scattering duplicate checks everywhere. - Separate caller-supplied input types from stored/returned resource types. - Paginate every collection that can grow; specify stable ordering, cursor or page semantics, limits, and behavior when data changes between requests. - State whether mutations are idempotent and how clients safely retry them. - For concurrent updates, define conflict handling (version/ETag, transaction, or explicit last-write-wins), rather than leaving races implicit. ## Evolve additively Prefer optional fields, new endpoints, and new enum variants over renaming, removing, or changing types. Before changing an existing interface: 1. Search all first-party callers, tests, fixtures, generated clients, docs, and telemetry for actual usage. 2. Assume unknown external consumers may rely on undocumented observable behavior. 3. Classify the change as additive, behavior-changing, or breaking. 4. For breaking changes, use a deprecation and migration plan; do not silently maintain two indefinite implementations. Be careful when adding enum variants: additions are compatible for producers but can break consumers with exhaustive switches. Document an unknown/fallback policy where independent deployment requires it. ## Verify the contract - Add contract tests for success, each error class, boundary values, and malformed third-party data. - Confirm generated schema/client artifacts match the committed contract. - Test retries and duplicate mutation requests where idempotency is promised. - Test pagination across inserts/deletes and concurrency conflicts. - Compare the implementation's observed responses with the documented schema. - Ensure examples are executable and use the same field/error shapes as the implementation. Do not call an interface stable merely because its happy path works. Stability means consumers can predict failures and evolve independently.