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.4paths:5 - "**/app/controllers/**/*.rb"6 - "**/src/api/**"7 - "**/src/actions/**"8 - "**/routes/**"9 - "**/controllers/**"10 - "**/endpoints/**"11---1213# API Design Standards1415Rules for designing and implementing RESTful APIs.1617**Enforcement**: code-reviewer skill (Step 8: Stack-Specific Checks), api-designer skill (design validation).1819## RESTful URL Naming2021- Use **plural nouns** for resource collections. No verbs in URLs.22- Use path hierarchy to express relationships.23- Use kebab-case for multi-word resources: `/v1/order-items`, not `/v1/orderItems`.24- Keep URLs shallow — maximum 3 levels of nesting. Use query parameters or separate endpoints beyond that.2526```27# Good # Bad28GET /v1/users GET /v1/getUser/12329GET /v1/users/123 POST /v1/createUser30GET /v1/users/123/orders GET /v1/user/123/getOrders31POST /v1/users POST /v1/deleteUser/12332PATCH /v1/users/12333```3435## HTTP Methods3637| Method | Purpose | Idempotent | Request Body | Success Code |38|----------|--------------------------|------------|--------------|--------------|39| `GET` | Read resource(s) | Yes | No | 200 |40| `POST` | Create new resource | No | Yes | 201 |41| `PUT` | Full replacement update | Yes | Yes | 200 |42| `PATCH` | Partial update | No* | Yes | 200 |43| `DELETE` | Remove resource | Yes | No | 204 |4445- Return `404` when a resource does not exist (GET, PUT, PATCH, DELETE).46- Return `409 Conflict` for duplicate creation attempts.47- Return `202 Accepted` for async operations that will complete later.4849## Response Conventions5051- Wrap collections in a `data` field: `{ "data": [...], "pagination": {...} }`.52- Return the created/updated resource in the response body for POST, PUT, PATCH.53- Use consistent date format: ISO 8601 (`2024-01-15T09:30:00Z`).54- Use camelCase for JSON response keys.55- Include `Location` header for POST (201) responses pointing to the new resource.5657## Error Format5859Every error — from every endpoint — uses this envelope. Never a bare string, never a bare array.6061```json62{63 "error": "Human-readable error message",64 "code": "VALIDATION_ERROR",65 "status": 422,66 "details": [{ "field": "email", "message": "Must be a valid email address" }],67 "requestId": "req-abc-123"68}69```7071- Always include a machine-readable `code` — clients branch on `code`, never on `error` text.72- Always include `requestId` for support and debugging.73- Never expose stack traces, internal paths, or implementation details in production responses.74- Status codes: `400` malformed · `401` unauthenticated · `403` unauthorized · `404` not found ·75 `409` conflict · `422` validation failure · `429` rate limited · `500` unexpected failure.7677## Input Validation7879- Validate **all** request inputs with a schema library (Zod on TS; strong params + model80 validations on Rails).81- Validate at the API boundary (controller / route handler), not deep in business logic.82- Return all validation errors at once, not one at a time.83- Strip unknown fields from validated input — never pass unexpected data downstream.8485## Pagination8687- **Cursor-based is the default.** Offset is acceptable only for small, stable datasets.88- Default page size **25**, maximum **100**, client-settable via `?limit=`.89- Always return pagination metadata — never a bare array.9091```json92{ "data": [], "pagination": { "nextCursor": "eyJpZCI6MTAwfQ==", "hasMore": true, "limit": 25 } }93```9495## Versioning9697- Version in the URL path: `/v1/users`. Bump the major **only for breaking changes**.98- Support the previous version for a documented deprecation period, with headers on every99 response from it:100 ```101 Deprecation: true102 Sunset: Sat, 01 Mar 2025 00:00:00 GMT103 ```104105## Rate Limiting and Health106107- Rate-limit all public endpoints; stricter limits on auth endpoints (login, password reset).108- Return `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` headers; `429` with109 `Retry-After` when exceeded.110- Every service exposes `GET /health` returning `status`, `version`, `uptime`, and `dependencies`.111112## Deep guides (read on demand, do not preload)113114- Rails error concern, 500 handler, request-spec contract → `references/errors-rails.md`115- Zod boundaries, server-action results, typed axios client → `references/errors-typescript.md`116- Keyset cursors, pagy, PostGIS proximity → `references/pagination-rails.md`117- `useInfiniteQuery`, FlatList wiring → `references/pagination-clients.md`118- Is-it-breaking table, v1/v2 side by side, sunset sequence → `references/versioning-and-deprecation.md`119- rack-attack tiers, 429 envelope, client backoff → `references/rate-limiting.md`120- Liveness vs deep health, dependency timeouts, ALB target group → `references/health-checks.md`