--- type: api title: REST API Overview description: Base URL, versioning, auth, CSRF, and the standard error envelope. tags: [api, rest, overview, csrf] timestamp: 2026-07-21T22:19:08Z --- # Base URL & versioning All endpoints live under `/api/v1`. The Angular SPA uses the same prefix; the SPA fallback only kicks in for non-API routes. | Path prefix | Purpose | |-----------------|--------------------------------------------------------| | `/api/v1/*` | Versioned JSON REST endpoints. | | `/api/docs` | Swagger UI (OpenAPI 3.1). | | `/api/docs-json`| Raw OpenAPI 3.1 document. | | `/uploads/*` | Static uploads directory (`UPLOAD_DIR`). | | `/*` | SPA fallback (`FRONTEND_DIST/index.html`). | # Authentication * `JwtAuthGuard` is registered as `APP_GUARD` and runs on every request unless the handler is marked with `@Public()` from `backend/src/common/decorators/public.decorator.ts`. * Successful auth produces an access JWT in the response body and an `HttpOnly` refresh cookie named `rt` (see `backend/src/modules/auth/cookie.ts`). * `Authorization: Bearer ` is attached by the `authInterceptor` on the frontend. # CSRF * `CsrfMiddleware` (registered globally) mints a `csrf` cookie on every response and, for unsafe methods (`POST`, `PUT`, `PATCH`, `DELETE`), compares the cookie against the `x-csrf-token` header using `crypto.timingSafeEqual`. * The frontend's `csrfInterceptor` runs *before* `authInterceptor` so the header is attached on every unsafe call. * Skip list (`SKIP_PATH_PREFIXES` in `backend/src/common/middleware/csrf.middleware.ts`): - `/api/v1/auth/register-first-admin` - `/api/v1/setup/create-admin` - `/api/v1/auth/login` is **no longer** skipped — the middleware still mints the cookie on the first response, and the SPA's `AuthService.login`/`AuthService.register` first call `GET /api/v1/auth/csrf` via `ensureCsrf()` to materialise the cookie before submitting. # Error envelope Every error flows through `GlobalExceptionFilter` and is shaped as: ```json { "code": "USERNAME_TAKEN", "message": "Username already exists", "details": null, "path": "/api/v1/setup/create-admin", "timestamp": "2026-07-21T14:43:00.000Z" } ``` | Field | Type | Notes | |-------------|---------------------|--------------------------------------------------------| | `code` | string | One of `ERROR_CODES` (`backend/src/common/errors/error-codes.ts`). | | `message` | string | Human-readable summary. | | `details` | unknown \| null | Optional structured details (e.g. zod field errors). | | `path` | string | Original request path. | | `timestamp` | ISO 8601 string | When the filter produced the response. | # Canonical error codes `USERNAME_TAKEN` is the most recently added code (used by the setup endpoint). Full list lives in `backend/src/common/errors/error-codes.ts`: `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `LAST_ADMIN`, `SYSTEM_INITIALIZED`, `RATE_LIMITED`, `CSRF_INVALID`, `WEAK_PASSWORD`, `USERNAME_TAKEN`, `INVALID_CREDENTIALS`, `TOKEN_REVOKED`, `THEME_INVALID`, `REGISTRATIONS_DISABLED`, `INVALID_OLD_PASSWORD`, `PASSWORD_POLICY`, `PASSWORDS_DO_NOT_MATCH`, `INTERNAL`. # See also - [Auth Endpoints](/api/auth.md) - [Admin Endpoints](/api/admin.md) - [Setup Endpoint](/api/setup.md) - [System Endpoints](/api/system.md) - [Uploads Endpoints](/api/uploads.md)