| name | rest-api-pagination-patterns |
| description | REST API pagination for inbound and outbound integrations: Salesforce QueryMore, cursor-based, offset-based, Link header, page-size tuning, rate limit interaction. NOT for Bulk API (use bulk-api-patterns). NOT for GraphQL connection pagination (use graphql-api-patterns). |
| category | integration |
| salesforce-version | Spring '25+ |
| well-architected-pillars | ["Performance","Reliability"] |
| tags | ["rest","pagination","querymore","cursor","offset","link-header"] |
| triggers | ["salesforce querymore nextrecordsurl pagination apex callout","rest api cursor based vs offset pagination","link header pagination github style rfc5988","rate limit interaction with pagination loop","offset pagination drift on insert delete","batch size tuning for inbound rest integration","we're having issues with link header"] |
| inputs | ["Integration direction (inbound to Salesforce, outbound from Salesforce)","API style (cursor, offset, Link header, custom)","Total result magnitude","Rate limit profile"] |
| outputs | ["Pagination loop pattern","Termination condition","Retry / rate-limit interaction","Volume test plan"] |
| dependencies | [] |
| version | 1.1.0 |
| author | Pranav Nagrecha |
| updated | "2026-05-19T00:00:00.000Z" |
REST API Pagination Patterns
Activate when integrating with a paginated REST API — either consuming an external API from Apex or serving a paginated endpoint from Salesforce. Pagination mistakes cause three classic failures: infinite loops, missed records, and duplicate records due to concurrent writes between page fetches.
Before Starting
- Identify pagination style. Cursor / Next-link / Offset / Link-header / Token-based — all have different termination semantics.
- Plan termination. Every pagination loop needs a safety cap (max iterations) plus the documented termination signal.
- Respect rate limits. Back off on 429; implement exponential backoff.
Core Concepts
Salesforce QueryMore (outbound to external consumer)
/services/data/v60.0/query?q=SELECT+Id+FROM+Account
→ { "done": true, "nextRecordsUrl": "/services/data/v60.0/query/01g...", "records": [...] }
Loop: fetch nextRecordsUrl until done == true.
Cursor-based (opaque token)
Response includes a cursor string; pass back as ?cursor=<value> until the server returns an empty cursor or explicit end-of-stream flag.
Offset / limit
Classic ?offset=0&limit=100. Simple but vulnerable to drift: records inserted/deleted between pages cause missed or duplicate rows.
Link header (RFC 5988)
Link: <https://api/x?page=2>; rel="next", <https://api/x?page=10>; rel="last"
Parse headers; loop until no rel="next" present.
Rate limit headers
X-RateLimit-Remaining, Retry-After — consult before issuing the next page.
Common Patterns
Pattern: QueryMore loop in Apex
HttpResponse resp = http.send(req);
Map<String,Object> body = (Map<String,Object>) JSON.deserializeUntyped(resp.getBody());
List<Object> all = new List<Object>();
all.addAll((List<Object>) body.get('records'));
while (body.get('done') == false) {
String nextUrl = (String) body.get('nextRecordsUrl');
HttpRequest r2 = new HttpRequest();
r2.setEndpoint(base + nextUrl);
// auth ...
resp = http.send(r2);
body = (Map<String,Object>) JSON.deserializeUntyped(resp.getBody());
all.addAll((List<Object>) body.get('records'));
}
Pattern: Cursor loop with safety cap
String cursor = null;
Integer safetyCap = 1000; // max pages
for (Integer i = 0; i < safetyCap; i++) {
// fetch with cursor
if (cursor == null || cursor == '') break;
}
if (i == safetyCap) throw new IntegrationException('Pagination safety cap hit');
Pattern: Rate-limit-aware pagination
Check X-RateLimit-Remaining; if < threshold, sleep per Retry-After before next page. In Apex: use Queueable chaining for long-running paginations (cannot Thread.sleep).
Decision Guidance
| Situation | Pagination style |
|---|
| Salesforce inbound data | QueryMore |
| Large stable dataset | Cursor |
| Small dataset, admin UI | Offset/limit |
| Complex filter with stable ordering | Cursor |
| Lots of concurrent writes | Cursor with created-since filter |
Recommended Workflow
- Identify the source API's pagination style from documentation.
- Implement the loop with an explicit termination signal AND a safety cap.
- For offset/limit: either confirm dataset is stable or add resume tokens.
- Parse rate-limit headers; back off on 429.
- For long paginations in Apex, chain Queueables — each handles one page batch.
- Persist intermediate results in case of mid-stream failure.
- Write integration test that mocks 3+ pages including empty-tail.
Review Checklist
Salesforce-Specific Gotchas
- Apex has a 100-callout-per-transaction limit. Paginating over thousands of pages requires Queueable chaining.
- QueryMore cursor expires after ~15 minutes. Long paginations need restart logic if they straddle the expiry.
Test.setMock supports only one response per request. Multi-page tests need a mock class tracking call count.
Output Artifacts
| Artifact | Description |
|---|
| Pagination helper Apex class | Reusable loop with safety cap |
| Rate-limit-aware http wrapper | Backoff + retry |
| Integration test fixtures | Multi-page mocks |
Related Skills
integration/bulk-api-patterns — for large-volume data operations
integration/rate-limit-handling — backoff strategies
apex/apex-queueable-patterns — chaining for long-running callouts