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.

For a read-only batch, the recovery loop is:
- Check that the body is an array. Handle a single error object separately.
- Match responses to requests by
id, regardless of response order. - Save successful results before scheduling retries.
- Retry failed or missing requests with a bounded attempt count and backoff.
- 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.
| Endpoint | Published size guidance |
|---|---|
| Alchemy | Under 50 for reliability. Its HTTP batch limit is 1,000; WebSocket limit is 20. |
| Erigon | Default --rpc.batch.limit is 100. |
| Geth | Default request limit is 1,000 items; maximum batch response is 25 MB. |
| Besu | Default HTTP batch limit is 1,024. |
| QuickNode | No single item limit across EVM endpoints is published in the cited guide. It recommends parallel calls when latency matters. |
| Dwellir | Size 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.

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:
| Provider | Cost of 50 eth_call members |
|---|---|
| Alchemy | 50 × 26 = 1,300 compute units (CU) |
| Infura | 50 × 80 = 4,000 method credits |
| Dwellir | 50 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 path | Calls made | Dwellir credits | Alchemy CU | Infura method credits |
|---|---|---|---|---|
| Send all 50 each time | 50 + 50 + 50 = 150 | 150 | 3,900 | 12,000 |
| Retry the failed ID only | 50 + 1 + 1 = 52 | 52 | 1,352 | 4,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.

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?
| Workload | Practical choice |
|---|---|
Independent eth_call reads | Try a small batch if one round trip helps. Compare it with parallel single calls. |
| Reads that must share one block state | Pin 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 backfill | Split into valid block windows first. Batch only after checking the endpoint's range and response-size limits. |
| A few known transaction hashes | Batch eth_getTransactionReceipt in small groups, then retry missing hashes. |
| Most receipts in one block | Check 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.


