Skip to main content

Rate limits, retries and idempotent pagination: an engineering guide

· 3 min read
Product documentation and integration guides

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​

  1. Plan RPS — requests per second allowed by your plan. Exceed it and the gateway answers 429 with Retry-After: 1. It is a soft wall: sleep and continue; nothing is lost, nothing is billed.
  2. Upstream timeout budget — the gateway allows up to 25 s per upstream call and returns 503 + Retry-After: 2 when exceeded. Large pages (a ~100-post timeline) legitimately take seconds; don't set client timeouts below 30.
  3. Monthly request quota — billed only on 200 and 404. Errors and timeouts cost nothing by design, so retrying is always financially safe.

The retry table​

ConditionRetry?Strategy
429yessleep Retry-After, same request
503yessleep Retry-After, same request
5xx otheryesexponential backoff, cap 3 attempts
400 Invalid Cursornofix caller — cursor was mangled
404noentity 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​

  1. 429 rate — your client is outpacing the plan; fix pacing.
  2. 503 rate — upstream pressure; widen per-page timing, reduce count.
  3. 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.