Rate limits, retries and idempotent pagination: an engineering guide
API clients fail in production for one of two reasons: they ignore rate limits, or they retry the wrong things. This guide is the playbook we give every integrator of getsocialapi.com — it generalizes to any cursor-paginated API.
The three numbers that govern your throughput
- Plan RPS — requests per second allowed by your plan. Exceed it
and the gateway answers
429withRetry-After: 1. It is a soft wall: sleep and continue; nothing is lost, nothing is billed. - Upstream timeout budget — the gateway allows up to 25 s per
upstream call and returns
503+Retry-After: 2when exceeded. Large pages (a ~100-post timeline) legitimately take seconds; don't set client timeouts below 30. - Monthly request quota — billed only on
200and404. Errors and timeouts cost nothing by design, so retrying is always financially safe.
The retry table
| Condition | Retry? | Strategy |
|---|---|---|
429 | yes | sleep Retry-After, same request |
503 | yes | sleep Retry-After, same request |
5xx other | yes | exponential backoff, cap 3 attempts |
400 Invalid Cursor | no | fix caller — cursor was mangled |
404 | no | entity gone; log and skip |
The 400-is-not-retryable rule matters most: a broken cursor is deterministic, and retrying it burns latency for the same answer.
Idempotent pagination: checkpoint everything
A cursor is a pure function argument — same cursor, same page. That makes checkpointing trivial and resumption exact:
import json, os, time
from getsocialapi import Client, ServiceUnavailable
ig = Client(api_key="gsa_live_...").instagram
CKPT = "followers_cursor.txt"
cursor = open(CKPT).read().strip() if os.path.exists(CKPT) else None
rows = []
while True:
try:
page = ig.followers("25025320", count=50, max_id=cursor)
except ServiceUnavailable as e:
time.sleep(e.retry_after or 2)
continue
rows += page["users"]
cursor = page.get("next_max_id")
open(CKPT, "w").write(cursor or "")
if not cursor:
break
Kill it anywhere — the process resumes from the checkpoint with zero duplicate requests. For million-item exports this is not an optimization; it is the only way to finish.
Politeness that pays
- Honor Retry-After verbatim — it reflects actual capacity, not punishment.
- Jitter your bursts — RPS limits are per-second buckets; even spacing beats bursts of exactly-N.
- Cache resolution results — username → pk lookups are stable; re-resolving per request wastes budget.
Observability: three metrics that predict incidents
429rate — your client is outpacing the plan; fix pacing.503rate — upstream pressure; widen per-page timing, reducecount.- Pages-per-cursor progress — a stalled cursor with no errors means an endpoint contract change; alert on it.
The billing asymmetry, restated
Only 200/404 bill. Every retry mechanic above is free. The correct posture is therefore aggressive about retrying transient states and strict about not retrying deterministic ones — the exact opposite of DOM-scraping instincts.
Build it once, sleep at night — 100 free calls to pressure-test your loop.
Build with GetSocialAPI
Explore Instagram API and Facebook API for capabilities, pricing and integration examples. Create an account to start with 100 free calls, or follow the quickstart.