API Reference

BSC RPC Docs

Standard Ethereum JSON-RPC with BSC semantics. Use any JSON-RPC client: ethers, web3.py, viem, alloy, go-ethereum, whatever you have. No custom SDK required.

Quick start

Endpoints

HTTP
https://va-bsc-01.streamsuite.io/?key=YOUR_API_KEY

Standard JSON-RPC POST. Methods go in the body. The API key can be sent in the query string (?key=...) or as a bearer header (Authorization: Bearer ...) — both are equivalent. The query form is fine for quick testing; the header form keeps the key out of access logs and URL-scrubbing tools (curl history, CI logs, observability dashboards).

WebSocket
wss://va-bsc-01.streamsuite.io/ws?key=YOUR_API_KEY

Persistent connection for subscriptions. Use eth_subscribe with one of the supported channels below.

5-second curl

Sanity-check it works

Paste any of these into a terminal. No SDK install required. If your first call doesn't return in <100ms (plus your network distance to Ashburn, VA), something's wrong — tell us.

curl
# Latest block — sub-millisecond server-side; total = our processing time + your RTT to Ashburn, VA
curl -X POST 'https://va-bsc-01.streamsuite.io/?key=YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

# Batched call (RPC supports JSON arrays — processed in parallel server-side)
curl -X POST 'https://va-bsc-01.streamsuite.io/?key=YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '[
    {"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1},
    {"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":2},
    {"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":3}
  ]'

# Bearer auth (equivalent to ?key=, keeps key out of URL logs)
curl -X POST 'https://va-bsc-01.streamsuite.io/' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

# Gzipped response — ~80% smaller on full-block / large-log queries
curl --compressed -X POST 'https://va-bsc-01.streamsuite.io/?key=YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",true],"id":1}'

Switching from another provider

Migration is a URL swap

We expose plain JSON-RPC. There's no proprietary protocol, custom SDK, or compute-unit math. If you're currently paying for BSC RPC somewhere else, the migration is one line of code.

From Alchemy
- https://bnb-mainnet.g.alchemy.com/v2/KEY
+ https://va-bsc-01.streamsuite.io/?key=KEY

No compute units, no per-method pricing tiers. Flat rate.

From QuickNode
- https://...quiknode.pro/KEY/
+ https://va-bsc-01.streamsuite.io/?key=KEY

No request-rate billing. Unlimited subs included.

From Chainstack
- https://bsc-mainnet.core.chainstack.com/KEY
+ https://va-bsc-01.streamsuite.io/?key=KEY

Dedicated bare-metal slot instead of shared pool.

All standard methods (eth_*, net_*, web3_*) work identically. Tier-gated extras (txpool_*, debug_*) are listed in the methods table below. WebSocket subscription channel names match standard go-ethereum: newHeads, logs, newPendingTransactions.

Methods by tier

What's included

MethodReal-TimeMempoolFull Node
eth_blockNumber#
eth_getBlockByNumber / Hash#
eth_getTransactionByHash#
eth_getTransactionReceipt#
eth_getBalance#
eth_getCode#
eth_getStorageAt#
eth_call#
eth_estimateGas#
eth_sendRawTransaction#
eth_getLogs#
eth_gasPrice / maxPriorityFeePerGas#
net_version / web3_clientVersion#
txpool_content#-
txpool_inspect#-
txpool_status#-
debug_traceTransaction#--

WebSocket subscriptions

eth_subscribe channels

ChannelReal-TimeMempoolFull Node
newHeads
logs
newPendingTransactions-
// Raw eth_subscribe over WebSocket
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_subscribe",
  "params": ["newHeads"]
}

// Logs with topic filter
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "eth_subscribe",
  "params": ["logs", {
    "address": "0x...",
    "topics": ["0xddf252ad..."]
  }]
}

// Pending tx hashes (Mempool and Full Node tiers) — hashes only
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "eth_subscribe",
  "params": ["newPendingTransactions"]
}

// Pending tx FULL OBJECTS (Mempool and Full Node tiers) — pass true as second param
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "eth_subscribe",
  "params": ["newPendingTransactions", true]
}

Code examples

Connect from anywhere

import { createPublicClient, http, webSocket } from 'viem';
import { bsc } from 'viem/chains';

const HTTP_URL = 'https://va-bsc-01.streamsuite.io/?key=YOUR_API_KEY';
const WS_URL   = 'wss://va-bsc-01.streamsuite.io/ws?key=YOUR_API_KEY';

// HTTP
const client = createPublicClient({ chain: bsc, transport: http(HTTP_URL) });
const block = await client.getBlockNumber();
console.log('Head block:', block);

