# Mitbringsl – REST API Specification Base URL: `/` Authentication: Bearer Token via `Authorization: Bearer ` header or session cookie. --- ## Health & Diagnostics ### `GET /healthz` Liveness probe. Returns HTTP 200 `OK`. ### `GET /readyz` Readiness probe. Checks PostgreSQL connection pool health. - **200 OK**: Database reachable. - **503 Service Unavailable**: Database error. --- ## Authentication (`/auth`) ### `POST /auth/register` Create a new email/password account. - **Request**: ```json { "email": "user@example.com", "password": "secretpassword", "display_name": "Max" } ``` - **Response (201 Created)**: ```json { "token": "", "expires_at": "2026-09-04T20:00:00Z", "user": { "id": "", "email": "user@example.com", "display_name": "Max" } } ``` ### `POST /auth/login` Authenticate with email/password. - **Request**: ```json { "email": "user@example.com", "password": "secretpassword" } ``` - **Response (200 OK)**: Same shape as register. ### `POST /auth/oidc` Authenticate via Google or custom OpenID Connect provider. - **Request**: ```json { "provider": "google", "id_token": "" } ``` - **Response (200 OK)**: Same shape as login. ### `POST /auth/logout` Revoke active session token. Returns `204 No Content`. --- ## Lists (`/api/lists`) All list endpoints return `404 Not Found` when the authenticated user is neither owner nor member of the list (no membership leaks), and `401` without a valid session. List responses carry an `invite_code` (8 characters) for sharing. ### `GET /api/lists` Fetch all active (non-deleted) lists the authenticated user owns **or** joined as a member. - **Response (200 OK)**: ```json { "lists": [ { "id": "", "name": "Wocheneinkauf", "invite_code": "A1B2C3D4", "updated_at": "2026-08-05T19:00:00Z", "hlc_ts": 177000000000000 } ] } ``` ### `POST /api/lists` Create a new list. The owner is automatically added to `list_members` (role `owner`) and an invite code is generated. - **Request**: `{ "name": "Supermarkt" }` - **Response (201 Created)**: `{ "id": "", "name": "Supermarkt", "invite_code": "A1B2C3D4", ... }` ### `GET /api/lists/{id}` Fetch list detail including items (owner or member only). - **Response (200 OK)**: ```json { "id": "", "name": "Wocheneinkauf", "invite_code": "A1B2C3D4", "updated_at": "...", "hlc_ts": 177000000000000, "items": [ { "id": "", "name": "Milch", "quantity": "1L", "checked": false, "hlc_ts": 177000000000001 } ] } ``` ### `POST /api/lists/{id}/invite` Return the invite code of a list (owner or member only). Generates a code on the fly for legacy lists without one. - **Request**: empty JSON object `{}` - **Response (200 OK)**: `{ "invite_code": "A1B2C3D4" }` ### `POST /api/lists/join` Join a shared list using its invite code (case-insensitive). Joining again is idempotent; the existing membership role is kept (an owner cannot be demoted). - **Request**: `{ "invite_code": "a1b2c3d4" }` - **Response (200 OK)**: the joined list (same shape as `GET /api/lists` items). - **400 Bad Request**: unknown or empty invite code. --- ## Sync & Ops (`/api/lists/{id}/ops`) Both endpoints require membership (owner or member); otherwise `404 Not Found`. ### `POST /api/lists/{id}/ops` Push a batch of client operations (max 100). - **Request**: ```json { "client_id": "", "ops": [ { "client_seq": 1, "op_type": "item_add", "target_id": "", "hlc_ts": 177000000000000, "payload": { "name": "Brot", "quantity": "1 Stück" } } ] } ``` - **Response (200 OK)**: ```json { "results": [ { "client_seq": 1, "seq": 42, "hlc_ts": 177000000000001 } ] } ``` ### `GET /api/lists/{id}/ops?since={seq}` Pull operations since server sequence `since`. - **Response (200 OK)**: ```json { "ops": [ { "seq": 42, "client_id": "", "op_type": "item_add", "target_id": "", "payload": { "name": "Brot" }, "client_seq": 1, "hlc_ts": 177000000000001, "created_at": "2026-08-05T20:00:00Z" } ], "has_more": false } ``` --- ## Suggestions (`/api/suggestions`) ### `GET /api/suggestions?q={query}` Fuzzy autocomplete search for item names (pg_trgm). - **Response (200 OK)**: ```json { "suggestions": ["milch", "mineralwasser", "müsli"] } ```