Org Skills

api-design

Design and review HTTP/REST APIs in any back-end stack — resource modelling and naming, methods and status codes, RFC 9457 Problem Details errors, cursor vs offset pagination, filtering and sorting, idempotency keys, ETag/If-Match concurrency, versioning and backward compatibility, long-running operations, webhooks, rate limiting, OAuth2 scopes, and an OpenAPI-first workflow with Spectral linting.

Download .zip Raw Source
When agents use itUse this whenever the user asks to design a new endpoint or API, write or review an OpenAPI/Swagger spec, choose status codes or an error format, add pagination, versioning or idempotency, plan a breaking change, design webhooks or async job endpoints, define rate limits, or asks "what should this URL look like" or "REST vs GraphQL vs gRPC". Also use it when reviewing controllers or route handlers for contract quality, and when deciding whether an event or message fits better than a synchronous API.

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill api-design -a github-copilot
# or with the org installer (adds .github/skills/api-design):
npx -y github:AGCO-Global/org-skills add skill api-design

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill api-design
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill api-design -g

Installs the backend-shared-skills plugin, which bundles all Backend / Shared skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install backend-shared-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill api-design -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill api-design -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill api-design -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill api-design -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

API Design

An API is a contract you cannot take back: once a client depends on a field, a status code or an ordering, it is yours forever. Design from the consumer's use cases, make the contract explicit in OpenAPI before code, and make every change additive.

1. Choose the style first

Need Choose Why
Public/partner API, CRUD-ish resources, cacheable REST over HTTP + JSON Universal tooling, HTTP caching, easy to evolve
Many clients needing different shapes of a rich graph (BFF, mobile) GraphQL Client-selected fields; budget query cost and depth
Internal service-to-service, low latency, streaming, strict schemas gRPC Protobuf contracts, HTTP/2, codegen; poor browser fit
Notify others that something happened; no reply needed Async events (Kafka, SNS/SQS, AMQP) Decoupled, resilient; document with AsyncAPI

The rest of this skill covers REST; the compatibility, idempotency and auth rules apply to all styles.

2. Resources and naming

  • Nouns, plural, lowercase kebab-case: /customers/{customerId}/orders. Nest at most one level; beyond that, use top-level resources with filters.
  • IDs are opaque strings (UUID/ULID), never sequential integers leaked to the public.
  • JSON fields in one case convention (camelCase is most common); timestamps in RFC 3339 UTC (2026-03-01T12:00:00Z); money as { "amount": "12.50", "currency": "EUR" } (string or integer minor units, never float).
  • Actions that don't map to CRUD become sub-resources or state changes: POST /orders/{id}/cancellation, not POST /cancelOrder.
  • Enums are documented as extensible; clients must tolerate unknown values.

3. Methods and status codes

Method Semantics Idempotent Success codes
GET Read Yes 200, 304
POST Create / non-idempotent action No (make it so with a key) 201 + Location, 202, 200
PUT Full replace (or create at known URI) Yes 200, 204, 201
PATCH Partial update (JSON Merge Patch, RFC 7396) Not inherently 200, 204
DELETE Remove Yes 204, 202

Errors: 400 malformed, 401 unauthenticated, 403 authenticated but not allowed, 404 not found (also for resources the caller may not see, to avoid leaking existence), 409 state conflict, 412 precondition failed, 415, 422 semantic validation, 428 precondition required, 429 rate limited, 500, 503 + Retry-After. Never 200 with an error body.

4. Errors (RFC 9457 Problem Details)

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{ "type": "https://api.example.com/problems/validation",
  "title": "Request validation failed", "status": 422,
  "detail": "2 fields are invalid", "instance": "/orders",
  "traceId": "4bf92f3577b34da6",
  "errors": [{ "pointer": "/items/0/quantity", "detail": "must be >= 1" }] }

type is a stable, documented identifier clients branch on; title/detail are for humans. No stack traces or SQL in any environment reachable by clients.

5. Collections: pagination, filtering, sorting

Choose When Shape
Cursor (keyset) — default Large or changing data, feeds, infinite scroll ?limit=50&cursor=eyJpZCI6...{ "data": [], "nextCursor": "…" }
Offset Small, stable sets needing "jump to page N" and a total ?page=3&pageSize=50 → include totalCount only if cheap
  • Server enforces a default and maximum limit; cursors are opaque and encode the sort key plus a unique tiebreaker.
  • Filtering: ?status=open&createdAfter=2026-01-01; sorting: ?sort=-createdAt,name. Allow-list sortable/filterable fields so each is backed by an index.
  • Sparse fieldsets (?fields=id,name) only when payloads are demonstrably heavy.

