Pagination
Cursor-based, stable under inserts. The same model used by Stripe and GitHub.
List endpoints (currently just GET /public/v1/meetings) use cursor
pagination. You get back a page of results plus an opaque cursor
that points at the next page; pass it back as the cursor query param
to advance.
The contract
Every list response has this shape:
{
"data": [ /* page of items */ ],
"pagination": {
"next_cursor": "eyJzIjoi…",
"has_more": true
}
}next_cursor— opaque base64url string. Pass it as?cursor=…on the next request.nullwhen there are no more pages.has_more—truewhen more rows exist past this page;falseon the last page.
Iterating
async function* iterMeetings(apiKey: string) {
let cursor: string | undefined;
while (true) {
const url = new URL('https://api.meetso.ai/public/v1/meetings');
url.searchParams.set('limit', '50');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
const body = await res.json();
yield* body.data;
if (!body.pagination.has_more) break;
cursor = body.pagination.next_cursor;
}
}Why cursors, not offsets
Offset pagination (?page=2) is unstable when the underlying data
changes between requests — if a new row is inserted at position 7
between fetching page 1 and page 2, you'd see the same row twice. The
cursor encodes the last seen (start_time, id) pair, so the next page
resumes from exactly where the previous one ended regardless of inserts.
This matches how Stripe, GitHub, and Linear paginate.
Cursor opacity
Don't try to parse, decode, or generate cursors yourself. The encoding is part of the public contract but the contents are not — we may extend it (e.g., to encode filter parameters) without warning. Round-trip the value as-is.
Page size
Pass limit to control page size. Defaults are documented per
endpoint, max is 100.
Edge cases
The filter changes mid-pagination. If you're paginating with
time_range=past and a meeting transitions from upcoming to past
between requests (i.e., its endTime crosses now), it can appear
in a later page even though it would have been past the cursor under
the old filter. Cursors don't snapshot the filter; they snapshot the
sort key. For most ingestion use cases this is fine — you'll just see
the meeting in a subsequent run.
A row is created during pagination. If you're walking from newest
to oldest and a brand-new meeting is created mid-iteration, you won't
see it in this run (its start_time is past the cursor). Run the
iteration again from the beginning to pick it up.
A row is deleted during pagination. If a row is deleted between your fetch of a cursor and your next request, the next request silently skips it — no error. Cursor pagination is naturally tolerant to deletes.