Concepts

Rate limits

Limits keep one busy client from slowing everyone else down. Hitting one is never fatal: wait, then try again.

Every request counts against a limit per IP address. Requests with a key also draw on a budget per key, and the endpoints that work without a key have tighter limits per IP of their own. Over any of them the answer is 429 rate_limited, with a Retry-After header giving the seconds to wait.

Handling a 429

Wait for the number of seconds in Retry-After, then send the same request again. Treat 503 the same way with a growing delay: a price feed or a node is briefly down. Send an idempotency_key on anything that moves money, so a retry can never act twice.

Retry
import time

import requests


def call(url, data, headers, attempts=5):
    for attempt in range(attempts):
        response = requests.post(url, data=data, headers=headers, timeout=30)
        if response.status_code == 429:
            time.sleep(int(response.headers.get("Retry-After", "5")))
        elif response.status_code == 503:
            time.sleep(2 ** attempt)
        else:
            return response
    return response
const sleep = (seconds) => new Promise((done) => setTimeout(done, seconds * 1000));

async function call(url, body, headers, attempts = 5) {
  let response;
  for (let attempt = 0; attempt < attempts; attempt++) {
    response = await fetch(url, { method: "POST", headers, body });
    if (response.status === 429) {
      await sleep(Number(response.headers.get("Retry-After") ?? 5));
    } else if (response.status === 503) {
      await sleep(2 ** attempt);
    } else {
      break;
    }
  }
  return response;
}
function call(string $url, array $data, string $key, int $attempts = 5): ?array
{
    for ($attempt = 0; $attempt < $attempts; $attempt++) {
        $wait = 5;
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_HTTPHEADER => ["Authorization: Bearer $key"],
            CURLOPT_POSTFIELDS => http_build_query($data),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$wait) {
                if (stripos($line, "Retry-After:") === 0) {
                    $wait = (int) trim(substr($line, 12));
                }
                return strlen($line);
            },
        ]);
        $body = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if ($status === 429) {
            sleep($wait);
        } elseif ($status === 503) {
            sleep(2 ** $attempt);
        } else {
            return json_decode($body, true);
        }
    }
    return null;
}