# Phase 4: REST API — query / filter / export / settings / clear **Status:** ⬜ Not Started **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** backend ## Objective Expose the audit log over the REST API: a filtered, keyset-paginated list endpoint; a streaming CSV/JSON export honoring the same filters; retention settings get/update; and a destructive clear. Apply the project's auth posture (stricter auth on export + clear). ## Tasks - [ ] `server/src/ledgrab/api/schemas/activity_log.py` (Pydantic): - `ActivityLogEntryResponse` (matches the frozen Phase 2 entry dict shape). - `ActivityLogPageResponse` { `entries: list[...]`, `next_before_seq: int | None`, `total: int` (optional/over filters), `has_more: bool` }. - `ActivityLogSettingsResponse` / `UpdateActivityLogSettingsRequest` (`enabled`, `max_days`, `max_entries`) with validation bounds. - [ ] `server/src/ledgrab/api/routes/activity_log.py` — `APIRouter(prefix="/api/v1/activity-log")`: - `GET ""` — list. Query params: `categories`, `severities`, `actor`, `entity_type`, `entity_id`, `since`/`until` (ISO), `q` (free-text), `before_seq` (cursor), `limit` (default 50, capped e.g. 200). `AuthRequired`. Maps params → `ActivityLogFilters`, calls `repo.query(...)`, returns page envelope. - `GET "/export"` — streaming export. Same filters; `format=csv|json`. Uses `StreamingResponse` over `repo.iter_export(...)`. **`require_authenticated()`** (may contain IPs/labels). Sets `Content-Disposition` with a timestamped filename. - `GET "/settings"` / `PUT "/settings"` — retention settings via the retention engine. `AuthRequired`; updates apply immediately. - `DELETE ""` — clear all entries. **`require_authenticated()`**. The clear is itself audited (recorder records a `system`/`activity_log_cleared` entry AFTER the wipe, so the log shows who cleared it and when). - Register the router in `server/src/ledgrab/api/__init__.py` (aggregator). - [ ] API tests `server/tests/api/routes/test_activity_log_api.py`: - list returns entries; each filter narrows results; `before_seq` cursor paginates without overlap/gaps; `limit` cap enforced; - export CSV and JSON both stream and honor filters; export requires authentication (401 for loopback-anonymous when keys configured); - settings get/update round-trip + validation rejects out-of-range; - clear empties the log, requires auth, and leaves exactly one post-clear audit entry. ## Files to Modify/Create - `server/src/ledgrab/api/schemas/activity_log.py` — new - `server/src/ledgrab/api/routes/activity_log.py` — new - `server/src/ledgrab/api/__init__.py` — modify: register router - `server/tests/api/routes/test_activity_log_api.py` — new ## Acceptance Criteria - List is filterable on every dimension and keyset-paginated (stable, no dupes/gaps). - Export streams CSV + JSON, honors filters, and requires authentication. - Settings get/update works and validates bounds; changes take effect immediately. - Clear requires authentication and is itself audited. - New + existing tests green; `ruff` clean. ## Notes - Auth helpers: `AuthRequired` dependency for normal endpoints; `require_authenticated()` for export + clear (pattern: backup download / secret reveal). See `api/auth.py` + `server/CLAUDE.md`. - Follow the existing route/schema conventions (one schema file per entity, router registered in `api/__init__.py`). Reference `api/routes/backup.py` for settings-style GET/PUT + a `StreamingResponse`/download pattern. - Reuse the entry→dict serializer from Phase 2 to keep the response shape single-sourced. - Backup/restore: no `STORE_MAP` change needed — backup is whole-DB; the table is auto-covered. ## Review Checklist - [ ] All tasks completed - [ ] Code follows project conventions (router registration, schema-per-entity, auth posture) - [ ] No unintended side effects - [ ] Build passes (ruff + pytest) - [ ] Tests pass (new + existing) ## Handoff to Next Phase