6. Idempotency and concurrency

  • POST that creates or charges accepts an Idempotency-Key header: store key + request hash + response for 24 h+; a replay returns the stored response; same key with a different body returns 422; concurrent in-flight duplicate returns 409.
  • Optimistic concurrency: GET returns ETag; PUT/PATCH/DELETE send If-Match; mismatch → 412; missing when required → 428. This prevents lost updates between clients.

7. Versioning and compatibility

Strategy Use when Trade-off
URI major version /v1/… Public APIs; default Visible, cache-friendly, coarse
Header / media type Accept: application/vnd.x.v2+json Fine-grained representation versions Harder to test and cache
Date-based version header Many small breaking changes over time Needs per-version transformation layer
No version, additive evolution only Internal APIs with known consumers Requires discipline and consumer contract tests

Non-breaking: adding optional request fields, response fields, endpoints, enum values (if documented as extensible). Breaking: removing/renaming fields, changing types or formats, making optional fields required, tightening validation, changing status codes, defaults or pagination semantics. Breaking changes ship as a new major version; deprecate the old with Deprecation and Sunset headers, a migration guide and usage metrics before removal.

8. Long-running operations

POST /reports202 Accepted + Location: /operations/{opId}. GET /operations/{opId} returns { "status": "running|succeeded|failed", "resultUrl": "...", "error": {...} } with Retry-After for polling. Offer a webhook callback for completion when clients shouldn't poll.

9. Webhooks

  • Sign every payload (HMAC-SHA256 over timestamp + body) and send the signature and timestamp in headers; receivers verify with constant-time comparison and reject stale timestamps (e.g. > 5 min) to stop replays.
  • At-least-once delivery: include a unique event id for dedup; retry with exponential backoff + jitter for ~24–72 h; dead-letter and let consumers replay.
  • Thin events (id, type, resource URL) are safer than full payloads; version the event schema.

10. Rate limiting and security

  • Return 429 with Retry-After; expose limits with RateLimit-Limit/RateLimit-Remaining/RateLimit-Reset (or the IETF RateLimit/RateLimit-Policy draft headers); document per-client quotas.
  • OAuth 2.x bearer tokens (authorization code + PKCE for users, client credentials for services); fine-grained scopes (orders:read, orders:write) checked per operation, plus object-level ownership checks on every ID.
  • TLS only; no secrets or tokens in query strings; validate request bodies against the schema; cap body size.

11. OpenAPI-first workflow

  1. Write or change the OpenAPI 3.1 spec first; review it in the PR like code.
  2. Lint with Spectral (plus a house ruleset: naming, Problem Details on every 4xx/5xx, pagination on lists, operationIds, security on every operation).
  3. Detect breaking changes in CI by diffing against the published spec (e.g. oasdiff).
  4. Generate server stubs/clients or validate requests against the spec; publish docs and mocks from it.

12. Deliverable format

## API design review: <API name>
Style decision: REST | GraphQL | gRPC | events — <one-line reason>

| # | Severity | Operation / path | Issue | Recommendation |
|---|---|---|---|---|
| 1 | blocker | POST /payments | No idempotency; retries double-charge | Require Idempotency-Key |

Breaking-change assessment: none | <list + migration plan>

Then the proposed contract as an OpenAPI snippet:

paths:
  /orders:
    get:
      operationId: listOrders
      security: [{ oauth2: [orders:read] }]
      parameters:
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 50 } }
        - { name: cursor, in: query, schema: { type: string } }
      responses:
        '200': { $ref: '#/components/responses/OrderPage' }
        '4XX': { $ref: '#/components/responses/Problem' }

Anti-patterns to reject

  • Verbs in paths (/getOrders, /createUser) and RPC-over-POST for everything.
  • 200 OK with { "success": false }; bespoke error shapes per endpoint.
  • Unbounded list endpoints; offset pagination over large, fast-changing tables.
  • Non-idempotent POST for payments or orders with client retries enabled.
  • Renaming or removing fields in place "because no one uses it".
  • Sequential integer IDs and existence leaks via 403 vs 404.
  • Code-first APIs with a generated spec nobody reviews or lints.
  • Tokens or PII in URLs; scopes so coarse that every client gets admin.