API Design Standards
Rules for designing and implementing RESTful APIs.
Enforcement: code-reviewer skill (Step 8: Stack-Specific Checks), api-designer skill (design validation).
RESTful URL Naming
- Use plural nouns for resource collections. No verbs in URLs.
- Use path hierarchy to express relationships.
- Use kebab-case for multi-word resources:
/v1/order-items, not /v1/orderItems.
- Keep URLs shallow — maximum 3 levels of nesting. Use query parameters or separate endpoints beyond that.
# Good # Bad
GET /v1/users GET /v1/getUser/123
GET /v1/users/123 POST /v1/createUser
GET /v1/users/123/orders GET /v1/user/123/getOrders
POST /v1/users POST /v1/deleteUser/123
PATCH /v1/users/123
HTTP Methods
| Method |
Purpose |
Idempotent |
Request Body |
Success Code |
GET |
Read resource(s) |
Yes |
No |
200 |
POST |
Create new resource |
No |
Yes |
201 |
PUT |
Full replacement update |
Yes |
Yes |
200 |
PATCH |
Partial update |
No* |
Yes |
200 |
DELETE |
Remove resource |
Yes |
No |
204 |
- Return
404 when a resource does not exist (GET, PUT, PATCH, DELETE).
- Return
409 Conflict for duplicate creation attempts.
- Return
202 Accepted for async operations that will complete later.
Response Conventions
- Wrap collections in a
data field: { "data": [...], "pagination": {...} }.
- Return the created/updated resource in the response body for POST, PUT, PATCH.
- Use consistent date format: ISO 8601 (
2024-01-15T09:30:00Z).
- Use camelCase for JSON response keys.
- Include
Location header for POST (201) responses pointing to the new resource.
Error Format
Every error — from every endpoint — uses this envelope. Never a bare string, never a bare array.
{
"error": "Human-readable error message",
"code": "VALIDATION_ERROR",
"status": 422,
"details": [{ "field": "email", "message": "Must be a valid email address" }],
"requestId": "req-abc-123"
}
- Always include a machine-readable
code — clients branch on code, never on error text.
- Always include
requestId for support and debugging.
- Never expose stack traces, internal paths, or implementation details in production responses.
- Status codes:
400 malformed · 401 unauthenticated · 403 unauthorized · 404 not found ·
409 conflict · 422 validation failure · 429 rate limited · 500 unexpected failure.
Input Validation
- Validate all request inputs with a schema library (Zod on TS; strong params + model
validations on Rails).
- Validate at the API boundary (controller / route handler), not deep in business logic.
- Return all validation errors at once, not one at a time.
- Strip unknown fields from validated input — never pass unexpected data downstream.
Pagination
- Cursor-based is the default. Offset is acceptable only for small, stable datasets.
- Default page size 25, maximum 100, client-settable via
?limit=.
- Always return pagination metadata — never a bare array.
{ "data": [], "pagination": { "nextCursor": "eyJpZCI6MTAwfQ==", "hasMore": true, "limit": 25 } }
Versioning
Rate Limiting and Health
- Rate-limit all public endpoints; stricter limits on auth endpoints (login, password reset).
- Return
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers; 429 with
Retry-After when exceeded.
- Every service exposes
GET /health returning status, version, uptime, and dependencies.
Deep guides (read on demand, do not preload)
- Rails error concern, 500 handler, request-spec contract →
references/errors-rails.md
- Zod boundaries, server-action results, typed axios client →
references/errors-typescript.md
- Keyset cursors, pagy, PostGIS proximity →
references/pagination-rails.md
useInfiniteQuery, FlatList wiring → references/pagination-clients.md
- Is-it-breaking table, v1/v2 side by side, sunset sequence →
references/versioning-and-deprecation.md
- rack-attack tiers, 429 envelope, client backoff →
references/rate-limiting.md
- Liveness vs deep health, dependency timeouts, ALB target group →
references/health-checks.md
1---2name: std-api-design3description: REST API design conventions — URL nouns, response envelope, error format, pagination, versioning, status codes. Use when designing or reviewing API endpoints.4---56# API Design Standards78Rules for designing and implementing RESTful APIs.910**Enforcement**: code-reviewer skill (Step 8: Stack-Specific Checks), api-designer skill (design validation).1112## RESTful URL Naming1314- Use **plural nouns** for resource collections. No verbs in URLs.15- Use path hierarchy to express relationships.16- Use kebab-case for multi-word resources: `/v1/order-items`, not `/v1/orderItems`.17- Keep URLs shallow — maximum 3 levels of nesting. Use query parameters or separate endpoints beyond that.1819```20# Good # Bad21GET /v1/users GET /v1/getUser/12322GET /v1/users/123 POST /v1/createUser23GET /v1/users/123/orders GET /v1/user/123/getOrders24POST /v1/users POST /v1/deleteUser/12325PATCH /v1/users/12326```2728## HTTP Methods2930| Method | Purpose | Idempotent | Request Body | Success Code |31|----------|--------------------------|------------|--------------|--------------|32| `GET` | Read resource(s) | Yes | No | 200 |33| `POST` | Create new resource | No | Yes | 201 |34| `PUT` | Full replacement update | Yes | Yes | 200 |35| `PATCH` | Partial update | No* | Yes | 200 |36| `DELETE` | Remove resource | Yes | No | 204 |3738- Return `404` when a resource does not exist (GET, PUT, PATCH, DELETE).39- Return `409 Conflict` for duplicate creation attempts.40- Return `202 Accepted` for async operations that will complete later.4142## Response Conventions4344- Wrap collections in a `data` field: `{ "data": [...], "pagination": {...} }`.45- Return the created/updated resource in the response body for POST, PUT, PATCH.46- Use consistent date format: ISO 8601 (`2024-01-15T09:30:00Z`).47- Use camelCase for JSON response keys.48- Include `Location` header for POST (201) responses pointing to the new resource.4950## Error Format5152Every error — from every endpoint — uses this envelope. Never a bare string, never a bare array.5354```json55{56 "error": "Human-readable error message",57 "code": "VALIDATION_ERROR",58 "status": 422,59 "details": [{ "field": "email", "message": "Must be a valid email address" }],60 "requestId": "req-abc-123"61}62```6364- Always include a machine-readable `code` — clients branch on `code`, never on `error` text.65- Always include `requestId` for support and debugging.66- Never expose stack traces, internal paths, or implementation details in production responses.67- Status codes: `400` malformed · `401` unauthenticated · `403` unauthorized · `404` not found ·68 `409` conflict · `422` validation failure · `429` rate limited · `500` unexpected failure.6970## Input Validation7172- Validate **all** request inputs with a schema library (Zod on TS; strong params + model73 validations on Rails).74- Validate at the API boundary (controller / route handler), not deep in business logic.75- Return all validation errors at once, not one at a time.76- Strip unknown fields from validated input — never pass unexpected data downstream.7778## Pagination7980- **Cursor-based is the default.** Offset is acceptable only for small, stable datasets.81- Default page size **25**, maximum **100**, client-settable via `?limit=`.82- Always return pagination metadata — never a bare array.8384```json85{ "data": [], "pagination": { "nextCursor": "eyJpZCI6MTAwfQ==", "hasMore": true, "limit": 25 } }86```8788## Versioning8990- Version in the URL path: `/v1/users`. Bump the major **only for breaking changes**.91- Support the previous version for a documented deprecation period, with headers on every92 response from it:93 ```94 Deprecation: true95 Sunset: Sat, 01 Mar 2025 00:00:00 GMT96 ```9798## Rate Limiting and Health99100- Rate-limit all public endpoints; stricter limits on auth endpoints (login, password reset).101- Return `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` headers; `429` with102 `Retry-After` when exceeded.103- Every service exposes `GET /health` returning `status`, `version`, `uptime`, and `dependencies`.104105## Deep guides (read on demand, do not preload)106107- Rails error concern, 500 handler, request-spec contract → `references/errors-rails.md`108- Zod boundaries, server-action results, typed axios client → `references/errors-typescript.md`109- Keyset cursors, pagy, PostGIS proximity → `references/pagination-rails.md`110- `useInfiniteQuery`, FlatList wiring → `references/pagination-clients.md`111- Is-it-breaking table, v1/v2 side by side, sunset sequence → `references/versioning-and-deprecation.md`112- rack-attack tiers, 429 envelope, client backoff → `references/rate-limiting.md`113- Liveness vs deep health, dependency timeouts, ALB target group → `references/health-checks.md`