agentsclimarketplace

Api design

Skill jacob-balslev/skills/skills/backend-engineering/api-design

Public Agent Skills library exported from skill-graph. Install: npx skills add jacob-balslev/skills

Install
npx -y skills add jacob-balslev/skills --skill api-design

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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`).

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

46.0 KB, as published. Nobody here has run it

API Design

Concept of the skill

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 & concurrencyIdempotency-Key for unsafe writes (request fingerprinting, replay, and conflict rules); ETag / If-Match optimistic concurrency for updates.
  • Async / long-running operations202 Accepted + operation-resource polling.
  • Rate-limit & deprecation signals429 + the RateLimit / RateLimit-Policy fields / Retry-After; Deprecation + Sunset headers.
  • 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

  1. 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.
  2. 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.
  3. 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.
  4. Write the contract before implementation: method, URI, request, response, errors, auth, idempotency, pagination, versioning, and concrete examples.
  5. 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.
  6. 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.
  7. 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.
  8. Define mutation semantics: idempotency keys with request fingerprinting and replay, optimistic concurrency (ETag/If-Match), async job behavior, and retry windows for unsafe operations.
  9. State auth, tenant, scope, object-level, function-level, and property-level authorization in the contract.
  10. 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.
  11. 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.

If the task is mainly...Use
REST/resource route taxonomy, request/response shape, pagination, idempotency, versioning, errorsapi-design
HTTP method/status/header semantics independent of one API surfacehttp-semantics
Next.js route.ts, runtime choice, raw body parsing, CORS mechanics, framework defaultsroute-handler-design
A contract across modules, jobs, services, agents, teams, or multiple transport typessystem-interface-contracts
Async topics, event envelopes, replay, dead-letter behavior, CloudEvents, AsyncAPIevent-contract-design
Tables, keys, constraints, indexes, normalization, stored-data lifecycleentity-relationship-modeling
Inbound third-party webhook signatures, provider retry contracts, raw payload persistencewebhook-integration
Verifying consumer/provider compatibility from the API contractcontract-testing
A behavior is already broken and needs root-cause isolationdebugging

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.

ParadigmPrefer whenBoundary
Resource-oriented JSON-over-HTTPConsumers 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-HTTPConsumers 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 IDLInternal 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 webhooksProducers 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."

Consumer and Task Framing

Before drawing routes, name:

  • Consumers: browser UI, mobile app, partner, internal service, CLI, background worker, admin operator, agent/tool.
  • Audience class: public, partner, internal, admin/operator, service-to-service, or agent/tool consumer.
  • Task: list, inspect, create, update, cancel, approve, export, retry, reconcile, search, or report.
  • Owner: team responsible for the contract and for client communication.
  • Stability: internal, preview, beta, stable, deprecated.
  • 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:

OperationTypical HTTP shapeNotes
ListGET /ordersDefine filters, sort, pagination, and stable ordering.
Read oneGET /orders/{orderId}Use an opaque stable identifier. Decide 404 vs hidden-by-policy 404.
CreatePOST /ordersUsually returns 201 Created, Location, and the created representation.
ReplacePUT /orders/{orderId}Full replacement; idempotent if the same representation is sent repeatedly.
Partial updatePATCH /orders/{orderId}Define patch format and concurrency behavior explicitly.
DeleteDELETE /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.

DecisionAPI design rule
MethodChoose by operation meaning, safety, idempotency, payload semantics, and client retry behavior.
Status familyThe first digit is the contract: 2xx fulfilled, 3xx further action, 4xx client-actionable problem, 5xx server or upstream failure.
RetryabilityState whether the client may retry, when, and whether it must reuse an idempotency key or conditional header.
Client actionError responses must tell the client whether to fix input, authenticate, ask for permission, retry later, or stop using the endpoint.
Representation metadataDefine Content-Type, content negotiation, caching, ETag, Vary, and Location when they matter.
CodeMeaningClient should
200 OKSuccess with a bodyUse the body
201 CreatedResource createdRead Location / returned resource
202 AcceptedAccepted, processing asyncPoll the operation/status URL
204 No ContentSuccess, no body (e.g. delete)Proceed; expect no body
400 Bad RequestMalformed / un-parseable requestFix the request; do not retry unchanged
401 UnauthorizedMissing/invalid credentialsAuthenticate, then retry
403 ForbiddenAuthenticated but not allowedDo not retry; lacks permission/scope
404 Not FoundResource absent (or hidden for authz)Do not retry as-is
409 ConflictState conflict (duplicate, version clash)Resolve conflict, then retry
410 GoneResource permanently removedStop calling; update integration
412 Precondition FailedIf-Match/If-Unmodified-Since failedRe-fetch, reconcile, retry
415 Unsupported Media TypeRequest body media type unsupportedSend a supported Content-Type
422 Unprocessable ContentSyntactically valid, semantically invalidFix field-level errors
428 Precondition RequiredServer requires a conditional requestRetry with If-Match
429 Too Many RequestsRate limitedBack off per Retry-After or the RateLimit field's reset parameter
500 Internal Server ErrorUnexpected server faultRetry with backoff (idempotent ops)
503 Service UnavailableTemporarily down/overloadedRetry 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.

Collection Endpoints — Pagination, Filtering & Sorting

A collection endpoint is incomplete until it defines ordering, pagination, filtering, sorting, and empty-result behavior.

StrategyBest forCost / risk
Cursor (keyset) — opaque cursor/next token over a stable sort keyLarge or actively changing collections; infinite scrollCannot jump to an arbitrary page; cursor must encode the sort
Offset?limit=&offset= (or page=)Small, bounded, slow-changing sets needing page numbersAt 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 typeContract decision
Natural idempotent updateUse PUT, DELETE, conditional PATCH, or a state transition with a deterministic post-condition.
Non-idempotent create/actionSupport client-generated idempotency keys when duplicate side effects would harm users.
Long-running workReturn 202 Accepted with an operation resource, cancellation semantics, completion states, and retry behavior.
Concurrency-sensitive updateUse 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.
  • Quota unit: request count, resource count, tokens, bytes, concurrent jobs, or cost units.
  • Window or budget reset policy.
  • Status code, normally 429 Too Many Requests.
  • 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:

SchemeExampleTrade-off
URI path/v1/ordersMost explicit and cache-friendly; coarse — a new version is a whole new tree. Best for public APIs needing parallel versions.
Header / media typeAccept: application/vnd.example.v2+jsonKeeps URLs stable; harder to test from a browser/curl.
Date-basedStripe-Version: 2026-01-15Fine-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).
  • Define operation IDs, parameters, request bodies, responses, schemas, security requirements, headers, examples, and error responses.
  • 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-patternWhy it failsReplace with
200 OK with an error bodyClients, caches, retries, and observability see success.Real 4xx/5xx status plus Problem Details or stable equivalent.
Database-shaped routes and fieldsStorage refactors become breaking API changes.Consumer-facing resource representations with opaque stable IDs.
Custom verbs or action sprawlEvery endpoint needs custom client behavior.Standard methods first; action endpoints only for real non-resource transitions.
Offset pagination over large mutable collectionsInserts/deletes cause duplicates, gaps, and expensive deep pages.Cursor/keyset pagination with deterministic ordering and a unique tiebreaker.
Non-idempotent retriesNetwork ambiguity can duplicate side effects.Idempotency keys, conditional requests, or explicit "do not retry" rules.
Public or partner breaking changes without a migration pathIndependent consumers cannot coordinate instantly.Compatibility rules, major-version or negotiated migration, Deprecation, and Sunset.
Closed enum additions without unknown handlingGenerated clients crash or reject future values.Open enum policy, fallback behavior, or version-gated additions.
Implicit null, missing, or unknown-field behaviorClients guess whether absence means unset, hidden, unauthorized, or unchanged.Document nullable vs optional, patch semantics, and unknown-field policy.
Over-broad input bindingMass assignment and property-level authorization bugs leak through the API.Separate request models, allowlisted writable fields, and property-level auth.
Chatty task flowClients stitch many calls together, creating latency and consistency problems.Expansion, includes, compound resources, batch endpoints, or async jobs when the task requires them.

Worked Example: List Orders

Contract decisions for GET /orders:

  • Consumers: admin UI and reporting integration.
  • Auth: user must have orders:read; tenant is derived from auth context, not query string.
  • Filters: status, created_at[gte], created_at[lt], customer_id.
  • Sorts: created_at, updated_at; server always appends id as a tiebreaker.
  • Pagination: cursor, default limit=50, max limit=200, opaque page[after].
  • Response: { "data": [...], "page": { "next": "..." } }.
  • Empty result: 200 with data: [].
  • Errors: invalid filter field → 400 problem; invalid cursor → 400 problem; unauthorized tenant access → 404 or 403 according to policy.
  • Contract fixtures: normal page, empty page, unsupported filter, invalid cursor, unauthorized access.

Worked Example: Create Order

Contract decisions for POST /orders:

  • 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.
  • Collection endpoints define filters, sorts, stable ordering, pagination style, page-size limits, allowlisted filter/sort fields, and empty-result behavior.
  • 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 insteadWhen
http-semanticsThe question is purely about HTTP method, status, header, caching, conditional-request, or content-negotiation semantics, rather than whole-surface API design.
route-handler-designYou 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-contractsThe boundary is broader than an HTTP API endpoint, such as jobs, modules, events, services, or agent interfaces.
event-contract-designYou need asynchronous event schema, envelope, topic/channel naming, replay, dead-letter, or compatibility rules.
entity-relationship-modelingYou need persistence structure, keys, constraints, indexes, normalization, or lifecycle.
webhook-integrationYou are implementing inbound third-party webhook handling, signatures, provider retry semantics, or raw payload persistence.
contract-testingThe API contract exists and the task is writing the provider/consumer tests that pin compatibility, rather than designing the contract those tests verify.
semanticsYou are naming a single field, status code, or error code for truthfulness, rather than designing the surface.
debuggingAn API already fails and needs root-cause diagnosis.

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.