Pagination (Phase 13)¶
What¶
Offset-based pagination on every list endpoint. Adds limit + offset query params and a total field on the response so consumers can render "showing N–M of T" with prev/next controls.
Defensive hardening before a partner firm hits volume. At ~50 engagements / 75 findings per engagement, payloads are still small (a full list of all engagements is ~50KB JSON), but a firm running 200+ engagements with 100+ findings each would saturate the response without a cap.
Endpoint changes¶
| Endpoint | Default limit | Max limit | Default sort |
|---|---|---|---|
GET /auditforge/engagement |
100 | 500 | updated_at desc |
GET /auditforge/engagement/{id}/findings |
200 | 1000 | severity desc, then confidence desc |
GET /auditforge/firm |
100 | 500 | display_name asc |
GET /auditforge/user |
100 | 500 | firm_id asc, then email asc |
GET /auditforge/findings/search |
(already had) 100 | 200 | (no change — already pagiated) |
/findings/portfolio-clusters is not paginated — clusters are themselves the aggregation; the result is always small.
Response shape¶
All list endpoints now return:
{
"<collection>": [...], // page of items
"count": 42, // items in this page
"total": 217, // full collection size after filters
"limit": 100,
"offset": 100 // 0-indexed
}
Frontend renders pagination controls when total > limit:
Validation¶
limit must be in [1, max_limit_per_endpoint]; offset must be >= 0. Invalid values return 422 with the constraint.
Sort order is fixed per endpoint¶
Different consumers want different orderings (most recent vs alphabetical vs severity), but for a single endpoint the order is fixed and documented. This makes pagination correct: callers iterate offset=0, offset=limit, etc. and see every record exactly once.
Adding a sort= query param is on the roadmap for endpoints where the partner wants choice (e.g., engagements by client_name). Today the defaults match the most common workflow:
- Engagements default to most-recently-active first (matches the dashboard's expected use)
- Findings default to severity desc (review priority)
- Firms / users default to alphabetical (predictable)
Per-firm scoping interaction¶
For non-admin session callers, the firm_id query param on /engagement is ignored (overridden to caller's firm) before pagination. Pagination operates on the scoped subset, so a partner with 30 engagements always sees total=30 regardless of how many engagements other firms have.
What's NOT paginated¶
| Endpoint | Why |
|---|---|
GET /engagement/{id} |
Single record; no list to paginate |
GET /findings/portfolio-clusters |
Clusters are the aggregation; result is always 0–8 items |
POST /findings/portfolio-clusters/recompute |
Same reason |
GET /auth/me |
Single caller record |
| Auth / firm CRUD detail / user detail | Single records |
Frontend¶
The existing UI components fetch with default limits (100 or 200) which covers all current data. Pagination controls in the UI are deferred — when a real partner crosses 100 engagements we'll add prev/next buttons; today the response shape is already correct, so adding UI later is a frontend-only change.
Cost¶
Zero LLM cost. The store loads always read the full collection (S3 → memory → cache); the new code just slices the in-memory result. Performance overhead is negligible (slicing a 1000-item list is microseconds).
For very large portfolios (10K+ engagements, hypothetical), an indexed S3 layout or DynamoDB-backed list would be needed. That's a separate scaling phase, not on the current roadmap.
Code¶
app/auditforge_endpoints.py— 4 list endpoints addlimit+offset+total+ sort + slice
Verification¶
End-to-end: GET /engagement?limit=2&offset=0 returns 2 engagements + total=N; GET /engagement?limit=2&offset=2 returns the next 2; consistent ordering across pages.
Related¶
- api-reference.md — list endpoints documented with the new params