# API Design

> API Design

- Skill: `handsomeboy990/api-design` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add handsomeboy990/api-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/handsomeboy990/api-design/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Handsomeboy990 (https://skillmd.com/u/handsomeboy990)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/handsomeboy990/api-design

---


# API Design

A contract is the most expensive thing to change, because someone else's code
depends on it. Design it deliberately, once, before it exists in the wild.

An API that grows endpoint by endpoint from whatever each screen needed is not
a contract; it is a collection of accidents with a base URL.

## 1. Before designing anything

```
who consumes it: our own client, a partner, the public, another service
what they need, expressed as their operations, not as our tables
what already exists in this project, and what conventions it set
whether the existing conventions are worth keeping, decided once
what must never be exposed, from the data model
```

An internal API for one client is allowed to be shaped by that client. A
public API is not, because its consumers will be shaped by it for years.

## 2. Choosing the style

| Style | Fits |
|---|---|
| REST over HTTP | resources with clear identity, caching, broad client support |
| RPC style endpoints | operations that are not resources: actions, workflows, computations |
| GraphQL | many clients with divergent field needs, and a team able to own query cost and depth limits |
| streaming or events | continuous data, or consumers that must not poll |

Mixing is normal: a REST surface with a few action endpoints is honest, and
better than pretending an operation is a resource.

GraphQL carries costs that are decided at design time, never later: query cost
limits, depth limits, per field authorization, and an N plus 1 strategy.

## 3. Resources and operations

```
name resources as plural nouns, actions as verbs on a sub path
one canonical identifier per resource, opaque unless there is a reason
no verb in a resource path, no resource in an action name
nesting only where the child cannot exist without the parent, one level
a collection endpoint is not the place to hide seventeen filters and a mode
```

Every operation declares: who may call it, what it reads, what it changes, and
what it costs.

## 4. Request and response shapes

```
one envelope convention across the whole surface, or none, decided once
field names in one case convention, applied everywhere
dates as a single unambiguous format with a timezone
money as an integer minor unit with a currency, never a float
enumerations as strings with declared values, never as integers
identifiers as strings, even when they are numbers today
optional means absent or null, chosen once and documented
no field the caller may not use, and no field only one client needs
```

Never return a field because it was easy to include. Every field is a promise.

## 5. Status codes and errors

Follow the map in `resources/contract-conventions.md`. Two rules dominate:

```
the status carries the outcome: never a 200 with an error inside
one error shape across the whole surface, with a stable machine readable code,
  a human message, and field level detail where it applies
```

Error codes are part of the contract. Clients branch on them, so they are
named, documented and never repurposed.

## 6. Collections

```
pagination decided once: cursor for large or changing sets, offset only for
  small stable ones, and never both
a declared maximum page size, enforced rather than trusted
filtering on a declared list of fields, with declared operators
sorting on a declared list of fields, with a deterministic tiebreaker
a total count only when it is affordable, and marked as approximate when it is
empty is an empty collection with a 200, never a 404
```

## 7. Writes

```
create returns the created resource, or its identifier and location
update: partial or full, chosen once for the whole surface
delete: idempotent, and soft or hard decided at the data model level
every unsafe operation states whether it is idempotent
retryable writes accept an idempotency key and return the original result
long operations return an accepted status and a way to observe completion
```

## 8. Authentication, authorization and limits

```
one authentication mechanism per audience, documented, with expiry and
  revocation semantics stated
authorization stated per operation, at object level, not only at route level
a caller who may not see a resource gets the same answer as for one that does
  not exist
rate limits declared with their window, their scope and their response
payload size limits declared
```

## 9. Versioning and change

```
additive changes are not versioned: new optional fields, new endpoints
breaking changes are versioned: removal, rename, type change, semantic change,
  a new required field, a narrowed enumeration, a changed default
the versioning mechanism is chosen once: path, header or media type
deprecation has a date, a replacement and a way to observe remaining usage
two versions run in parallel only for as long as the policy states
```

A change is breaking if any conforming client could stop working. The judge is
the contract, not the current clients.

## 10. Specification

The contract is written down in a machine readable form where the ecosystem
supports one, generated from the code or checked against it, never maintained
by hand as a second source of truth. Where it cannot be generated, a drift
check belongs in the pipeline.

## 11. Prohibitions

- Never expose an internal model directly as a response shape.
- Never let a client supply a value the server must decide: price, role,
  owner, status, timestamps.
- Never leak storage detail through field names, error text or identifiers.
- Never add a mode flag that changes the meaning of a response.
- Never break a published contract without a version and a deprecation path.
- Never document an endpoint that does not exist, or omit one that does.

## 12. Protocol

1. Establish the consumers and the operations they need.
2. Read the existing surface and record its conventions.
3. Choose the style, and say why.
4. Define resources, operations and identifiers.
5. Define request and response shapes, field by field.
6. Define the single error format and the codes this surface uses.
7. Decide pagination, filtering, sorting, idempotency and limits.
8. State authentication and per operation authorization.
9. Decide the versioning policy and what counts as breaking here.
10. Write or generate the specification, and hand it to implementation.
11. Hand the same contract to `api-testing` before the first client uses it.

## 13. Auto-critique

Score from 0 to 5: fit to consumer needs, consistency with the existing
surface, shape discipline, status and error coherence, collection rules,
idempotency, authorization stated per operation, versioning policy, quality of
the specification.

Threshold: no axis below 3, average at least 4. A surface with two error
formats, or an operation whose authorization is undefined, is redesigned
before it is built.

## 14. Interfaces

- Upstream: `requirements-analysis`, `architecture-design`,
  `technology-selection`.
- Lateral: `input-validation` for the validation rules the contract implies,
  `backend-engineering` for implementation, `security-audit` for exposure.
- Downstream: `api-testing` to verify the contract holds,
  `technical-documentation` to publish it, `release-engineering` for
  deprecation and version rollout.

