Prompt
How do I pull bulk datasets from an API without running into pagination issues?
Latest observation
To pull bulk datasets from an API reliably, the main goal is to make your client stateful, resumable, and defensive against changing data and rate limits. The best approach depends on how the API paginates, but these patterns usually work well:
1) Prefer cursor-based pagination over offset-based pagination
If the API supports it, use a cursor or page token:
- Good:
?limit=100&cursor=abc123 - Less ideal:
?limit=100&offset=5000
Why cursor-based is better:
- More stable if records are inserted/deleted while you’re fetching
- Less likely to skip or duplicate rows
- Often faster for large datasets
2) Fetch in chunks and persist progress
Don’t try to load everything in memory at once.
Store:
- last cursor / page token
- last successful timestamp or ID
- batch number
- fetch status
That lets you:
- resume after failure
- avoid re-downloading from scratch
- debug where the pull stopped
3) Use a deterministic sort order
If the API allows it, request a stable ordering such as:
sort=created_at ascsort=id asc
This helps prevent duplicates or missing records when data changes during the export.
4) Avoid offset pagination for mutable data
Offset pagination can break when records are added/deleted while you’re paging:
- Page 1: rows 1–100
- Page 2: rows 101–200
If a row is inserted before page 2 is fetched, the results shift and you can miss or duplicate records.
If offset is your only option:
- Pull from a snapshot if supported
- Or use a time window / ID window approach
- Or deduplicate client-side using a primary key
5) Pull by time windows for large datasets
For APIs with timestamps, query in ranges:
created_at >= 2024-01-01 and < 2024-02-01
Then subdivide further if a window returns too many results.
This is especially useful for:
- logs
- events
- transactions
- audit records
6) Handle rate limits and transient failures
Bulk extraction often fails due to:
- 429 Too Many Requests
- 5xx server errors
- connection timeouts
Use:
- exponential backoff
- retries with jitter
- respect
Retry-Afterheaders - concurrency limits
7) Deduplicate on ingestion
Even with perfect pagination, duplicates can happen.
Use a unique key like:
- record ID
- composite key
- checksum/hash
Then perform:
- upsert
- merge
- idempotent writes
8) Validate completeness
After the pull, verify:
- count of records fetched vs expected total
- min/max timestamps
- gaps in ID sequence
- duplicate rate
- checksum/sample reconciliation if available
9) If the API offers bulk export, use it
Many APIs have a better option for large datasets:
- async export jobs
- downloadable CSV/JSON files
- data warehouse sync endpoints
- snapshots
These are often more reliable than paging through millions of rows.
10) Build a resumable extraction loop
A robust loop usually looks like:
- Request first page
- Save results
- Save next cursor/page token
- Repeat until no more data
- Retry safely on failure
- Resume from last saved state
Recommended pattern
If available, use this order of preference:
- Bulk export/snapshot
- Cursor-based pagination
- Time-window partitioning
- Offset pagination only as a last resort
Example pseudocode
cursor = load_saved_cursor()
while True:
resp = api.get("/items", params={"limit": 1000, "cursor": cursor})
items = resp["data"]
if not items:
break
save_items(items)
cursor = resp.get("next_cursor")
save_cursor(cursor)
if not cursor:
break
If you want, I can also give you:
- a Python implementation
- a Node.js implementation
- a pattern for offset-based APIs
- or a general ETL design for pulling millions of records safely