Skip to content
AI & Agents
Skill

/api-design

Design or review an HTTP/REST/GraphQL API for versioning, pagination, error shapes, idempotency, auth, status codes, cache headers, and breaking-change management. Use when asked to "design an API", "shape the endpoints", "design the schema", "add a new endpoint", "review this

BOOST
From plugin
gsd-pi
1.3k37 skills13 agents
Install
$ npx -y skills add open-gsd/gsd-pi --skill api-design --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/api-design

Context preview

The summary Claude sees to decide when to auto-load this skill.

Design or review an HTTP/REST/GraphQL API for versioning, pagination, error shapes, idempotency, auth, status codes, cache headers, and breaking-change management. Use when asked to "design an API", "shape the endpoints", "design the schema", "add a new endpoint", "review this

SKILL.md

api-design.SKILL.md
name: api-design
description: Design or review an HTTP/REST/GraphQL API for versioning, pagination, error shapes, idempotency, auth, status codes, cache headers, and breaking-change management. Use when asked to "design an API", "shape the endpoints", "design the schema", "add a new endpoint", "review this API", or when building/modifying a public or internal HTTP surface. HTTP-specific complement to `design-an-interface`.

<objective> Shape an HTTP or GraphQL API so callers get predictable, evolvable, and honest semantics. The deliverable is a concrete endpoint/schema sketch with: URL or operation names, method/verb, request shape, response shape, error shape, auth model, pagination strategy, and versioning stance. Optimize for "clients that exist in 2 years" over "client that's easy to write today". </objective>

<context> gsd-pi has `design-an-interface` for general module-interface design; this skill is the HTTP/GraphQL specialization. REST and GraphQL carry baggage — status codes, verbs, nullability, pagination — that a generic interface-design discussion glosses over.

Invocation points:

  • Adding a new public API endpoint
  • Redesigning an internal API boundary between services
  • Code review of a PR that introduces HTTP handlers
  • A slice whose acceptance criteria include "the API works"
  • A GraphQL schema change

</context>

<core_principle> **CALLERS OUTLIVE YOUR ASSUMPTIONS.** An API you ship today has to keep working when your internals change, when the mobile app version two is still in use, and when a third party integrates against it. Design for extension, not just for the current caller.

**HONEST STATUS CODES.** 200 OK with `{"error": "not found"}` is a lie. 404 says not found. Use the HTTP semantics the protocol offers — HTTP clients, caches, and intermediaries rely on them.

**PAGINATION IS NON-OPTIONAL.** Any list endpoint that doesn't paginate will eventually get a request for "all records" that kills your database. </core_principle>

<process>

Step 1: Gather the contract

Answer, or ask (one round, 1–3 questions):

1. **Who are the callers?** Internal service / mobile app / public third-party / same-repo frontend. 2. **What's the versioning stance?** None / URL-path (`/v1/`) / header-based / GraphQL schema evolution. 3. **Auth model?** Public / API key / OAuth / session cookie / mTLS / none-but-internal-only. 4. **Idempotency expectation?** Is a retry safe? Required? 5. **Consistency model?** Read-your-writes, eventual, serializable?

Step 2: Resource and operation naming

REST

  • Nouns not verbs in URLs: `POST /users`, not `POST /createUser`.
  • Plural resources: `/users/42`, not `/user/42`.
  • Nested only when the relationship is hierarchical and the child has no independent identity: `/users/42/sessions/3`. Otherwise flat: `/sessions/3?userId=42`.
  • Use subresources for actions that don't fit CRUD: `POST /users/42:deactivate` (colon syntax) or `POST /users/42/actions/deactivate`.

GraphQL

  • Queries are nouns; mutations are verbs: `user(id)`, `createUser(input)`, `deactivateUser(id)`.
  • Group related mutations under an input type: `createUser(input: CreateUserInput!)`.
  • Return the affected object plus any derived/computed fields from mutations — lets clients avoid a refetch.

Step 3: Methods and status codes

REST

| Method | Intent | Idempotent? | Default success | |---|---|---|---| | GET | Read | Yes | 200, or 304 if conditional | | POST | Create or non-idempotent action | No | 201 with `Location` on create, 200 on action | | PUT | Replace (full-object) | Yes | 200 with body, or 204 | | PATCH | Partial update | No (usually) | 200 with body | | DELETE | Remove | Yes | 204 |

Errors:

  • 400: caller screwed up the request shape
  • 401: no/invalid auth
  • 403: authed but not allowed
  • 404: resource doesn't exist
  • 409: conflict (version mismatch, unique constraint)
  • 410: gone (vs 404 when the resource previously existed and you want to signal that)
  • 422: validation failed
  • 429: rate-limited — include `Retry-After`
  • 500: genuinely unexpected server error
  • 503: service down or overloaded — include `Retry-After`

Never 200-with-error-body. Never 500 for a 4xx cause.

GraphQL

  • Top-level errors (`errors[]`) for transport-level failures. Domain errors (validation, not-found, forbidden) go in the typed return — use a union or result type.
  • Partial results are expected; design the schema so `null` on a field is meaningful, not a signal of generic failure.

Step 4: Pagination

  • **Cursor-based by default.** Opaque cursor string, `limit`, return `nextCursor` when more exists. Scales, stable under writes.
  • **Offset-based only when:** dataset is small, user needs jump-to-page semantics (admin tables), and you're willing to accept stability drift.
  • **Never "return everything"** as default. Put a hard upper bound on `limit` (e.g., 200).
  • GraphQL: use Relay-style connections (`edges`, `pageInfo`) if the ecosystem expects it; otherwise a simpler `{items, nextCursor}` is fine.

Step 5: Error shape

Standardize one shape and use it everywhere. Example REST:

{
  "error": {
    "code": "user_not_found",
    "message": "No user with id 42",
    "details": { "userId": 42 },
    "requestId": "req_abc123"
  }
}
  • `code` is machine-readable; stable; documented.
  • `message` is human-readable; can change.
  • `details` carries structured context.
  • `requestId` lets callers report bugs.

Errors don't leak stack traces, file paths, or internal queries.

Step 6: Idempotency, caching, concurrency

  • **Idempotency keys** for POST operations that mustn't double-execute on retry. Caller passes `Idempotency-Key: <uuid>`; server dedupes for a window.
  • **ETags** for GET + conditional updates (`If-Match` on PUT/PATCH).
  • **Cache-Control** on GETs that are safely cacheable.
  • **Optimistic concurrency:** when multiple writers collide, 409 with the current state. Don't silently clobber.

Step 7: Versioning and evolution

  • **Additive changes
Read more
Ships withgsd-pi

GSD Pi is a local-first coding agent for planning, implementing, verifying, and tracking project work from the command line.

Get the whole plugin

Other skills on gsd-pi.