Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
RPC Troubleshooting12 min read

Polkadot RPC Rate Limits and 429s: Per-Second Limits, Flags, and Handling

Understand Polkadot RPC rate limits: Substrate flags, public endpoint restrictions, and how to handle 429s with batching, caching, and backoff.

TL;DR

Polkadot RPC endpoints enforce rate limits to protect infrastructure. Substrate nodes have built-in flags like --rpc-rate-limit and --rpc-eth-rate-limit. Public endpoints often set stricter limits, returning 429 or disconnecting WebSockets. This article explains the mechanics, how to handle 429s with batching and backoff, and how to tune your own node.

Direct Answer: What Are Polkadot RPC Rate Limits?

Polkadot RPC rate limits are restrictions on how many requests you can send to an RPC endpoint within a given time window. Public endpoints like rpc.polkadot.io typically enforce around 5 requests per second per IP, while self-hosted Substrate nodes use the --rpc-rate-limit flag to set a per-minute cap (default 100 calls per minute). When you exceed these limits, you receive an HTTP 429 Too Many Requests response, or in the case of WebSocket connections, the server may silently disconnect you.

The exact limits vary by provider. For example, OnFinality's Polkadot RPC endpoints have documented limits that differ from community nodes. Always check your provider's documentation for specific numbers.

  • Public endpoints: ~5 req/s (documented / varies by provider)
  • Self-hosted Substrate: --rpc-rate-limit default 100 calls/min
  • 429 is an HTTP status; WebSocket disconnects are silent

How Substrate RPC Rate Limiting Works

Substrate-based chains like Polkadot have built-in RPC rate limiting. The key flags are --rpc-rate-limit (for general RPC calls) and --rpc-eth-rate-limit (for Ethereum-compatible RPC methods). These flags set a per-minute call limit per IP. Additionally, --rpc-max-connections-per-ip limits the number of WebSocket connections per IP.

The rate limiter uses a token bucket algorithm. Each IP has a bucket that fills at a certain rate and drains as requests are made. When the bucket is empty, requests are rejected with a 429 or the connection is dropped.

Public endpoints often set stricter limits than the Substrate defaults to protect shared infrastructure. For example, rpc.polkadot.io is heavily restricted. This is documented in the Polkadot node infrastructure docs.

  • --rpc-rate-limit: sets calls per minute (default 100)
  • --rpc-eth-rate-limit: for eth_* methods
  • --rpc-max-connections-per-ip: limits WebSocket connections
  • Token bucket algorithm: refills over time

429 vs. Timeout vs. RPC Error Codes

A 429 Too Many Requests is an HTTP status code indicating you've exceeded the rate limit. It's different from a timeout, which means the server didn't respond within a specified time. RPC error codes (like -32000) are JSON-RPC errors returned by the node for invalid requests or internal errors.

When you get a 429, the response may include a Retry-After header indicating how long to wait before retrying. WebSocket connections don't return HTTP status codes; instead, the server may close the connection or send a custom error message.

Understanding the difference helps you choose the right handling strategy: for 429s, use backoff; for timeouts, increase timeout or reduce load; for RPC errors, fix the request.

  • 429: rate limit exceeded, retry after Retry-After
  • Timeout: no response within time limit, increase timeout or reduce load
  • RPC error: invalid request, fix the request

Identifying the Limiting Scope

Before optimizing, determine whether the limit is per-connection or per-IP, and whether it applies to HTTP or WebSocket. For HTTP, the limit is usually per IP. For WebSocket, it may be per connection or per IP depending on the provider.

You can test by sending a burst of requests from a single connection and observing when you get a 429 or disconnect. Also check if the limit applies to all methods or only specific ones (e.g., eth_* methods).

Use the RPC Assistant to see documented limits for various providers.

  • Check provider docs for per-IP vs per-connection limits
  • Test with a burst of requests to see the threshold
  • Some providers limit specific methods like eth_*

Reducing Request Volume

The most effective way to avoid 429s is to reduce the number of requests you make. Instead of polling for new blocks with chain_getBlock, subscribe to new blocks using chain_subscribeFinalizedHeads. This pushes updates to you, reducing polling overhead.

Use JSON-RPC batching to combine multiple calls into a single HTTP request. This is especially effective for read-only calls like chain_getHeader or state_getStorage. The polkadot.js API supports batching via the .batch() method.

