SuperAPI AI Directory

awesome-api-design

Coding & development

View repository

Updated 10-15 23:45

About this skill

API Design

Shape an HTTP API so its consumers can build against it for years without a breaking surprise. The contract is the product: URLs, methods, payloads, errors, and evolution rules are decided here, deliberately — everything left implicit becomes an accidental contract the first client depends on.

When to Activate

  • "Design the API for X", "review this API design / OpenAPI spec", "how should we version / paginate / handle retries".
  • A design doc needs its API section made concrete (awesome-design-doc hands off here).
  • A new endpoint is being added to an existing API and must match its conventions.

Do not activate to define the error envelope's fields or retry classification (awesome-error-standards owns that contract) or to audit implemented handlers for vulnerabilities (awesome-security-audit).

Work Process

  1. Inventory the incumbent conventions and the project's own words — an existing API's casing, id format, pagination style, and envelope win over any guideline here: consistency within one API beats global best practice. Only a new API starts from the defaults below. Read the recorded decisions (existing ADRs) and the project's glossary (CONTEXT.md, a domain doc, or the terms its code and tests already use) before naming a single resource: a path is a public, long-lived name, and one that renames a concept the codebase already has costs every reader a translation.
  2. Model resources, not procedures — nouns with identity and lifecycle (/orders/{id}), actions as state transitions on them (POST /orders/{id}/cancel when a pure verb is unavoidable — never /doCancelOrder). Nest at most one level deep; deeper hierarchies become query filters (/comments?post_id=…), because every nesting level hardcodes an ownership assumption into every client URL.
  3. Decide the evolution rules before v1 ships — additive changes (new optional field, new endpoint) go in place; anything breaking (remove/rename/retype a field, tighten validation) only ever creates a new version. Publish that taxonomy with the API so consumers know what is safe to ignore. Evolve by layering — a redesigned abstraction ships beside the old one and existing integrations keep working until their owners move; deprecation is announced with a sunset window, never enforced by an in-place change.
  4. Design list endpoints for growth — opaque cursor pagination (encode the (sort_key, id) position, return has_more + next_cursor in a list envelope) over offset, which skips and duplicates rows under concurrent writes and dies on deep pages. Filtering and sorting are an allowlist of named parameters over indexed fields, never a pass-through to the query layer.
  5. Make unsafe methods retry-safe — mutations accept an Idempotency-Key; same key + same body replays the stored response, same key + different body is 409. Without it, every client retry is a potential duplicate side effect. (Retry classification and the envelope format: awesome-error-standards.)
  6. Specify concurrency and partial updates — updates that can conflict get optimistic concurrency (ETag + If-Match, 409 with the current version on mismatch); PATCH semantics are declared (merge-patch vs replace), and an empty PATCH is rejected, not silently a no-op.
  7. Write the contract down as the source of truth — an OpenAPI/schema document that generates or validates the implementation, not prose that drifts from it. Ids are opaque and prefixed (ord_…), timestamps ISO 8601 UTC, money integer minor units with currency, enums closed with a documented default for unknown values on the consumer side.

Design review checklist

When reviewing an existing design, walk the same decisions as findings:

  • Naming and casing consistent across every endpoint; no mixed camelCase/snake_case payloads.
  • No verbs in resource paths except modeled state transitions; no RPC-style /getX endpoints beside REST resources.
  • Every list endpoint paginated from day one — retrofitting pagination is a breaking change.
  • Every mutation idempotent-by-key or documented as naturally idempotent.
  • Error responses reference one stable envelope (awesome-error-standards) — not per-endpoint improvisation.
  • Versioning and deprecation policy stated; no "we'll decide when we break something".
  • Webhooks the API sends are treated as public surface: signed, versioned, redeliverable — same contract discipline as endpoints.
  • Nothing in the response a client shouldn't see: internal ids, flags, or fields leaking through by serializer default.

Change review

When the input is a change to a shipped contract (a diff to a spec, schema, handler or payload), review the change, not the end state. Compatibility is never declared from the new schema alone: read the old and new shapes side by side, and the code on both sides of the wire. Walk four scenarios and record the result of each:

  • Old writer, new reader: a payload produced before the change reaches code after it.
  • New writer, old reader: a payload produced after the change reaches code that has not been updated yet (still-deployed servers, shipped mobile or SDK builds).
  • Rollback: the old code returns while values only the new code writes are already stored or in flight.
  • Payloads at rest: old-format messages in queues, cache entries, stored rows and files are read by new code, and new-format ones by old code after a rollback.

Search for consumers both ways, in every repository you can reach:

  • A client reads a field the server stopped sending, renamed or retyped. This breaks silently: no error, a missing or wrong value.
  • The server returns a status code or error code the client has no branch for.
  • A request field becomes required, or a validation tightens, while existing callers omit it.
  • A new enum value reaches a consumer with an exhaustive switch or a closed type.

"No consumer found" is not "unused": external clients, other repositories, scripts and stored data are invisible to a code search. Say what was searched and treat the rest as unknown.

Verdict, one per change: Compatible (all four scenarios pass, evidence cited), Order-dependent (safe only in a stated deploy order or with a stated migration step; name it), Breaking (some scenario fails for a live consumer; the fix is a new version or a layered shape), Not determinable (a scenario or consumer set could not be checked; name what is missing and never round up to Compatible).

Output Format

API Design — <scope> — <date>

Contract decisions:
- <area: resources | versioning | pagination | idempotency | concurrency> — <decision> — <the consumer consequence it buys>
...

Spec: <OpenAPI/schema skeleton or diff, when producing one>
Deviations from incumbent conventions: <each with its justification, or "none">
Open questions: <decisions needing the user, with a recommended default each>

Self-check before delivering

  • Run the design review checklist above against your own output — a finding in your own design gets fixed before delivery, not shipped with a caveat.
  • Every contract decision names the consumer consequence it buys; "because best practice" is not a justification — delete or justify.
  • Step 1 has evidence: name the incumbent spec, routes, or client code actually inspected. Defaults applied to an API that has conventions is the failure mode.
  • Every mutation in the design answers the retry question (idempotency key or naturally idempotent — stated which); every list endpoint answers the growth question.
  • Each open question carries a recommended default; a bare question pushes the design work back to the reader.

Anti-patterns

Anti-pattern Instead
Offset pagination on a growing table Opaque cursor over an indexed sort key
Breaking change hidden as a "fix" New version; the old shape never changes under a client
Per-endpoint error shapes One envelope, one shared client-side parser
Sequential integer ids in URLs Opaque prefixed ids; sequence leaks volume and invites enumeration
PUT that silently drops unknown fields Declared PATCH semantics; strict validation with named rejections
Prose spec that trails the implementation Machine-readable contract that generates or gates the code
Designing for the first client's screen Resources model the domain; view composition belongs to the client