- Offset (
limit+offset) — simple, fine for small result sets and one-off reads. - Cursor (
cursor) — recommended for large exports and incremental syncs: constant cost at any depth, and rows inserted while you page can never make you skip or double-read a record.
Parameters
integer
default:"20"
Records per page. Minimum
1, maximum 100. Applies in both modes.integer
default:"0"
Number of records to skip. Combine with
limit to page:
?limit=50&offset=100 returns records 101–150. Ignored when cursor is set.string
Opaque token from a previous response’s
meta.next_cursor. When set, results page by a
stable (created_at, id) keyset — newest first — and offset is ignored. Available on
every list endpoint except /tutors, /students, /parents, /conversations, and the
/coverage/* routes (offset-only).The meta object
Cursor-mode responses also carry a standard
Link: <...>; rel="next" header, so generic
HTTP paginators can follow pages without parsing the body.
Polling for new records
Tools like Zapier and Make poll a list endpoint on a timer, read only the first page, and treat any record ID they haven’t seen before as new. They do not page backwards through history. That only works if new records sort to the top./students and /parents are ordered newest enrolment first (enrolled_at
descending) for exactly this reason — poll page 1 on your interval and you will see every
new record.
/sessions has two sort axes
/sessions accepts ?sort=created_at (default) or ?sort=starts_at:
created_at(default) — when the session was booked. Poll page 1 on this axis and you will see every new booking, same as every other endpoint above.starts_at— when the session is scheduled to happen. Useful for an agenda/calendar view (“what’s coming up”), but do NOT poll it to detect new bookings: a session booked today for next month sorts above one booked today for tomorrow, and a session backfilled for a past date sorts near the bottom.
from/to always filter on starts_at, regardless of which sort you use — so a calendar
consumer typically sets both sort=starts_at and a from/to window.
If you’d rather be pushed than poll, webhooks deliver the same records the moment
they change.
enrolled_at is the cursor, not created_at
On /students and /parents these two fields mean different things:
enrolled_at— when they joined your organisation. This is the sort key.created_at— when the underlying account was created: possibly in a different organisation, possibly years earlier.
enrolled_at of this morning and sits at the top of page 1. Sort and filter on
enrolled_at.
If you’d rather be pushed than poll, webhooks deliver the same records the
moment they change.
Paging through everything
Preferred (cursor): start withcursor=start, then follow meta.next_cursor
until it comes back null:
next_cursor, so you can switch to cursor paging from
page 2 without starting over. Cursor pages are always ordered newest-created first
regardless of the endpoint’s offset-mode order (so /sessions by cursor walks booking
order, not schedule order — exactly what a sync wants).
Legacy (offset): increase offset by limit until a page returns fewer than
limit records (or, where total is present, until offset + count >= total).
Offset paging keeps working forever, but deep offsets get slower and a record created
mid-walk shifts the window, so a sync can miss or double-read a row.
Single-record and rollup endpoints (e.g.
/sessions/{id}, /reports/summary,
/parents/{id}/balance) return the object directly, with no data/meta envelope.