All Blog Posts
JSON-RPC batch sizing and partial failures

JSON-RPC batch sizing and partial failures

By Elias Faltin 7min read

A batch of 50 RPC calls can return HTTP 200 even when one call fails. If your client checks only the status code, it misses that result. If it retries the full batch, it repeats 49 successful calls and pays for them again.

The fix has two parts: size batches for your provider and check every response by id. This guide shows the published limits, the retry cost, and when separate requests are the better choice.

What the response actually tells you

A JSON-RPC batch is an array of requests in one body. The server returns an array of responses, but the specification does not promise the same order. Match each response to its request by id, then check for result or error.

A valid single request with an id gets one response object. A notification has no id and gets no response. Invalid JSON or an empty batch gets a single error object. Your client needs to handle each shape.

Alchemy documents HTTP 200 for batches even when some members fail. The status code confirms delivery of the HTTP response. It does not confirm every RPC result.

An HTTP 200 batch response with successful ids 1 and 3, an error for id 2, and an instruction to retry only id 2.

For a read-only batch, the recovery loop is:

  1. Check that the body is an array. Handle a single error object separately.
  2. Match responses to requests by id, regardless of response order.
  3. Save successful results before scheduling retries.
  4. Retry failed or missing requests with a bounded attempt count and backoff.
  5. Alert on RPC errors even when the HTTP status is 200.

Use unique IDs within each batch. An id correlates a response; it does not make a database write idempotent. If a timeout hides the outcome of a transaction submission, reconcile it by its transaction hash. Key your own stored work by a stable job and work-item identifier, not by a reusable JSON-RPC id.

Choose a batch size for the endpoint

The largest accepted batch is rarely the best starting size. A slow member holds up the full response, and large bodies increase timeout and retry costs. QuickNode recommends parallel single requests for latency-sensitive EVM workloads.

These published values apply as of 23 September 2026. Node defaults can change with configuration.

EndpointPublished size guidance
AlchemyUnder 50 for reliability. Its HTTP batch limit is 1,000; WebSocket limit is 20.
ErigonDefault --rpc.batch.limit is 100.
GethDefault request limit is 1,000 items; maximum batch response is 25 MB.
BesuDefault HTTP batch limit is 1,024.
QuickNodeNo single item limit across EVM endpoints is published in the cited guide. It recommends parallel calls when latency matters.
DwellirSize for the workload and the account's response-per-second limit. Each member counts against that limit.

Sources: Alchemy batches, Alchemy WebSocket limit, Erigon RPC daemon, Geth configuration, Besu JSON-RPC, QuickNode guidance, and Dwellir rate limits.

Published batch sizing guidance: Alchemy under 50; Erigon default 100; Geth default 1,000; Besu default 1,024.

Start below the smallest limit in your path. Measure response size, timeout rate, and latency before raising the size. Do not apply Alchemy's advice as a universal limit for every provider.

Dwellir has one extra constraint for log queries. If any eth_getLogs member violates the plan's block-range limit, Dwellir rejects the entire batch before forwarding. No member executes. Validate log windows before combining them with other calls.

A batch still bills its members

One POST does not mean one billable call. For 50 eth_call requests, the published method rates give this comparison:

ProviderCost of 50 eth_call members
Alchemy50 × 26 = 1,300 compute units (CU)
Infura50 × 80 = 4,000 method credits
Dwellir50 API credits

Alchemy lists 26 CU for eth_call. Infura lists 80 credits for eth_call. Dwellir counts each batch member as one response, with no method multiplier. These are usage units, not dollar prices. Compare the plans and allowances before converting them to money.

Method weights change the comparison. Alchemy lists eth_getLogs at 60 CU and a receipt lookup at 20 CU. Infura lists those methods at 255 and 80 credits. Dwellir charges one API credit per response for those methods, subject to its log-range rules.

Retry the failed IDs, not the array

Suppose one call in a 50-member eth_call batch returns a transient error twice, then succeeds on attempt 3. Both paths below reach the same successful result in three total attempts.

Retry pathCalls madeDwellir creditsAlchemy CUInfura method credits
Send all 50 each time50 + 50 + 50 = 1501503,90012,000
Retry the failed ID only50 + 1 + 1 = 52521,3524,160

The second path avoids 98 repeated calls. The table assumes every call is billed at its published method rate. Actual charges for failed requests can depend on the provider and error type, so use it to compare retry shapes rather than predict an invoice.

Three attempts for a 50-call batch: retrying all members uses 150 Dwellir credits; retrying one failed member uses 52.

A missing response is different from a returned error. After a timeout, you may not know which requests ran. Retrying reads is generally safe. For writes or transaction submissions, reconcile the outcome before resending. Keep transaction submissions separate from large read batches.

Batch or split the workload?

WorkloadPractical choice
Independent eth_call readsTry a small batch if one round trip helps. Compare it with parallel single calls.
Reads that must share one block statePin every call to the same block, or use Multicall3 when one on-chain execution is required. A JSON-RPC batch alone does not make calls atomic.
eth_getLogs backfillSplit into valid block windows first. Batch only after checking the endpoint's range and response-size limits.
A few known transaction hashesBatch eth_getTransactionReceipt in small groups, then retry missing hashes.
Most receipts in one blockCheck eth_getBlockReceipts support and compare cost and latency. On Infura, 1,000 credits beats 80 credits per receipt at 13 or more receipts.

The Dwellir log-range guide lists plan limits. On Dwellir, a valid log window costs one response credit, so splitting a wide scan into more windows increases the number of credits. For Infura's receipt comparison, 12 individual lookups cost 960 credits and 13 cost 1,040. A block-level request can still win below that threshold if latency matters.

Before you ship

Set a batch size per endpoint. Log both HTTP status and per-ID RPC errors. Check every response by id, save successes, and retry only failed reads. Track timeouts separately because they leave completion uncertain. Test the billing and latency of your actual method mix before making batching the default.

Dwellir bills each batch member as one API response, including trace and debug methods. See shared node pricing to compare allowances, or create a free account to test the request shape on your endpoint.

read another blog post