Files
bastien 1a8e6decdb feat(rules): rest-api path-scoped rule distilled from agent-skills
Contract-first order, one error envelope + HTTP map, paginated lists,
idempotency (key from intent, atomic claim, payload guard, duplicate
policy, retention), naming, Hyrum's law. Versioning points to CLAUDE.md
§ Web APIs — always versioned; the upstream one-version rule is dropped.
2026-09-27 20:17:38 +02:00

1.7 KiB

paths
paths
**/api/**
**/routes/**
**/controllers/**
**/*.route.*
**/*.controller.*
**/openapi.*
**/*.openapi.*

REST API — contract, errors, lists, idempotency

Contract first

Order: typed input/output → schemas (server-generated fields like id, createdAt apart from client input) → error codes → implementation. Validate at boundaries only (route handlers, external responses, env loading); trust internal code and your own database reads.

Errors

One envelope everywhere: { error: { code, message, details? } }. HTTP map: 400 invalid · 401 auth · 403 forbidden · 404 missing · 409 conflict · 422 semantic · 500 server (never leak internals). Never mix throw / null / envelope styles across endpoints.

Lists

Every list endpoint paginated: page, pageSize, totalItems, totalPages. Filters as query params (?status=x&createdAfter=…), never in the body.

Idempotency

Key from intent, not attempt (charge:v1:${orderId}, never randomUUID() or a timestamp). Claim atomically via a unique constraint — check-then-insert is a race, not a guard. Same key, different payload: fail loudly, never replay the first response. Pick the in-flight-duplicate policy on purpose: 409 reject, bounded wait, or 202 + status URL. Retention outlives the longest retry path, dead-letter replay included.

Naming

Plural nouns for endpoints (/api/tasks), no verbs. camelCase query params and response fields. UPPER_SNAKE enum values. Boolean fields prefixed is/has/can.

Hyrum's law

Every observable behavior — undocumented quirks, error text, timing — becomes a de facto contract once someone depends on it.

Versioning

CLAUDE.md § Web APIs — always versioned. Not repeated here.