REST API Pagination in Follow Up Boss
Follow Up Boss supports two pagination patterns: classic limit/offset for filtered list endpoints and keyset cursor pagination via _metadata.next for full database sweeps. The cursor value is an opaque token — not a URL — so you append it as ?next=<token> to your original endpoint. Page up to 100 records at a time and add a delay between pages to stay within rate limits.
If you are building an integration, a data pipeline, or a bulk automation on top of Follow Up Boss, pagination is the first real engineering decision you have to make correctly. Get it wrong and you either silently drop records or hammer the API until you get rate-limited. This guide explains exactly how FUB's pagination works — with real request patterns drawn from production integrations.
Does Follow Up Boss use limit/offset or cursor pagination?
Follow Up Boss uses both, depending on the endpoint and use case. Understanding when each applies prevents subtle data loss bugs.
| Pattern | Parameter | Best for | Risk |
|---|---|---|---|
| Limit / offset | ?limit=100&offset=0 |
Filtered pulls by tag, status, or date range | Duplicate or missing records if the dataset mutates mid-sweep |
| Keyset cursor | ?next=<cursor_token> |
Full database sweeps, decay detection, bulk analysis | Cursor is opaque — do not parse it; just pass it back verbatim |
How does limit/offset pagination work in the FUB API?
The /v1/people endpoint (and most list endpoints) accept limit and offset as query parameters. The maximum page size is 100 records. To pull all contacts tagged "Nurture" you would increment the offset by the page size until the response contains fewer records than the requested limit — that signals the last page.
Example request (Node.js):
const BASE = 'https://api.followupboss.com/v1/people';
const AUTH = Buffer.from(`${apiKey}:`).toString('base64');
async function fetchPeopleByTag(tag) {
const allPeople = [];
let offset = 0;
const limit = 100;
while (true) {
const url = `${BASE}?limit=${limit}&offset=${offset}` +
`&tags=${encodeURIComponent(tag)}&fields=allFields`;
const res = await axios.get(url, {
headers: { Authorization: `Basic ${AUTH}`, Accept: 'application/json' }
});
const page = res.data.people || [];
allPeople.push(...page);
if (page.length < limit) break; // last page
offset += limit;
await delay(1000); // 1 s between pages — respect rate limits
}
return allPeople;
}
This pattern mirrors what Follow Up Ace uses internally for tag-filtered pulls in its nurture scheduler (chat-app/firestore/functions/nurture.js, fetchPeopleByTag).
What is the keyset cursor in FUB and how do you use it?
For full-database sweeps — where you need to visit every contact regardless of tag or status — Follow Up Boss returns a cursor token in the response metadata. The response body includes a _metadata object with a next field. That value is an opaque string token, not a URL.
The critical mistake developers make: they try to call axios.get(response._metadata.next) directly. Because the cursor is not a URL, this throws an invalid URL error. The correct approach is to append the cursor as a next query parameter on your original endpoint URL.
Example (Node.js):
async function sweepAllContacts(authHeader) {
const BASE = 'https://api.followupboss.com/v1/people';
let nextCursor = null;
let totalFetched = 0;
do {
const params = new URLSearchParams({
limit: '100',
sort: 'created',
fields: 'allCustom',
});
if (nextCursor) params.set('next', nextCursor);
const res = await axios.get(`${BASE}?${params}`, {
headers: { Authorization: authHeader, 'Content-Type': 'application/json' }
});
const people = res.data.people || [];
// process people here...
totalFetched += people.length;
// Extract the next cursor from _metadata
nextCursor = res.data._metadata?.next || null;
if (nextCursor) await delay(1000); // pace between pages
} while (nextCursor);
return totalFetched;
}
This pattern is directly derived from how Follow Up Ace's decay analysis worker sweeps the full contact database (chat-app/workers/aceIntelligenceDecayChildInitializerWorker.js, lines 116–168).
What is the maximum page size in Follow Up Boss?
The maximum value for the limit parameter is 100. Requests with a higher limit may be silently clamped or return an error — always code to the 100-record ceiling. Do not assume a large limit will return more than 100 records.
How do you handle Follow Up Boss API rate limits during pagination?
Follow Up Boss enforces rate limits at the API key level. The exact limits are documented in the official FUB API documentation and subject to change — verify current limits before production deployment. From production integration experience, the practical patterns that prevent rate-limit errors are:
- Add a delay between pages. A 1-second delay between page requests is a conservative baseline for bulk sweeps. You can reduce this if you are making fewer total requests per run.
- Respect 429 responses. When the API returns HTTP 429 (Too Many Requests), read the
Retry-Afterheader and wait that long before retrying. Do not immediately retry on 429. - Implement exponential backoff. For transient errors (5xx responses), use exponential backoff with jitter rather than a fixed retry interval.
- Cap the records per run. For background workers, set a maximum number of contacts to process per execution and persist the cursor so the next run continues where the last one left off. Follow Up Ace caps its decay sweep at a configurable
ACE_DECAY_MAX_CONTACTS_PER_RUN(default 1,000) and saves the resume cursor across runs.
How do you resume a pagination sweep that was interrupted?
This is the key operational advantage of cursor pagination over limit/offset. A cursor token represents a stable position in the ordered dataset. If your sweep is interrupted (worker crash, timeout, rate limit), you save the last successful cursor and resume from it on the next run — you do not have to start from offset=0.
Implementation pattern:
- Before starting the sweep, check your persistent store (Firestore, Redis, or a database) for a saved cursor
- If a saved cursor exists, start the sweep with
?next=<saved_cursor>instead of from the beginning - After each successful page, immediately persist the cursor before processing the records — this way a crash during processing does not lose your place
- When the sweep reaches the end (no more
_metadata.next), clear the saved cursor so the next scheduled run starts fresh
This "checkpoint" pattern is how Follow Up Ace implements its nightly account-wide intelligence sweeps — it covers the full database over multiple scheduled runs rather than in a single long-running process.
What fields should you request when paginating through contacts?
FUB's /v1/people endpoint returns a default set of fields unless you specify otherwise. For most integrations, you want to request only what you need:
fields=allFields— returns standard fields and custom fields; use when you need the complete contact recordfields=allCustom— returns only custom fields; use when you are reading or updating custom field values (smaller response, faster pages)- Comma-separated field names — for targeted pulls where you only need a few specific fields
Requesting the full allFields payload when you only need IDs and custom fields doubles your data transfer unnecessarily and slows large sweeps. Be specific about what you request.
How do you filter paginated results in the FUB API?
You can combine pagination parameters with filtering parameters in the same request. Common filter parameters for the /v1/people endpoint:
| Filter parameter | Example value | Effect |
|---|---|---|
tags |
Nurture |
Only contacts with this tag |
sort |
created |
Order by creation date ascending |
sort |
-updated |
Order by last update descending (prefix - = descending) |
stage |
Active Clients |
Only contacts in this pipeline stage |
Always verify current parameter names and supported values in the official FUB API documentation, as the API evolves. The patterns above reflect production usage; individual parameters may change.
What common pagination bugs should you test for?
- Off-by-one on the last page — if the total record count is exactly divisible by your page size, your loop will make one extra request that returns an empty
peoplearray. Handle the empty array case explicitly rather than relying on an error to stop the loop. - Mutating dataset during sweep — with limit/offset pagination, if a new contact is added while you are on page 3, your page 4 may skip a record. Cursor pagination is stable against inserts but not against deletes. If you need a perfectly consistent snapshot, take it at a quiet moment or use webhooks to capture changes during the sweep.
- Treating the cursor as a URL — the single most common mistake. The
_metadata.nextvalue is an opaque string. Pass it as a query parameter, never as the URL itself. - Not persisting the cursor before processing — if you persist after processing and processing fails, you re-fetch a page but never save where you left off. Persist the cursor on successful fetch, then process.
For more on building robust FUB integrations, see Follow Up Boss API real-time integration explained and the guide on rate limiting basics for the FUB API. If you want to interact with FUB through natural language rather than raw API calls, the Follow Up Ace agentic layer exposes 215 MCP tools that handle pagination, authentication, and error handling for you.