GET /v1/stats/contracts is the one list endpoint #220 left unpaginated.
Current behaviour
services/api/handlers/stats.go:625 ends the aggregation query with a bare LIMIT $4. There is no cursor, no has_more, and no way to page past the cap — a caller with more contracts than the limit simply cannot reach the rest.
#220 ("enforce and document keyset pagination consistently across list endpoints") named stats listings explicitly in its scope. The work done under that issue applied keyset pagination to /v1/api-keys, /v1/webhooks, and /v1/admin/contracts, but not this endpoint, so the "consistently" half is unmet.
Why this matters
This is the endpoint a dashboard hits first. Silently truncating at the limit is the failure mode keyset pagination exists to remove, and it is the last endpoint forcing SDK auto-pagers to special-case a route.
Scope
- Apply the same opaque keyset cursor used by the other list endpoints.
- Return the same envelope shape:
data, next_cursor, has_more.
- Note that the existing ordering is by
event_count DESC, which is not a total order — ties need a stable tiebreaker (e.g. contract_id) or the cursor can skip or repeat rows.
Done when
- The endpoint accepts a cursor and returns
next_cursor/has_more.
- A test proves no row is skipped or duplicated across pages when several contracts share an
event_count.
GET /v1/stats/contractsis the one list endpoint #220 left unpaginated.Current behaviour
services/api/handlers/stats.go:625ends the aggregation query with a bareLIMIT $4. There is no cursor, nohas_more, and no way to page past the cap — a caller with more contracts than the limit simply cannot reach the rest.#220 ("enforce and document keyset pagination consistently across list endpoints") named stats listings explicitly in its scope. The work done under that issue applied keyset pagination to
/v1/api-keys,/v1/webhooks, and/v1/admin/contracts, but not this endpoint, so the "consistently" half is unmet.Why this matters
This is the endpoint a dashboard hits first. Silently truncating at the limit is the failure mode keyset pagination exists to remove, and it is the last endpoint forcing SDK auto-pagers to special-case a route.
Scope
data,next_cursor,has_more.event_count DESC, which is not a total order — ties need a stable tiebreaker (e.g.contract_id) or the cursor can skip or repeat rows.Done when
next_cursor/has_more.event_count.