Pagination

Every list endpoint is cursor-paginated. There is no offset.

List endpoints return a uniform envelope:

{
  "object": "list",
  "data": [ /* … */ ],
  "has_more": true,
  "next_cursor": "MjAyNi0wNy0xNVQwOTozMToxMi4wMDBafDNmMWMyZTAw"
}
  • data — the page, newest first.
  • has_more — whether another page exists.
  • next_cursor — pass it back as ?cursor= for the next page. null means you've reached the end.

Parameters

ParameterDefaultNotes
limit501–100. Anything outside that range is a 422.
cursor—Opaque. Only ever pass back a next_cursor you received.

Why cursors, not pages

?page=2 breaks under a moving dataset: if three leads are updated while you're iterating, rows shift between pages and you silently skip some. A cursor pins the exact position in the sort order, so a lead updated mid-iteration reappears at the front rather than displacing the page you haven't read yet.

That is also why a malformed cursor is a 422 rather than a silent reset to page one — quietly restarting an iteration is how integrations double-process records.

Iterating

async function* allLeads(token: string) {
  let cursor: string | null = null;
  do {
    const url = new URL("https://replyfirst.ae/api/v1/leads");
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("cursor", cursor);

    const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
    if (!res.ok) throw new Error((await res.json()).error);

    const page = await res.json();
    yield* page.data;
    cursor = page.next_cursor;
  } while (cursor);
}

for await (const lead of allLeads(process.env.RF_ORG_TOKEN!)) {
  console.log(lead.id, lead.qualification_stage);
}

Incremental sync

Don't re-walk the whole list on every sync. Record the newest updated_at you've seen, then pass it as updated_after on the next run:

curl "https://replyfirst.ae/api/v1/leads?updated_after=2026-07-26T00:00:00Z&limit=100" \
  -H "Authorization: Bearer $RF_ORG_TOKEN"

Better still, let the webhook push changes to you and use updated_after only to backfill after downtime.

Next

On this page