Docs
Method support, rate limits, error codes, and the routing model behind Azul. Public traffic needs no key. Privileged keys raise every cap below.
Copy the URL, make your first request.
No key, no signup. Point any client at the endpoint and call eth_chainId, you should get back 0x2105 (decimal 8453).
https://rpc.baseazul.devcurl -X POST https://rpc.baseazul.dev \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'# => {"jsonrpc":"2.0","id":1,"result":"0x2105"}import { createPublicClient, http, fallback } from 'viem';import { base } from 'viem/chains';// Azul first; transparently falls back to the public RPC if it ever blips.export const client = createPublicClient({ chain: base, transport: fallback( [http('https://rpc.baseazul.dev'), http('https://mainnet.base.org')], { rank: true }, // poll latency, keep the fastest path on top ),});const tip = await client.getBlockNumber(); // => latest Base block (bigint)import { JsonRpcProvider } from 'ethers';const provider = new JsonRpcProvider('https://rpc.baseazul.dev');const tip = await provider.getBlockNumber();// => latest Base block height, e.g. 21000000 (number)import { createConfig, http } from 'wagmi';import { base } from 'wagmi/chains';export const config = createConfig({ chains: [base], transports: { [base.id]: http('https://rpc.baseazul.dev'), },});from web3 import Web3w3 = Web3(Web3.HTTPProvider("https://rpc.baseazul.dev"))tip = w3.eth.block_number# => latest Base block height, e.g. 21000000The endpoint#
One HTTPS URL serves all standard JSON-RPC methods. Public traffic does not require an API key. Privileged keys exist for higher limits and access to restricted namespaces.
The endpoint is standard JSON-RPC 2.0. Content-Type must be application/json. Body is a single request object or an array (batch). Responses are gzip/zstd compressed when the client advertises support.
Method support#
All standard eth_*, net_*, and web3_* methods are available on the public path. Restricted namespaces are blocked unless you authenticate with an API key.
| Field | Value | Support |
|---|---|---|
eth_* | All standard reads, writes, filters | Full support |
net_* | — | Full support |
web3_* | — | Full support |
eth_sendRawTransaction | Submission optimized for lowest inclusion latency | Full support |
eth_getLogs | Up to 5,000-block window per call | Full support |
debug_* | Blocked on public path. Available with API key. | Not available on the public path |
trace_* | Parity-style traces not exposed. | Not available on the public path |
txpool_* | Blocked on public path. | Not available on the public path |
Need debug_* or trace_* for production analytics? Request an API key.
Rate limits#
Per-IP limits on anonymous traffic. API-key holders bypass all of these.
| Field | Value |
|---|---|
Standard methods | 100 req/s sustained · burst 400 |
Heavy methods | 10 req/s sustained. eth_getLogs, large eth_call |
Batch size | 50 requests per batched call |
Max response body | 50 MB · 413 if exceeded |
Daily egress (anon) | 1 TB raw JSON / day · 503 once exhausted |
API key | Bypasses every cap above |
-32005 with a Retry-After header. Back off with jitter; don’t spin. See pricing and when you need a key →Error codes#
JSON-RPC errors follow standard codes. HTTP-level errors come back as raw responses.
| Code | Trigger |
|---|---|
-32000 | Method blocked on public path (restricted namespace). |
-32005 | Rate limit exceeded. Back off or use an API key. |
-32601 | Method not found on the underlying node. |
-32602 | Invalid params. eth_getLogs window > 5,000 blocks is the usual culprit. |
-32603 | Internal error / routing temporarily unavailable. |
HTTP 413 | Response too large (>50 MB). Trim your request. |
HTTP 503 | Daily egress quota exhausted (anonymous only). Use a key. |
Performance#
Server-side latency from us-east-1. /status owns the live proof; these are the published reference values.
- 4 ms
- p50 · median
- 15 ms
- p95 · tail
- ~1s
- Block tip
server-side
server-side
behind the head
Batching#
Batch up to 50 requests in a single POST. Useful for fan-out reads (multicall warmup, ENS resolution, parallel eth_call). One TLS handshake, lower tail latency.
[ {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}, {"jsonrpc":"2.0","method":"eth_getBalance","params":["0xabc…","latest"],"id":2}, {"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":3}][ {"jsonrpc":"2.0","id":1,"result":"0x1414958"}, {"jsonrpc":"2.0","id":2,"result":"0x2386f26fc10000"}, {"jsonrpc":"2.0","id":3,"result":"0x2105"}]eth_getLogs window#
Max range is 5,000 blocks per call. Wider ranges return -32602. Page through history by chunking the range and parallelizing requests, see Page through eth_getLogs.
Request an API key#
One key raises every cap on this page and opens restricted namespaces (debug_*, trace_*). Tell us your workload and we’ll set you up, a free key for normal development, no strings. (WebSocket subscriptions and streaming feeds are Edge early access; if you’re a fill-sensitive desk, see Edge below.)
Authenticate
Once you have a key, present it in the X-API-Key request header (or ?api_key= on the WebSocket URL):
curl -s https://rpc.baseazul.dev \ -H 'content-type: application/json' \ -H 'X-API-Key: YOUR_KEY' \ --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'Add a fallback#
Azul is single-region. For production, pair it with a paid provider. viem has a built-in fallback transport: the first URL wins, and the second takes over on error or timeout.
import { createPublicClient, http, fallback } from 'viem';import { base } from 'viem/chains';export const client = createPublicClient({ chain: base, transport: fallback([ http('https://rpc.baseazul.dev'), // Azul, primary, fast, no logging http('https://mainnet.base.org'), // Coinbase default, last resort ], { retryCount: 1, retryDelay: 100 }),});Subscribe to new blocks#
Don’t poll eth_blockNumber in a tight loop. WebSocket subscriptions are Edge early access. If your workload depends on live tip-follow, request a key and we’ll bring you in. Here is the shape it takes:
import { createPublicClient, webSocket } from 'viem';import { base } from 'viem/chains';const client = createPublicClient({ chain: base, transport: webSocket('wss://rpc.baseazul.dev/ws?api_key=YOUR_KEY'),});const unwatch = client.watchBlockNumber({ onBlockNumber: (n) => console.log('new tip', n),});Page through eth_getLogs#
Chunk wide ranges into 5,000-block windows and parallelize. Concurrent batches are accepted up to your per-IP cap.
async function getLogsRange(client, address, fromBlock, toBlock) { const PAGE = 5000; const tasks = []; for (let start = fromBlock; start <= toBlock; start += PAGE) { const end = Math.min(start + PAGE - 1, toBlock); tasks.push(client.getLogs({ address, fromBlock: start, toBlock: end })); } return (await Promise.all(tasks)).flat();}CORS#
The endpoint sets Access-Control-Allow-Origin: *. Browser fetch works from any origin. Preflight (OPTIONS) responds with 204 and a 24-hour max-age.
Routing model#
Azul picks a propagation path to the Base sequencer for every request, chosen for low latency and reliable inclusion. Reads and writes use separate paths, each tuned to its own workload.
The result is fast inclusion and failover that holds up at one transaction or a thousand.
Sandwich resistance#
Sandwich attacks skim value from your trades. They need a window: a pending interval during which a searcher can see your transaction and wedge their own ahead of it.
Azul minimizes that window. Transactions are not parked anywhere inspectable for longer than necessary; the routing prioritizes getting them included over making them visible.