REST API design patterns for resource naming, HTTP methods, status codes, pagination strategy, error envelopes, versioning, and rate limiting. Use when designing, reviewing, or refactoring HTTP APIs and endpoint contracts.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add kid-sid/codex-spellbook --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/kid-sid-api-design)More formats (shields.io, HTML) on the badges page.
---
name: api-design
description: REST API design patterns for resource naming, HTTP methods, status codes, pagination strategy, error envelopes, versioning, and rate limiting. Use when designing, reviewing, or refactoring HTTP APIs and endpoint contracts.
---
# API Design
Design resource-oriented HTTP APIs with stable contracts, predictable errors, and explicit pagination and versioning choices.
## When to Activate
- Design a new HTTP or REST endpoint
- Review existing route naming or handler contracts
- Choose between offset and cursor pagination
- Define status codes and error payloads
- Introduce a breaking API change
- Add rate limiting or idempotency behavior
- Generate or review an OpenAPI spec
## Resource Design
| Concern | Preferred | Avoid |
| --- | --- | --- |
| Collections | `/users` | `/getUsers` |
| Single resource | `/users/{user_id}` | `/user?id=123` |
| Nested resource | `/users/{user_id}/tokens` | `/userTokens/{user_id}` |
| Actions | `/jobs/{job_id}:cancel` when no resource exists | `/cancelJob/{job_id}` |
Rules:
- Use plural nouns for collections.
- Use kebab-case for multi-word path segments.
- Put identifiers in the path and filters in the query string.
- Model actions as resources first; use action suffixes only when CRUD does not fit.
BAD
```http
GET /getUserOrders?userId=42&limit=5000
```
GOOD
```http
GET /users/42/orders?limit=100&cursor=eyJpZCI6Im9yZF8xIn0=
```
## HTTP Semantics
| Method | Use When | Idempotent | Response |
| --- | --- | --- | --- |
| `GET` | Read resources | Yes | `200`, `206`, `304` |
| `POST` | Create resource or submit command | No | `201`, `202` |
| `PUT` | Replace full resource | Yes | `200`, `204` |
| `PATCH` | Partial update | Usually | `200`, `204` |
| `DELETE` | Remove resource | Yes | `204` |
| Situation | Code | Notes |
| --- | --- | --- |
| Created synchronously | `201 Created` | Return `Location` header |
| Accepted async work | `202 Accepted` | Return job resource |
| Validation failure | `400 Bad Request` or `422 Unprocessable Entity` | Match validator semantics |
| Authentication missing | `401 Unauthorized` | Include auth challenge when applicable |
| Permission denied | `403 Forbidden` | Caller is authenticated |
| Missing resource | `404 Not Found` | Do not leak existence across tenants |
| Version conflict | `409 Conflict` | ETag or business conflict |
| Rate limited | `429 Too Many Requests` | Include reset headers |
## Pagination
| Strategy | Use When | Strengths | Tradeoffs |
| --- | --- | --- | --- |
| Offset pagination | Small admin lists, stable datasets | Simple for humans | Slow on large offsets, duplicate/skipped rows under writes |
| Cursor pagination | User-facing feeds, large tables, append-heavy data | Stable under writes, faster scans | Harder to debug manually |
Default:
- Use cursor pagination for externally consumed list endpoints.
- Use offset pagination only for bounded internal tooling or page-number UX.
```json
{
"data": [{ "id": "usr_123", "email": "a@example.com" }],
"page": {
"next_cursor": "eyJpZCI6InVzcl8xMjMifQ==",
"has_more": true
}
}
```
## Error Envelope
Use one shape across the API:
```json
{
"error": {
"code": "validation_error",
"message": "email must be a valid address",
"details": [
{ "field": "email", "reason": "invalid_format" }
],
"request_id": "req_01HV..."
}
}
```
Rules:
- `code` is stable and machine-readable.
- `message` is human-readable and safe to log.
- `details` is optional and field-scoped.
- Always include `request_id` for support correlation.
BAD
```json
{ "message": "Something went wrong" }
```
GOOD
```json
{
"error": {
"code": "rate_limited",
"message": "Too many requests",
"request_id": "req_123"
}
}
```
## Versioning and Rate Limiting
| Approach | Preferred | Avoid |
| --- | --- | --- |
| Public API version | `/v1/...` at the router boundary | Version per endpoint ad hoc |
| Breaking change rollout | Ship `/v2`, deprecate `/v1`, publish sunset date | Mutate `/v1` response shape silently |
| Additive changes | New optional fields | Reusing field names with changed meaning |
| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit` | Total quota in current window |
| `X-RateLimit-Remaining` | Requests left in window |
| `X-RateLimit-Reset` | Unix timestamp when quota resets |
| `Retry-After` | Seconds until retry after `429` |
## Checklist
- [ ] Paths use plural nouns and stable identifiers
- [ ] HTTP method matches the state transition
- [ ] Success and failure codes are explicit for each endpoint
- [ ] List endpoints declare pagination strategy and bounds
- [ ] Error responses use the shared error envelope
- [ ] Breaking changes are isolated behind a version boundary
- [ ] Rate-limited endpoints return quota headers
- [ ] Examples show realistic URLs and payloads
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!