Use when designing or reviewing HTTP API surfaces: consumer tasks, audience class, protocol/paradigm fit, resources/actions, route taxonomy, request and response schemas, status codes in context, pagination, filtering, sorting, field selection, idempotency, auth and tenant boundaries, error envelopes, rate-limit signals, versioning, deprecation, discovery, and contract artifacts. Do NOT use for pure HTTP protocol semantics (use `http-semantics`), framework-specific route handler mechanics (use `route-handler-design`), non-HTTP system contracts (use `system-interface-contracts`), async event contracts (use `event-contract-design`), database design (use `entity-relationship-modeling`), inbound provider webhook mechanics (use `webhook-integration`), or post-failure diagnosis (use `debugging`). Do NOT use for define the broader contract between a job, service, and dashboard. Do NOT use for design database tables, foreign keys, and views.
Use when designing or reviewing HTTP API surfaces: consumer tasks, audience class, protocol/paradigm fit, resources/actions, route taxonomy, request and response schemas, status codes in context, pagination, filtering, sorting, field selection, idempotency, auth and tenant boundaries, error envelopes, rate-limit signals, versioning, deprecation, discovery, and contract artifacts.
Coverage
Design clear, durable HTTP API surfaces — the contract another program depends on. This skill covers:
Consumer & task framing — who calls the API, the job each call accomplishes, the audience class, the owner, and the stability/release channel that set the compatibility bar.
Protocol/paradigm fit — confirming HTTP/REST is the right shape before drawing routes (vs GraphQL, gRPC, or async events).
Resource & action modeling — resources vs RPC-style action endpoints, route naming, when an operation is a sub-resource, a state transition, or an async job.
Schemas — request/response/error body shapes as public representations separate from internal models; validation, required vs optional, nullability, read-only/write-only, and additive evolution — including extensible enums (treat enum sets as open so a new server-added value never breaks a strict client).
Status codes in context — choosing the code that matches retry behavior and the required client action (the status-code reference table below).
Error envelopes — a single consistent error shape; RFC 9457 Problem Details as the portable default.
Collections — pagination (cursor vs offset), filtering, sorting, field selection, stable ordering, and empty-result behavior.
Idempotency & concurrency — Idempotency-Key for unsafe writes (request fingerprinting, replay, and conflict rules); ETag / If-Match optimistic concurrency for updates.
Versioning — URI vs header vs date-based; additive-change rules and the deprecation lifecycle.
Auth, tenant & authorization boundaries — authentication, scopes, tenancy, and per-object/per-property authorization (mass-assignment and output-exposure control) in the contract.
Discovery & contract artifacts — OpenAPI (the latest version your toolchain supports) as the machine-readable source of truth; an API catalog for portfolio-level discovery; AI/tool discoverability; contract tests and fixtures.
This skill owns the product-facing contract of an HTTP API. It does not own the broader interface contract between systems, async event envelopes, stored data design, inbound provider webhook mechanics, framework-specific route handler implementation, or diagnosis of an already failing endpoint.
Philosophy of the skill
An API is a product surface for another program. Its main job is stable meaning under change: a consumer that integrated last year should keep working, and a client should be able to tell what happened, what it can do next, and whether retrying is safe without reading server code. Internal convenience should not leak into routes, schemas, or errors unless consumers actually need it.
Prefer boring consistency. A small set of predictable patterns — one error shape, one pagination style, one idempotency mechanism — beats clever endpoint-specific behavior that every client has to rediscover. Consistency is itself a feature: it is what lets a client author one HTTP layer instead of one per endpoint.
Assume observable behavior becomes part of the contract. Clients can come to depend on response shape, ordering, error codes, null behavior, latency class, and even undocumented fields once those details are visible. Decide deliberately what is public and what may change.
Do not expose the database, framework, or internal service layout by accident. A resource is the representation the client needs, not necessarily a table. A route is the public language of the product, not the shape of the implementation. A storage table can split, merge, denormalize, or migrate without changing the API if the representation is stable.
Design the contract first, then implement to it. A contract written as an OpenAPI document (or even a reviewed Markdown spec) before code exists is cheap to change; the same change after three teams have coded against it is expensive. The contract is the artifact frontend, backend, and test authors agree on in parallel — that parallelism is the whole point of writing it down.
Evolve additively; break loudly. Adding an optional field, a new endpoint, or a new enum value a client can ignore is safe. Removing a field, renaming it, tightening validation, or changing a status code is breaking — it requires a new version and an announced deprecation, never a silent in-place edit.
Standards are defaults, not ceremony. Start from HTTP semantics, Problem Details, OpenAPI, documented deprecation headers, and explicit compatibility rules. Deviate only when the consumers and the migration cost justify it.
Method
Triage consumers, audience, and tasks before drawing routes. List who calls the API and the job each call accomplishes; name the owner, the stability level (internal / preview / beta / stable / deprecated), the release channel, and the data sensitivity. Classify the audience — public / partner / internal / admin / service-to-service / agent-or-tool — because that classification sets the compatibility bar, the auth model, and how loudly you must version. Design for those tasks, not for your table layout.
Decide whether this is API design or a handoff to a neighboring skill (see Boundary Triage), then choose the protocol/paradigm fit before drawing routes — a graph-shaped client-driven read surface may belong in GraphQL, a low-latency internal call in gRPC, and a fire-and-forget notification in an async event contract.
Model resources first; use actions only when standard resource operations do not fit. Most operations are CRUD on nouns (/orders, /orders/{id}). When a verb has no clean resource (/orders/{id}/cancel, /payments/{id}/refunds), model it as a state transition or a sub-resource, not a free-floating RPC.
Write the contract before implementation: method, URI, request, response, errors, auth, idempotency, pagination, versioning, and concrete examples.
Define request, response, and error schemas as public representations — separate from internal models. Decide required vs optional, nullable vs absent, read-only/write-only, and enum-vs-open-string for every field. Validate input and return field-level errors.
Choose HTTP method and status behavior by client action and retry semantics (table below). The code is a contract: it tells the client whether to retry, re-auth, fix input, or give up.
Define collection semantics: pagination style (cursor for large/changing sets, offset only for small bounded ones), filtering grammar, sort keys with a stable unique tiebreaker, optional sparse field selection, stable total ordering, and empty-result behavior.
Define mutation semantics: idempotency keys with request fingerprinting and replay, optimistic concurrency (ETag/If-Match), async job behavior, and retry windows for unsafe operations.
State auth, tenant, scope, object-level, function-level, and property-level authorization in the contract.
Publish machine-readable contract artifacts and add fixtures or contract tests. Generate or hand-write an OpenAPI document; add contract tests or recorded fixtures so drift between doc and implementation is caught.
Define versioning, compatibility, deprecation, sunset, and discovery before the first breaking change — pick one versioning scheme, document the support window, and plan to emit Deprecation/Sunset headers.
Boundary Triage
Use this skill when the task is the shape and behavior of an HTTP API endpoint or documented JSON-over-HTTP contract.
Inbound third-party webhook signatures, provider retry contracts, raw payload persistence
webhook-integration
Verifying consumer/provider compatibility from the API contract
contract-testing
A behavior is already broken and needs root-cause isolation
debugging
Protocol and Paradigm Triage
This skill may help choose the initial API style, but it should not take over full ownership of non-HTTP or specialized schema work.
Paradigm
Prefer when
Boundary
Resource-oriented JSON-over-HTTP
Consumers need broad tooling, cacheable/linkable resources, simple public or partner compatibility, and OpenAPI-style documentation.
api-design owns the endpoint contract; verify method/header details with http-semantics.
GraphQL-over-HTTP
Consumers need query-shaped reads, selective nested data, or frontend-driven composition, and the organization can govern schema evolution and resolver cost.
Use api-design for the selection tradeoff only; schema/resolver ownership belongs to a GraphQL-specific or broader interface-contract skill when available.
gRPC or typed IDL
Internal service-to-service calls need strict generated clients, streaming, low-latency binary transport, or strongly versioned RPC methods.
Route detailed ownership to system-interface-contracts and transport-specific skills; do not force gRPC design into this HTTP API skill.
Async events or webhooks
Producers push state changes, consumers subscribe, replay matters, or delivery/retry contracts dominate.
Use event-contract-design for event streams and webhook-integration for inbound provider webhooks.
Protocol choice depends on consumers, tooling, compatibility, cache behavior, operations, contract ownership, and migration cost. Avoid categorical rules like "public means REST" or "internal means gRPC."
Change tolerance: coordinated deploy, independent deploy, public backward compatibility, or migration window.
Data sensitivity: tenant, principal, scopes, object-level checks, property-level exposure.
If the API has multiple independent consumers, design for the least coordinated one. A public or partner API needs stronger compatibility and deprecation discipline than an internal route called only by one UI bundle. A co-deployed internal surface may relax the version label — but never the additive-vs-breaking discipline.
Resources vs Actions
Start with resources and standard operations:
Operation
Typical HTTP shape
Notes
List
GET /orders
Define filters, sort, pagination, and stable ordering.
Read one
GET /orders/{orderId}
Use an opaque stable identifier. Decide 404 vs hidden-by-policy 404.
Create
POST /orders
Usually returns 201 Created, Location, and the created representation.
Replace
PUT /orders/{orderId}
Full replacement; idempotent if the same representation is sent repeatedly.
Partial update
PATCH /orders/{orderId}
Define patch format and concurrency behavior explicitly.
Delete
DELETE /orders/{orderId}
Idempotent post-condition; repeated calls may return different responses.
Use an action endpoint when the operation is not a natural resource state transfer:
POST /orders/{orderId}/cancel
POST /exports
POST /orders/{orderId}/refund
POST /imports/{importId}/retry
Action endpoints still need resource discipline: request schema, response schema, auth, idempotency, retry behavior, error types, and compatibility rules. Avoid inventing custom HTTP verbs. Pick one action style and use it consistently. Reach for an action endpoint only when the operation genuinely is not a resource lifecycle change.
Status Codes — Retry & Client-Action Semantics
API design does not replace http-semantics, but every API contract must respect HTTP semantics. Pick the code by what you want the client to do, not just by category.
Decision
API design rule
Method
Choose by operation meaning, safety, idempotency, payload semantics, and client retry behavior.
Status family
The first digit is the contract: 2xx fulfilled, 3xx further action, 4xx client-actionable problem, 5xx server or upstream failure.
Retryability
State whether the client may retry, when, and whether it must reuse an idempotency key or conditional header.
Client action
Error responses must tell the client whether to fix input, authenticate, ask for permission, retry later, or stop using the endpoint.
Representation metadata
Define Content-Type, content negotiation, caching, ETag, Vary, and Location when they matter.
Code
Meaning
Client should
200 OK
Success with a body
Use the body
201 Created
Resource created
Read Location / returned resource
202 Accepted
Accepted, processing async
Poll the operation/status URL
204 No Content
Success, no body (e.g. delete)
Proceed; expect no body
400 Bad Request
Malformed / un-parseable request
Fix the request; do not retry unchanged
401 Unauthorized
Missing/invalid credentials
Authenticate, then retry
403 Forbidden
Authenticated but not allowed
Do not retry; lacks permission/scope
404 Not Found
Resource absent (or hidden for authz)
Do not retry as-is
409 Conflict
State conflict (duplicate, version clash)
Resolve conflict, then retry
410 Gone
Resource permanently removed
Stop calling; update integration
412 Precondition Failed
If-Match/If-Unmodified-Since failed
Re-fetch, reconcile, retry
415 Unsupported Media Type
Request body media type unsupported
Send a supported Content-Type
422 Unprocessable Content
Syntactically valid, semantically invalid
Fix field-level errors
428 Precondition Required
Server requires a conditional request
Retry with If-Match
429 Too Many Requests
Rate limited
Back off per Retry-After or the RateLimit field's reset parameter
500 Internal Server Error
Unexpected server fault
Retry with backoff (idempotent ops)
503 Service Unavailable
Temporarily down/overloaded
Retry per Retry-After
422 is the right code for "well-formed JSON, business rule violated" (RFC 9110 §15.5.21); reserve 400 for requests the server cannot parse. Both are common — pick one convention and apply it everywhere. Never return 200 OK with an error body for a failed operation — that breaks clients, caches, observability, and retry logic.
Request and Response Schemas
Treat schemas as public representations:
Separate request models from response models from database models.
Mark required, optional, nullable, read-only, write-only, deprecated, and output-only fields.
Define stable identifier format and opacity. Do not expose internal IDs unless they are intended public IDs.
Name timestamps, time zones, units, currencies, and precision explicitly.
Define enum extension policy: closed set, open set with unknown handling, or version-gated additions. Treat enum sets as open by default so a new server-added value never breaks a strict client. If the OpenAPI toolchain supports a vendor extension such as x-extensible-enum, use it as documentation or generator guidance — but do not depend on one extension unless the supported tooling is named.
Define nested object expansion rules (include, expand, fields) if clients can request related data.
For bulk operations, define partial-failure behavior and whether ordering is preserved.
Provide one success example and at least one failure example for every nontrivial operation.
Validation should happen at the API boundary. Do not bind arbitrary request JSON directly to internal entities — that causes mass-assignment and property-authorization failures. Allowlist writable input fields and reject or ignore unknown fields deliberately, with a documented policy.
At deep offsets the DB reads and discards all skipped rows; inserts cause skipped or duplicated items between pages
Default to cursor-based pagination for anything that grows; offset is acceptable only for small admin lists. Always define a stable total ordering (e.g. created_at, id) — pagination over a non-deterministic order silently drops and repeats rows.
Cursor rules:
Ordering must be stable and deterministic; add a unique tiebreaker to every sort, even when the visible sort key is not unique.
Cursors are opaque to clients and must bind to the filter and sort context that produced them.
Define default and maximum page size.
Return pagination metadata consistently (next/prev cursors and/or link relations).
Do not promise exact total counts unless the system can produce them cheaply and correctly.
An empty list is a successful 200 response, not an error.
Offset rules:
Name a maximum offset or page limit.
Warn when ordering is not stable under concurrent writes.
Use only when the collection is naturally stable or consumer impact is low.
Filtering, sorting & field selection — filters and sorts are part of the contract, not pass-through query language:
Pick one grammar and document the allowed fields and operators (?status=open&sort=-created_at).
Allowlist filter/sort fields and operators — never interpolate them into a query.
Define case sensitivity, time zone, date inclusivity, null handling, and enum matching.
Define sort direction syntax and default sort; reject unsupported filters/sorts with a predictable error.
Offer sparse field selection (?fields=id,status) only when payload size is a real problem; an extra option is a permanent contract. Document default fields, allowed fields, dependency fields, and auth behavior.
Never use field omission as the only authorization control — enforce authorization before shaping the response.
Mutations, Idempotency & Optimistic Concurrency
For every mutating endpoint, decide whether duplicate requests are safe. GET/PUT/DELETE are idempotent by HTTP definition (RFC 9110); POST and PATCH are not.
Mutation type
Contract decision
Natural idempotent update
Use PUT, DELETE, conditional PATCH, or a state transition with a deterministic post-condition.
Non-idempotent create/action
Support client-generated idempotency keys when duplicate side effects would harm users.
Long-running work
Return 202 Accepted with an operation resource, cancellation semantics, completion states, and retry behavior.
Concurrency-sensitive update
Use ETag plus If-Match, a version field, or another explicit precondition.
Idempotency keys. For create/charge-style POSTs, accept an Idempotency-Key request header — the de-facto industry pattern (Stripe, PayPal, Square). It was specified in the IETF httpapi working-group draft draft-ietf-httpapi-idempotency-key-header, but that draft has lapsed (the -07 revision expired 2026-04-18), so treat the convention as the reference, not a ratified standard, and pair it with the provider contracts your clients actually depend on. When using idempotency keys, define:
Header/field name (usually Idempotency-Key) and which methods accept it — do not require it for naturally idempotent GET/DELETE.
Key ownership and scope: client-generated, scoped to tenant/user/API key and endpoint.
Entropy and length requirements.
Store-and-replay: atomically persist key, request fingerprint, response status, and response body; replay the identical stored response for any retry within the retention window, so a network retry never double-creates.
Request fingerprint: bind the key to a hash of the request payload. A same key arriving later with a different body returns a deterministic client error (409/422) — never silently apply the new body under an old key.
In-flight collision behavior: a second request with the same key while the first is still processing returns 409 (or waits on the original) rather than executing twice concurrently.
TTL and pruning behavior.
Optimistic concurrency. Return an ETag on reads; require If-Match: <etag> on updates. A stale If-Match fails with 412 Precondition Failed instead of clobbering a change the client never saw; a domain state conflict returns 409 Conflict. Do not collapse both into a generic 400. Use 428 Precondition Required to force conditional writes.
Async & Long-Running Operations
Do not hold a connection open for work that takes seconds-to-minutes. Return 202 Accepted with a link to an operation resource (/operations/{id} or the eventual resource's status URL); the client polls it and reads status: pending|succeeded|failed plus a result or error, with cancellation semantics defined. This keeps timeouts, retries, and progress observable. (If the consumer needs push notification of completion, that is a webhook/event-contract concern — route to webhook-integration / event-contract-design.)
Auth, Tenancy & Exposure
Authorization is part of API design, not only middleware. For each operation, specify:
Principal: user, service account, API key, anonymous caller, or delegated actor.
Tenant boundary and how the tenant is selected — derive it from the token claim, not a client-supplied id.
Scopes, roles, or permissions required, and what 401 vs 403 mean here.
Object-level authorization: can this principal act on this specific resource?
Function-level authorization: can this principal perform this operation?
Property-level authorization: can this principal read or write each sensitive field?
Hidden-resource policy: return 403, 404, or a redacted representation?
Audit and correlation identifiers required for sensitive operations.
Property-level authorization matters in both directions (OWASP API3:2023, broken object property-level authorization). Output schemas can leak fields that should be hidden; input schemas can allow writes to fields that should be server-controlled. Bind writable fields to an allowlist so a client cannot set role, owner_id, or is_admin via mass assignment; bind readable fields so internal columns do not leak in responses.
Error Envelopes — RFC 9457 Problem Details
Use one error shape across the whole API. The portable default is RFC 9457 Problem Details for HTTP APIs (published July 2023; obsoletes RFC 7807, same wire format), served as Content-Type: application/problem+json. Migrate old RFC 7807 references to RFC 9457 while preserving the same client-facing problem-type identities where possible — the upgrade is additive, so there is no urgency to migrate the wire format if you already standardized on 7807.
{
"type": "https://api.example.com/problems/insufficient-funds",
"title": "Insufficient funds",
"status": 403,
"detail": "Charge of $50.00 exceeds the available balance of $12.40.",
"instance": "/accounts/12345/charges/67890",
"errors": [
{ "detail": "amount exceeds balance", "pointer": "/amount" }
]
}
type is a stable URI identifying the problem class — the field clients branch on. Treat changing a type URI or its meaning as a breaking change. RFC 9457 added an IANA "HTTP Problem Types" registry for common type values.
title is human-readable and constant per type; detail is instance-specific. Never make clients parse detail prose for logic.
Problem Details base members are type, title, status, detail, and instance. Validation arrays such as errors, violations, or invalid_params are RFC 9457 extension members (§3.2), not standardized base fields — so the errors array shown above is part of your contract to document. Define its member name, item schema, JSON Pointer/field-path convention, ordering, localization, and compatibility rules.
Always include a machine-readable error code or type in addition to the HTTP status, plus field-level errors for validation failures so a form can highlight the right input.
Avoid stack traces, database names, secrets, internal hostnames, or raw upstream messages in public errors.
Do not return 200 OK with an error body — that defeats every generic HTTP client and is a classic semantic failure.
Validation errors should distinguish: malformed JSON / wrong content type; missing required field; unknown-field policy violation; field type/format violation; domain-rule violation; authorization failure; state conflict.
Rate Limits & Quotas
A rate-limited API must tell clients how to behave. Define:
Partition key: user, tenant, token, IP, endpoint, or business flow.
Retry-After behavior on retryable throttling. When both Retry-After and a RateLimit reset are present, they should point to the same instant.
Rate-limit headers or body fields, with exact names and semantics. The IETF RateLimit / RateLimit-Policy structured-field headers (draft-ietf-httpapi-ratelimit-headers) carry remaining quota and the window reset as parameters of the single RateLimit field (not the older separate RateLimit-Limit/-Remaining/-Reset headers). It is still an active Internet-Draft, not an RFC — if you adopt it, document the convention explicitly and keep clients tolerant of provider-specific alternatives (e.g. x-ratelimit-*).
Browser visibility: expose the needed headers via CORS if browser clients must read them.
Classification: whether exhausted quota is a retryable condition, a plan limit, or a permanent business error.
Versioning, Compatibility, Deprecation & Sunset
Define compatibility rules before the first breaking change. For any externally-consumed API, do not ship an unversioned "default" surface — it creates silent breakage the day a change lands. (A tightly-controlled internal API with a known, co-deployed set of clients may defer an exposed version, but it still owes the same additive-vs-breaking discipline.)
Usually backward-compatible (do not bump the version): adding optional response fields; adding new enum values only if clients are required to ignore unknown values; adding optional request fields; adding new endpoints; adding new filters/sorts when old behavior is unchanged.
Usually breaking (need a new version + migration path): removing or renaming fields; making optional request fields required; changing field meaning, unit, precision, enum semantics, or identifier format; changing default sort/order in a client-visible way; changing error type/code semantics; changing pagination cursor format without preserving old cursors during a transition; tightening authorization clients previously depended on.
Pick one versioning scheme and use it consistently:
Scheme
Example
Trade-off
URI path
/v1/orders
Most explicit and cache-friendly; coarse — a new version is a whole new tree. Best for public APIs needing parallel versions.
Header / media type
Accept: application/vnd.example.v2+json
Keeps URLs stable; harder to test from a browser/curl.
Date-based
Stripe-Version: 2026-01-15
Fine-grained, one pinned version per account; large internal transformation cost (Stripe's model).
Avoid minor/patch versions in routes. Additive changes land in place; incompatible changes need a major version or a negotiated migration path. Stripe's decade of backward compatibility is built on absorbing this complexity internally via a per-version transformation layer rather than forcing every client to migrate at once.
Deprecation & sunset are distinct stages:
Signal a deprecated endpoint/version with the Deprecation response header (RFC 9745) — "no longer recommended." Use Link: <...>; rel="deprecation" to point to migration documentation.
When the resource will actually stop responding, add the Sunset header (RFC 8594) with the removal date — its timestamp must not precede the Deprecation date.
Announce with documentation and client communication before behavior changes; keep deprecated behavior unchanged until the sunset/migration gate; track real usage before removal. Deprecation is a signal, not a behavior change — do not silently remove an endpoint because the server no longer needs it.
Discovery & Contract Artifacts
Every nontrivial API should have a machine-readable contract artifact. For HTTP APIs, prefer OpenAPI:
Use the current OpenAPI release your toolchain supports (3.2 is current, 3.1 is broadly tooled with full JSON Schema alignment).
Include pagination parameters and response links, idempotency-header semantics for mutating operations, and deprecation/sunset headers where applicable.
Keep examples valid against schemas — validate the generated examples, not only the schema shapes.
For public API portfolios, publish an API catalog (/.well-known/api-catalog, RFC 9727) so consumers can discover available surfaces, documentation, usage policies, versions, and OpenAPI definitions.
For agent/tool consumers, make the same public contract easy to ingest without inventing a second source of truth:
Keep OpenAPI (or the canonical contract artifact) authoritative.
Provide short, stable links to authentication, rate limits, pagination, idempotency, error types, deprecation policy, and examples.
Optionally publish AI-readable documentation such as /llms.txt or an equivalent docs index that links to the canonical contract. Treat such files as public documentation support, not a required verification gate, and never a place for secrets, customer data, private paths, or unreleased internals.
Test whether an agent can answer basic integration questions from the published docs — but fix the canonical contract when the agent-facing summary and the contract disagree.
Contract artifacts do not replace contract tests. OpenAPI describes the provider surface; consumer-driven contract tests capture what a specific consumer relies on and verify the provider against that expectation.
Universal API Anti-Patterns
Anti-pattern
Why it fails
Replace with
200 OK with an error body
Clients, caches, retries, and observability see success.
Real 4xx/5xx status plus Problem Details or stable equivalent.
Database-shaped routes and fields
Storage refactors become breaking API changes.
Consumer-facing resource representations with opaque stable IDs.
Custom verbs or action sprawl
Every endpoint needs custom client behavior.
Standard methods first; action endpoints only for real non-resource transitions.
Offset pagination over large mutable collections
Inserts/deletes cause duplicates, gaps, and expensive deep pages.
Cursor/keyset pagination with deterministic ordering and a unique tiebreaker.
Non-idempotent retries
Network ambiguity can duplicate side effects.
Idempotency keys, conditional requests, or explicit "do not retry" rules.
Public or partner breaking changes without a migration path
Requires Idempotency-Key because duplicate order creation can harm users.
Idempotency key is scoped to tenant, authenticated principal, endpoint, and request fingerprint.
Reusing the same key with the same request replays the original status and body.
Reusing the same key with a different request returns a deterministic client error.
Concurrent duplicate-key attempts return the stored result or an in-flight conflict response defined by the contract.
Success returns 201 Created, Location: /orders/{orderId}, and the order representation.
Semantic validation returns 422 Problem Details with field-level errors.
State conflict (e.g. unavailable inventory) returns 409 with a stable problem type.
A server timeout does not license client-side duplicate creation; the client retries with the same idempotency key.
Evals
This skill ships a comprehension-eval artifact at examples/evals/api-design.json. The checklist below is the authoring gate for API surface decisions; the eval file is the grader surface.
Verification
Consumers, tasks, owners, stability level, and release channel are named.
Audience class and protocol/paradigm fit are named before detailed route design.
The task belongs to API design rather than a neighboring skill.
Routes use consistent resource/action language and avoid implementation leakage.
HTTP method and status choices match client action, retry, and cache semantics (400 vs 422, 401 vs 403, 409/412 for conflicts).
Request schemas are separate from internal models and reject or handle unknown fields deliberately.
Response schemas mark required, optional, nullable, read-only, deprecated, and sensitive fields, and enum-growth policy is explicit.
Request and response examples cover success, validation failure, auth failure, state conflict, and server failure where relevant.
One consistent error envelope (RFC 9457 Problem Details or equivalent); errors carry a machine-readable type/code, never 200 + error body.
Mutating operations define idempotency (Idempotency-Key with request fingerprinting + replay), concurrency (ETag/If-Match), and duplicate-request behavior, or explicitly reject retries.
Long-running work returns 202 + a pollable operation resource, not a held connection.
Auth, scope, tenant, object-level, function-level, and property-level boundaries are visible in the contract (tenancy derived from the token, not client input); writable/readable fields are allowlisted (no mass assignment, no internal-field leakage).
Rate-limit/quota responses define status, retry timing, and client-visible headers or fields.
One versioning scheme chosen; additive-vs-breaking rules, deprecation (Deprecation/Sunset), and migration window are stated.
A machine-readable contract (current OpenAPI) exists for nontrivial surfaces; public/agent-facing docs point back to it; contract tests/fixtures guard drift.
Do NOT Use When
Use instead
When
http-semantics
The question is purely about HTTP method, status, header, caching, conditional-request, or content-negotiation semantics, rather than whole-surface API design.
route-handler-design
You are implementing the handler itself — framework-specific request parsing, middleware order, runtime choice, raw body parsing, CORS mechanics — rather than defining the externally-visible contract.
system-interface-contracts
The boundary is broader than an HTTP API endpoint, such as jobs, modules, events, services, or agent interfaces.
event-contract-design
You need asynchronous event schema, envelope, topic/channel naming, replay, dead-letter, or compatibility rules.
entity-relationship-modeling
You need persistence structure, keys, constraints, indexes, normalization, or lifecycle.
webhook-integration
You are implementing inbound third-party webhook handling, signatures, provider retry semantics, or raw payload persistence.
contract-testing
The API contract exists and the task is writing the provider/consumer tests that pin compatibility, rather than designing the contract those tests verify.
semantics
You are naming a single field, status code, or error code for truthfulness, rather than designing the surface.
debugging
An API already fails and needs root-cause diagnosis.
1---2name: api-design3description: Use when designing or reviewing HTTP API surfaces: consumer tasks, audience class, protocol/paradigm fit, resources/actions, route taxonomy, request and response schemas, status codes in context, pagination, filtering, sorting, field selection, idempotency, auth and tenant boundaries, error envelopes, rate-limit signals, versioning, deprecation, discovery, and contract artifacts. Do NOT use for pure HTTP protocol semantics (use `http-semantics`), framework-specific route handler mechanics (use `route-handler-design`), non-HTTP system contracts (use `system-interface-contracts`), async event contracts (use `event-contract-design`), database design (use `entity-relationship-modeling`), inbound provider webhook mechanics (use `webhook-integration`), or post-failure diagnosis (use `debugging`). Do NOT use for define the broader contract between a job, service, and dashboard. Do NOT use for design database tables, foreign keys, and views.4license: MIT5---6# API Design78## Concept of the skill910Use when designing or reviewing HTTP API surfaces: consumer tasks, audience class, protocol/paradigm fit, resources/actions, route taxonomy, request and response schemas, status codes in context, pagination, filtering, sorting, field selection, idempotency, auth and tenant boundaries, error envelopes, rate-limit signals, versioning, deprecation, discovery, and contract artifacts.1112## Coverage1314Design clear, durable HTTP API surfaces — the contract another program depends on. This skill covers:1516- **Consumer & task framing** — who calls the API, the job each call accomplishes, the audience class, the owner, and the stability/release channel that set the compatibility bar.17- **Protocol/paradigm fit** — confirming HTTP/REST is the right shape before drawing routes (vs GraphQL, gRPC, or async events).18- **Resource & action modeling** — resources vs RPC-style action endpoints, route naming, when an operation is a sub-resource, a state transition, or an async job.19- **Schemas** — request/response/error body shapes as *public representations* separate from internal models; validation, required vs optional, nullability, read-only/write-only, and additive evolution — including extensible enums (treat enum sets as open so a new server-added value never breaks a strict client).20- **Status codes in context** — choosing the code that matches retry behavior and the required client action (the status-code reference table below).21- **Error envelopes** — a single consistent error shape; RFC 9457 Problem Details as the portable default.22- **Collections** — pagination (cursor vs offset), filtering, sorting, field selection, stable ordering, and empty-result behavior.23- **Idempotency & concurrency** — `Idempotency-Key` for unsafe writes (request fingerprinting, replay, and conflict rules); `ETag` / `If-Match` optimistic concurrency for updates.24- **Async / long-running operations** — `202 Accepted` + operation-resource polling.25- **Rate-limit & deprecation signals** — `429` + the `RateLimit` / `RateLimit-Policy` fields / `Retry-After`; `Deprecation` + `Sunset` headers.26- **Versioning** — URI vs header vs date-based; additive-change rules and the deprecation lifecycle.27- **Auth, tenant & authorization boundaries** — authentication, scopes, tenancy, and per-object/per-property authorization (mass-assignment and output-exposure control) in the contract.28- **Discovery & contract artifacts** — OpenAPI (the latest version your toolchain supports) as the machine-readable source of truth; an API catalog for portfolio-level discovery; AI/tool discoverability; contract tests and fixtures.2930This skill owns the product-facing contract of an HTTP API. It does **not** own the broader interface contract between systems, async event envelopes, stored data design, inbound provider webhook mechanics, framework-specific route handler implementation, or diagnosis of an already failing endpoint.3132## Philosophy of the skill33An API is a product surface for another program. Its main job is **stable meaning under change**: a consumer that integrated last year should keep working, and a client should be able to tell what happened, what it can do next, and whether retrying is safe without reading server code. Internal convenience should not leak into routes, schemas, or errors unless consumers actually need it.3435Prefer **boring consistency**. A small set of predictable patterns — one error shape, one pagination style, one idempotency mechanism — beats clever endpoint-specific behavior that every client has to rediscover. Consistency is itself a feature: it is what lets a client author one HTTP layer instead of one per endpoint.3637**Assume observable behavior becomes part of the contract.** Clients can come to depend on response shape, ordering, error codes, null behavior, latency class, and even undocumented fields once those details are visible. Decide deliberately what is public and what may change.3839**Do not expose the database, framework, or internal service layout by accident.** A resource is the representation the client needs, not necessarily a table. A route is the public language of the product, not the shape of the implementation. A storage table can split, merge, denormalize, or migrate without changing the API if the representation is stable.4041**Design the contract first, then implement to it.** A contract written as an OpenAPI document (or even a reviewed Markdown spec) before code exists is cheap to change; the same change after three teams have coded against it is expensive. The contract is the artifact frontend, backend, and test authors agree on in parallel — that parallelism is the whole point of writing it down.4243**Evolve additively; break loudly.** Adding an optional field, a new endpoint, or a new enum value a client can ignore is safe. Removing a field, renaming it, tightening validation, or changing a status code is breaking — it requires a new version and an announced deprecation, never a silent in-place edit.4445**Standards are defaults, not ceremony.** Start from HTTP semantics, Problem Details, OpenAPI, documented deprecation headers, and explicit compatibility rules. Deviate only when the consumers and the migration cost justify it.4647## Method48491. **Triage consumers, audience, and tasks before drawing routes.** List who calls the API and the job each call accomplishes; name the owner, the stability level (internal / preview / beta / stable / deprecated), the release channel, and the data sensitivity. Classify the audience — public / partner / internal / admin / service-to-service / agent-or-tool — because that classification sets the compatibility bar, the auth model, and how loudly you must version. Design for those tasks, not for your table layout.502. **Decide whether this is API design or a handoff to a neighboring skill** (see Boundary Triage), then **choose the protocol/paradigm fit** before drawing routes — a graph-shaped client-driven read surface may belong in GraphQL, a low-latency internal call in gRPC, and a fire-and-forget notification in an async event contract.513. **Model resources first; use actions only when standard resource operations do not fit.** Most operations are CRUD on nouns (`/orders`, `/orders/{id}`). When a verb has no clean resource (`/orders/{id}/cancel`, `/payments/{id}/refunds`), model it as a state transition or a sub-resource, not a free-floating RPC.524. **Write the contract before implementation:** method, URI, request, response, errors, auth, idempotency, pagination, versioning, and concrete examples.535. **Define request, response, and error schemas as public representations** — separate from internal models. Decide required vs optional, nullable vs absent, read-only/write-only, and enum-vs-open-string for every field. Validate input and return field-level errors.546. **Choose HTTP method and status behavior by client action and retry semantics** (table below). The code is a contract: it tells the client whether to retry, re-auth, fix input, or give up.557. **Define collection semantics:** pagination style (cursor for large/changing sets, offset only for small bounded ones), filtering grammar, sort keys with a stable unique tiebreaker, optional sparse field selection, stable total ordering, and empty-result behavior.568. **Define mutation semantics:** idempotency keys with request fingerprinting and replay, optimistic concurrency (`ETag`/`If-Match`), async job behavior, and retry windows for unsafe operations.579. **State auth, tenant, scope, object-level, function-level, and property-level authorization** in the contract.5810. **Publish machine-readable contract artifacts and add fixtures or contract tests.** Generate or hand-write an OpenAPI document; add contract tests or recorded fixtures so drift between doc and implementation is caught.5911. **Define versioning, compatibility, deprecation, sunset, and discovery before the first breaking change** — pick one versioning scheme, document the support window, and plan to emit `Deprecation`/`Sunset` headers.6061## Boundary Triage6263Use this skill when the task is the shape and behavior of an HTTP API endpoint or documented JSON-over-HTTP contract.6465| If the task is mainly... | Use |66|---|---|67| REST/resource route taxonomy, request/response shape, pagination, idempotency, versioning, errors | `api-design` |68| HTTP method/status/header semantics independent of one API surface | `http-semantics` |69| Next.js `route.ts`, runtime choice, raw body parsing, CORS mechanics, framework defaults | `route-handler-design` |70| A contract across modules, jobs, services, agents, teams, or multiple transport types | `system-interface-contracts` |71| Async topics, event envelopes, replay, dead-letter behavior, CloudEvents, AsyncAPI | `event-contract-design` |72| Tables, keys, constraints, indexes, normalization, stored-data lifecycle | `entity-relationship-modeling` |73| Inbound third-party webhook signatures, provider retry contracts, raw payload persistence | `webhook-integration` |74| Verifying consumer/provider compatibility from the API contract | `contract-testing` |75| A behavior is already broken and needs root-cause isolation | `debugging` |7677## Protocol and Paradigm Triage7879This skill may help choose the initial API style, but it should not take over full ownership of non-HTTP or specialized schema work.8081| Paradigm | Prefer when | Boundary |82|---|---|---|83| Resource-oriented JSON-over-HTTP | Consumers need broad tooling, cacheable/linkable resources, simple public or partner compatibility, and OpenAPI-style documentation. | `api-design` owns the endpoint contract; verify method/header details with `http-semantics`. |84| GraphQL-over-HTTP | Consumers need query-shaped reads, selective nested data, or frontend-driven composition, and the organization can govern schema evolution and resolver cost. | Use `api-design` for the selection tradeoff only; schema/resolver ownership belongs to a GraphQL-specific or broader interface-contract skill when available. |85| gRPC or typed IDL | Internal service-to-service calls need strict generated clients, streaming, low-latency binary transport, or strongly versioned RPC methods. | Route detailed ownership to `system-interface-contracts` and transport-specific skills; do not force gRPC design into this HTTP API skill. |86| Async events or webhooks | Producers push state changes, consumers subscribe, replay matters, or delivery/retry contracts dominate. | Use `event-contract-design` for event streams and `webhook-integration` for inbound provider webhooks. |8788Protocol choice depends on consumers, tooling, compatibility, cache behavior, operations, contract ownership, and migration cost. Avoid categorical rules like "public means REST" or "internal means gRPC."8990## Consumer and Task Framing9192Before drawing routes, name:9394- **Consumers:** browser UI, mobile app, partner, internal service, CLI, background worker, admin operator, agent/tool.95- **Audience class:** public, partner, internal, admin/operator, service-to-service, or agent/tool consumer.96- **Task:** list, inspect, create, update, cancel, approve, export, retry, reconcile, search, or report.97- **Owner:** team responsible for the contract and for client communication.98- **Stability:** internal, preview, beta, stable, deprecated.99- **Change tolerance:** coordinated deploy, independent deploy, public backward compatibility, or migration window.100- **Data sensitivity:** tenant, principal, scopes, object-level checks, property-level exposure.101102If the API has multiple independent consumers, **design for the least coordinated one.** A public or partner API needs stronger compatibility and deprecation discipline than an internal route called only by one UI bundle. A co-deployed internal surface may relax the version *label* — but never the additive-vs-breaking discipline.103104## Resources vs Actions105106Start with resources and standard operations:107108| Operation | Typical HTTP shape | Notes |109|---|---|---|110| List | `GET /orders` | Define filters, sort, pagination, and stable ordering. |111| Read one | `GET /orders/{orderId}` | Use an opaque stable identifier. Decide 404 vs hidden-by-policy 404. |112| Create | `POST /orders` | Usually returns `201 Created`, `Location`, and the created representation. |113| Replace | `PUT /orders/{orderId}` | Full replacement; idempotent if the same representation is sent repeatedly. |114| Partial update | `PATCH /orders/{orderId}` | Define patch format and concurrency behavior explicitly. |115| Delete | `DELETE /orders/{orderId}` | Idempotent post-condition; repeated calls may return different responses. |116117Use an action endpoint when the operation is not a natural resource state transfer:118119- `POST /orders/{orderId}/cancel`120- `POST /exports`121- `POST /orders/{orderId}/refund`122- `POST /imports/{importId}/retry`123124Action endpoints still need resource discipline: request schema, response schema, auth, idempotency, retry behavior, error types, and compatibility rules. Avoid inventing custom HTTP verbs. Pick one action style and use it consistently. Reach for an action endpoint only when the operation genuinely is not a resource lifecycle change.125126## Status Codes — Retry & Client-Action Semantics127128API design does not replace `http-semantics`, but every API contract must respect HTTP semantics. Pick the code by what you want the client to *do*, not just by category.129130| Decision | API design rule |131|---|---|132| Method | Choose by operation meaning, safety, idempotency, payload semantics, and client retry behavior. |133| Status family | The first digit is the contract: 2xx fulfilled, 3xx further action, 4xx client-actionable problem, 5xx server or upstream failure. |134| Retryability | State whether the client may retry, when, and whether it must reuse an idempotency key or conditional header. |135| Client action | Error responses must tell the client whether to fix input, authenticate, ask for permission, retry later, or stop using the endpoint. |136| Representation metadata | Define `Content-Type`, content negotiation, caching, `ETag`, `Vary`, and `Location` when they matter. |137138| Code | Meaning | Client should |139|---|---|---|140| `200 OK` | Success with a body | Use the body |141| `201 Created` | Resource created | Read `Location` / returned resource |142| `202 Accepted` | Accepted, processing async | Poll the operation/status URL |143| `204 No Content` | Success, no body (e.g. delete) | Proceed; expect no body |144| `400 Bad Request` | Malformed / un-parseable request | Fix the request; do **not** retry unchanged |145| `401 Unauthorized` | Missing/invalid credentials | Authenticate, then retry |146| `403 Forbidden` | Authenticated but not allowed | Do not retry; lacks permission/scope |147| `404 Not Found` | Resource absent (or hidden for authz) | Do not retry as-is |148| `409 Conflict` | State conflict (duplicate, version clash) | Resolve conflict, then retry |149| `410 Gone` | Resource permanently removed | Stop calling; update integration |150| `412 Precondition Failed` | `If-Match`/`If-Unmodified-Since` failed | Re-fetch, reconcile, retry |151| `415 Unsupported Media Type` | Request body media type unsupported | Send a supported `Content-Type` |152| `422 Unprocessable Content` | Syntactically valid, semantically invalid | Fix field-level errors |153| `428 Precondition Required` | Server requires a conditional request | Retry with `If-Match` |154| `429 Too Many Requests` | Rate limited | Back off per `Retry-After` or the `RateLimit` field's reset parameter |155| `500 Internal Server Error` | Unexpected server fault | Retry with backoff (idempotent ops) |156| `503 Service Unavailable` | Temporarily down/overloaded | Retry per `Retry-After` |157158`422` is the right code for "well-formed JSON, business rule violated" (RFC 9110 §15.5.21); reserve `400` for requests the server cannot parse. Both are common — pick one convention and apply it everywhere. Never return `200 OK` with an error body for a failed operation — that breaks clients, caches, observability, and retry logic.159160## Request and Response Schemas161162Treat schemas as public representations:163164- Separate **request models from response models from database models.**165- Mark required, optional, nullable, read-only, write-only, deprecated, and output-only fields.166- Define stable identifier format and opacity. Do not expose internal IDs unless they are intended public IDs.167- Name timestamps, time zones, units, currencies, and precision explicitly.168- Define **enum extension policy:** closed set, open set with unknown handling, or version-gated additions. Treat enum sets as open by default so a new server-added value never breaks a strict client. If the OpenAPI toolchain supports a vendor extension such as `x-extensible-enum`, use it as documentation or generator guidance — but do not depend on one extension unless the supported tooling is named.169- Define nested object expansion rules (`include`, `expand`, `fields`) if clients can request related data.170- For **bulk operations**, define partial-failure behavior and whether ordering is preserved.171- Provide one success example and at least one failure example for every nontrivial operation.172173Validation should happen at the API boundary. **Do not bind arbitrary request JSON directly to internal entities** — that causes mass-assignment and property-authorization failures. Allowlist writable input fields and reject or ignore unknown fields deliberately, with a documented policy.174175## Collection Endpoints — Pagination, Filtering & Sorting176177A collection endpoint is incomplete until it defines ordering, pagination, filtering, sorting, and empty-result behavior.178179| Strategy | Best for | Cost / risk |180|---|---|---|181| **Cursor (keyset)** — opaque `cursor`/`next` token over a stable sort key | Large or actively changing collections; infinite scroll | Cannot jump to an arbitrary page; cursor must encode the sort |182| **Offset** — `?limit=&offset=` (or `page=`) | Small, bounded, slow-changing sets needing page numbers | At deep offsets the DB reads and discards all skipped rows; inserts cause **skipped or duplicated** items between pages |183184Default to **cursor-based** pagination for anything that grows; offset is acceptable only for small admin lists. Always define a **stable total ordering** (e.g. `created_at, id`) — pagination over a non-deterministic order silently drops and repeats rows.185186**Cursor rules:**187188- Ordering must be stable and deterministic; add a **unique tiebreaker** to every sort, even when the visible sort key is not unique.189- Cursors are opaque to clients and must bind to the filter and sort context that produced them.190- Define default and maximum page size.191- Return pagination metadata consistently (`next`/`prev` cursors and/or link relations).192- Do not promise exact total counts unless the system can produce them cheaply and correctly.193- An empty list is a successful `200` response, not an error.194195**Offset rules:**196197- Name a maximum offset or page limit.198- Warn when ordering is not stable under concurrent writes.199- Use only when the collection is naturally stable or consumer impact is low.200201**Filtering, sorting & field selection** — filters and sorts are part of the contract, not pass-through query language:202203- Pick one grammar and document the allowed fields and operators (`?status=open&sort=-created_at`).204- **Allowlist** filter/sort fields and operators — never interpolate them into a query.205- Define case sensitivity, time zone, date inclusivity, null handling, and enum matching.206- Define sort direction syntax and default sort; reject unsupported filters/sorts with a predictable error.207- Offer sparse field selection (`?fields=id,status`) only when payload size is a real problem; an extra option is a permanent contract. Document default fields, allowed fields, dependency fields, and auth behavior.208- **Never use field omission as the only authorization control** — enforce authorization before shaping the response.209210## Mutations, Idempotency & Optimistic Concurrency211212For every mutating endpoint, decide whether duplicate requests are safe. `GET`/`PUT`/`DELETE` are idempotent by HTTP definition (RFC 9110); `POST` and `PATCH` are not.213214| Mutation type | Contract decision |215|---|---|216| Natural idempotent update | Use `PUT`, `DELETE`, conditional `PATCH`, or a state transition with a deterministic post-condition. |217| Non-idempotent create/action | Support client-generated idempotency keys when duplicate side effects would harm users. |218| Long-running work | Return `202 Accepted` with an operation resource, cancellation semantics, completion states, and retry behavior. |219| Concurrency-sensitive update | Use `ETag` plus `If-Match`, a version field, or another explicit precondition. |220221**Idempotency keys.** For create/charge-style `POST`s, accept an **`Idempotency-Key`** request header — the de-facto industry pattern (Stripe, PayPal, Square). It was specified in the IETF `httpapi` working-group draft `draft-ietf-httpapi-idempotency-key-header`, but that draft has lapsed (the `-07` revision expired 2026-04-18), so treat the convention as the reference, not a ratified standard, and pair it with the provider contracts your clients actually depend on. When using idempotency keys, define:222223- **Header/field name** (usually `Idempotency-Key`) and which methods accept it — do not require it for naturally idempotent `GET`/`DELETE`.224- **Key ownership and scope:** client-generated, scoped to tenant/user/API key and endpoint.225- **Entropy and length requirements.**226- **Store-and-replay:** atomically persist key, request fingerprint, response status, and response body; replay the identical stored response for any retry within the retention window, so a network retry never double-creates.227- **Request fingerprint:** bind the key to a hash of the request payload. A same key arriving later with a *different* body returns a deterministic client error (`409`/`422`) — never silently apply the new body under an old key.228- **In-flight collision behavior:** a second request with the same key while the first is still processing returns `409` (or waits on the original) rather than executing twice concurrently.229- **TTL and pruning behavior.**230231**Optimistic concurrency.** Return an `ETag` on reads; require `If-Match: <etag>` on updates. A stale `If-Match` fails with `412 Precondition Failed` instead of clobbering a change the client never saw; a domain state conflict returns `409 Conflict`. Do not collapse both into a generic `400`. Use `428 Precondition Required` to force conditional writes.232233## Async & Long-Running Operations234235Do not hold a connection open for work that takes seconds-to-minutes. Return `202 Accepted` with a link to an **operation resource** (`/operations/{id}` or the eventual resource's status URL); the client polls it and reads `status: pending|succeeded|failed` plus a result or error, with cancellation semantics defined. This keeps timeouts, retries, and progress observable. (If the consumer needs *push* notification of completion, that is a webhook/event-contract concern — route to `webhook-integration` / `event-contract-design`.)236237## Auth, Tenancy & Exposure238239Authorization is part of API design, not only middleware. For each operation, specify:240241- **Principal:** user, service account, API key, anonymous caller, or delegated actor.242- **Tenant boundary** and how the tenant is selected — derive it from the token claim, **not** a client-supplied id.243- **Scopes, roles, or permissions** required, and what `401` vs `403` mean here.244- **Object-level authorization:** can this principal act on this specific resource?245- **Function-level authorization:** can this principal perform this operation?246- **Property-level authorization:** can this principal read or write each sensitive field?247- **Hidden-resource policy:** return `403`, `404`, or a redacted representation?248- **Audit and correlation identifiers** required for sensitive operations.249250Property-level authorization matters in **both directions** (OWASP API3:2023, broken object property-level authorization). Output schemas can leak fields that should be hidden; input schemas can allow writes to fields that should be server-controlled. Bind writable fields to an allowlist so a client cannot set `role`, `owner_id`, or `is_admin` via mass assignment; bind readable fields so internal columns do not leak in responses.251252## Error Envelopes — RFC 9457 Problem Details253254Use **one** error shape across the whole API. The portable default is RFC 9457 *Problem Details for HTTP APIs* (published July 2023; obsoletes RFC 7807, same wire format), served as `Content-Type: application/problem+json`. Migrate old RFC 7807 references to RFC 9457 while preserving the same client-facing problem-type identities where possible — the upgrade is additive, so there is no urgency to migrate the wire format if you already standardized on 7807.255256```json257{258 "type": "https://api.example.com/problems/insufficient-funds",259 "title": "Insufficient funds",260 "status": 403,261 "detail": "Charge of $50.00 exceeds the available balance of $12.40.",262 "instance": "/accounts/12345/charges/67890",263 "errors": [264 { "detail": "amount exceeds balance", "pointer": "/amount" }265 ]266}267```268269- `type` is a stable URI identifying the problem class — the field clients branch on. **Treat changing a `type` URI or its meaning as a breaking change.** RFC 9457 added an IANA "HTTP Problem Types" registry for common `type` values.270- `title` is human-readable and constant per `type`; `detail` is instance-specific. Never make clients parse `detail` prose for logic.271- Problem Details **base members** are `type`, `title`, `status`, `detail`, and `instance`. Validation arrays such as `errors`, `violations`, or `invalid_params` are RFC 9457 **extension** members (§3.2), not standardized base fields — so the `errors` array shown above is part of *your* contract to document. Define its member name, item schema, JSON Pointer/field-path convention, ordering, localization, and compatibility rules.272- Always include a machine-readable error **code or `type`** in addition to the HTTP status, plus field-level errors for validation failures so a form can highlight the right input.273- Avoid stack traces, database names, secrets, internal hostnames, or raw upstream messages in public errors.274- Do **not** return `200 OK` with an error body — that defeats every generic HTTP client and is a classic semantic failure.275276Validation errors should distinguish: malformed JSON / wrong content type; missing required field; unknown-field policy violation; field type/format violation; domain-rule violation; authorization failure; state conflict.277278## Rate Limits & Quotas279280A rate-limited API must tell clients how to behave. Define:281282- **Partition key:** user, tenant, token, IP, endpoint, or business flow.283- **Quota unit:** request count, resource count, tokens, bytes, concurrent jobs, or cost units.284- **Window or budget reset policy.**285- **Status code,** normally `429 Too Many Requests`.286- **`Retry-After`** behavior on retryable throttling. When both `Retry-After` and a `RateLimit` reset are present, they should point to the same instant.287- **Rate-limit headers or body fields, with exact names and semantics.** The IETF `RateLimit` / `RateLimit-Policy` structured-field headers (`draft-ietf-httpapi-ratelimit-headers`) carry remaining quota and the window reset as parameters of the single `RateLimit` field (not the older separate `RateLimit-Limit`/`-Remaining`/`-Reset` headers). It is still an active Internet-Draft, not an RFC — if you adopt it, document the convention explicitly and keep clients tolerant of provider-specific alternatives (e.g. `x-ratelimit-*`).288- **Browser visibility:** expose the needed headers via CORS if browser clients must read them.289- **Classification:** whether exhausted quota is a retryable condition, a plan limit, or a permanent business error.290291## Versioning, Compatibility, Deprecation & Sunset292293Define compatibility rules **before the first breaking change**. For any externally-consumed API, do not ship an unversioned "default" surface — it creates silent breakage the day a change lands. (A tightly-controlled *internal* API with a known, co-deployed set of clients may defer an exposed version, but it still owes the same additive-vs-breaking discipline.)294295**Usually backward-compatible (do not bump the version):** adding optional response fields; adding new enum values *only if* clients are required to ignore unknown values; adding optional request fields; adding new endpoints; adding new filters/sorts when old behavior is unchanged.296297**Usually breaking (need a new version + migration path):** removing or renaming fields; making optional request fields required; changing field meaning, unit, precision, enum semantics, or identifier format; changing default sort/order in a client-visible way; changing error `type`/code semantics; changing pagination cursor format without preserving old cursors during a transition; tightening authorization clients previously depended on.298299Pick **one** versioning scheme and use it consistently:300301| Scheme | Example | Trade-off |302|---|---|---|303| **URI path** | `/v1/orders` | Most explicit and cache-friendly; coarse — a new version is a whole new tree. Best for public APIs needing parallel versions. |304| **Header / media type** | `Accept: application/vnd.example.v2+json` | Keeps URLs stable; harder to test from a browser/curl. |305| **Date-based** | `Stripe-Version: 2026-01-15` | Fine-grained, one pinned version per account; large internal transformation cost (Stripe's model). |306307Avoid minor/patch versions in routes. Additive changes land in place; incompatible changes need a major version or a negotiated migration path. Stripe's decade of backward compatibility is built on absorbing this complexity internally via a per-version transformation layer rather than forcing every client to migrate at once.308309**Deprecation & sunset** are distinct stages:310311- Signal a deprecated endpoint/version with the `Deprecation` response header (RFC 9745) — "no longer recommended." Use `Link: <...>; rel="deprecation"` to point to migration documentation.312- When the resource will actually stop responding, add the `Sunset` header (RFC 8594) with the removal date — its timestamp must **not** precede the `Deprecation` date.313- Announce with documentation and client communication *before* behavior changes; keep deprecated behavior unchanged until the sunset/migration gate; track real usage before removal. Deprecation is a signal, not a behavior change — do not silently remove an endpoint because the server no longer needs it.314315## Discovery & Contract Artifacts316317Every nontrivial API should have a machine-readable contract artifact. For HTTP APIs, prefer **OpenAPI**:318319- Use the current OpenAPI release your toolchain supports (3.2 is current, 3.1 is broadly tooled with full JSON Schema alignment).320- Define operation IDs, parameters, request bodies, responses, schemas, security requirements, headers, examples, and error responses.321- Include pagination parameters and response links, idempotency-header semantics for mutating operations, and deprecation/sunset headers where applicable.322- Keep examples valid against schemas — validate the generated examples, not only the schema shapes.323324For public API **portfolios**, publish an API catalog (`/.well-known/api-catalog`, RFC 9727) so consumers can discover available surfaces, documentation, usage policies, versions, and OpenAPI definitions.325326For **agent/tool consumers**, make the same public contract easy to ingest without inventing a second source of truth:327328- Keep OpenAPI (or the canonical contract artifact) authoritative.329- Provide short, stable links to authentication, rate limits, pagination, idempotency, error types, deprecation policy, and examples.330- Optionally publish AI-readable documentation such as `/llms.txt` or an equivalent docs index that links to the canonical contract. Treat such files as public documentation support, not a required verification gate, and never a place for secrets, customer data, private paths, or unreleased internals.331- Test whether an agent can answer basic integration questions from the published docs — but fix the *canonical contract* when the agent-facing summary and the contract disagree.332333Contract artifacts do not replace contract tests. OpenAPI describes the provider surface; consumer-driven contract tests capture what a specific consumer relies on and verify the provider against that expectation.334335## Universal API Anti-Patterns336337| Anti-pattern | Why it fails | Replace with |338|---|---|---|339| `200 OK` with an error body | Clients, caches, retries, and observability see success. | Real 4xx/5xx status plus Problem Details or stable equivalent. |340| Database-shaped routes and fields | Storage refactors become breaking API changes. | Consumer-facing resource representations with opaque stable IDs. |341| Custom verbs or action sprawl | Every endpoint needs custom client behavior. | Standard methods first; action endpoints only for real non-resource transitions. |342| Offset pagination over large mutable collections | Inserts/deletes cause duplicates, gaps, and expensive deep pages. | Cursor/keyset pagination with deterministic ordering and a unique tiebreaker. |343| Non-idempotent retries | Network ambiguity can duplicate side effects. | Idempotency keys, conditional requests, or explicit "do not retry" rules. |344| Public or partner breaking changes without a migration path | Independent consumers cannot coordinate instantly. | Compatibility rules, major-version or negotiated migration, `Deprecation`, and `Sunset`. |345| Closed enum additions without unknown handling | Generated clients crash or reject future values. | Open enum policy, fallback behavior, or version-gated additions. |346| Implicit null, missing, or unknown-field behavior | Clients guess whether absence means unset, hidden, unauthorized, or unchanged. | Document nullable vs optional, patch semantics, and unknown-field policy. |347| Over-broad input binding | Mass assignment and property-level authorization bugs leak through the API. | Separate request models, allowlisted writable fields, and property-level auth. |348| Chatty task flow | Clients stitch many calls together, creating latency and consistency problems. | Expansion, includes, compound resources, batch endpoints, or async jobs when the task requires them. |349350## Worked Example: List Orders351352Contract decisions for `GET /orders`:353354- Consumers: admin UI and reporting integration.355- Auth: user must have `orders:read`; tenant is derived from auth context, not query string.356- Filters: `status`, `created_at[gte]`, `created_at[lt]`, `customer_id`.357- Sorts: `created_at`, `updated_at`; server always appends `id` as a tiebreaker.358- Pagination: cursor, default `limit=50`, max `limit=200`, opaque `page[after]`.359- Response: `{ "data": [...], "page": { "next": "..." } }`.360- Empty result: `200` with `data: []`.361- Errors: invalid filter field → `400` problem; invalid cursor → `400` problem; unauthorized tenant access → `404` or `403` according to policy.362- Contract fixtures: normal page, empty page, unsupported filter, invalid cursor, unauthorized access.363364## Worked Example: Create Order365366Contract decisions for `POST /orders`:367368- Requires `Idempotency-Key` because duplicate order creation can harm users.369- Idempotency key is scoped to tenant, authenticated principal, endpoint, and request fingerprint.370- Reusing the same key with the same request replays the original status and body.371- Reusing the same key with a different request returns a deterministic client error.372- Concurrent duplicate-key attempts return the stored result or an in-flight conflict response defined by the contract.373- Success returns `201 Created`, `Location: /orders/{orderId}`, and the order representation.374- Semantic validation returns `422` Problem Details with field-level errors.375- State conflict (e.g. unavailable inventory) returns `409` with a stable problem type.376- A server timeout does not license client-side duplicate creation; the client retries with the same idempotency key.377378## Evals379380This skill ships a comprehension-eval artifact at [`examples/evals/api-design.json`](https://github.com/jacob-balslev/skill-graph/blob/main/examples/evals/api-design.json). The checklist below is the authoring gate for API surface decisions; the eval file is the grader surface.381382## Verification383384- [ ] Consumers, tasks, owners, stability level, and release channel are named.385- [ ] Audience class and protocol/paradigm fit are named before detailed route design.386- [ ] The task belongs to API design rather than a neighboring skill.387- [ ] Routes use consistent resource/action language and avoid implementation leakage.388- [ ] HTTP method and status choices match client action, retry, and cache semantics (`400` vs `422`, `401` vs `403`, `409`/`412` for conflicts).389- [ ] Request schemas are separate from internal models and reject or handle unknown fields deliberately.390- [ ] Response schemas mark required, optional, nullable, read-only, deprecated, and sensitive fields, and enum-growth policy is explicit.391- [ ] Request and response examples cover success, validation failure, auth failure, state conflict, and server failure where relevant.392- [ ] One consistent error envelope (RFC 9457 Problem Details or equivalent); errors carry a machine-readable `type`/code, never `200` + error body.393- [ ] Collection endpoints define filters, sorts, stable ordering, pagination style, page-size limits, allowlisted filter/sort fields, and empty-result behavior.394- [ ] Mutating operations define idempotency (`Idempotency-Key` with request fingerprinting + replay), concurrency (`ETag`/`If-Match`), and duplicate-request behavior, or explicitly reject retries.395- [ ] Long-running work returns `202` + a pollable operation resource, not a held connection.396- [ ] Auth, scope, tenant, object-level, function-level, and property-level boundaries are visible in the contract (tenancy derived from the token, not client input); writable/readable fields are allowlisted (no mass assignment, no internal-field leakage).397- [ ] Rate-limit/quota responses define status, retry timing, and client-visible headers or fields.398- [ ] One versioning scheme chosen; additive-vs-breaking rules, deprecation (`Deprecation`/`Sunset`), and migration window are stated.399- [ ] A machine-readable contract (current OpenAPI) exists for nontrivial surfaces; public/agent-facing docs point back to it; contract tests/fixtures guard drift.400401## Do NOT Use When402403| Use instead | When |404|---|---|405| `http-semantics` | The question is purely about HTTP method, status, header, caching, conditional-request, or content-negotiation semantics, rather than whole-surface API design. |406| `route-handler-design` | You are implementing the handler itself — framework-specific request parsing, middleware order, runtime choice, raw body parsing, CORS mechanics — rather than defining the externally-visible contract. |407| `system-interface-contracts` | The boundary is broader than an HTTP API endpoint, such as jobs, modules, events, services, or agent interfaces. |408| `event-contract-design` | You need asynchronous event schema, envelope, topic/channel naming, replay, dead-letter, or compatibility rules. |409| `entity-relationship-modeling` | You need persistence structure, keys, constraints, indexes, normalization, or lifecycle. |410| `webhook-integration` | You are implementing inbound third-party webhook handling, signatures, provider retry semantics, or raw payload persistence. |411| `contract-testing` | The API contract exists and the task is writing the provider/consumer tests that pin compatibility, rather than designing the contract those tests verify. |412| `semantics` | You are naming a single field, status code, or error code for truthfulness, rather than designing the surface. |413| `debugging` | An API already fails and needs root-cause diagnosis. |414415## References416417- RFC 9110 — HTTP Semantics: https://www.rfc-418419…(truncated)
Run npx skillmds@latest add jacob-balslev/api-design in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Use when designing or reviewing HTTP API surfaces: consumer tasks, audience class, protocol/paradigm fit, resources/actions, route taxonomy, request and response schemas, status codes in context, pagination, filtering, sorting, field selection, idempotency, auth and tenant boundaries, error envelopes, rate-limit signals, versioning, deprecation, discovery, and contract artifacts. Do NOT use for pure HTTP protocol semantics (use `http-semantics`), framework-specific route handler mechanics (use `route-handler-design`), non-HTTP system contracts (use `system-interface-contracts`), async event contracts (use `event-contract-design`), database design (use `entity-relationship-modeling`), inbound provider webhook mechanics (use `webhook-integration`), or post-failure diagnosis (use `debugging`). Do NOT use for define the broader contract between a job, service, and dashboard. Do NOT use for design database tables, foreign keys, and views. It is listed under Product & Planning on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: makes network calls, reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
jacob-balslev (@jacob-balslev) published this skill. Their other Agent Skills are listed on their SkillMD profile.