Skip to content

Rate Limit Handling

Limits

  • Standard: 100 requests/minute per wallet
  • Premium: 1000 requests/minute per wallet
  • Cached responses (30s) do NOT count toward limit
  • Free endpoints do NOT count

Retry with Exponential Backoff

import time
import httpx

def request_with_retry(url, max_retries=3):
    for attempt in range(max_retries):
        response = client.get(url)
        if response.status_code != 429:
            return response.json()
        wait = 2 ** attempt  # 1s, 2s, 4s
        print(f"Rate limited. Retry in {wait}s...")
        time.sleep(wait)
    raise Exception("Max retries exceeded")

Queue-Based Approach (Async)

import asyncio

class RateLimitedQueue:
    def __init__(self, rpm=100):
        self.interval = 60.0 / rpm
        self.semaphore = asyncio.Semaphore(1)

    async def fetch(self, url):
        async with self.semaphore:
            result = await client.aget(url)
            await asyncio.sleep(self.interval)
            return result

# Usage
queue = RateLimitedQueue(rpm=90)  # Use 90 to stay under 100
results = await asyncio.gather(*[
    queue.fetch(f"https://api.foursec.xyz/price/{s}")
    for s in ['ETH', 'BTC', 'SOL']
])

Sliding Window Counter

import time
from collections import deque

class SlidingWindowLimiter:
    def __init__(self, max_requests=90, window=60):
        self.max_requests = max_requests
        self.window = window
        self.requests = deque()

    def can_request(self):
        now = time.time()
        # Remove old requests outside window
        while self.requests and self.requests[0] < now - self.window:
            self.requests.popleft()
        if len(self.requests) < self.max_requests:
            self.requests.append(now)
            return True
        return False

    def wait_time(self):
        if not self.requests:
            return 0
        oldest = self.requests[0]
        return max(0, oldest + self.window - time.time())

# Usage
limiter = SlidingWindowLimiter(max_requests=90, window=60)

def safe_request(url):
    while not limiter.can_request():
        time.sleep(limiter.wait_time())
    return client.get(url).json()

Multiple Wallets (Load Balancing)

For high-throughput needs, rotate across multiple wallets:

from itertools import cycle

wallets = [
    Account.from_key("0x_key1"),
    Account.from_key("0x_key2"),
    Account.from_key("0x_key3"),
]
wallet_cycle = cycle(wallets)
def get_next_client():
    wallet = next(wallet_cycle)
    return X402Client(wallet=wallet)

# Each wallet has its own 100 req/min limit
# 3 wallets = 300 req/min effective throughput

Released under the MIT License.