Designing and reviewing service interfaces — REST (versioning, resource modelling, status codes, pagination, idempotency, errors, OpenAPI, .http files), SOAP/WSDL when it is genuinely required and how to migrate off it, GraphQL and gRPC trade-offs, and API authentication/authorisation. Invoke when creating or changing any endpoint, contract, WSDL, OpenAPI/Swagger spec or client, when deciding between REST/SOAP/GraphQL/gRPC, or on explicit request — "API", "REST", "endpoint", "SOAP", "WSDL", "...
Scanned 9/6/2026
npx -y skills add cyber93de/aiflow --skill api-design --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Api Design?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/cyber93de-api-design)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: api-design
description: Designing and reviewing service interfaces — REST (versioning, resource modelling, status codes, pagination, idempotency, errors, OpenAPI, .http files), SOAP/WSDL when it is genuinely required and how to migrate off it, GraphQL and gRPC trade-offs, and API authentication/authorisation. Invoke when creating or changing any endpoint, contract, WSDL, OpenAPI/Swagger spec or client, when deciding between REST/SOAP/GraphQL/gRPC, or on explicit request — "API", "REST", "endpoint", "SOAP", "WSDL", "OpenAPI", "GraphQL", "gRPC", "versioning".
---
# API design — REST · SOAP · GraphQL · gRPC
Implements `AGENTS.md` §3b (REST interfaces, MANDATORY) and the §2a rule that DTOs — never domain
objects — cross a process boundary.
## Choosing the style
| Style | Fits | Cost you accept |
|-------|------|-----------------|
| **REST/JSON** | default for anything public or cross-team | chattiness, over/under-fetching |
| **gRPC** | internal service-to-service, high volume, streaming | binary on the wire, browser needs a proxy, tighter coupling |
| **GraphQL** | many heterogeneous clients over one rich graph | query-cost control, caching, N+1 resolvers — all now *your* problem |
| **SOAP/WSDL** | an existing partner/enterprise contract demands it | verbosity, tooling weight, scarce expertise |
§3a says state of the art is the default: a **new** SOAP interface, or XML payloads on a new REST
API, is not built silently. Ask why (PO-level question, options + consequences) and record the
answer. Legitimate reasons exist — a bank or authority mandates it, WS-Security/WS-AtomicTransaction
is contractually required, the partner's toolchain is SOAP-only. "That's how we've always done it"
is not one.
**Consuming or maintaining SOAP:** generate the client from the WSDL, never hand-roll XML; keep the
generated code out of the domain behind a port (§2a); pin the WSDL/XSD version in the repo and diff
it on change; disable external entity resolution (XXE — see the **security** skill). To migrate off
it, put a REST facade in front, move consumers one at a time, and retire the SOAP endpoint on a
published deprecation date — never a big-bang cutover.
## REST rules (§3b)
**Versioning from day one.** `/api/v1/…` by default (or header/media-type versioning if the project
already uses it — consistently, not both). Breaking change ⇒ new version + a documented deprecation
window and a `Deprecation`/`Sunset` header on the old one. An unversioned new API is a review
finding. Additive changes (new optional field, new endpoint) are not breaking — removing or
renaming a field, tightening validation, or changing a status code is.
**Resources, not verbs.** `POST /api/v1/orders/42/cancellations` beats `POST /cancelOrder?id=42`.
Plural nouns, nesting only where the child cannot exist alone.
**Status codes mean things.** 200/201(+`Location`)/204 · 400 malformed · 401 unauthenticated ·
403 authenticated-but-not-allowed · 404 · 409 conflict · 412 precondition · 422 semantically
invalid · 429 rate-limited (+`Retry-After`) · 5xx *your* fault. Never 200-with-`{"error":…}`.
**Errors are a contract.** Use RFC 9457 `application/problem+json` (`type`, `title`, `status`,
`detail`, `instance`) plus a stable machine-readable code and, for validation, per-field entries.
Never leak stack traces, SQL, or internal hostnames.
**Collections** are always paginated — cursor-based for large or live data, offset only for small
bounded sets — with documented `sort` and `filter` and a **maximum** page size the server enforces.
An endpoint that can return everything will eventually be asked to.
**Idempotency:** GET/PUT/DELETE are idempotent by definition; make `POST` idempotent with an
`Idempotency-Key` header wherever a retry could double-charge or double-create. Concurrency via
`ETag` + `If-Match` (return 412 on mismatch), not last-write-wins.
**Security** (§3b, details in the **security** skill): HTTPS only; OAuth 2.x / OIDC, short-lived
validated JWTs (signature **and** `exp`, `aud`, `iss`), or managed API keys with rotation —
**Basic Auth is insufficient** beyond a throwaway local demo. Authorise **per endpoint and per
object**, not just "is logged in". Rate-limit. Validate and bound every input. CORS allow-lists a
known origin set, never `*` with credentials.
**Documentation is generated, not written twice.** OpenAPI from the code (or code from the spec —
pick one direction and keep it), published with the service, and diffed in CI so a breaking change
is visible in review.
**`.http` files are mandatory** (§3b): `http/<resource>.http`, one request block per operation,
happy path plus one auth and one error case, host/port/credentials from `.env`
(`{{$dotenv APP_HOST}}`, or `http-client.env.json` + a gitignored
`http-client.private.env.json` for IntelliJ). A changed endpoint with a stale `.http` file is a
review finding.
## GraphQL specifics
Depth and complexity limits (an unbounded nested query is a DoS), persisted queries in production,
DataLoader-style batching against N+1 resolvers, field-level authorisation (a nested field is a
separate authz decision), and errors in the `errors` array with codes — not as `null` with no
explanation. Version by additive evolution + `@deprecated`, not by `/v2`.
## gRPC specifics
Proto files are the contract: additive changes only, never reuse a field number, reserve removed
ones. Deadlines on every call, TLS/mTLS between services, and a schema-compatibility check in CI.
## Review checklist
- [ ] Versioned (§3b), and the change is additive — or the version was bumped with a deprecation plan
- [ ] Real authN + per-endpoint/per-object authZ; no Basic Auth; HTTPS only
- [ ] Correct status codes; `problem+json` errors with a stable code; no internals leaked
- [ ] Collections paginated with an enforced max page size; sort/filter documented
- [ ] Idempotency where a retry could duplicate an effect; `ETag`/`If-Match` on updates
- [ ] Every input validated and bounded; payload size limited; rate limit in place
- [ ] DTOs at the boundary — no domain entity serialised directly (§2a)
- [ ] OpenAPI/WSDL/proto updated and diffed; `.http` file current (§3b)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!