Public RPC Endpoint Rate Limits, Measured

Free public RPC endpoints rarely publish limits. We measured six official ones on 7 Nov 2026: 429 shapes, batch caps, missing methods and when to use a key.

Public RPC Endpoint Rate Limits, Measured

Four of the six official public RPC endpoints we checked publish no rate limit at all. Solana and BNB Chain publish a number (Solana’s is per IP), and Base shows its limit only inside a 429 response: 25 requests per second. In our run on 7 November 2026, only Solana and Base throttled a burst of 150 requests. The other four hit other limits instead: batch caps, eth_getLogs ranges, shallow state, missing debug and trace methods and, on OP Mainnet and Arbitrum One, no WebSocket.

This is for you if you are choosing between a chain’s free endpoint and a keyed one, or if your app already gets odd errors from a public URL and you want to know whether it is a rate limit or something else.

How we measured

We tested six endpoints from the chains’ own documentation: Solana, Base, OP Mainnet, Arbitrum One, BNB Chain and Avalanche C-Chain. Every call ran on 7 November 2026 from one IP in Belgium, on Node 24.19.0, with no API key. This is one run per endpoint, so the limits you see from another network or at another time can differ.

Each endpoint got the same rate pattern from one script (probe.mjs, in full below):

  1. 30 sequential calls.
  2. A burst of 150 calls with 25 in flight at once.
  3. A poll once per second, up to 120 seconds, until one call succeeds.

On top of that came capability spot checks, one call each, with a control on the same endpoint in the same minute: a plain eth_blockNumber (Solana getSlot), a valid read and an invented method name. Across the six endpoints we sent roughly 1,500 requests, and each stayed under 300. The test is polite by design: it did not look for the limit, so “no throttle” below means “above what this pattern produced”. It never means unlimited.

Results at a glance

The table shows the rate pattern. The details follow in the next sections.

EndpointNumber in docsBurst of 150
Solana100 per 10 s per IP10 OK, 140 throttled
Basenone found51 OK, 99 throttled
OP Mainnetnone found150 OK
Arbitrum Onenone found150 OK
BNB Chain10K per 5 min150 OK
Avalanche C-Chainnone found150 OK

All 30 sequential calls passed on every endpoint. In this run we saw no timeout at 10 seconds, no connection reset, no challenge page and no HTTP 403 on any of the six. A smaller re-check later the same day did see requests hang past 10 seconds on Solana and Base during a burst, so plan for timeouts as well as 429s. One run on a quiet day says nothing about a launch or a mint, when public endpoints are under the most load.

In one line each:

  • Solana: throttled with “Connection rate limits exceeded”, not a request-count message. It worked again 9 seconds after the burst.
  • Base: throttled at 25 per second, and refused eth_getLogs with a separate 429.
  • OP Mainnet: refuses batches of more than 10 items.
  • Arbitrum One: old state came and went between calls and between runs, and it does not serve WebSocket.
  • BNB Chain: state older than about 128 blocks is gone.
  • Avalanche C-Chain: a batch of 50 returns a plain-text HTTP 500.

What a throttle looks like

Only Solana and Base throttled, and their 429s look nothing alike. If your error handling expects one shape, one of them slips through.

Solana

The first failure in the burst was HTTP 429 with a JSON-RPC style body, retry-after: 10 and 13 x-ratelimit-* headers:

{"jsonrpc":"2.0","error":{"code": 429,"message":"Connection rate limits exceeded"}, "id": 1 }

The message says connection rate, not request count. Our likely reading: Node’s fetch opened new connections for the 25 parallel requests, while the 30 sequential calls reused one connection and all passed. We did not count connections. Solana’s docs list “Maximum connection rate per 10 seconds per IP: 40” and “Maximum concurrent connections per IP: 40”. We did not isolate which of the two fired, and the headers on the throttled response still showed x-ratelimit-connrate-remaining: 38 and x-ratelimit-conn-remaining: 38, which does not obviously match the message. We report both and do not reconcile them. The first call that worked again came 9 seconds after the burst ended, which fits retry-after: 10.

If you hit this one, the fix is partly in your client: keep connections alive and cap how many you open, not only how many requests you send.

Base

Base’s first throttled response was HTTP 429 with content-type: text/plain and these headers: x-ratelimit-limit: 25, 25;w=1, x-ratelimit-remaining: 0 and x-ratelimit-reset: 1. There was no Retry-After. The body:

{"code":-32007,"message":"25/second request limit reached"}

