Ethereum

Ethereum

Rate Limits

Rate limiting caps how many requests your key can make in a window so one noisy client cannot starve the Ethereum nodes behind the endpoint, which keeps latency predictable for everyone on chain 1. TheRPC applies limits at several levels at once: per-second and per-minute request rates, a daily quota, and a ceiling on concurrent connections. Usage is metered in Compute Units, so a heavy debug_traceTransaction costs far more than a plain eth_blockNumber. The exact ceilings depend on your subscription plan and are shown live in the Dashboard.

Error Responses

When you cross a limit, the call comes back as a JSON-RPC error with code -32029 and a "Rate limit exceeded" message instead of an Ethereum result, so check the error field before reading result. Alongside the body, the HTTP response carries rate-limit headers — your ceiling, how many calls remain in the window, and the Unix timestamp when it resets — which let you slow down before you hit the wall rather than after.

Rate Limit Error — JSON-RPC

{
"jsonrpc": "2.0",
"error": {
"code": -32029,
"message": "Rate limit exceeded"
},
"id": 1
}

Rate Limit Response Headers

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1628696400

Best Practices

Three strategies keep you under your limits. Retry the -32029 error with exponential backoff so a brief spike settles instead of compounding. Batch independent reads into one JSON-RPC array — several balance or block lookups travel as a single request. And replace polling loops with eth_subscribe over WebSocket, so the node pushes new blocks and logs to you rather than you spending calls asking for them. The sections below show each in code.

Implement Retries

async function callWithRetry(method, params, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await makeRequest(method, params);
return response;
} catch (error) {
if (error.code === -32029) {
// Rate limit exceeded
const backoffTime = Math.pow(2, i) * 1000;
await new Promise((resolve) => setTimeout(resolve, backoffTime));
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}

Batch Requests

[
{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
"id": 1
},
{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 2
}
]

Use WebSocket Subscriptions

For anything live — new blocks each 12-second slot, contract logs, or pending transactions — eth_subscribe over WebSocket replaces a polling loop with a single open connection the node pushes to. That collapses dozens of repeated eth_blockNumber or eth_getLogs calls into one subscription, which is the single biggest win against rate limits for real-time workloads. See the eth_subscribe reference for the subscription types and setup.

Monitoring

  • Dashboard metrics — request volume and CU consumption per key over time, so you can see which Ethereum workload is spending the most.
  • Response headers — the X-RateLimit-* values on every HTTP response give you remaining quota and reset time in real time.
  • Usage alerts — get notified as you approach your plan's ceiling, before requests start failing with -32029.

Quota Management

Read the X-RateLimit-Remaining and X-RateLimit-Reset headers off each response and act on them in code — throttle yourself when remaining nears zero, and resume after the reset timestamp passes. Caching cuts the request volume that counts against quota in the first place: values that change slowly on Ethereum, like the chain ID, a contract's deployed bytecode, or an old finalized block, can be cached for a long time, while head-of-chain reads need only a short cache tuned to the 12-second slot.

Track Usage

function trackApiUsage(response) {
const limits = {
limit: response.headers['X-RateLimit-Limit'],
remaining: response.headers['X-RateLimit-Remaining'],
reset: response.headers['X-RateLimit-Reset'],
};
console.log(`API calls remaining: ${limits.remaining}/${limits.limit}`);
}

Implement Caching

const cache = new Map();
async function getCachedBlockNumber(cacheTime = 5000) {
const cached = cache.get('blockNumber');
if (cached && Date.now() - cached.timestamp < cacheTime) {
return cached.value;
}
const newValue = await web3.eth.getBlockNumber();
cache.set('blockNumber', {
value: newValue,
timestamp: Date.now(),
});
return newValue;
}

Plan Limits

Plans differ on the levers that matter: request rate, daily CU quota, how many concurrent WebSocket connections you can hold, and how many active subscriptions each allows. Higher tiers also open up the heavy namespaces — archive-state reads, the geth-style debug_ tracers, and the Erigon trace_ API — which run against archive nodes and are not on the entry plans. Compare the tiers side by side on the pricing page.

Upgrade Options

  • Optimize first — batch independent reads and drop calls you do not need before paying for more.
  • Add caching — serve slow-changing data like chain ID and bytecode from a local cache instead of re-fetching.
  • Switch to WebSocket — move real-time polling onto eth_subscribe so live data stops eating your quota.
  • Upgrade the plan — if you hit limits steadily rather than in bursts, a higher tier raises the rate, quota, and connection ceilings and unlocks archive, debug, and trace access.

Ready to call this in production?

Free tier covers personal projects. Pay-as-you-go scales without a card.