[{"data":1,"prerenderedAt":1663},["ShallowReactive",2],{"page-\u002Fgetting-started-with-python-apis-for-builders\u002Fmaking-http-requests-with-requests-library\u002Fbest-practices-for-api-rate-limiting\u002F":3,"faq-schema-\u002Fgetting-started-with-python-apis-for-builders\u002Fmaking-http-requests-with-requests-library\u002Fbest-practices-for-api-rate-limiting\u002F":1645},{"id":4,"title":5,"body":6,"description":1638,"extension":1639,"meta":1640,"navigation":201,"path":1641,"seo":1642,"stem":1643,"__hash__":1644},"content\u002Fgetting-started-with-python-apis-for-builders\u002Fmaking-http-requests-with-requests-library\u002Fbest-practices-for-api-rate-limiting\u002Findex.md","Best Practices for API Rate Limiting in Python: Builder’s Implementation Guide",{"type":7,"value":8,"toc":1630},"minimark",[9,13,22,25,31,55,60,67,72,91,114,123,128,815,819,822,827,875,878,882,888,893,904,909,1303,1307,1310,1315,1358,1366,1371,1503,1507,1575,1579,1595,1611,1617,1626],[10,11,5],"h1",{"id":12},"best-practices-for-api-rate-limiting-in-python-builders-implementation-guide",[14,15,16,17,21],"p",{},"Hitting ",[18,19,20],"code",{},"429 Too Many Requests"," isn’t just an annoyance—it’s a direct threat to your automation uptime, API quota budget, and infrastructure costs. For builders and side-hustlers relying on third-party APIs, unmanaged request bursts trigger IP bans, break data pipelines, and waste paid tier allocations.",[14,23,24],{},"Implementing robust client-side rate limiting is non-negotiable for production systems. Follow these patterns to prevent quota exhaustion, maintain reliable data syncs, and scale your automations without manual intervention.",[14,26,27],{},[28,29,30],"strong",{},"Core Principles:",[32,33,34,46,49,52],"ul",{},[35,36,37,38,41,42,45],"li",{},"Always respect server-provided ",[18,39,40],{},"Retry-After"," and ",[18,43,44],{},"X-RateLimit"," headers before applying custom logic.",[35,47,48],{},"Implement exponential backoff with jitter to prevent thundering herd problems during quota resets.",[35,50,51],{},"Use token bucket or leaky bucket algorithms for predictable outbound request pacing.",[35,53,54],{},"Separate rate limiting from business logic and error handling to maintain clean, testable code architecture.",[56,57,59],"h2",{"id":58},"decoding-rate-limit-headers-quota-signals","Decoding Rate Limit Headers & Quota Signals",[14,61,62,63,66],{},"Server-side APIs broadcast their capacity limits directly in HTTP response headers. Ignoring these signals forces you to guess delay durations, which either wastes throughput or triggers immediate ",[18,64,65],{},"429"," responses.",[14,68,69],{},[28,70,71],{},"Standard headers to monitor:",[32,73,74,80,86],{},[35,75,76,79],{},[18,77,78],{},"X-RateLimit-Limit",": Maximum requests allowed per window.",[35,81,82,85],{},[18,83,84],{},"X-RateLimit-Remaining",": Requests left before the window resets.",[35,87,88,90],{},[18,89,40],{},": Explicit wait time (integer seconds or HTTP-date) before retrying.",[14,92,93,94,96,97,100,101,103,104,106,107,109,110,113],{},"Parse these values dynamically. When ",[18,95,84],{}," drops below a safe threshold (e.g., ",[18,98,99],{},"\u003C 10%","), proactively throttle. If a ",[18,102,65],{}," occurs, extract ",[18,105,40],{}," and sleep exactly that duration. When parsing ",[18,108,40],{},", handle both integer strings and ISO 8601 timestamps. Header-driven pacing consistently outperforms hardcoded ",[18,111,112],{},"time.sleep()"," delays.",[14,115,116,117,122],{},"When building your initial request handlers, ensure you understand how to properly extract and validate response metadata. Refer to ",[118,119,121],"a",{"href":120},"\u002Fgetting-started-with-python-apis-for-builders\u002Fmaking-http-requests-with-requests-library\u002F","Making HTTP Requests with Requests Library"," for foundational patterns on parsing status codes and headers safely.",[14,124,125],{},[28,126,127],{},"Production-Ready Header-Driven Retry:",[129,130,135],"pre",{"className":131,"code":132,"language":133,"meta":134,"style":134},"language-python shiki shiki-themes github-light github-dark","import os\nimport time\nimport random\nimport logging\nimport requests\nfrom datetime import datetime, timezone\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\ndef fetch_with_header_backoff(url: str, max_retries: int = 5) -> dict:\n \"\"\"Fetches data with header-aware retry logic and exponential fallback.\"\"\"\n session = requests.Session()\n session.headers.update({\"Authorization\": f\"Bearer {os.getenv('API_KEY')}\"})\n \n for attempt in range(max_retries):\n try:\n response = session.get(url, timeout=(3.05, 10))\n response.raise_for_status()\n return response.json()\n except requests.exceptions.HTTPError as e:\n if response.status_code == 429:\n # Parse Retry-After (supports int seconds or HTTP-date)\n retry_header = response.headers.get(\"Retry-After\")\n if retry_header:\n if retry_header.isdigit():\n wait_time = int(retry_header)\n else:\n target_dt = datetime.strptime(retry_header, \"%a, %d %b %Y %H:%M:%S %Z\")\n wait_time = max(0, (target_dt.replace(tzinfo=timezone.utc) - datetime.now(timezone.utc)).total_seconds())\n else:\n # Fallback: exponential backoff (2^attempt seconds)\n wait_time = min(2 ** attempt, 30)\n \n # Add jitter to prevent synchronized retries\n jitter = random.uniform(0, 1)\n sleep_duration = min(wait_time + jitter, 60)\n logger.warning(f\"Rate limited. Sleeping for {sleep_duration:.2f}s (Attempt {attempt+1}\u002F{max_retries})\")\n time.sleep(sleep_duration)\n continue\n raise e\n except requests.exceptions.RequestException as e:\n logger.error(f\"Network error: {e}\")\n time.sleep(2 ** attempt)\n \n raise RuntimeError(f\"Failed after {max_retries} retries. Check API quota or endpoint health.\")\n","python","",[18,136,137,150,158,166,174,182,196,203,226,242,247,284,291,302,341,347,365,373,404,410,419,434,451,458,474,482,490,504,512,541,573,580,586,612,617,623,643,667,716,722,728,737,749,771,784,789],{"__ignoreMap":134},[138,139,142,146],"span",{"class":140,"line":141},"line",1,[138,143,145],{"class":144},"szBVR","import",[138,147,149],{"class":148},"sVt8B"," os\n",[138,151,153,155],{"class":140,"line":152},2,[138,154,145],{"class":144},[138,156,157],{"class":148}," time\n",[138,159,161,163],{"class":140,"line":160},3,[138,162,145],{"class":144},[138,164,165],{"class":148}," random\n",[138,167,169,171],{"class":140,"line":168},4,[138,170,145],{"class":144},[138,172,173],{"class":148}," logging\n",[138,175,177,179],{"class":140,"line":176},5,[138,178,145],{"class":144},[138,180,181],{"class":148}," requests\n",[138,183,185,188,191,193],{"class":140,"line":184},6,[138,186,187],{"class":144},"from",[138,189,190],{"class":148}," datetime ",[138,192,145],{"class":144},[138,194,195],{"class":148}," datetime, timezone\n",[138,197,199],{"class":140,"line":198},7,[138,200,202],{"emptyLinePlaceholder":201},true,"\n",[138,204,206,209,213,216,219,223],{"class":140,"line":205},8,[138,207,208],{"class":148},"logging.basicConfig(",[138,210,212],{"class":211},"s4XuR","level",[138,214,215],{"class":144},"=",[138,217,218],{"class":148},"logging.",[138,220,222],{"class":221},"sj4cs","INFO",[138,224,225],{"class":148},")\n",[138,227,229,232,234,237,240],{"class":140,"line":228},9,[138,230,231],{"class":148},"logger ",[138,233,215],{"class":144},[138,235,236],{"class":148}," logging.getLogger(",[138,238,239],{"class":221},"__name__",[138,241,225],{"class":148},[138,243,245],{"class":140,"line":244},10,[138,246,202],{"emptyLinePlaceholder":201},[138,248,250,253,257,260,263,266,269,272,275,278,281],{"class":140,"line":249},11,[138,251,252],{"class":144},"def",[138,254,256],{"class":255},"sScJk"," fetch_with_header_backoff",[138,258,259],{"class":148},"(url: ",[138,261,262],{"class":221},"str",[138,264,265],{"class":148},", max_retries: ",[138,267,268],{"class":221},"int",[138,270,271],{"class":144}," =",[138,273,274],{"class":221}," 5",[138,276,277],{"class":148},") -> ",[138,279,280],{"class":221},"dict",[138,282,283],{"class":148},":\n",[138,285,287],{"class":140,"line":286},12,[138,288,290],{"class":289},"sZZnC"," \"\"\"Fetches data with header-aware retry logic and exponential fallback.\"\"\"\n",[138,292,294,297,299],{"class":140,"line":293},13,[138,295,296],{"class":148}," session ",[138,298,215],{"class":144},[138,300,301],{"class":148}," requests.Session()\n",[138,303,305,308,311,314,317,320,323,326,329,332,335,338],{"class":140,"line":304},14,[138,306,307],{"class":148}," session.headers.update({",[138,309,310],{"class":289},"\"Authorization\"",[138,312,313],{"class":148},": ",[138,315,316],{"class":144},"f",[138,318,319],{"class":289},"\"Bearer ",[138,321,322],{"class":221},"{",[138,324,325],{"class":148},"os.getenv(",[138,327,328],{"class":289},"'API_KEY'",[138,330,331],{"class":148},")",[138,333,334],{"class":221},"}",[138,336,337],{"class":289},"\"",[138,339,340],{"class":148},"})\n",[138,342,344],{"class":140,"line":343},15,[138,345,346],{"class":148}," \n",[138,348,350,353,356,359,362],{"class":140,"line":349},16,[138,351,352],{"class":144}," for",[138,354,355],{"class":148}," attempt ",[138,357,358],{"class":144},"in",[138,360,361],{"class":221}," range",[138,363,364],{"class":148},"(max_retries):\n",[138,366,368,371],{"class":140,"line":367},17,[138,369,370],{"class":144}," try",[138,372,283],{"class":148},[138,374,376,379,381,384,387,389,392,395,398,401],{"class":140,"line":375},18,[138,377,378],{"class":148}," response ",[138,380,215],{"class":144},[138,382,383],{"class":148}," session.get(url, ",[138,385,386],{"class":211},"timeout",[138,388,215],{"class":144},[138,390,391],{"class":148},"(",[138,393,394],{"class":221},"3.05",[138,396,397],{"class":148},", ",[138,399,400],{"class":221},"10",[138,402,403],{"class":148},"))\n",[138,405,407],{"class":140,"line":406},19,[138,408,409],{"class":148}," response.raise_for_status()\n",[138,411,413,416],{"class":140,"line":412},20,[138,414,415],{"class":144}," return",[138,417,418],{"class":148}," response.json()\n",[138,420,422,425,428,431],{"class":140,"line":421},21,[138,423,424],{"class":144}," except",[138,426,427],{"class":148}," requests.exceptions.HTTPError ",[138,429,430],{"class":144},"as",[138,432,433],{"class":148}," e:\n",[138,435,437,440,443,446,449],{"class":140,"line":436},22,[138,438,439],{"class":144}," if",[138,441,442],{"class":148}," response.status_code ",[138,444,445],{"class":144},"==",[138,447,448],{"class":221}," 429",[138,450,283],{"class":148},[138,452,454],{"class":140,"line":453},23,[138,455,457],{"class":456},"sJ8bj"," # Parse Retry-After (supports int seconds or HTTP-date)\n",[138,459,461,464,466,469,472],{"class":140,"line":460},24,[138,462,463],{"class":148}," retry_header ",[138,465,215],{"class":144},[138,467,468],{"class":148}," response.headers.get(",[138,470,471],{"class":289},"\"Retry-After\"",[138,473,225],{"class":148},[138,475,477,479],{"class":140,"line":476},25,[138,478,439],{"class":144},[138,480,481],{"class":148}," retry_header:\n",[138,483,485,487],{"class":140,"line":484},26,[138,486,439],{"class":144},[138,488,489],{"class":148}," retry_header.isdigit():\n",[138,491,493,496,498,501],{"class":140,"line":492},27,[138,494,495],{"class":148}," wait_time ",[138,497,215],{"class":144},[138,499,500],{"class":221}," int",[138,502,503],{"class":148},"(retry_header)\n",[138,505,507,510],{"class":140,"line":506},28,[138,508,509],{"class":144}," else",[138,511,283],{"class":148},[138,513,515,518,520,523,525,528,530,533,536,539],{"class":140,"line":514},29,[138,516,517],{"class":148}," target_dt ",[138,519,215],{"class":144},[138,521,522],{"class":148}," datetime.strptime(retry_header, ",[138,524,337],{"class":289},[138,526,527],{"class":221},"%a",[138,529,397],{"class":289},[138,531,532],{"class":221},"%d",[138,534,535],{"class":221}," %b",[138,537,538],{"class":289}," %Y %H:%M:%S %Z\"",[138,540,225],{"class":148},[138,542,544,546,548,551,553,556,559,562,564,567,570],{"class":140,"line":543},30,[138,545,495],{"class":148},[138,547,215],{"class":144},[138,549,550],{"class":221}," max",[138,552,391],{"class":148},[138,554,555],{"class":221},"0",[138,557,558],{"class":148},", (target_dt.replace(",[138,560,561],{"class":211},"tzinfo",[138,563,215],{"class":144},[138,565,566],{"class":148},"timezone.utc) ",[138,568,569],{"class":144},"-",[138,571,572],{"class":148}," datetime.now(timezone.utc)).total_seconds())\n",[138,574,576,578],{"class":140,"line":575},31,[138,577,509],{"class":144},[138,579,283],{"class":148},[138,581,583],{"class":140,"line":582},32,[138,584,585],{"class":456}," # Fallback: exponential backoff (2^attempt seconds)\n",[138,587,589,591,593,596,598,601,604,607,610],{"class":140,"line":588},33,[138,590,495],{"class":148},[138,592,215],{"class":144},[138,594,595],{"class":221}," min",[138,597,391],{"class":148},[138,599,600],{"class":221},"2",[138,602,603],{"class":144}," **",[138,605,606],{"class":148}," attempt, ",[138,608,609],{"class":221},"30",[138,611,225],{"class":148},[138,613,615],{"class":140,"line":614},34,[138,616,346],{"class":148},[138,618,620],{"class":140,"line":619},35,[138,621,622],{"class":456}," # Add jitter to prevent synchronized retries\n",[138,624,626,629,631,634,636,638,641],{"class":140,"line":625},36,[138,627,628],{"class":148}," jitter ",[138,630,215],{"class":144},[138,632,633],{"class":148}," random.uniform(",[138,635,555],{"class":221},[138,637,397],{"class":148},[138,639,640],{"class":221},"1",[138,642,225],{"class":148},[138,644,646,649,651,653,656,659,662,665],{"class":140,"line":645},37,[138,647,648],{"class":148}," sleep_duration ",[138,650,215],{"class":144},[138,652,595],{"class":221},[138,654,655],{"class":148},"(wait_time ",[138,657,658],{"class":144},"+",[138,660,661],{"class":148}," jitter, ",[138,663,664],{"class":221},"60",[138,666,225],{"class":148},[138,668,670,673,675,678,680,683,686,688,691,693,696,698,701,704,706,709,711,714],{"class":140,"line":669},38,[138,671,672],{"class":148}," logger.warning(",[138,674,316],{"class":144},[138,676,677],{"class":289},"\"Rate limited. Sleeping for ",[138,679,322],{"class":221},[138,681,682],{"class":148},"sleep_duration",[138,684,685],{"class":144},":.2f",[138,687,334],{"class":221},[138,689,690],{"class":289},"s (Attempt ",[138,692,322],{"class":221},[138,694,695],{"class":148},"attempt",[138,697,658],{"class":144},[138,699,700],{"class":221},"1}",[138,702,703],{"class":289},"\u002F",[138,705,322],{"class":221},[138,707,708],{"class":148},"max_retries",[138,710,334],{"class":221},[138,712,713],{"class":289},")\"",[138,715,225],{"class":148},[138,717,719],{"class":140,"line":718},39,[138,720,721],{"class":148}," time.sleep(sleep_duration)\n",[138,723,725],{"class":140,"line":724},40,[138,726,727],{"class":144}," continue\n",[138,729,731,734],{"class":140,"line":730},41,[138,732,733],{"class":144}," raise",[138,735,736],{"class":148}," e\n",[138,738,740,742,745,747],{"class":140,"line":739},42,[138,741,424],{"class":144},[138,743,744],{"class":148}," requests.exceptions.RequestException ",[138,746,430],{"class":144},[138,748,433],{"class":148},[138,750,752,755,757,760,762,765,767,769],{"class":140,"line":751},43,[138,753,754],{"class":148}," logger.error(",[138,756,316],{"class":144},[138,758,759],{"class":289},"\"Network error: ",[138,761,322],{"class":221},[138,763,764],{"class":148},"e",[138,766,334],{"class":221},[138,768,337],{"class":289},[138,770,225],{"class":148},[138,772,774,777,779,781],{"class":140,"line":773},44,[138,775,776],{"class":148}," time.sleep(",[138,778,600],{"class":221},[138,780,603],{"class":144},[138,782,783],{"class":148}," attempt)\n",[138,785,787],{"class":140,"line":786},45,[138,788,346],{"class":148},[138,790,792,794,797,799,801,804,806,808,810,813],{"class":140,"line":791},46,[138,793,733],{"class":144},[138,795,796],{"class":221}," RuntimeError",[138,798,391],{"class":148},[138,800,316],{"class":144},[138,802,803],{"class":289},"\"Failed after ",[138,805,322],{"class":221},[138,807,708],{"class":148},[138,809,334],{"class":221},[138,811,812],{"class":289}," retries. Check API quota or endpoint health.\"",[138,814,225],{"class":148},[56,816,818],{"id":817},"implementing-exponential-backoff-with-jitter","Implementing Exponential Backoff with Jitter",[14,820,821],{},"When an API is overloaded or undergoing maintenance, retrying immediately compounds the problem. Exponential backoff spaces out retries geometrically, while jitter randomizes the delay to prevent distributed workers from hitting the endpoint simultaneously.",[14,823,824],{},[28,825,826],{},"Implementation rules:",[32,828,829,842,852,866],{},[35,830,831,834,835,838,839,841],{},[28,832,833],{},"Base delay:"," Start small (",[18,836,837],{},"1-2s",") and multiply by ",[18,840,600],{}," per attempt.",[35,843,844,847,848,851],{},[28,845,846],{},"Jitter:"," Add ",[18,849,850],{},"random.uniform(0, 1)"," to the calculated delay.",[35,853,854,857,858,861,862,865],{},[28,855,856],{},"Hard caps:"," Never exceed a maximum sleep threshold (e.g., ",[18,859,860],{},"60s",") or absolute timeout (e.g., ",[18,863,864],{},"300s"," total).",[35,867,868,871,872,874],{},[28,869,870],{},"Fail fast:"," Define a strict ",[18,873,708],{}," limit. If the API remains unresponsive after retries, log the failure, alert your monitoring system, and exit gracefully.",[14,876,877],{},"This pattern is critical for side-hustle automations running on cron jobs or serverless functions where indefinite blocking wastes compute credits.",[56,879,881],{"id":880},"client-side-throttling-with-token-bucket","Client-Side Throttling with Token Bucket",[14,883,884,885,887],{},"Proactive throttling prevents ",[18,886,65],{}," errors entirely by pacing requests before they reach the network. The token bucket algorithm is ideal for this: it maintains a bucket of \"tokens\" that refill at a fixed rate. Each API call consumes one token. If the bucket is empty, the request blocks until a token refills.",[14,889,890],{},[28,891,892],{},"Why token bucket over fixed windows?",[32,894,895,898,901],{},[35,896,897],{},"Smoother request distribution (avoids burst spikes at window boundaries).",[35,899,900],{},"Naturally handles idle periods (unused tokens accumulate up to capacity).",[35,902,903],{},"Easy to adjust dynamically when your subscription tier changes.",[14,905,906],{},[28,907,908],{},"Lightweight Synchronous Token Bucket:",[129,910,912],{"className":131,"code":911,"language":133,"meta":134,"style":134},"import time\nimport threading\n\nclass TokenBucket:\n \"\"\"Thread-safe client-side rate limiter using the token bucket algorithm.\"\"\"\n def __init__(self, rate: float, capacity: int):\n self.rate = rate # Tokens added per second\n self.capacity = capacity # Max tokens in bucket\n self.tokens = float(capacity)\n self.last_refill = time.monotonic()\n self._lock = threading.Lock()\n\n def consume(self, tokens: int = 1) -> bool:\n with self._lock:\n now = time.monotonic()\n elapsed = now - self.last_refill\n self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)\n self.last_refill = now\n\n if self.tokens >= tokens:\n self.tokens -= tokens\n return True\n return False\n\n def wait_for_token(self, tokens: int = 1) -> None:\n \"\"\"Blocks execution until a token is available.\"\"\"\n while not self.consume(tokens):\n time.sleep(1 \u002F self.rate) # Sleep roughly until next refill\n\n# Usage Example\nlimiter = TokenBucket(rate=5.0, capacity=10) # 5 req\u002Fsec, burst up to 10\nlimiter.wait_for_token()\n# Execute API call here\n",[18,913,914,920,927,931,941,946,968,984,999,1014,1026,1038,1042,1066,1076,1085,1101,1135,1146,1150,1164,1176,1183,1190,1194,1216,1221,1234,1251,1255,1260,1293,1298],{"__ignoreMap":134},[138,915,916,918],{"class":140,"line":141},[138,917,145],{"class":144},[138,919,157],{"class":148},[138,921,922,924],{"class":140,"line":152},[138,923,145],{"class":144},[138,925,926],{"class":148}," threading\n",[138,928,929],{"class":140,"line":160},[138,930,202],{"emptyLinePlaceholder":201},[138,932,933,936,939],{"class":140,"line":168},[138,934,935],{"class":144},"class",[138,937,938],{"class":255}," TokenBucket",[138,940,283],{"class":148},[138,942,943],{"class":140,"line":176},[138,944,945],{"class":289}," \"\"\"Thread-safe client-side rate limiter using the token bucket algorithm.\"\"\"\n",[138,947,948,951,954,957,960,963,965],{"class":140,"line":184},[138,949,950],{"class":144}," def",[138,952,953],{"class":221}," __init__",[138,955,956],{"class":148},"(self, rate: ",[138,958,959],{"class":221},"float",[138,961,962],{"class":148},", capacity: ",[138,964,268],{"class":221},[138,966,967],{"class":148},"):\n",[138,969,970,973,976,978,981],{"class":140,"line":198},[138,971,972],{"class":221}," self",[138,974,975],{"class":148},".rate ",[138,977,215],{"class":144},[138,979,980],{"class":148}," rate ",[138,982,983],{"class":456},"# Tokens added per second\n",[138,985,986,988,991,993,996],{"class":140,"line":205},[138,987,972],{"class":221},[138,989,990],{"class":148},".capacity ",[138,992,215],{"class":144},[138,994,995],{"class":148}," capacity ",[138,997,998],{"class":456},"# Max tokens in bucket\n",[138,1000,1001,1003,1006,1008,1011],{"class":140,"line":228},[138,1002,972],{"class":221},[138,1004,1005],{"class":148},".tokens ",[138,1007,215],{"class":144},[138,1009,1010],{"class":221}," float",[138,1012,1013],{"class":148},"(capacity)\n",[138,1015,1016,1018,1021,1023],{"class":140,"line":244},[138,1017,972],{"class":221},[138,1019,1020],{"class":148},".last_refill ",[138,1022,215],{"class":144},[138,1024,1025],{"class":148}," time.monotonic()\n",[138,1027,1028,1030,1033,1035],{"class":140,"line":249},[138,1029,972],{"class":221},[138,1031,1032],{"class":148},"._lock ",[138,1034,215],{"class":144},[138,1036,1037],{"class":148}," threading.Lock()\n",[138,1039,1040],{"class":140,"line":286},[138,1041,202],{"emptyLinePlaceholder":201},[138,1043,1044,1046,1049,1052,1054,1056,1059,1061,1064],{"class":140,"line":293},[138,1045,950],{"class":144},[138,1047,1048],{"class":255}," consume",[138,1050,1051],{"class":148},"(self, tokens: ",[138,1053,268],{"class":221},[138,1055,271],{"class":144},[138,1057,1058],{"class":221}," 1",[138,1060,277],{"class":148},[138,1062,1063],{"class":221},"bool",[138,1065,283],{"class":148},[138,1067,1068,1071,1073],{"class":140,"line":304},[138,1069,1070],{"class":144}," with",[138,1072,972],{"class":221},[138,1074,1075],{"class":148},"._lock:\n",[138,1077,1078,1081,1083],{"class":140,"line":343},[138,1079,1080],{"class":148}," now ",[138,1082,215],{"class":144},[138,1084,1025],{"class":148},[138,1086,1087,1090,1092,1094,1096,1098],{"class":140,"line":349},[138,1088,1089],{"class":148}," elapsed ",[138,1091,215],{"class":144},[138,1093,1080],{"class":148},[138,1095,569],{"class":144},[138,1097,972],{"class":221},[138,1099,1100],{"class":148},".last_refill\n",[138,1102,1103,1105,1107,1109,1111,1113,1116,1119,1121,1123,1125,1127,1130,1132],{"class":140,"line":367},[138,1104,972],{"class":221},[138,1106,1005],{"class":148},[138,1108,215],{"class":144},[138,1110,595],{"class":221},[138,1112,391],{"class":148},[138,1114,1115],{"class":221},"self",[138,1117,1118],{"class":148},".capacity, ",[138,1120,1115],{"class":221},[138,1122,1005],{"class":148},[138,1124,658],{"class":144},[138,1126,1089],{"class":148},[138,1128,1129],{"class":144},"*",[138,1131,972],{"class":221},[138,1133,1134],{"class":148},".rate)\n",[138,1136,1137,1139,1141,1143],{"class":140,"line":375},[138,1138,972],{"class":221},[138,1140,1020],{"class":148},[138,1142,215],{"class":144},[138,1144,1145],{"class":148}," now\n",[138,1147,1148],{"class":140,"line":406},[138,1149,202],{"emptyLinePlaceholder":201},[138,1151,1152,1154,1156,1158,1161],{"class":140,"line":412},[138,1153,439],{"class":144},[138,1155,972],{"class":221},[138,1157,1005],{"class":148},[138,1159,1160],{"class":144},">=",[138,1162,1163],{"class":148}," tokens:\n",[138,1165,1166,1168,1170,1173],{"class":140,"line":421},[138,1167,972],{"class":221},[138,1169,1005],{"class":148},[138,1171,1172],{"class":144},"-=",[138,1174,1175],{"class":148}," tokens\n",[138,1177,1178,1180],{"class":140,"line":436},[138,1179,415],{"class":144},[138,1181,1182],{"class":221}," True\n",[138,1184,1185,1187],{"class":140,"line":453},[138,1186,415],{"class":144},[138,1188,1189],{"class":221}," False\n",[138,1191,1192],{"class":140,"line":460},[138,1193,202],{"emptyLinePlaceholder":201},[138,1195,1196,1198,1201,1203,1205,1207,1209,1211,1214],{"class":140,"line":476},[138,1197,950],{"class":144},[138,1199,1200],{"class":255}," wait_for_token",[138,1202,1051],{"class":148},[138,1204,268],{"class":221},[138,1206,271],{"class":144},[138,1208,1058],{"class":221},[138,1210,277],{"class":148},[138,1212,1213],{"class":221},"None",[138,1215,283],{"class":148},[138,1217,1218],{"class":140,"line":484},[138,1219,1220],{"class":289}," \"\"\"Blocks execution until a token is available.\"\"\"\n",[138,1222,1223,1226,1229,1231],{"class":140,"line":492},[138,1224,1225],{"class":144}," while",[138,1227,1228],{"class":144}," not",[138,1230,972],{"class":221},[138,1232,1233],{"class":148},".consume(tokens):\n",[138,1235,1236,1238,1240,1243,1245,1248],{"class":140,"line":506},[138,1237,776],{"class":148},[138,1239,640],{"class":221},[138,1241,1242],{"class":144}," \u002F",[138,1244,972],{"class":221},[138,1246,1247],{"class":148},".rate) ",[138,1249,1250],{"class":456},"# Sleep roughly until next refill\n",[138,1252,1253],{"class":140,"line":514},[138,1254,202],{"emptyLinePlaceholder":201},[138,1256,1257],{"class":140,"line":543},[138,1258,1259],{"class":456},"# Usage Example\n",[138,1261,1262,1265,1267,1270,1273,1275,1278,1280,1283,1285,1287,1290],{"class":140,"line":575},[138,1263,1264],{"class":148},"limiter ",[138,1266,215],{"class":144},[138,1268,1269],{"class":148}," TokenBucket(",[138,1271,1272],{"class":211},"rate",[138,1274,215],{"class":144},[138,1276,1277],{"class":221},"5.0",[138,1279,397],{"class":148},[138,1281,1282],{"class":211},"capacity",[138,1284,215],{"class":144},[138,1286,400],{"class":221},[138,1288,1289],{"class":148},") ",[138,1291,1292],{"class":456},"# 5 req\u002Fsec, burst up to 10\n",[138,1294,1295],{"class":140,"line":582},[138,1296,1297],{"class":148},"limiter.wait_for_token()\n",[138,1299,1300],{"class":140,"line":588},[138,1301,1302],{"class":456},"# Execute API call here\n",[56,1304,1306],{"id":1305},"async-distributed-rate-limiting-strategies","Async & Distributed Rate Limiting Strategies",[14,1308,1309],{},"Single-process scripts fail when you scale to multiple workers, cron jobs, or async event loops. In-memory counters reset per process, causing independent workers to collectively exhaust shared API quotas.",[14,1311,1312],{},[28,1313,1314],{},"Scaling patterns:",[32,1316,1317,1331,1348],{},[35,1318,1319,1322,1323,1326,1327,1330],{},[28,1320,1321],{},"Centralized state:"," Use Redis or SQLite to track token consumption across processes. Atomic ",[18,1324,1325],{},"DECR"," or ",[18,1328,1329],{},"UPDATE"," operations ensure quota accuracy.",[35,1332,1333,1336,1337,1339,1340,1343,1344,1347],{},[28,1334,1335],{},"Async-compatible limiters:"," Replace ",[18,1338,112],{}," with ",[18,1341,1342],{},"asyncio.sleep()",". Libraries like ",[18,1345,1346],{},"aiolimiter"," prevent event loop blocking while enforcing concurrency limits.",[35,1349,1350,1353,1354,1357],{},[28,1351,1352],{},"Distributed resets:"," Implement leader election or a shared Redis key with ",[18,1355,1356],{},"EXPIRE"," to synchronize window resets across workers.",[14,1359,1360,1361,1365],{},"As you transition from local scripts to deployed services, understanding how to architect resilient, quota-aware systems becomes a core competency. See ",[118,1362,1364],{"href":1363},"\u002Fgetting-started-with-python-apis-for-builders\u002F","Getting Started with Python APIs for Builders"," for deployment patterns that integrate rate limiting into production-ready architectures.",[14,1367,1368],{},[28,1369,1370],{},"Async Semaphore + Token Pattern:",[129,1372,1374],{"className":131,"code":1373,"language":133,"meta":134,"style":134},"import asyncio\nfrom aiolimiter import AsyncLimiter\n\nasync def fetch_concurrent(urls: list[str], max_per_second: float = 10):\n limiter = AsyncLimiter(max_per_second)\n \n async def safe_fetch(url: str):\n async with limiter:\n # Replace with your async HTTP client (aiohttp\u002Fhttpx)\n pass\n \n await asyncio.gather(*(safe_fetch(u) for u in urls))\n",[18,1375,1376,1383,1395,1399,1426,1436,1440,1456,1465,1470,1475,1479],{"__ignoreMap":134},[138,1377,1378,1380],{"class":140,"line":141},[138,1379,145],{"class":144},[138,1381,1382],{"class":148}," asyncio\n",[138,1384,1385,1387,1390,1392],{"class":140,"line":152},[138,1386,187],{"class":144},[138,1388,1389],{"class":148}," aiolimiter ",[138,1391,145],{"class":144},[138,1393,1394],{"class":148}," AsyncLimiter\n",[138,1396,1397],{"class":140,"line":160},[138,1398,202],{"emptyLinePlaceholder":201},[138,1400,1401,1404,1406,1409,1412,1414,1417,1419,1421,1424],{"class":140,"line":168},[138,1402,1403],{"class":144},"async",[138,1405,950],{"class":144},[138,1407,1408],{"class":255}," fetch_concurrent",[138,1410,1411],{"class":148},"(urls: list[",[138,1413,262],{"class":221},[138,1415,1416],{"class":148},"], max_per_second: ",[138,1418,959],{"class":221},[138,1420,271],{"class":144},[138,1422,1423],{"class":221}," 10",[138,1425,967],{"class":148},[138,1427,1428,1431,1433],{"class":140,"line":176},[138,1429,1430],{"class":148}," limiter ",[138,1432,215],{"class":144},[138,1434,1435],{"class":148}," AsyncLimiter(max_per_second)\n",[138,1437,1438],{"class":140,"line":184},[138,1439,346],{"class":148},[138,1441,1442,1445,1447,1450,1452,1454],{"class":140,"line":198},[138,1443,1444],{"class":144}," async",[138,1446,950],{"class":144},[138,1448,1449],{"class":255}," safe_fetch",[138,1451,259],{"class":148},[138,1453,262],{"class":221},[138,1455,967],{"class":148},[138,1457,1458,1460,1462],{"class":140,"line":205},[138,1459,1444],{"class":144},[138,1461,1070],{"class":144},[138,1463,1464],{"class":148}," limiter:\n",[138,1466,1467],{"class":140,"line":228},[138,1468,1469],{"class":456}," # Replace with your async HTTP client (aiohttp\u002Fhttpx)\n",[138,1471,1472],{"class":140,"line":244},[138,1473,1474],{"class":144}," pass\n",[138,1476,1477],{"class":140,"line":249},[138,1478,346],{"class":148},[138,1480,1481,1484,1487,1489,1492,1495,1498,1500],{"class":140,"line":286},[138,1482,1483],{"class":144}," await",[138,1485,1486],{"class":148}," asyncio.gather(",[138,1488,1129],{"class":144},[138,1490,1491],{"class":148},"(safe_fetch(u) ",[138,1493,1494],{"class":144},"for",[138,1496,1497],{"class":148}," u ",[138,1499,358],{"class":144},[138,1501,1502],{"class":148}," urls))\n",[56,1504,1506],{"id":1505},"common-mistakes-to-avoid","Common Mistakes to Avoid",[32,1508,1509,1521,1551,1557,1569],{},[35,1510,1511,1518,1519,66],{},[28,1512,1513,1514,1517],{},"Hardcoding ",[18,1515,1516],{},"time.sleep(1)",":"," Ignores server capacity signals, causing unnecessary delays or immediate ",[18,1520,65],{},[35,1522,1523,1533,1534,41,1536,1539,1540,397,1543,1546,1547,1550],{},[28,1524,1525,1526,703,1529,1532],{},"Retrying on all ",[18,1527,1528],{},"4xx",[18,1530,1531],{},"5xx"," errors:"," Only retry ",[18,1535,65],{},[18,1537,1538],{},"503",". Retrying ",[18,1541,1542],{},"401",[18,1544,1545],{},"403",", or ",[18,1548,1549],{},"404"," wastes quota and triggers security flags.",[35,1552,1553,1556],{},[28,1554,1555],{},"Ignoring distributed state:"," In-memory counters in multi-worker setups cause quota overages and IP bans.",[35,1558,1559,1565,1566,1568],{},[28,1560,1561,1562,1564],{},"Blocking ",[18,1563,112],{}," in async loops:"," Freezes the entire event loop. Use ",[18,1567,1342],{}," or async-native limiters.",[35,1570,1571,1574],{},[28,1572,1573],{},"Skipping jitter:"," Deterministic backoff causes synchronized retry storms, overwhelming recovering APIs and extending outage windows.",[56,1576,1578],{"id":1577},"frequently-asked-questions","Frequently Asked Questions",[14,1580,1581,1584,1585,1587,1588,1590,1591,1594],{},[28,1582,1583],{},"How do I handle 429 Too Many Requests without crashing my Python script?","\nCatch the ",[18,1586,65],{}," status code, extract the ",[18,1589,40],{}," header, apply exponential backoff with jitter, and retry only up to a defined limit. Never retry indefinitely or ignore the header. Wrap network calls in ",[18,1592,1593],{},"try\u002Fexcept"," blocks and log failures for observability.",[14,1596,1597,1600,1601,1604,1605,703,1608,1610],{},[28,1598,1599],{},"Should I use a third-party library like tenacity or ratelimit?","\nUse ",[18,1602,1603],{},"tenacity"," for declarative retry\u002Fbackoff logic and ",[18,1606,1607],{},"ratelimit",[18,1609,1346],{}," for outbound pacing. Custom implementations work for simple scripts but lack production-grade edge case handling, thread safety, and distributed state management.",[14,1612,1613,1616],{},[28,1614,1615],{},"How do I sync rate limits across multiple Python workers or cron jobs?","\nUse a centralized state store like Redis or SQLite to track token consumption across processes. Avoid in-memory counters, which reset per worker and cause quota overages. Implement atomic operations or Redis Lua scripts for race-condition-free tracking.",[14,1618,1619,1622,1623,1625],{},[28,1620,1621],{},"What is the difference between server-side and client-side rate limiting?","\nServer-side limits are enforced by the API provider to protect infrastructure. Client-side limits are implemented by you to pace requests, avoid ",[18,1624,65],{}," errors, and optimize quota usage before hitting the server. Both are required for reliable, cost-effective automation.",[1627,1628,1629],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":134,"searchDepth":152,"depth":152,"links":1631},[1632,1633,1634,1635,1636,1637],{"id":58,"depth":152,"text":59},{"id":817,"depth":152,"text":818},{"id":880,"depth":152,"text":881},{"id":1305,"depth":152,"text":1306},{"id":1505,"depth":152,"text":1506},{"id":1577,"depth":152,"text":1578},"Hitting 429 Too Many Requests isn’t just an annoyance—it’s a direct threat to your automation uptime, API quota budget, and infrastructure costs. For builders and side-hustlers relying on third-party APIs, unmanaged request bursts trigger IP bans, break data pipelines, and waste paid tier allocations.","md",{},"\u002Fgetting-started-with-python-apis-for-builders\u002Fmaking-http-requests-with-requests-library\u002Fbest-practices-for-api-rate-limiting",{"title":5,"description":1638},"getting-started-with-python-apis-for-builders\u002Fmaking-http-requests-with-requests-library\u002Fbest-practices-for-api-rate-limiting\u002Findex","KcaXjBQ_52MbgV5r44p1xIMjWtw78CYydKL9mBsC2es",{"@context":1646,"@type":1647,"mainEntity":1648},"https:\u002F\u002Fschema.org","FAQPage",[1649,1654,1657,1660],{"@type":1650,"name":1583,"acceptedAnswer":1651},"Question",{"@type":1652,"text":1653},"Answer","Catch the 429 status code, extract the Retry-After header, apply exponential backoff with jitter, and retry only up to a defined limit. Never retry indefinitely or ignore the header. Wrap network calls in try\u002Fexcept blocks and log failures for observability.",{"@type":1650,"name":1599,"acceptedAnswer":1655},{"@type":1652,"text":1656},"Use tenacity for declarative retry\u002Fbackoff logic and ratelimit\u002Faiolimiter for outbound pacing. Custom implementations work for simple scripts but lack production-grade edge case handling, thread safety, and distributed state management.",{"@type":1650,"name":1615,"acceptedAnswer":1658},{"@type":1652,"text":1659},"Use a centralized state store like Redis or SQLite to track token consumption across processes. Avoid in-memory counters, which reset per worker and cause quota overages. Implement atomic operations or Redis Lua scripts for race-condition-free tracking.",{"@type":1650,"name":1621,"acceptedAnswer":1661},{"@type":1652,"text":1662},"Server-side limits are enforced by the API provider to protect infrastructure. Client-side limits are implemented by you to pace requests, avoid 429 errors, and optimize quota usage before hitting the server. Both are required for reliable, cost-effective automation.",1778017886093]