That is a bare object. It has no jsonrpc, no id and no error key, so a client that only reads response.error finds nothing. Check for a top-level code as well. The w=1 in the header is a one-second window. After the burst, a single call worked again within 1 second.

Two more Base details:

  • eth_getLogs answered HTTP 429 with a proper JSON-RPC envelope, error -32011 “request limit reached” and x-ratelimit-limit: 5, 5;w=1. It did so on every eth_getLogs call we sent, including a lone one-block call after a pause, while other methods on the same host worked. The header suggests 5 per second, but we never saw a success.
  • A JSON-RPC batch of 100 eth_blockNumber calls returned HTTP 200 with 25 results and 75 per-item -32007 errors, so each call inside a batch counts. A batch of 10 passed with 10 results.

The lesson is two checks: handle a 429 body that is not a JSON-RPC error, and check every item of a batch even when the HTTP status is 200. For retry and backoff code, see RPC rate limits explained. For one chain’s limits worked through end to end, see TronGrid rate limits.

Limits that are not rate limits

Most of the other failures were not about request counts. They are limits on what one request may ask for, and they hit apps that never get close to a request cap.

Batch caps

  • OP Mainnet: a batch of 10 passed. A batch of 11, 50 or 100 got HTTP 413 with error -32014 (“To send batches over 10 items, consider using a dedicated API provider”).
  • Avalanche C-Chain: a batch of 20 passed. A batch of 50 or 100 got HTTP 500 with a plain-text body, Internal Server Error (21 bytes), not a JSON-RPC error.
  • Arbitrum One, BNB Chain and Solana: a batch of 100 returned 100 results.
  • Base: a batch of 100 returned HTTP 200 with 75 of the items refused (see above).

eth_getLogs ranges

We called eth_getLogs for USDC over 1,000 and 10,000 blocks, with no topic filter. Base answered 429 -32011 on both. OP Mainnet returned 5,177 logs for 1,000 blocks and HTTP 400 -32020 “backend response too large” for 10,000. Arbitrum One returned 2,548 logs for 1,000 blocks and -32000 “logs matched by query exceeds limit of 10000” for 10,000. BNB Chain’s dataseed answered -32005 “limit exceeded” on both. Avalanche answered -32602 with a 100-block cap on both. The full ladder, with more endpoints, is in the eth_getLogs block range guide.

Old state

We asked each endpoint for eth_getBalance and an eth_call to totalSupply() at blocks about 30 and about 365 days back, picked by timestamp. A zero balance proves nothing, so we also checked the eth_call.

  • Base: answered at a block from 7 November 2025, and at block 1 for an eth_call to the WETH predeploy (USDC did not exist yet).
  • OP Mainnet: answered at a block from 7 November 2025.
  • Avalanche C-Chain: answered at a block from 12 November 2025.
  • Arbitrum One: the depth changed. In our first run, a block from 8 October 2026 gave ERR ok ERR ok ok ok across six calls 2 seconds apart, a block about 8 hours back failed six of six and 7 November 2025 failed with -32000 “missing trie node”. A re-check later the same day found only about the last 30 minutes of state, on every call. So no single depth number is right: plan for recent state only.
  • BNB Chain: eth_getBalance worked 100 blocks behind head and failed from 128 blocks back with -32000 “missing trie node”. The blocks themselves still exist at every depth. State is what is missing.
  • Solana: getBlock worked at a slot from 19 December 2025, the deepest we tested (about 10.6 months back). A re-check of getFirstAvailableBlock returned 0.

Debug and trace

We called debug_traceTransaction (callTracer) and trace_transaction on a recent transaction, one call each. Every endpoint refused, but with different codes:

  • Base: HTTP 401, -32611 “rejected due to request filter settings”.
  • OP Mainnet: HTTP 403, -32601 “rpc method is not whitelisted”.
  • Arbitrum One and Avalanche: HTTP 200, -32601 “the method … does not exist”.
  • BNB Chain: -32002 “the resource … is not available.”

On Base, OP Mainnet, Arbitrum One and Avalanche, the reply to debug and trace was identical in shape to the reply to an invented method name. From the outside you cannot tell “this node does not have the method” from “the method is blocked by policy”. BNB Chain answered its invented method with -32601 and debug with -32002, so it tells the two cases apart.

WebSocket

