Logo
New RPC users get 35% off their first monthView the offer
RPC Assistant

Why does my Python Solana JSON-RPC call return 429 Too Many Requests?

Summary

A 429 from a Solana JSON-RPC endpoint means the provider is throttling your request rate, not that your JSON payload is malformed. Python clients hit this most often when they poll in tight loops, batch too aggressively, or share one public endpoint across many workers. The fix is usually a mix of client-side backoff, request shaping, and moving to an endpoint with capacity that matches your workload. This article shows how to read the error, apply retries and jitter in Python, reduce request volume, and decide when a shared or dedicated endpoint is the right next step.

A 429 response from a Solana JSON-RPC endpoint is a rate-limit signal from the provider, not a problem with your JSON body. The server understood your request and chose to reject it because you exceeded an allowance tied to your API key, IP, or plan. In Python this usually appears as requests.exceptions.HTTPError: 429 Client Error: Too Many Requests or as a JSON-RPC error object with code -32429 on some providers. The practical question is not "what does 429 mean" but "what should I change next" — backoff, request shaping, or a different endpoint.

Diagnose first: is it rate limiting, bad JSON, or a dead endpoint?

Before you rewrite code, confirm what you are actually hitting. The three failures look similar in logs but need different fixes.

Symptom in PythonLikely causeFirst thing to check
HTTP 429 with Retry-After or rate-limit headersProvider throttlingRequests per second and burst size
JSON-RPC error -32600 / -32700Malformed JSON or bad batchjson.dumps output and content type
Timeouts, connection resets, 5xxEndpoint or network issueEndpoint URL, DNS, provider status
429 only under concurrencyShared key or IP across workersHow many processes share one endpoint
429 after a burst of getProgramAccountsHeavy method costMethod mix, not just request count

A quick probe with curl isolates the endpoint from your Python code:

curl -s -o /dev/null -w "%{http_code}\n" \
  -X POST https://solana.api.onfinality.io/public \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"getHealth"}'

If a single request returns 200 but your script returns 429, the problem is your request pattern, not the endpoint. If even a single request returns 429, you are sharing a throttled key or IP with other traffic.

Reading the 429 response body correctly

Many Solana providers return a JSON-RPC error object rather than a plain HTTP error. Parse both layers in Python so you do not swallow the real cause.

import requests

ENDPOINT = "https://solana.api.onfinality.io/public"
payload = {"jsonrpc": "2.0", "id": 1, "method": "getSlot"}

resp = requests.post(ENDPOINT, json=payload, timeout=10)

if resp.status_code == 429:
    retry_after = resp.headers.get("Retry-After")
    print("Throttled. Retry-After:", retry_after)
    print("Body:", resp.text[:300])
else:
    data = resp.json()
    if "error" in data:
        print("JSON-RPC error:", data["error"])
    else:
        print("Slot:", data["result"])

Log the status code, the Retry-After header when present, and the JSON-RPC error code. That combination tells you whether the limit is per-second, per-minute, or method-weighted.

Fix 1: exponential backoff with jitter in Python

Retrying immediately makes throttling worse. Use exponential backoff with jitter, and cap the number of attempts so a persistent 429 fails loudly instead of hanging.

import random
import time
import requests

ENDPOINT = "https://solana.api.onfinality.io/public"

def rpc_call(method, params=None, max_attempts=5):
    payload = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params or []}
    for attempt in range(max_attempts):
        resp = requests.post(ENDPOINT, json=payload, timeout=10)
        if resp.status_code == 429:
            retry_after = resp.headers.get("Retry-After")
            wait = float(retry_after) if retry_after else min(2 ** attempt, 30)
            wait += random.uniform(0, 0.5)  # jitter
            time.sleep(wait)
            continue
        resp.raise_for_status()
        data = resp.json()
        if "error" in data:
            raise RuntimeError(data["error"])
        return data["result"]
    raise RuntimeError("Exceeded retry attempts after repeated 429s")

Jitter matters when many workers retry at once. Without it, your processes synchronize and re-trigger the same limit.

Fix 2: reduce request volume before you change providers

Throttling is often a symptom of inefficient polling. Before upgrading infrastructure, check whether you can send fewer, better requests.

  • Batch independent calls. Solana JSON-RPC accepts arrays of requests. Batch getSlot, getBalance, and getAccountInfo calls that do not depend on each other. Keep batches modest — very large batches can be rejected or time out.
  • Use WebSocket subscriptions instead of polling. slotSubscribe, accountSubscribe, and logsSubscribe push updates over wss://solana.api.onfinality.io/public-ws and remove the need for tight polling loops.
  • Cache stable data. Token metadata, program IDs, and epoch info change slowly. Cache them instead of re-fetching per request.
  • Avoid unbounded getProgramAccounts. These are expensive. Filter with dataSlice and filters so the node does less work per call.
  • Stagger startup bursts. Services that fire hundreds of calls on boot look like an attack to a rate limiter. Add a small ramp-up delay.