// WebSocket: watch new blocks
const ws = createPublicClient({ chain: bsc, transport: webSocket(WS_URL) });
const unwatch = ws.watchBlocks({
  onBlock: (b) => console.log('newHead', b.number),
});

// Mempool tier+: subscribe to pending transactions
const unwatchPending = ws.watchPendingTransactions({
  onTransactions: (hashes) => console.log('pending:', hashes.length, 'tx'),
});

WebSocket reconnection

Reconnect & resubscribe pattern

The connection will drop occasionally — network blips, server restarts, idle timeouts. Our 30-minute stress test recorded 0 WS reconnects, but production is messy. Bots that don't handle reconnect silently stop receiving events. Pattern below: exponential backoff, resubscribe on reconnect, reset backoff on success.

TypeScript · ws
// Production-ready WebSocket pattern: exponential backoff + resubscribe.
// The connection WILL drop occasionally (network blip, server restart,
// nginx idle timeout). Your bot's job is to reconnect transparently.

import WebSocket from 'ws';

const WS_URL = 'wss://va-bsc-01.streamsuite.io/ws?key=YOUR_API_KEY';
let backoffMs = 250;
const MAX_BACKOFF = 30_000;

function connect() {
  const ws = new WebSocket(WS_URL);

  ws.on('open', () => {
    backoffMs = 250;                 // reset backoff on successful connect
    // resubscribe to whatever channels your bot needs
    ws.send(JSON.stringify({
      jsonrpc: '2.0', id: 1, method: 'eth_subscribe',
      params: ['newHeads'],
    }));
  });

  ws.on('message', (data) => {
    const msg = JSON.parse(data.toString());
    if (msg.method === 'eth_subscription') {
      handleEvent(msg.params.result);   // your handler
    }
  });

  ws.on('close', (code, reason) => {
    console.warn('ws closed', code, reason.toString(), 'retry in', backoffMs);
    setTimeout(connect, backoffMs);
    backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF);
  });

  ws.on('error', (err) => {
    console.error('ws error', err.message);
    // close handler will fire next; don't double-reconnect here
  });
}

function handleEvent(_evt: unknown) { /* your bot logic */ }
connect();

ethers' WebSocketProvider and viem's webSocket transport both have built-in reconnection but the resubscribe semantics vary by version. If you're subscribing to anything stateful, prefer the manual pattern above — you control exactly what gets re-armed.

BSC-specific

Things BSC does differently than Ethereum

BSC is a geth fork with significant changes. If you're coming from ETH mainnet experience, read these once.

Block time: ~440ms (not 12s).

Eight blocks per validator turn, 13 validators in active rotation. If you poll eth_blockNumber instead of subscribing to newHeads, you'll burn requests AND lag the chain. Use the subscription.

Fast finality (~3 blocks).

BSC has BEP-126 fast finality. After a tx is 3 blocks deep (~1.3 sec) it's finalized and reorgs are vanishingly rare. For most apps you can treat the head as final after one or two blocks of confirmation; for high-value flows, wait 3.

Gas floor: 0.05 Gwei (not 1 Gwei).

As of 2026, the BSC validator-accepted gas-price floor is 0.05 Gwei. eth_gasPrice returns a conservative default (typically 1 Gwei) for backward compatibility — if you blindly use it for tx submission, you'll overpay 20×. Set the gas price yourself based on observed inclusion patterns.

Mempool visibility is structurally lower than ETH.

Major BSC validators (notably 48Club) run private order flow — a significant fraction of value-extracting txs never appear in the public mempool. txpool_content /pendingTransactions WS sub shows you the public pool, which is real and useful but not the whole picture. If MEV opportunities depend on private flow, you need a relationship with a validator, not just better RPC.

debug_traceTransaction is chain-tip only (~128 blocks, ~60 sec).

Trace state is pruned aggressively. Tracing a tx within the last ~60 seconds works; older than that returns required historical state unavailable (reexec=128). Cache traces at chain tip if you need them later, or use a callTracer variant that doesn't need full state. Deeper trace history is the Archive add-on (ask).

No EIP-1559 base-fee on BSC.

eth_maxPriorityFeePerGas returns the same value as eth_gasPrice because BSC has no base-fee. Don't try to build a 1559 maxFee / priorityFee split — just set legacy gasPrice.

tx receipts & logs: ~4.5d history.

The node retains 900,000 blocks of receipts and logs (~4.5 days). Older queries return transaction indexing is in progress. If you need deeper history, batch your scans within the retention window or ask about Archive.

Rate limits

None on request rate.

Unlimited request rate, unlimited WebSocket subscriptions, no compute-unit math. We cap operators per node instead (10 max per colocation group), so you never compete with someone else's polling loop. When a group fills we provision a new bare-metal server — existing customers stay on their original group and aren't rebalanced.

