From 1a8e6decdbdd04b8773c30c3fa67bead995e8922 Mon Sep 17 00:00:00 2001 From: bastien Date: Sun, 27 Sep 2026 20:17:38 +0200 Subject: [PATCH] feat(rules): rest-api path-scoped rule distilled from agent-skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- rules/rest-api.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 rules/rest-api.md diff --git a/rules/rest-api.md b/rules/rest-api.md new file mode 100644 index 0000000..e971f83 --- /dev/null +++ b/rules/rest-api.md @@ -0,0 +1,43 @@ +--- +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.