RPC Rate Limit Exceeded and How to Stop Hitting It

2026-09-03

RPC Rate Limit Exceeded and How to Stop Hitting It

A wallet stops refreshing, an application hangs on a spinner, and somewhere behind it a message says the rate limit was exceeded. Nothing is wrong with your keys, your balance, or the chain. An operator has decided you asked more questions in a given window than your plan allows, and it is refusing the ones that arrive above that line.

RPC Rate Limit Exceeded and How to Stop Hitting It: key points at a glance

What a rate limit error is actually reporting

An RPC node is a machine that answers questions about the chain on behalf of wallets and applications that do not run their own. Whoever operates that machine also decides how many questions each caller may ask, and enforces the decision by refusing whatever arrives above the ceiling.

RFC 6585 gives that refusal a status code of its own. It states that the 429 status code indicates that the user has sent too many requests in a given amount of time, a condition the section itself labels rate limiting, and adds that the response representations SHOULD include details explaining the condition, and MAY include a Retry-After header indicating how long to wait before making a new request.

Two consequences follow, and both are easy to miss while staring at a broken screen. The refusal is about the caller rather than about the call, so the identical request would have succeeded a moment earlier. And the ceiling is one operator's policy rather than a property of the network, so a second endpoint under a different policy can answer the same request without complaint.

The refusal arrives in more than one envelope

Not every throttled call comes back as an HTTP status. JSON-RPC 2.0 carries errors inside the response body, and its specification reserves the codes -32000 to -32099 for implementation-defined server-errors, which is where a provider's throttling message can sit. The HTTP layer then reports an ordinary success.

Where the refusal lands What it looks like Why it gets missed
HTTP status A 429 response, sometimes carrying a Retry-After header Invisible to a client that only checks whether the connection worked
JSON-RPC body An error object with an implementation-defined server error code The HTTP status is a success, so a status check passes it through
Client wording A stale balance, a spinner, or a generic network failure The wording is written for a person and does not name the layer that refused

Diagnosis therefore starts by deciding which of the three you are looking at. A wallet that says nothing more than that it could not connect is not evidence that no refusal occurred; it is evidence that the wallet did not surface one.

Limits are not always counted in requests

A ceiling expressed in calls per second is only one shape a limit takes. Where an operator weights calls instead of counting them, a heavy method draws more from the same budget than a light one, and the budget empties faster than the call count suggests.

That is why a handful of calls and an exceeded limit can both be accurate descriptions of the same minute. A log query across a wide block range is one call by count and a large withdrawal by weight. Reading the operator's own description of what its budget counts settles this faster than experimenting against it.

Where the request volume actually comes from

Volume accumulates rather than being chosen. It comes from loops nobody thinks of as loops: a screen that re-reads a balance on a timer, a component that re-fetches on every re-render, a background watcher asking whether a transaction has landed yet.

The arithmetic is unforgiving because the interval is small and the session is long. A screen refreshing one balance every second produces 43,200 calls across a twelve-hour session on its own, before any user action is counted. Against a plan that allows 100,000 calls a day, one open tab has taken a large share of the day.

Retries are the second source, and they compound the first. A client that answers each refusal by sending again turns one exceeded ceiling into a stream of them, and it does so at the moment the operator is least willing to serve it.

Retrying without making the refusal worse

Resending at the interval that caused the refusal reproduces the refusal. The correction is to wait longer after each failure rather than the same amount, so the interval grows while the ceiling stays where it is, and to stop after a bounded number of attempts rather than continuing indefinitely.

Add randomness to that wait. Clients refused at the same instant that back off by the same rule return at the same instant too, so the recovery arrives as another burst. Spreading the wait by a random offset breaks that synchronisation, and it costs nothing.

Where a Retry-After header is present, it replaces the guess. The operator has stated how long to wait, and honouring that value is both faster than a schedule you invented and less likely to be counted against you.

Cutting the request count instead of raising the ceiling

