Learn how to handle rate limits gracefully — exponential backoff, Retry-After headers, and best practices for staying within the eToro API's request budgets.
Every API has rate limits — the eToro API is no exception. This guide covers how to detect, handle, and avoid rate-limit errors so your application stays resilient under load.
For the authoritative rate limit numbers and tiers, always refer to the official Rate Limits documentation.
When your application exceeds the allowed request rate, the API responds with HTTP 429 Too Many Requests. The response includes headers that tell you what to do next.
| Header | Meaning |
|---|---|
Retry-After |
Seconds to wait before retrying |
X-RateLimit-Limit |
Maximum requests allowed in the window |
X-RateLimit-Remaining |
Requests left in the current window |
X-RateLimit-Reset |
Unix timestamp when the window resets |
Header availability may vary by endpoint. Check the API Reference for details.
When you receive a 429, don't retry immediately. Use exponential backoff with jitter to spread retries across time and avoid a "thundering herd" of requests when the window resets.
import time
import random
import requests
def fetch_with_backoff(url, headers, max_retries=5):
for attempt in range(max_retries):
resp = requests.get(url, headers=headers)
if resp.status_code != 429:
return resp
retry_after = int(resp.headers.get("Retry-After", 2 ** attempt))
jitter = random.uniform(0, retry_after * 0.5)
wait = retry_after + jitter
print(f"Rate limited. Waiting {wait:.1f}s (attempt {attempt + 1})")
time.sleep(wait)
raise Exception("Max retries exceeded")
async function fetchWithBackoff(url, headers, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await fetch(url, { headers });
if (resp.status !== 429) return resp;
const retryAfter = parseInt(resp.headers.get("Retry-After") ?? String(2 ** attempt), 10);
const jitter = Math.random() * retryAfter * 0.5;
const wait = retryAfter + jitter;
console.log(`Rate limited. Waiting ${wait.toFixed(1)}s (attempt ${attempt + 1})`);
await new Promise((r) => setTimeout(r, wait * 1000));
}
throw new Error("Max retries exceeded");
}
Retry-AfterAlways check the Retry-After header first. It tells you exactly how long to wait — no guesswork needed.
Market data that doesn't change frequently (instrument metadata, exchange lists) can be cached locally. This dramatically reduces your request count.
import requests
BASE = "https://public-api.etoro.com/api/v1"
# Cache instrument types — they rarely change
instrument_types = requests.get(
f"{BASE}/market-data/instrument-types",
headers=headers,
).json()
Instead of polling /market-data/rates every second, subscribe to price updates via the WebSocket stream. This uses one persistent connection instead of hundreds of HTTP requests.
const ws = new WebSocket("wss://ws.etoro.com/ws");
ws.onopen = () => {
ws.send(JSON.stringify({
operation: "Authenticate",
data: { userKey: USER_KEY, apiKey: API_KEY },
}));
ws.send(JSON.stringify({
operation: "Subscribe",
data: { topics: ["instrument:100000"], snapshot: false },
}));
};
If you need to fetch data for many instruments, don't fire all requests at once. Use a simple rate limiter:
import time
instrument_ids = [1001, 1002, 1003, 1004, 1005]
for iid in instrument_ids:
resp = requests.get(
f"{BASE}/market-data/rates",
headers=headers,
params={"instrumentIds": iid},
)
print(resp.status_code)
time.sleep(0.2) # 5 requests/second
Check X-RateLimit-Remaining on every response. When it gets low, proactively slow down before you hit the wall.
| Scenario | Recommended action |
|---|---|
Got a 429 |
Wait for Retry-After seconds, then retry with backoff |
X-RateLimit-Remaining is low |
Slow down request rate proactively |
| Need real-time prices | Use WebSocket instead of polling |
| Static data (instruments, exchanges) | Cache locally, refresh periodically |
| Batch operations | Spread requests over time with a rate limiter |
Was this helpful?