Pagination
List endpoints never return everything. Each returns one page wrapped in the same envelope, plus opaque cursors for moving through the rest.
Parameters
limit is clamped, not rejected. Values outside 1–100 are silently coerced into range, so limit=5000 returns 100 rather than an error. Do not rely on the number you sent being the number you get.
starting_after and ending_before are mutually exclusive. If you send both, starting_after wins.
Paging forward
Read next_cursor from the response and send it back as starting_after. Stop when has_more is false.
Send filters on the first request only
This is the part that surprises people. Filters are pinned into the cursor. Send q, donor_id, type and similar on the first request; every later page carries them automatically.
On the scholarship reporting endpoints this is not merely unnecessary but wrong — award_cycle_id is baked into the cursor, so repeating it on a later page can conflict with what the cursor already asserts.
Cursors are opaque and signed
A cursor is a signed token bound to your institution and to the filters that produced it. It is not a page number, an offset, or a record id, and it is not meant to be read.
Never build, edit, truncate, or store one for later reuse. A cursor that has been tampered with, has expired, belongs to another institution, or disagrees with the filters on the request returns 400 invalid_cursor.
If you need to resume work later, re-run the first request rather than persisting a cursor.