Batching is the first reduction, and it belongs to the protocol rather than to any one provider. JSON-RPC 2.0 states that to send several Request objects at the same time, the Client MAY send an Array filled with Request objects, and that the Server should respond with an Array containing the corresponding Response objects. Folding ten calls into one array turns that 43,200-call session into 4,320 requests.

Caching is the second. Values that cannot change between blocks do not need re-reading between blocks: a token's decimals, a contract address, a receipt for a transaction that already settled. Anything final is cacheable indefinitely, and re-reading it is pure spend.

Subscriptions are the third, where the endpoint offers them. Polling asks whether something has changed yet, over and over; a subscription asks once and is told when the answer changes. The two carry the same information and cost very different amounts of budget.

What you observe Where the ceiling actually is What changes it
Refusals under light use A weighted budget spent by heavy methods Narrow the block ranges and split the query
Refusals that multiply after the first Retries resending into the ceiling Back off with a growing, randomised wait
Refusals from one idle tab A polling loop running on a timer Batch, cache, or subscribe instead of polling
Refusals on one network only A policy attached to that endpoint Add a second endpoint for that network

When a retry is not the safe move

Reads and writes are not equally repeatable. Asking for a balance twice costs an extra call and nothing else. Sending a signed transaction twice is a different event, and a refusal at the endpoint does not tell you which side of that boundary the transaction stopped on.

Before you resend, establish whether the first attempt reached the mempool. A transaction the network already holds and a fresh transaction expressing the same intention are not interchangeable, and treating them as one is how a duplicate gets broadcast. On Solana the freshness reference makes the timing explicit: a submission delayed by a long backoff can run into an expired blockhash and need rebuilding rather than resending.

The same caution applies to what your client believes afterwards. A wallet whose reads are being refused is comparing its own count of sent transactions against a view it could not refresh, which is one of the routes to a nonce too high message. The counter is not wrong; the picture it was compared against is missing.

Switching endpoints repairs one cause, not the others

If the ceiling belongs to the operator, moving to another operator moves you under a different ceiling, and switching endpoints is a routine repair for that case. Verify the chain ID on the new entry before routing anything through it, and keep the entry that already worked. What a switch does not reach is anything the chain has already recorded: a transaction that ran and was rolled back is reverted, and every honest endpoint reports that receipt identically.

If the volume belongs to you, the move buys time and nothing else. The same polling loop meets the next operator's ceiling on the same schedule, and rotating between endpoints to stay under several ceilings at once hides the loop rather than fixing it.

A third case is worth separating from both. An endpoint dedicated to your own key is not simply a larger allowance; it also isolates you from other callers sharing a public one, so a refusal you receive afterwards is genuinely yours to explain.

The bottom line

A rate limit error is a statement about how much you asked for, not about whether you were entitled to ask. It names a budget, a window, and an operator, and a useful response starts by identifying which of the three is binding.

Read the refusal where it lands, honour Retry-After when it is offered, and back off with a growing and randomised wait instead of resending on the old schedule. Then reduce the volume rather than chasing a bigger ceiling: batch what can travel together, cache what cannot change, and subscribe instead of polling. An allowance twice the size, consumed by the same loop, runs out on the same afternoon. To keep learning the fundamentals, follow more from Bitbase Academy.

Related reading

Other Bitbase articles on this topic:

- How to Switch RPC Endpoints Safely

- Maximum Transaction Fee Exceeded: What That Wallet Warning Means

- What Is a Crypto QR Code?

- Seed Phrase vs Passphrase: The Difference and Why It Matters

- What Is Blockchain Finality?

Disclaimer: This article is educational content from Bitbase Academy, provided for information only. It does not constitute investment, trading, tax, or financial advice. Crypto assets are volatile; assess your own risk. Written as of September 2026; refer to the latest official information.

References

[1] M. Nottingham and R. Fielding, Additional HTTP Status Codes, RFC 6585, IETF, April 2012 rfc-editor.org

[2] JSON-RPC 2.0 Specification, JSON-RPC Working Group, updated 4 January 2013 jsonrpc.org

Related Articles

More Recommendations