Here is a batched call pattern:

batch = [
    {"jsonrpc": "2.0", "id": 1, "method": "getSlot"},
    {"jsonrpc": "2.0", "id": 2, "method": "getVersion"},
    {"jsonrpc": "2.0", "id": 3, "method": "getHealth"},
]
resp = requests.post(ENDPOINT, json=batch, timeout=10)
for item in resp.json():
    print(item.get("id"), item.get("result") or item.get("error"))

Fix 3: separate keys and workers

If several services, containers, or CI jobs share one API key or one public IP, they compete for the same allowance. Give each workload its own key where the provider supports it, and avoid running test suites against the same endpoint as production traffic. A single runaway loop can throttle everything behind the same key.

When to move off a shared public endpoint

Client-side fixes have limits. If you have already batched, cached, and backed off, and you still hit 429 during normal traffic, the endpoint's capacity no longer matches your workload. At that point the decision is about endpoint type, not code.

SituationShared public endpointDedicated node
Prototyping, low request volumeUsually fineOverkill
Steady production traffic with burstsMay throttle under loadPredictable capacity
Heavy getProgramAccounts or archive queriesOften throttledBetter suited
Strict latency or isolation needsShared resourcesIsolated resources
Multiple teams on one keyContention riskCleaner separation

OnFinality provides Solana RPC through both a shared RPC API service and dedicated nodes when you need isolated capacity. You can review the Solana network page for supported transports and endpoint details, and compare plans on RPC pricing. If you are still evaluating options, the provider selection guide covers the criteria that matter for production workloads.

A minimal production-ready Python client

Combine backoff, batching, and a session with connection pooling. Reuse the session so you are not opening a new TCP connection per call.

import random
import time
import requests

class SolanaRPC:
    def __init__(self, endpoint, max_attempts=5):
        self.endpoint = endpoint
        self.max_attempts = max_attempts
        self.session = requests.Session()

    def call(self, method, params=None):
        payload = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params or []}
        for attempt in range(self.max_attempts):
            resp = self.session.post(self.endpoint, json=payload, timeout=10)
            if resp.status_code == 429:
                wait = float(resp.headers.get("Retry-After", min(2 ** attempt, 30)))
                time.sleep(wait + random.uniform(0, 0.5))
                continue
            resp.raise_for_status()
            data = resp.json()
            if "error" in data:
                raise RuntimeError(data["error"])
            return data["result"]
        raise RuntimeError("Repeated 429 responses")

rpc = SolanaRPC("https://solana.api.onfinality.io/public")
print(rpc.call("getSlot"))

Monitoring so 429s do not surprise you

Track throttle rate as a first-class metric, not just an error count. Useful signals:

  • 429 responses per minute, split by method.
  • Retry attempts per successful call.
  • p95 and p99 latency for the heaviest methods.
  • WebSocket reconnect frequency if you use subscriptions.

Alert when 429s rise during normal traffic, not only during known spikes. A slow upward trend usually means your workload grew past the endpoint's allowance.

Key Takeaways

  • A Solana 429 is a provider rate-limit signal, not a JSON formatting error.
  • Parse both the HTTP status and the JSON-RPC error body in Python.
  • Use exponential backoff with jitter and a retry cap.
  • Reduce volume with batching, WebSocket subscriptions, caching, and filtered getProgramAccounts.
  • Separate API keys and workers so one loop cannot throttle everything.
  • If throttling persists after client-side fixes, evaluate a shared or dedicated endpoint that matches your workload.

Frequently Asked Questions

Does a 429 mean my JSON is invalid?

No. Invalid JSON usually returns a parse error such as -32700. A 429 means the request was understood but rejected due to rate limits.

How long should I wait before retrying?

Honor the Retry-After header when present. Otherwise start with a short delay and increase it exponentially, capped at a sensible maximum, with random jitter.

Can batching make 429s worse?

Large batches can count as many requests or exceed payload limits. Keep batches modest and split heavy methods into separate calls.

Should I switch providers the moment I see a 429?

No. First confirm it is not a client-side pattern issue. If throttling continues under normal, optimized traffic, then compare endpoint capacity and consider a dedicated node.

Do WebSocket subscriptions avoid 429s?

They reduce polling, which lowers request volume, but they have their own connection limits. Use them alongside backoff, not instead of it.

RPC Knowledge Base

Related RPC details

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started