Base, OP and Arbitrum document that their public RPC has no WebSocket, and we measured it: wss://mainnet.base.org and wss://mainnet.optimism.io did not open, and an HTTP upgrade request to them got 405, the same as a plain GET. wss://arb1.arbitrum.io/ws got 404, also the same as a plain GET. BNB Chain’s docs name no WebSocket URL, and wss://bsc-dataseed.bnbchain.org did not open either. Those URLs are our guesses of a WebSocket address, so the test only shows that those guesses do not serve WebSocket.

The control: Avalanche’s documented wss://api.avax.network/ext/bc/C/ws answered 101 Switching Protocols to the same upgrade request from the same machine, and eth_subscribe for newHeads was acknowledged. So WebSocket works from our network. wss://api.mainnet.solana.com also opened and slotSubscribe was acknowledged. Solana’s x-ratelimit-pubsub-limit header read 5, and we did not test what happens at a sixth subscription.

OP Mainnet and Arbitrum do document a different WebSocket feed: OP’s subblocks WebSocket (documented as “strictly rate-limited”) and Arbitrum’s sequencer feed. Both opened and streamed data. They are not an eth_subscribe endpoint, so they do not replace one.

Chain by chain

What each chain’s own docs say, read on 7 November 2026, then what we measured.

Solana

Solana’s docs say the endpoints are “subject to rate limits”, that the numbers “are not guaranteed to be the most up-to-date” and that the public endpoints are “not intended for production applications”. The Mainnet limits it lists:

  • Maximum number of requests per 10 seconds per IP: 100
  • Maximum number of requests per 10 seconds per IP for a single RPC: 40
  • Maximum concurrent connections per IP: 40
  • Maximum connection rate per 10 seconds per IP: 40
  • Maximum amount of data per 30 seconds: 100 MB

The live x-ratelimit-* headers disagree with the request numbers. On a plain HTTP 200 they read x-ratelimit-tier: free, x-ratelimit-method-limit: 150, x-ratelimit-rps-limit: 250, x-ratelimit-endpoint-limit: unlimited, x-ratelimit-conn-limit: 40, x-ratelimit-connrate-limit: 40 and x-ratelimit-pubsub-limit: 5. The connection numbers match the docs. The request numbers (150 per method, 250 overall) do not match 40 per method and 100 total, and the header names do not say which window they cover. Our burst hit the connection limit first, so we cannot say which request numbers are enforced. We state both and do not resolve it.

The docs print https://api.mainnet.solana.com, and that is the host we measured. The older api.mainnet-beta.solana.com still answers and shares the counter: x-ratelimit-method-remaining read 149 on the first call, then 148 on the next call to the older host and 148 on a third. One note on sources: solana.com answered our scripts with a security challenge, so we read the docs text from Solana’s public docs repository, last changed on 17 September 2026. We cannot prove the live page is identical.

Solana’s docs list 429 for rate limits (use Retry-After) and 403 for a blocked IP or website. We saw no 403. In a spot check, getSignaturesForAddress with a limit of 1,000 returned 1,000 signatures. More on Solana is on the Solana network page.

Base

Base’s docs say its public endpoints are “rate-limited and are not suitable for production traffic”, that they are HTTP only and that WebSocket connections are not available on them. We found no numeric limit anywhere in the docs. The 25 per second above comes from the header on a 429. We could not tell whether the limit is per IP, per key or global. Every eth_getLogs call we sent got a 429, whatever its size. See the Base network page.

OP Mainnet

OP’s docs say the public RPC URLs “are rate limited and do not support websocket connections” and are “perfect for development, testing, and proof-of-concept work”. We found no number. Our burst of 150 did not throttle. The measured limits were the 10-item batch cap and the eth_getLogs response size. See the Optimism network page.

Arbitrum One

Arbitrum’s docs call arb1.arbitrum.io/rpc a “general-purpose read/write endpoint, useful for development and low-volume reads”, with no rate-limit guarantees. They also say the public endpoints do not support WebSocket or IPv6. We found no number, and the burst did not throttle. The finding to plan around is old state that changed between calls and between runs. See the Arbitrum network page.

BNB Chain

BNB Chain’s docs say “The rate limit of BSC endpoint on Testnet and Mainnet is 10K/5min” and that eth_getLogs is disabled on the listed mainnet endpoints. The page does not say “per IP”, and we did not test the 10K figure: our 181 calls were far below it. We measured bsc-dataseed.bnbchain.org, the first host listed. eth_getLogs answered -32005 “limit exceeded”, and state older than about 128 blocks was gone. See the BNB Chain network page.

Avalanche C-Chain

