agentleFS
reddygopalkrishnareddygopalkrishna/developer-agent-skills/skills/api-design/SKILL.md

api-design

Skill8 starsChanged 9 days agoMITView on GitHub

Designs and reviews HTTP/REST APIs that are consistent, evolvable and safe to retry - resource naming, method semantics, status codes, RFC 9457 errors, pagination, versioning, idempotency keys, ETags, rate limits, long-running operations, webhooks and OpenAPI contracts. Use when creating or changing endpoints, writing an OpenAPI spec, reviewing an API PR, deciding if a change is breaking, or choosing between REST, GraphQL and gRPC.

191 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use db-design, everything-claude-code and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • yuexueyuSkill

    db-design

    数据库工程师技能 - Schema设计、索引优化、并发安全、数据库审计。当你涉及数据库表/字段/索引/SQL/迁移/MCP数据库操作、建表改表、写查询时必须使用此技能。即使用户只是说"加个字段"或"查下数据",也应触发。

    Also in 4 projects that use this

  • InfopibeSkill

    everything-claude-code

    Also in 9 projects that use this

  • mryeastcodeSkill

    everything-claude-code-conventions

    Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

    Also in 8 projects that use this

  • tpavanipradeepSkill

    nextjs-turbopack

    Next.js 16+ and Turbopack — incremental bundling, FS caching, dev speed, and when to use Turbopack vs webpack.

    Also in 15 projects that use this

  • Indium-AI-LabsSkill

    data-pipeline-design

    Design batch or incremental ETL and ELT pipelines and warehouse models: grain, partitioning, idempotent loads, late-arriving data, data quality tests, PII controls, and cost monitoring, before building them. Use when adding or changing a dbt model, warehouse table, scheduled transformation, or ingestion job. Not for transactional schema design (use the database-design skill) or event-sourced stores (use the event-sourcing-architecture skill).

    Also in 3 projects that use this

  • thejordanleopoldSkill

    third-party-integration

    Use when integrating with external APIs, payment processors (Stripe), webhooks, or third-party services. Use when implementing API clients with authentication and rate limiting, handling webhooks with signature verification and idempotency, writing contract tests for external services, or isolating third-party dependencies behind adapters. Triggers: \"Stripe\", \"payment integration\", \"webhook\", \"third-party API\", \"external service\", \"API client\", \"integrate with\", \"signature verification\", \"idempotency key\".

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

9 rules. 0 are common, 8 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 165 edited versions we read keep these.

  • 8

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Not for: internal function signatures.

    line 18
  2. Distinctive:

    Find the existing conventions first: Read the current OpenAPI spec, two or three existing endpoints, and the error format.

    line 24
  3. Distinctive:

    Write the contract before the code: Add or update the OpenAPI (3.1+) definition: paths, schemas, required fields, examples, every error …

    line 26
  4. Distinctive:

    Run the review checklist in references/review-checklist.md before calling it done.

    line 30
  5. Distinctive:

    Never return 200 with an error payload.

    line 81
  6. Distinctive:

    Put field-level validation errors in one response, not one error per round trip.

    line 100
  7. Distinctive:

    Never include secrets, SQL, stack traces or internal hostnames.

    line 101
  8. Very specific:

    Always cap limit (for example default 20, max 100) and document both.

    line 113
  9. Distinctive:

    Read references/graphql-and-grpc.md when the API is GraphQL or gRPC, or when choosing between them.

    line 179

“Common” is measured: kept by at least 6 in 10 of the 165 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

API Design

An API is a contract you cannot take back. Design it so clients can retry safely, evolve without breaking, and understand every failure from the response alone.

When to use

  • Adding or changing an endpoint, request/response shape, or error format.
  • Writing or reviewing an OpenAPI document or an API pull request.
  • Deciding whether a change is breaking and how to ship it.
  • Choosing REST vs GraphQL vs gRPC for a new interface.

Not for: internal function signatures.

