Pagination
Every list endpoint in the v1 API uses cursor-based pagination. Cursors are opaque to clients — they’re HMAC-signed tokens encoding the sort key of the last record on the page. You don’t need to (and shouldn’t) decode or construct them yourself.
Query parameters
Section titled “Query parameters”| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
limit | integer | 25 | 100 | Number of records to return per page. |
cursor | string | - | - | Opaque cursor returned by a previous page in meta.pagination.cursor. |
Response shape
Section titled “Response shape”List responses include a pagination object inside meta:
{ "data": [ /* records */ ], "meta": { "request_id": "req_8f3c1b2a", "pagination": { "cursor": "eyJpZCI6NDIsImMiOiIyMDI2LTA0LTAxVDE1OjMyOjExWiJ9.7e4f...", "has_more": true, "count": 25 } }}cursor— Pass to the next request to get the next page.nullon the last page.has_more—trueif there are more records after this page.count— Number of records in the current page.
Why keyset cursors?
Section titled “Why keyset cursors?”Cursors encode the sort key of the last seen record (id + createdAt) and are HMAC-signed so the server can detect tampering. Compared to offset/limit pagination, keyset cursors give you:
- Consistency under writes. New records inserted while you’re paginating won’t shift items between pages or cause duplicates.
- Stable performance. The server can use an indexed range scan instead of skipping rows. Pages 1 and 1000 cost the same.
- No skew on deletions. Soft-deleted records don’t shift the offset.
Looping through every page
Section titled “Looping through every page”async function* listAllContacts(apiKey) { let cursor = null; do { const url = new URL('https://app.buildworkpro.com/api/v1/contacts'); url.searchParams.set('limit', '100'); if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const body = await res.json();
for (const contact of body.data) yield contact;
cursor = body.meta.pagination.has_more ? body.meta.pagination.cursor : null; } while (cursor);}
for await (const contact of listAllContacts(process.env.BUILDWORKPRO_API_KEY)) { console.log(contact.email);}