Avalanche’s docs print https://api.avax.network/ext/bc/C/rpc and its WebSocket address. We found no rate limit for that host in the docs. The Data API docs have a section on rate limits for RPC endpoints, with an unauthenticated tier counted in compute units, but they never name api.avax.network, so we did not treat it as this host’s limit. Our burst of 150 did not throttle. The measured limits that are not in the docs: a 100-block eth_getLogs cap and the HTTP 500 on batches of 50 or more. See the Avalanche network page.

Ethereum and Polygon

Ethereum is not in the table because ethereum.org’s node-provider page (last updated 21 October 2026) lists no Foundation-run endpoint. Its list is third-party providers.

Polygon is not in the table either. Polygon’s RPC page lists third-party hosts, and another Polygon page points to polygon-rpc.com, which answered HTTP 401 “tenant disabled” on 7 November 2026.

Probe an endpoint yourself

This is the script we used. It has no dependencies. We ran it on Node 24.19.0. It uses fetch, AbortSignal.timeout and top-level await in an ES module. It prints counts, the first failure and the recovery wait. It does not report response times.

// probe.mjs  Usage: node probe.mjs <rpc-url> <method> ['<params-json>']   (tested on Node 24, no dependencies)
const [url, method, params = "[]"] = process.argv.slice(2);
const body = JSON.stringify({ jsonrpc: "2.0", id: 1, method, params: JSON.parse(params) });
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function call() {
  try {
    const res = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, signal: AbortSignal.timeout(10_000) });
    const text = await res.text();
    let rpc = null;
    try { const j = JSON.parse(text); if (j.error) rpc = `${j.error.code} ${String(j.error.message).slice(0, 50)}`; } catch {}
    return { status: res.status, rpc, ok: res.status === 200 && !rpc, res, text };
  } catch (e) { // timeout at 10 s, or a connection failure such as ECONNRESET
    return { status: e.name === "TimeoutError" ? "timeout" : `error:${e.cause?.code ?? e.name}`, ok: false };
  }
}
const tally = (rs) => rs.reduce((t, r) => { const k = r.rpc ? `${r.status} rpc ${r.rpc}` : String(r.status); t[k] = (t[k] ?? 0) + 1; return t; }, {});
const pick = (h) => Object.fromEntries([...h].filter(([k]) => /ratelimit|retry-after|content-type|^server$|cf-ray/i.test(k)));

console.log("start", new Date().toISOString(), url, method);
const seq = [];
for (let i = 0; i < 30; i++) seq.push(await call()); // 1. 30 sequential calls
console.log("sequential x30:", tally(seq));
if (seq[0].res) console.log("first response headers:", pick(seq[0].res.headers));

const burst = []; let n = 0; // 2. 150 requests, 25 at a time
await Promise.all(Array.from({ length: 25 }, async () => { while (n++ < 150) burst.push(await call()); }));
console.log("burst x150 @25:", tally(burst));
const first = burst.find((r) => !r.ok); // first throttled or failed response, in order of arrival
if (first) console.log("first failure:", first.status, first.res ? pick(first.res.headers) : "", "\nbody[0..300]:", (first.text ?? "").slice(0, 300));

const t0 = Date.now(); let polls = 0, back = null; // 3. poll once per second, max 120 s
for (let i = 0; i <= 120 && back === null; i++) { polls++; if ((await call()).ok) back = Math.round((Date.now() - t0) / 1000); else await sleep(1000); }
console.log("single request OK again after:", back === null ? ">120 s" : `${back} s`, `(${polls} polls)`);

Run it against any endpoint, with the method and parameters as arguments:

node probe.mjs https://mainnet.base.org eth_blockNumber '[]'
node probe.mjs https://api.mainnet.solana.com getSlot '[]'

Here is the trimmed output from our Base run:

start 2026-11-07T11:39:38.517Z https://mainnet.base.org eth_blockNumber
sequential x30: { '200': 30 }
burst x150 @25: { '200': 51, '429': 99 }
first failure: 429 { 'content-type': 'text/plain; charset=utf-8', server: 'cloudflare', 'x-ratelimit-limit': '25, 25;w=1', 'x-ratelimit-remaining': '0', 'x-ratelimit-reset': '1' }
body[0..300]: {"code":-32007,"message":"25/second request limit reached"}
single request OK again after: 1 s (2 polls)

A call counts as “OK” only if the status is 200 and the body has no JSON-RPC error. That is the check that catches Base’s per-item errors. Point it at free endpoints with care, because it sends about 180 requests in a short run. Run it once, not in a loop.

When a keyed endpoint makes sense

