--- name: response-format description: RESTful plural nouns, consistent error format, pagination, OpenAPI documentation applies-to: claudetools-api, gururmm --- # API Standards ## URL structure - RESTful with plural nouns: `/api/users`, `/api/agents`, `/api/clients` - Nested resources for sub-items: `/api/agents/:id/checks`, `/api/sites/:id/channel` - Use kebab-case for multi-word segments: `/api/policy-assignments`, `/api/check-results` ## Error format All error responses use a consistent envelope: ```json { "detail": "Human-readable error description", "error_code": "MACHINE_READABLE_CODE", "status_code": 404 } ``` - `detail` — for humans; may be shown in UI - `error_code` — for client code to switch on; use `UPPER_SNAKE` format - `status_code` — redundant with HTTP status but helps clients that lose the HTTP layer ## Pagination Paginate all list endpoints that can return more than ~50 items. Use cursor-based or offset-based pagination: ```json { "items": [...], "total": 148, "page": 1, "per_page": 25 } ``` Do not return unbounded arrays from production endpoints. ## Documentation - Document with OpenAPI — FastAPI generates this automatically from type annotations and docstrings - For Axum/Rust endpoints, add route comments with request/response shapes until an OpenAPI generator is wired ## sqlx query style (GuruRMM server) Use `sqlx::query()` (runtime) not `sqlx::query!()` (compile-time macro) for new queries. The compile-time macro requires `cargo sqlx prepare` after every schema change and rebuilding the `.sqlx/` cache. Runtime queries avoid this overhead. The offline cache (`server/.sqlx/`) only needs updating when `query!()` macros change. When adding a new query, default to `sqlx::query()` unless there is a specific reason to use the proc macro. ## Migration discipline (GuruRMM) - Never manually pre-apply migrations via psql without also recording the checksum in `_sqlx_migrations` — sqlx will fail on startup if it finds a missing row for a migration it doesn't recognize - Use `ADD COLUMN IF NOT EXISTS` in all migrations so they are idempotent - Apply migrations by letting the server binary run them on startup (`sqlx::migrate!()`) - The correct sequence for a new migration: add SQL file → apply to DB (server startup) → `cargo sqlx prepare` → commit `.sqlx/` → rebuild