Cache storage reads that don't change frequently. For example, if you need the same storage value multiple times, store it locally and refresh it only when a new block arrives.

  • Use subscriptions instead of polling
  • Batch multiple calls into one request
  • Cache storage reads and invalidate on new blocks

Handling 429s with Backoff and Retry-After

When you do get a 429, implement exponential backoff with jitter. If the response includes a Retry-After header, wait that many seconds before retrying. Otherwise, start with a short delay (e.g., 1 second) and double it up to a maximum (e.g., 30 seconds).

For WebSocket connections, if the server disconnects, reconnect with a delay and consider reducing your request rate.

Here's a Node.js example using polkadot.js that demonstrates batching and backoff on 429:

  • Exponential backoff: 1s, 2s, 4s, ... up to max
  • Honor Retry-After header if present
  • For WS, reconnect with delay
const { ApiPromise, WsProvider } = require('@polkadot/api');

async function main() {
  const provider = new WsProvider('wss://rpc.polkadot.io');
  const api = await ApiPromise.create({ provider });

  // Batching example: get multiple headers in one request
  const batch = api.createType('Vec<BlockNumber>', [100, 200, 300]);
  const headers = await api.rpc.chain.getHeader.batch(batch);
  console.log('Headers:', headers.map(h => h.number.toString()));

  // Backoff on 429 (HTTP example)
  const fetch = require('node-fetch');
  async function rpcCall(method, params) {
    let delay = 1000;
    while (true) {
      const res = await fetch('https://rpc.polkadot.io', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
      });
      if (res.status === 429) {
        const retryAfter = res.headers.get('Retry-After');
        const wait = retryAfter ? parseInt(retryAfter) * 1000 : delay;
        console.log(`429, waiting ${wait}ms`);
        await new Promise(r => setTimeout(r, wait));
        delay = Math.min(delay * 2, 30000);
      } else {
        return res.json();
      }
    }
  }

  const result = await rpcCall('chain_getHeader', []);
  console.log('Header:', result.result.number);

  await api.disconnect();
}

main().catch(console.error);

// Expected output: Headers: ['100', '200', '300'] and a header number

Common Failures and Fixes

Even with best practices, you may encounter issues. Here are common failures and how to fix them:

  1. Silent WebSocket disconnects: The server closes the connection without a 429. Fix: implement reconnection logic with exponential backoff and reduce request rate.

  1. 429 on HTTP but not WS: Some providers have different limits for HTTP and WS. Fix: use WS for subscriptions and HTTP for occasional reads.

  1. Rate limit on specific methods: Some providers limit eth_* methods more strictly. Fix: use Substrate-native methods when possible.

  1. Batching not reducing 429s: If the provider counts each batch as one request, batching helps. If not, you may need to reduce overall volume.

  • Silent WS disconnect: implement reconnection with backoff
  • HTTP vs WS limits: use appropriate protocol for each use case
  • Method-specific limits: prefer native Substrate methods
  • Batching may not help if provider counts each call individually

Tradeoffs and Limitations

Rate limits are necessary to protect RPC infrastructure from abuse, but they can be frustrating for developers. The tradeoff is between reliability and accessibility. Public endpoints are free but heavily limited; dedicated endpoints or self-hosted nodes offer higher limits but require payment or maintenance.

Self-hosting a node gives you full control over rate limits via flags like --rpc-rate-limit. However, running a node requires significant resources and maintenance. For production workloads, consider using a provider like OnFinality's API service with RPC pricing that scales with your needs.

Remember that rate limits are not a substitute for good client design. Always minimize requests, use subscriptions, and handle errors gracefully.

  • Public endpoints: free but limited
  • Self-hosted: full control but high maintenance
  • Provider endpoints: scalable but cost money
  • Good client design is essential regardless of limits

Next Steps and Further Reading

Now that you understand Polkadot RPC rate limits, you can optimize your application to avoid 429s. Start by identifying your usage pattern and implementing batching and subscriptions. If you need higher limits, consider a dedicated endpoint or your own node.

For more help, check out the OnFinality Learn hub for other tutorials, and the generic RPC 429 handling guide. You can also explore Polkadot RPC endpoints to compare providers.

If you're building on Polkadot, you might also be interested in our Polkadot network page for network details.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started