---
name: "api_design"
description: "Design HTTP APIs that age well: resource modeling, versioning and deprecation policy, error conventions (RFC 9457 problem details), pagination, idempotency, and docs-first OpenAPI contracts. Triggers when someone asks to design an API, add or review endpoints, set up versioning, fix inconsistent errors, or establish API design standards."
---

# API Design

## Purpose

Design REST/HTTP APIs around resources (not actions), write a machine-readable contract (OpenAPI 3.1) before code, and bake in versioning, error conventions, and pagination so the API can evolve without breaking clients.

## Workflow

1. **Model resources.** Identify domain nouns and map each to a plural collection path: `/users`, `/orders`. Nest only where ownership is real (`/users/{id}/orders`). No verbs in paths — HTTP methods express actions. If an endpoint wants a verb, it is a smell; model it as a resource instead (e.g. `POST /reports` not `/generateReport`) or, rarely, a clearly-named controller sub-resource (`POST /orders/{id}/cancellations`).
2. **Assign methods and semantics.** GET = retrieve (safe, idempotent); POST = create (not idempotent — require an `Idempotency-Key` header for retries); PUT = full replace (idempotent); PATCH = partial update; DELETE = remove (idempotent). Return 201 on create, 204 on successful delete, 410 Gone (never 404) for sunset resources.
3. **Fix the error contract.** Every error returns the same envelope: RFC 9457 problem details (`type`, `title`, `status`, `detail`, `instance`) plus one machine-readable `code` extension. Validation failures include field-level detail. Never leak stack traces or internals in production.
4. **Paginate every collection.** No unbounded list responses. Cursor-based pagination for feeds/large datasets, page-based for static data. Include rate-limit headers on all responses.
5. **Write the OpenAPI 3.1 contract first.** One document is the source of truth. Generate mock servers (Prism), client SDKs, and contract tests (Schemathesis/Pact) from it. If spec and code disagree, the spec is the bug — fix one of them.
6. **Set the versioning and deprecation policy.** Bump the major version (`/v1` → `/v2`) only on breaking changes; additive, backward-compatible changes stay on the same version. Version the whole API surface, never individual endpoints. Support N and N-1 concurrently. Deprecated responses carry `Sunset` (RFC 8594, HTTP-date) and `Deprecation` headers, `deprecated: true` in OpenAPI, and a changelog entry with a migration guide.
7. **Lint the design.** Run `bin/api-lint.py` against the OpenAPI file to catch verb-in-path, naming, missing-error-response, and unversioned-collection violations before review.

## Output Contract

- An OpenAPI 3.1 document covering every endpoint, with consistent resource naming, the error envelope defined once in `components` and referenced everywhere, `deprecated: true` plus sunset dates on retiring operations, and pagination parameters on all list endpoints.
- A changelog entry for any breaking change plus a migration guide and an announced sunset date (realistic window: months, not days).
- Every claim in the design traceable to the contract or an RFC — no hand-wavy "probably fine".

## Operating Rules

1. **Never invent standards.** Verify RFC numbers before citing them: `Sunset` is RFC 8594; problem details are RFC 9457 (not the obsolete 7807); the `Deprecation` header is RFC 9745 (Standards Track, 2025) — its value is an RFC 9651 structured-field date (`@`-prefixed Unix timestamp, e.g. `Deprecation: @1768435200`), not an HTTP-date. Unverifiable = cut or label as opinion.
2. **The contract is the design.** If an endpoint cannot be expressed cleanly in OpenAPI, the design is wrong, not the spec.
3. **Additive changes don't version.** New endpoints, optional fields, and optional query params ship on the current version. Bump only when existing clients can break: removed/renamed fields, tightened validation, changed status codes, new required fields, changed auth.
4. **Errors are UX.** Every error response must tell the caller what went wrong, which field caused it, and a stable `code` a machine can switch on. "Internal Server Error" alone is a design failure.
5. **Idempotency is a requirement, not a feature.** Any non-idempotent mutation (POST) gets an `Idempotency-Key` mechanism; document its behavior on retry.
6. **No verbs in paths, ever.** If the team demands one, record it as a deliberate exception with an expiry, not a precedent.
7. **Deprecation is a lifecycle, not a label.** Deprecated means: headers on every response, sunset date announced, migration guide published, usage logged so you know who hasn't moved.
8. **Skip this skill's weight for RPC internals.** For high-performance service-to-service RPC, gRPC beats REST — say so instead of forcing REST onto it. For complex, client-driven querying, GraphQL beats REST — say so.
