Pagination
How to size a list response, and the one resource that hands you the next page.
One resource continues. The rest answer in one response. Knowing which is which is the whole of this page, because a client that waits for a continuation nobody emits stops after one page and believes it is done.
Every list answers the same envelope
{ "items": [], "count": 0, "total": 0 }
count is how many rows this response carries. total is how many exist across
every response. When they are equal you have everything — that comparison
works whether or not the resource paginates.
The one resource that continues
GET /api/feedback answers a Link header when rows remain.
Link: <https://lateano.com/api/feedback?cursor=bzoxMA>; rel="next"
Follow it. Do not construct it — the cursor is opaque and is meant to stay that way. It encodes an offset today and is not promised to tomorrow, and a client that decodes it is a client the next implementation breaks.
The link is absent on the last page, which is how you stop without counting.
Send a cursor this resource did not mint — a truncated paste, a value from an
older deployment — and you get the first page rather than a refusal: a 422 on a
link somebody followed is a worse answer than starting over.
It reads ?limit= the way the table below says.
limit is not one parameter
This is the part worth reading twice, because the same spelling does not mean the same thing at every address.
| Resource | ?limit= |
|---|---|
GET /api/feedback |
Read. Default 10, capped at 25, clamped rather than refused. |
GET /api/collections |
Read. No default and no ceiling — omitted, every collection comes back. |
GET /api/site-pages |
Read. No default and no ceiling — omitted, every page comes back. |
GET /api/tags |
Read. No default and no ceiling — omitted, the whole vocabulary comes back. |
GET /api/pages |
Not a search parameter at all — limit is a field of the query grammar, and the grammar refuses what it will not answer. |
The first row is the one that catches people. Only /api/feedback reads
limit as a page size, with a default and a ceiling. The three rows under it
are vocabularies: their sets are closed and small, so leaving limit off asks
for all of it rather than for a first page, and naming a number larger than the
set is not an error — you get the set.
The query surface refuses rather than clamps
GET /api/pages is the cacheable spelling of a query, and its limit belongs to
the grammar rather than to this parameter. The grammar refuses what it cannot
answer, with a 422, and that is deliberate — the caps are a property of the
question, so an unanswerable question is corrected rather than quietly reduced:
?limit=1000exceeds the row allowance and is refused.?limit=abcis not an integer and is refused.- A bare
/api/pagesnaming neither a search nor a filter is refused: the whole corpus is not a question.
So do not carry a “clamp it and move on” assumption from the list resources to
this one. See Errors for why a 422 means stop
sending this rather than resend it differently.
The client worth writing
Follow rel="next" when it is present and stop when it is absent. That client
works today against GET /api/feedback, does the right thing against the
resources that answer in one response, and keeps working unchanged if any of them
ever starts continuing.