A public endpoint is a good fit for a script, a tutorial or a first test. Move to a keyed endpoint when one of these applies:

  • You serve production traffic or a user-facing app. Solana, Base and OP Mainnet say in their docs that their public endpoints are not for that.
  • Your bursts go above a few dozen requests per second.
  • You need WebSocket subscriptions. Base, OP and Arbitrum document none on the public RPC.
  • You need debug or trace methods.
  • You need historical state that must come back the same on every call.
  • You send large batches. The caps we saw were 10 items on OP Mainnet and somewhere between 20 and 50 on Avalanche.
  • You want errors you can predict, with a documented status and body for each.

BoltRPC is one keyed option. This is how it behaves, from our docs page rather than from this test: monthly plans have no rate limit, while the free trial and on-demand usage without a plan are limited to 100 requests per second, burst 200. A 429 carries Retry-After and X-RateLimit-* headers, and a WebSocket connection is closed with code 4029 when your request units run out. We did not burst BoltRPC in this test. See pricing for plans and units and the docs for the error table.

Whether a chain offers debug, trace, WebSocket or archive state on BoltRPC is listed on its network page, for example Solana and Base. Check the page for the chain you need before you rely on a method.

FAQ

What is the rate limit of a free public RPC endpoint?

It depends on the chain, and most do not publish a number. Of the six official endpoints we checked on 7 November 2026, Solana’s docs list 100 requests per 10 seconds per IP and BNB Chain’s list 10K per 5 minutes. Base shows 25 per second in its 429 response headers. OP Mainnet, Arbitrum One and Avalanche C-Chain published none that we found, and a burst of 150 requests with 25 in flight did not throttle them. That is one run from one IP, so it is not a statement about their limits.

What is the Solana public RPC rate limit?

Solana’s docs list 100 requests per 10 seconds per IP, 40 per 10 seconds per IP for a single RPC method, 40 concurrent connections per IP, 40 new connections per 10 seconds per IP and 100 MB of data per 30 seconds. The live response headers on 7 November 2026 showed 150 per method and 250 overall, which do not match the docs, and the docs say the numbers may be out of date. Our burst got 429s with the message “Connection rate limits exceeded” before any request-count message, and the endpoint worked again 9 seconds later.

Does the Base public RPC support WebSocket?

No. Base’s docs say the public endpoints are HTTP only and that WebSocket RPC connections (eth_subscribe, newHeads, logs) are not available on them. We tried wss://mainnet.base.org, which did not open, and a plain HTTP upgrade request returned 405, the same as a plain GET. Avalanche’s documented WebSocket opened from the same machine with the same script, so the failure is the endpoint’s, not our network’s.

Why does a public RPC return 429 or HTTP 200 with errors?

A 429 means you went over a limit, and the body and headers differ by endpoint: Solana sends JSON with retry-after, while Base sends plain text with no Retry-After and no error key. HTTP 200 with errors comes from batches: on Base, a batch of 100 returned HTTP 200 with 75 per-item -32007 errors because each call in a batch counted. Check every item in a batch, not only the HTTP status.

Can I use a public RPC endpoint in production?

The chains’ own docs say not to. Solana: “The public RPC endpoints are not intended for production applications.” Base: “Public endpoints are rate-limited and are not suitable for production traffic.” OP’s docs say its public endpoints are “not suitable for production use”. Arbitrum’s docs call its endpoint useful for “development and low-volume reads” and give no rate-limit guarantees. If real users depend on your app, their own advice points to a keyed or dedicated endpoint.

Next steps

Start your free 14-day trial

Questions we get about this.

Content is for informational purposes only and does not constitute financial, legal or technical advice. Code examples and configurations are provided as-is. Verify against official documentation and test in your own environment before deploying to production.

More from the blog.

All articles →
Ethereum

Glamsterdam Upgrade: What Changes for RPC Users

Glamsterdam brings ePBS and block-level access lists to Ethereum. What changes in JSON-RPC headers, logs and gas estimates, and where the fork stood 7 Nov 2026.

Nov 10, 2026 · 13 min read
Pricing

Alchemy Alternative: What Switching Really Costs

Switching from Alchemy pays off for standard JSON-RPC at volume. A worked 50M-call bill, what you give up and the migration steps.

Oct 24, 2026 · 18 min read
RPC

eth_getLogs Block Range Limits by Chain, Measured

No standard eth_getLogs block range limit exists. We measured 15 chains on 24 October 2026, from 10 blocks to no cap, and what each -32005 error means.

Oct 24, 2026 · 32 min read