Pagination
Read a list page by page with limit and cursor, reread only the changes with since, and handle deleted objects.
A list never returns everything at once. It returns one page, sorted by date of change and then by id, and tells you whether another page remains. A page costs one credit, whatever its number of objects.
The parameters of a list
| Parameter | Role |
|---|---|
limit | Objects per page, from 1 to 200. Default: 50. |
cursor | The next_cursor of the previous page. |
since | ISO 8601 date. Returns the objects with updated_at >= since. |
include_deleted | With true, adds the deleted objects. |
student_id, status | Filters, on the lists that know them only. |
A parameter that a list does not know, or a limit outside 1 to 200, is refused with a 400 VALIDATION, and the call stays billed.
Follow the pages
Every response has the same shape:
{
"data": [ … ],
"has_more": true,
"next_cursor": "MjAyNi0xMC0yNVQwOToxMjowMC4wMDBafDQzMTg"
}While has_more is true, call the same route again with cursor set to next_cursor. On the last page, next_cursor is null. The cursor is opaque: it encodes the position of the last object received, so do not build one. Keep the same filters (since, student_id, status, include_deleted) from one page to the next.
These loops use the call function from Write a script:
Python
def read_all(path, **filters):
items, cursor = [], None
while True:
params = {**filters, "limit": 200}
if cursor:
params["cursor"] = cursor
page = call("GET", path, params=params)
items += page["data"]
if not page["has_more"]:
return items
cursor = page["next_cursor"]JavaScript
async function readAll(path, filters = {}) {
const items = [];
let cursor;
do {
const query = new URLSearchParams({ ...filters, limit: "200" });
if (cursor) query.set("cursor", cursor);
const page = await call("GET", `${path}?${query}`);
items.push(...page.data);
cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);
return items;
}Reread only the changes
After a first full read, keep the largest updated_at you received. Pass it as since on the next read, with include_deleted=true: you receive what changed, deletions included. Without it, a read by since never sees a deleted object.
The test is inclusive. The object with exactly that date comes back a second time, and an object modified while you read may come back further on. Deduplicate by id and keep the last one received.
The updated_at of a student also moves on each login of the student. A daily sync therefore sees again the students who logged in since the day before, with no visible change.
Deleted objects
Without include_deleted, a deleted object simply disappears from the list. With include_deleted=true, it comes back with a deleted_at set: remove it from your copy. The field exists only on the resources that carry it, not on Student: a student who leaves your coaching is read in GET /students/departures, described in the reference.