The paginated envelope is a third envelope with four fixed keys
Two envelopes carry a result and a refusal. A paginated result is a third, and its pagination block belongs to the envelope rather than to the rows it sits beside.
The rule
- Alongside
{success, data}and{success, error}, a paginated response is{success, data, pagination}, wherepaginationcarries exactlytotal,limit,offsetandhas_more, in that order. has_moreis page arithmetic,(offset + limit) < total, and not a second query.- It is spelled
has_morein every paginated response, whatever the spelling convention of that endpoint's rows: it belongs to the shared envelope, and an implementation must not rename it to match the rows beside it. - A door must not build a pagination block of its own.
What it means
A result and a refusal are the two envelopes every door already answers in. A paginated result is a third, and it is a shape of its own, not the result envelope with a block appended by whichever door happens to return one: the same four keys, in the same order, on every paginated door.
has_more is arithmetic the door itself can do — whether the offset and
limit it just reported would still leave rows unseen — not a second query
run to find out. And it keeps its snake_case spelling regardless of the
casing convention the rows beside it use: the block belongs to the shared
envelope, not to the endpoint it happens to sit in, so a door must not rename
it to match its own rows, and must not build a pagination block of its own.
Example
Two workflows, unfiltered: three keys, and the pagination block reports the whole set (rows elided).
GET /api/flowdrop/workflows
{"success": true, "data": [ … ], "pagination": {"total": 2, "limit": 50, "offset": 0, "has_more": false}}The same four keys, in the same order, sit beside rows spelled the other way.
GET /api/flowdrop/workflows/{workflow}/playground/sessions
{"success": true, "data": [ … ], "pagination": {"total": 1, "limit": 50, "offset": 0, "has_more": false}}has_more is spelled the same both times: once beside rows spelled the same
way as the pagination block, once beside rows that are not.