| name | rest-api-architect |
| version | 1.1.0 |
| description | Cross-language REST conventions — resource URLs, method semantics, status codes, URL-prefix versioning, cursor pagination, snake_case JSON, ISO 8601 timestamps, RFC 7807 errors, Idempotency-Key, ETag/If-Match, OpenAPI as source of truth. Framework-agnostic. Use when designing or auditing REST endpoints. |
REST API Architecture
Cross-language conventions for HTTP/JSON REST APIs. Framework-agnostic. Pair with fastapi-architect or gin-architect for implementation. See STACK.md for the specs this skill is built on.
1. URL & resource design
- Plural nouns for collections:
/v1/users, /v1/orders. Never verbs in URLs (/v1/createUser is wrong — the method is the verb).
- Resource ID in the path:
/v1/users/{user_id}, never as a query parameter.
- Nest only one level deep for true containment:
/v1/orders/{order_id}/items. Beyond one level, use IDs and filter queries instead — deeper hierarchies turn brittle the moment relationships change.
snake_case in URLs and query params. Consistent with the JSON casing below.
- Sub-resources expose hierarchy, not actions.
/v1/orders/{id}/cancellation (PUT to create) instead of POST /v1/orders/{id}/cancel. The state change is what is created, not a verb on the parent.
2. HTTP method semantics
| Method | Purpose | Idempotent | Safe |
|---|
GET | Read | Yes | Yes (no side effects) |
POST | Create (server assigns ID) or action that doesn't fit elsewhere | No | No |
PUT | Replace entire resource (client supplies full state) | Yes | No |
PATCH | Partial update | No (unless body is itself idempotent — usually not) | No |
DELETE | Remove | Yes | No |
PUT requires the full resource representation. A PUT with only some fields is a bug — that's PATCH's job.
PATCH body uses JSON merge patch (RFC 7396) — flat field-set means "change these, leave the rest". Don't invent your own dialect.
POST is also for actions that don't map to CRUD — e.g. /v1/payments/{id}/refunds (creates a refund). The resource is the action's outcome.
3. Status codes
Use the right code for the situation. The full table — every code, when to use it, the common confusions (401 vs 403, 404 vs 410, 422 vs 400) — is in STATUS_CODES.md.
One hard rule: never 200 OK for errors. Returning {"success": false, "error": ...} with a 200 status is wrong and breaks every HTTP-aware tool.
4. Versioning — URL prefix
- Path prefix only:
/v1/users, /v2/users. No header-based versioning, no query-param versioning.
- Bump the version when a breaking change ships. Additive changes (new optional field, new endpoint) stay in the same version.
- Run versions side-by-side until clients migrate. Deprecate with
Deprecation and Sunset response headers (RFC 8594) before removing.
- Internal microservices can skip versioning until an external consumer appears — but it's cheap to start with
/v1/ from day one.
5. Pagination — cursor, not offset
Cursor pagination is stable under concurrent writes and O(1) per page; offset is O(N) and reads can shift between pages. Request/response shape + rules in PAYLOADS § 1.
6. Filtering, sorting, searching
- Filtering:
?status=paid&customer_id=01J9.... Equality only by default — operator syntax (?price[gte]=100) is fine for richer endpoints but document each operator in OpenAPI.
- Sorting:
?sort=-created_at,name — comma-separated, prefix - for descending. Document allowed sort fields.
- Searching:
?q=alice for free-text search across documented columns. Don't expose raw SQL LIKE patterns from clients.
- Sparse fieldsets:
?fields=id,email,created_at to limit response payload — useful for list endpoints. Validate against the schema.
7. Error contracts — RFC 7807 Problem Details
Every error response uses application/problem+json. Canonical shape + rules in PAYLOADS § 2; structured 422 validation shape in PAYLOADS § 3.
Key rules:
type is a stable URL — clients switch on it. Never change once published.
- One shape for every error. Don't mix RFC 7807 with framework defaults.
correlation_id required on 5xx so support can match server logs.
8. Idempotency — Idempotency-Key mandatory
Every POST and PATCH requires an Idempotency-Key header. Without it the server returns 400 Bad Request. The server caches the response keyed by (caller_id, method, path, key) for 24h and replays on retry; GET/PUT/DELETE are already idempotent by HTTP semantics and don't need it.
Full implementation reference — cache shape, TTL choice, concurrent-request handling, storage options, client guidance, common mistakes — in IDEMPOTENCY.md.
9. Concurrency control — ETag + If-Match mandatory
Every editable resource exposes an ETag on read. Every PUT and PATCH requires If-Match matching the current ETag, or returns 412 Precondition Failed. If-None-Match on GET enables 304 Not Modified caching for free.
Full implementation reference — where the ETag value comes from (default: monotonic version column), strong vs weak, the 412 retry flow, common mistakes — in CONCURRENCY.md.
10. Auth & security headers
- Authentication via
Authorization: Bearer <token>. No tokens in query params (they leak into logs and referrer headers).
401 for missing/invalid credentials; 403 for valid credentials lacking permission. Mixing these confuses clients debugging access issues.
- HTTPS always. Reject plaintext in production at the load balancer; redirect at the edge.
- CORS: explicit allow-list of origins; never
Access-Control-Allow-Origin: * for authenticated endpoints.
- Security headers (added at the gateway or app layer):
Strict-Transport-Security, X-Content-Type-Options: nosniff, Content-Security-Policy (if serving HTML), Referrer-Policy: no-referrer.
- No PII in URLs. Emails, names, IDs that map to PII go in headers or bodies — URLs end up in access logs, browser histories, and proxy caches.
11. Authentication patterns
Two patterns cover almost every API: in-house OAuth2 + JWT for single-service deployments, external IdP (Keycloak / Auth0 / Cognito / Entra) for multi-service, MFA, social login, or SSO/compliance needs. Full reference — Argon2id, HS256→RS256 switching, refresh-token rotation + reuse detection, JWKS verification with cached keys, mandatory aud/iss checks, when to switch A→B, per-endpoint authorization — in AUTH_PATTERNS.md.
Framework-specific implementation:
12. Content negotiation & encoding
Content-Type: application/json; charset=utf-8 for request and response bodies.
Accept: application/json assumed; servers may return application/problem+json for errors regardless of Accept.
- JSON field names:
snake_case. Matches Python and Go server conventions; client-side translation is trivial.
- Timestamps: ISO 8601 with timezone, always as a string.
"2026-05-20T14:23:00Z" or "2026-05-20T14:23:00+00:00". Never Unix epoch numbers — they're ambiguous about units (seconds vs ms) and harder to log-grep.
- Decimals as strings for money (
"99.99") — JSON numbers are floats and lose precision.
- UUIDs as canonical hex strings with dashes (
"01j9x...-..."). UUID v7 by default (sortable, distributed) — matches sql-architect.
null is intentional absence; missing field is "not provided" — these mean different things in PATCH. Document the distinction.
13. Caching
Cache-Control on every response. Defaults: private, no-store for authenticated user data; public, max-age=300 for genuinely public reference data (e.g. countries).
- ETag enables conditional GET (§9) — clients automatically reuse cached responses with a
304.
Vary: Authorization, Accept-Encoding when responses differ by these — without Vary, intermediaries serve the wrong cached entry to a different user.
14. Rate limiting
429 Too Many Requests when the limit is hit.
RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset on every response; Retry-After on 429 and 503. Header reference in PAYLOADS § 4.
- Limit per caller (API key, user id), not per IP — IPs aren't reliable identity.
15. OpenAPI as the source of truth
- OpenAPI 3.1 spec is the contract. Every endpoint, every model, every error code, every header documented.
- Generated from the code, not hand-written — frameworks (FastAPI, gin-openapi, etc.) emit it from the route definitions. Hand-written specs rot the day after they ship.
- CI snapshot-tests the spec — diff against
openapi.snapshot.json on every PR; any change is reviewed.
- CI lints the spec with
redocly lint — catches missing descriptions, broken $refs, and style violations before the diff review even starts.
- Spec is published at a stable URL (e.g.
/v1/openapi.json) and consumed by client-SDK generators, Postman collections, and API documentation tooling.
- Rendered docs UI is project-dependent — Scalar is a good modern default (open-source, fast, themeable), but Redoc, Swagger UI, or a generated static site are equally valid depending on the project's constraints.
- Examples on every model and parameter. They drive the rendered docs and seed mock servers.
What this skill does NOT cover
- Framework specifics (
Depends, gin.Context, middleware ordering) — see the framework architect skills.
- Auth scheme deep dives (OAuth2 flows, OIDC, IdPs) — see the framework auth sections.
- Database access patterns — see sql-architect.
- HATEOAS / HAL / JSON-API — out of scope; this skill defines plain JSON REST.