Pair with schema work: the API and the tables behind it are designed together. Use the database-schema-design skill for the tables if it is installed. If it is not, at minimum give every table a primary key, enforce NOT NULL, UNIQUE and foreign keys in the database, store times as timestamptz (or the database's UTC timestamp type) and money as numeric/decimal, and index every column the API filters or sorts on.

Workflow

  1. Find the existing conventions first. Read the current OpenAPI spec, two or three existing endpoints, and the error format. Consistency with the existing API beats any rule below; flag conflicts instead of silently diverging.
  2. Model resources, not actions. List the nouns, their identifiers, and their relationships. Map operations to HTTP methods (table below). Only fall back to an action sub-resource (POST /orders/{id}/cancel) when the operation is not a state change you can express with PUT/PATCH.
  3. Write the contract before the code. Add or update the OpenAPI (3.1+) definition: paths, schemas, required fields, examples, every error response. Run a linter (for example Spectral) if the repo has one.
  4. Decide failure behavior explicitly: which status codes, which are retryable, what the problem-details type is for each error.
  5. Check compatibility. Compare against the previous spec (for example oasdiff breaking old.yaml new.yaml) and against the breaking-change rules below. If breaking, stop and agree a versioning or migration plan with the user.
  6. Implement and test the contract: request validation, error mapping, pagination edges (empty, last page), idempotent retries, and conditional requests.
  7. Run the review checklist in references/review-checklist.md before calling it done.

Resource naming

  • Plural nouns, lowercase, hyphenated paths: /v1/payment-methods/{paymentMethodId}.
  • Nest only for true ownership and at most one level: /orders/{id}/items. Otherwise use filters: /items?orderId=....
  • IDs are opaque strings in the contract even if they are integers today.
  • JSON field names: pick one case (camelCase or snake_case) and never mix. Match the existing API.
  • Timestamps: RFC 3339 in UTC (2026-11-03T14:05:00Z). Durations: ISO 8601 or explicit units in the field name (timeoutSeconds).
  • Money: integer minor units or a decimal string, always with an ISO 4217 currency code. Never binary floats.
  • Enums: strings, documented as extensible; clients must tolerate unknown values.

Method semantics

MethodUse forSafeIdempotentTypical success
GETRead a resource or collectionYesYes200, 304
HEADHeaders only (existence, ETag)YesYes200, 304
POSTCreate in a collection, or non-idempotent actionNoNo (make it so with an Idempotency-Key)201 + Location, 202, 200
PUTFull replace at a known URI (or create at client-chosen URI)NoYes200, 204, 201
PATCHPartial updateNoNot by default200, 204
DELETERemoveNoYes204, 202, 200
  • GET never changes state. No GET /users/{id}/delete.
  • PATCH body format must be declared: JSON Merge Patch (RFC 7396, application/merge-patch+json) for simple cases, JSON Patch (RFC 6902, application/json-patch+json) when you need array operations or test-and-set.
  • Repeating a DELETE on an already-deleted resource may return 404; that is still idempotent (the server state is the same).

Status codes

CodeMeaningNotes
200 OKSuccess with body
201 CreatedResource createdInclude Location header and usually the resource
202 AcceptedWork queued, not doneReturn a status resource (see long-running operations)
204 No ContentSuccess, no body
304 Not ModifiedConditional GET matchedNo body
400 Bad RequestMalformed syntax, invalid JSON, wrong types
401 UnauthorizedMissing or invalid credentialsSend WWW-Authenticate
403 ForbiddenAuthenticated but not allowedUse 404 instead when existence itself is secret
404 Not FoundNo such resource (for this caller)
405 Method Not AllowedMethod not supported on the resourceSend Allow
409 ConflictState conflict (duplicate, invalid transition)
412 Precondition FailedIf-Match did not matchLost-update protection
415 Unsupported Media TypeWrong Content-Type
422 Unprocessable ContentWell-formed but fails validationPick 400 or 422 for validation and use it everywhere
428 Precondition RequiredServer requires If-Match
429 Too Many RequestsRate limitedSend Retry-After
500 Internal Server ErrorUnexpected bugNever leak stack traces
502 / 504Upstream failed / timed outRetryable for idempotent requests
503 Service UnavailableOverloaded or in maintenanceSend Retry-After

Never return 200 with an error payload.

Errors: RFC 9457 problem details

Return Content-Type: application/problem+json for every 4xx/5xx:

{
  "type": "https://api.example.com/problems/insufficient-funds",
  "title": "Insufficient funds",
  "status": 422,
  "detail": "Balance is 30.00 USD; the transfer needs 50.00 USD.",
  "instance": "/v1/transfers/tr_123",
  "errors": [{ "pointer": "/amount", "detail": "exceeds available balance" }]
}
  • type is a stable identifier clients branch on; title is constant per type; detail is for humans and may change.
  • Extension members (like errors above, or traceId) are allowed; document them.
  • Put field-level validation errors in one response, not one error per round trip.
  • Never include secrets, SQL, stack traces or internal hostnames.

Pagination, filtering, sorting

Cursor (keyset)Offset
Stable under inserts/deletesYesNo (items skip or repeat)
Performance on deep pagesConstantDegrades with offset size
Jump to page NNoYes
Default choiceYes, for feeds, large or changing dataSmall, static admin lists
  • Cursor responses: { "data": [...], "nextCursor": "opaque-string-or-null" }. The cursor is opaque (encode the sort key and ID); clients must not parse it.
  • Always cap limit (for example default 20, max 100) and document both.
  • Sort must be deterministic: add the ID as a tiebreaker.
  • Filters as query parameters with documented operators: ?status=active&createdAfter=2026-01-01T00:00:00Z.
  • Sort parameter: ?sort=-createdAt,name with an allowlist of sortable fields.
  • Optionally expose Link headers (RFC 8288) with rel="next".

Versioning and compatibility

Non-breaking (ship anytime): adding an endpoint; adding an optional request field; adding a response field; adding an enum value if clients were told enums are extensible; relaxing validation.

Breaking (needs a new version or a migration plan): removing or renaming a field, endpoint or enum value; making an optional field required; changing a type or format; changing default behavior or sort order; tightening validation; changing status codes or error types clients rely on; changing auth requirements; changing pagination style.

Strategies:

StrategyExampleWhen
URL major version/v1/ordersDefault for public APIs; simple to route and cache
Header or media typeAccept: application/vnd.example.v2+jsonWhen URLs must stay stable
Date-based versionsApi-Version: 2026-11-01Many small changes; per-client pinning

Deprecate before removing: announce, mark deprecated: true in OpenAPI, optionally send a Deprecation header (RFC 9745) and a Sunset header (RFC 8594), measure remaining traffic, then remove.

Idempotency keys for POST

Clients retry after timeouts. Without idempotency, a retried POST /payments charges twice.

  1. Client sends Idempotency-Key: <uuid> (the header is an IETF draft and is widely used, for example by Stripe).
  2. Server stores key + request fingerprint + response, scoped to the caller, with a TTL (for example 24 hours).
  3. Same key, same body: return the stored response. Same key, different body: 422 (or 409). Key still in progress: 409.
  4. Store the key in the same transaction as the side effect, or use a unique constraint, so concurrent duplicates cannot both succeed.

Conditional requests (ETags)

  • Return ETag on GET. Clients send If-None-Match and get 304 when unchanged (saves bandwidth).
  • For updates, require If-Match: "<etag>"; return 412 on mismatch to prevent lost updates. Return 428 if the header is missing and you require it.
  • A version number or updated_at hash is a fine ETag source.

Rate limiting

  • Return 429 with Retry-After (seconds or HTTP date). 503 + Retry-After for overload.
  • Advertise limits. RateLimit / RateLimit-Policy headers are an IETF draft; X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset are the common legacy form. Match what the API already uses.
  • Limit per credential, not only per IP.

Long-running operations

  1. POST /v1/reports returns 202 Accepted, Location: /v1/operations/op_123.
  2. GET /v1/operations/op_123 returns { "status": "running" | "succeeded" | "failed", "result": ..., "error": <problem details> }.
  3. On success, link to the created resource (or 303 See Other to it).
  4. Optionally offer a webhook on completion. Never hold an HTTP request open for minutes.

Webhooks

  • Sign every delivery: HMAC-SHA256 over id + timestamp + raw body with a per-endpoint secret. Consider following the Standard Webhooks specification (standardwebhooks.com); it uses headers along the lines of webhook-id, webhook-timestamp and webhook-signature, but check the exact header names and signed-payload format against the current spec before implementing.
  • Receivers: verify the signature on the raw bytes with a constant-time compare; reject timestamps older than a few minutes (replay protection).
  • Delivery is at-least-once: include a unique event ID, retry with exponential backoff, and tell consumers to dedupe by ID.
  • Order is not guaranteed: include the resource version or timestamp, or send thin events ("order.updated, id=...") and let receivers fetch current state.
  • Receivers should return 2xx fast and process asynchronously.

REST vs GraphQL vs gRPC

ChooseWhen
REST + OpenAPIPublic or partner APIs, broad client support, HTTP caching matters
GraphQLMany client types with different data needs over one graph; a team that will own schema governance
gRPCInternal service-to-service calls, streaming, strict schemas, polyglot codegen

Read references/graphql-and-grpc.md when the API is GraphQL or gRPC, or when choosing between them.

Anti-patterns

  • Verbs in paths (/getUser, /createOrder) — use methods on resources.
  • 200 with {"error": ...} — breaks every client, proxy and monitor.
  • Leaking the database schema — internal column names and join tables become a contract you can never change.
  • Unbounded lists — every collection endpoint paginates from day one.
  • Silent breaking change "because no one uses that field" — prove it with traffic data or version it.
  • Retry-unsafe POSTs for money or messages — require an Idempotency-Key.
  • Different error shapes per endpoint — one problem-details format everywhere.
  • Booleans that will become states (isActive) — prefer a status enum when a third state is plausible.

Done when

  • OpenAPI spec updated, linted, with examples and every error response documented.
  • Breaking-change check run against the previous spec; any breaking change has an agreed plan.
  • Errors use problem details; codes match the table; no stack traces leak.
  • Collections paginate with a capped limit and deterministic sort.
  • Non-idempotent writes that matter accept an Idempotency-Key; updates that can race use ETags.
  • Tests cover validation errors, auth failures, pagination edges and retries.

References

  • references/review-checklist.md — read when reviewing an API PR or before finishing a design.
  • references/graphql-and-grpc.md — read when the interface is GraphQL or gRPC.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.