Docs
Pagination
Lists come back newest first in pages. Keep paging until has_more is false, and pass next_cursor back as starting_after.
The shape of a page
GET /v1/users/ava/ledger?limit=50
{
"data": [ { "id": "led_01JA0Q8X2M4N6P8R0T2V4W6Y8Z", … }, … ],
"has_more": true,
"next_cursor": "led_01J9ZZ3K7T1B5C9D2E4F6G8H0J"
}
GET /v1/users/ava/ledger?limit=50&starting_after=led_01J9ZZ3K7T1B5C9D2E4F6G8H0J
limit: items per page, 1 to 100. The default is 50 (the inbox: 20).starting_after: thenext_cursorof the previous page. Leave it out for the first page. Treat the cursor as an opaque string.has_more:truewhile older items exist.next_cursor: what to send asstarting_afterfor the next page. It isnullon the last page.
A cursor points at an item, not at a page number, so new activity never shifts the pages you have not read yet: new items appear at the front of the list and your cursor keeps its place.
Which endpoints page this way
| Endpoint | Order | Cursor |
|---|---|---|
GET /v1/users/{external_id}/ledger | Newest first | next_cursor as starting_after |
GET /v1/gift-transactions | Newest first | next_cursor as starting_after |
GET /v1/users/{external_id}/rejected-requests | Newest first | The id of the last item as starting_after; has_more says whether to continue |
GET /v1/users/{external_id}/inbox and GET /v1/client/me/inbox | Newest first, last 24 hours | next_cursor as cursor (Inbox) |
GET /v1/events | Oldest first | next_cursor as cursor, or the last sequence you processed as after_sequence (Webhooks) |
Filtered pages can be short
The ledger and gift transaction lists accept filters: type, reason,
reference, from and to on the ledger; sender,
receiver, gift_id, status, from and
to on gift transactions. A filtered page looks at no more than
2,000 rows for one request, so that no single call can run long. When fewer rows
than that match, you get a full page as usual. When the matches are rare, a page can hold fewer
than limit items, even none, while has_more is still true:
the scan stopped at its cap, not at the end of your data.
So never treat a short or empty page as the end. Stop only when has_more is
false, and always send next_cursor on:
async function allGiftsOf(user, { gs }) {
const out = [];
let cursor;
do {
const q = new URLSearchParams({ sender: user, status: "completed", limit: "100", from, to });
if (cursor) q.set("starting_after", cursor);
const page = await gs("/v1/gift-transactions?" + q);
out.push(...page.data);
cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);
return out;
}
Date ranges
from is inclusive and to is exclusive, both ISO 8601 timestamps.
GET /v1/gift-transactions lists the whole project, so without a
sender or receiver it needs both from and
to, and the range may span at most 31 days. A longer range, or one
that ends before it starts, is a 400 invalid_request with
details.field set to to. To cover a longer period, ask for one month
at a time, each with its own cursor. With a sender or receiver the
range is optional and has no length limit.
GET /v1/gift-transactions?from=2026-10-01T00:00:00Z&to=2026-11-01T00:00:00Z&limit=100
Things worth knowing
- Reads are not counted towards your plan, whatever the page size.
- A page is a snapshot of what existed when it was read. A gift reversed later changes that item's
statusthe next time you read it. - An unknown
starting_afteror one of the wrong kind is a400 invalid_request; start again from the first page. - Leaderboards are not paged: they return the top 100 at most (Leaderboards, away and inbox).
Questions or something unclear? support@giftingstack.com.