Connection-level guards (not usage limits):

TierConcurrent (HTTP + WSS)
Real-Time10
Mempool20
Full Node30
  • Cap counts HTTP and WebSocket connections together. A single bot with sensible HTTP pooling and 1-2 WSS subscriptions uses ~5-11 connections — comfortably below every cap.
  • Hit the cap with a real workload? You're likely running multiple bots or services from one key. Buy another slot (or request a dedicated server, which lifts the cap entirely — not a published tier, contact us).
  • 100 concurrent connections per source IP (HTTP + WSS combined). Anti-abuse cap, not usage. Independent of the per-key tier cap above.

Node details

What you're connecting to

Chain
BNB Smart Chain mainnet (chainId 56)
Client
BSC full node, pruning mode
Block time
~440ms (fast finality)
Location
Ashburn, VA. Tier-III datacenter (N+1 power & cooling)
Hardware
Bare-metal dedicated server, NVMe storage
Network
15+ carriers on-net (Verizon, Lumen, AT&T, Cogent, Zayo, ...)
Compliance
Facility: SOC 2 Type II, ISO 27001:2022, HIPAA, PCI-DSS
Block headers
All (back to genesis)
Transaction history
~4.5 days (900k blocks)
Log history
~4.5 days (900k blocks)
State history
Latest only (no archive)
Trace history
Last ~128 blocks (~60 sec) — chain-tip only, Full Node tier

Errors

What to expect when something is off

HTTPBodyMeaning & fix
401{"error":{"code":-32000,"message":"invalid or missing API key"}}Key missing, malformed, or revoked. Check for whitespace; if you just rotated, wait 5s for edge sync; if cancelled subscription, reactivate via dashboard.
403{"error":{"code":-32601,"message":"method <NAME> requires Mempool tier or higher"}}Method exists on the node but isn't included in your tier (e.g. txpool_* on Real-Time, debug_* on Real-Time or Mempool). Upgrade tier, or use an alternative method. Blocked-for-safety methods (admin_*, personal_*, miner_*) return "method <NAME> is not available" regardless of tier.
429{"error":{"code":-32005,"message":"connection limit exceeded for this API key"}}Connection cap hit. Either you exceeded your tier's concurrent-connection cap (Real-Time 10 / Mempool 20 / Full Node 30, HTTP + WSS combined) or the 100-per-source-IP anti-abuse cap. EIP-1474 code -32005 means "limit exceeded" — ethers / viem / web3.py surface this as a parseable error rather than failing on response decode. Pool subscriptions, distribute load across IPs, or request a dedicated server.
200{"error":{"code":-32000,"message":"does not exist/is not available"}}geth doesn't expose this method (e.g. parity-style trace_*). Use the equivalent debug_* method instead.
200{"error":{"code":-32000,"message":"transaction indexing is in progress"}}Querying a transaction or receipt older than our 4.5-day retention window. Re-query with a more recent tx, or upgrade to Full Node + ask us about an archive add-on.

Best practices

Batch where you can

JSON-RPC supports sending an array of calls per HTTP POST. We process them in parallel internally. Cuts round trips meaningfully for indexers.

Use WebSocket subscriptions instead of polling

For block triggers, use eth_subscribe newHeads rather than polling eth_blockNumber every second. Same data, near-zero overhead.

Chunk eth_getLogs into ≤1000-block ranges

Log queries scan blocks and are I/O-sensitive — our 30-min stress test (see /benchmarks) shows eth_getLogs p50 stays under 1ms but the p99 tail ranges 40–300ms at 12–20 concurrent clients on a 50-block window. Wider ranges and higher concurrency push it further. For live event tracking, use eth_subscribe logs with a topic filter (constant-cost regardless of chain depth). For historical scans, chunk by 1000 blocks max and parallelize 3–5 chunks at a time. Don't request a single 500k-block window — you'll get the wide tail every time and may time out.

Ask for gzipped responses

Send Accept-Encoding: gzip and we'll return gzipped response bodies. Measured ~80% size reduction on full-block responses (e.g. eth_getBlockByNumber latest true) and similar on large eth_getLogs results. Most HTTP clients (ethers, web3.py, viem, alloy, curl --compressed) auto-set this header and auto-decompress.

Don't hard-code the key in client code

Keep it server-side. If it leaks, rotate from the dashboard — old key invalidates within 5 seconds.

Co-locate if your bot needs <10ms RTT

From a VPS in our datacenter, you'll see <1ms; from Frankfurt it's ~80ms. Ask about colocation for sub-1ms latency.

Ready to plug in?

Pick a tier, pay, get a key in seconds. No demo call required.