Summary
Solana's RPC API is the JSON-RPC interface your app uses to read accounts, submit transactions, and subscribe to on-chain events. This reference walks through the endpoint shape, the method groups you will actually call, and how to debug the errors that show up in production.
It also covers when a public endpoint is enough and when a dedicated Solana node from OnFinality makes more sense for consistent throughput, WebSocket subscriptions, and heavier read workloads.
Solana does not expose a REST API for chain data. Everything your app reads or writes goes through a single JSON-RPC endpoint, which means the endpoint you pick and the methods you call shape your latency, your error rate, and your bill. This page is a working reference for that interface: how requests are shaped, which methods matter, how subscriptions behave, and how to debug the failures you will hit once real traffic arrives.
Which Solana endpoint should you connect to?
Start by matching the endpoint to the job. A quick script, a hackathon demo, or a read-only dashboard can usually run against a public endpoint. A wallet, a trading bot, an indexer, or anything that fans out many concurrent reads will feel the difference between shared and dedicated capacity almost immediately.
| Your situation | Sensible starting point | Why |
|---|---|---|
| Prototyping, one-off scripts, learning the API | Public Solana endpoint | No setup, fine for low request volume |
| Testing program logic before mainnet | Solana Devnet RPC | Free SOL from a faucet, safe to break things |
| Wallet or dApp with steady user traffic | Managed Solana RPC | Predictable capacity without running a validator |
| Indexer, bot, or high read fan-out | Dedicated Solana node | Isolated throughput and your own WebSocket capacity |
| Need historical account or transaction state | Archive-capable node | Standard nodes prune older ledger data |
OnFinality runs Solana mainnet RPC over both HTTP and WebSocket, so you can point a single provider at both your request/response calls and your subscription calls. If you are still weighing providers, the RPC provider selection guide covers the evaluation criteria in more depth.
Endpoint shape and a first request
A Solana RPC endpoint is a single URL that accepts HTTP POST requests with a JSON body. There is no path per method; the method name lives in the body. The public OnFinality Solana endpoint is:
https://solana.api.onfinality.io/public
A minimal call to fetch the current slot looks like this:
curl https://solana.api.onfinality.io/public \
-X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getSlot",
"params": []
}'
The response follows the JSON-RPC 2.0 envelope: a jsonrpc field, an id that echoes your request, and either a result or an error object. Every Solana method uses that same envelope, so once your client handles it correctly you can call any method without changing transport code.
Method groups you will actually call
Solana's method list is long, but production apps cluster around a handful of groups. Knowing which group a method belongs to tells you how expensive it is and how it fails.
| Group | Representative methods | Typical use | Cost profile |
|---|---|---|---|
| Account reads | getAccountInfo, getMultipleAccounts, getProgramAccounts | Balances, token accounts, program state | Cheap per call, but getProgramAccounts can be heavy |
| Block and slot data | getSlot, getBlock, getBlockHeight, getLatestBlockhash | Confirmations, transaction building | Moderate; getBlock returns large payloads |
| Transaction submission | sendTransaction, simulateTransaction | Sending signed transactions | Sensitive to load; simulate first |
| Token and SPL helpers | getTokenAccountsByOwner, getTokenAccountBalance | Wallet balances and token lists | Moderate fan-out per user |
| Fees and priority | getRecentPrioritizationFees, getFeeForMessage | Setting compute unit price | Cheap, but call it fresh |
| Subscriptions (WebSocket) | accountSubscribe, logsSubscribe, slotSubscribe | Live updates without polling | Long-lived connections |
Two practical notes. First, getProgramAccounts is the method most likely to time out on a shared endpoint, because it can scan a large account set; scope it with filters and a dataSlice whenever possible. Second, getLatestBlockhash results expire, so fetch a fresh blockhash close to the moment you sign rather than caching one for minutes.
Building a transaction with the JSON-RPC API
Most teams use @solana/web3.js or a similar client rather than hand-rolling JSON. The client still speaks the same RPC methods under the hood, so pointing it at your endpoint is a one-line change:
import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
const connection = new Connection(
"https://solana.api.onfinality.io/public",
{ commitment: "confirmed" }
);
const balance = await connection.getBalance(
new PublicKey("11111111111111111111111111111111")
);
console.log("lamports:", balance, "SOL:", balance / LAMPORTS_PER_SOL);
The commitment level you pass matters. processed is fastest but can be rolled back; confirmed is the usual default for user-facing balances; finalized is the safest for settlement logic. Pick one deliberately and keep it consistent across your reads, because mixing commitment levels is a common source of confusing UI state.
WebSocket subscriptions and when to use them
Polling getSlot or getAccountInfo in a loop burns requests and still feels laggy. Solana's WebSocket interface pushes updates instead. The public OnFinality WebSocket endpoint is:
wss://solana.api.onfinality.io/public-ws
A subscription request uses the same JSON-RPC envelope, sent over the socket:
const ws = new WebSocket("wss://solana.api.onfinality.io/public-ws");
ws.onopen = () => {
ws.send(JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "logsSubscribe",
params: [
{ mentions: ["YourProgramPublicKeyHere"] },
{ commitment: "confirmed" }
]
}));
};
ws.onmessage = (event) => {
const payload = JSON.parse(event.data);
if (payload.method === "logsNotification") {
console.log("program log:", payload.params.result.value.logs);
}
};
Subscriptions are long-lived, so plan for reconnection. Shared endpoints may close idle or overloaded sockets, and a dropped socket that your app does not notice looks exactly like a stalled chain. Add a heartbeat, reconnect with backoff, and re-subscribe on reconnect. If your workload depends on many concurrent subscriptions, that is a strong signal to move to a dedicated node where the connection budget is yours.
Debug path for common Solana RPC errors
When something breaks, the error text usually points at the layer. Use this table to route the fix.
| Symptom | Likely cause | Next step |
|---|---|---|
429 or rate-limit response | Shared endpoint under load | Back off, batch reads, or move to dedicated capacity |
Blockhash not found | Stale or expired blockhash | Fetch a fresh blockhash immediately before signing |
| Transaction confirms then disappears | Commitment level mismatch | Align reads and confirmation logic on confirmed or finalized |
getProgramAccounts times out | Unfiltered account scan | Add filters and dataSlice, or use a dedicated node |
| WebSocket stops updating | Socket closed silently | Add heartbeat, reconnect, and re-subscribe |
-32002 transaction simulation failure | Program logic rejected the tx | Run simulateTransaction and read the logs before resending |
A useful habit is to log the raw JSON-RPC error object, not just a friendly message. Solana returns structured error data, including logs for simulation failures, and that detail is usually enough to identify the failing instruction.
Production readiness checklist
Before you point real users at an endpoint, confirm these items:
- Commitment levels are explicit across every read and confirmation path.
- Retries use backoff, not tight loops, so a slow moment does not become a self-inflicted spike.
- Blockhash freshness is handled at signing time, not cached.
- WebSocket reconnection is implemented and tested by killing the socket on purpose.
- Heavy reads are scoped with filters,
dataSlice, and batching where the API allows it. - A fallback endpoint is configured so a single provider issue does not take the app down.
- Request volume is measured so you can tell whether shared capacity still fits.
If several of these are hard to satisfy on a shared endpoint, that is the point to look at dedicated nodes or review RPC pricing to compare options.
Devnet, mainnet, and moving between them
Devnet mirrors the mainnet API surface, so code that works against mainnet generally works against Solana Devnet RPC with a different URL and a faucet-funded keypair. Keep the endpoint in configuration rather than hard-coded, and keep a separate keypair per environment. The most common migration bug is a program ID or token mint that was updated in one environment but not the other.
OnFinality exposes Solana mainnet and devnet as separate network entries, so you can register both and switch by config. See the Solana network page for the current endpoint details and transport support, and supported RPC networks for the full list.
Key Takeaways
- Solana exposes one JSON-RPC interface for reads, writes, and subscriptions; the method name lives in the request body, not the URL.
- Match the endpoint to the workload: public for prototypes, managed for steady traffic, dedicated for high fan-out or subscription-heavy apps.
getProgramAccountsand long-lived WebSockets are the two areas most likely to push you off a shared endpoint.- Most production bugs trace back to commitment levels, stale blockhashes, or unnoticed socket drops, not to the API itself.
- OnFinality serves Solana RPC over HTTP and WebSocket, with dedicated node options when shared capacity is not enough.
Frequently Asked Questions
Is the Solana RPC API the same as the Solana JSON-RPC API?
Yes. When people say "Solana RPC API" they mean the JSON-RPC 2.0 interface served over HTTP and WebSocket. There is no separate REST API for chain data.
Do I need an API key to call a Solana RPC endpoint?
Public endpoints typically work without a key, which is fine for low-volume use. Managed and dedicated endpoints use keys or private URLs so your traffic is isolated and measurable.
Why does my transaction fail with "Blockhash not found"?
The blockhash you signed against expired before the transaction landed. Fetch a fresh blockhash right before signing and retry with backoff.
Should I poll or use WebSocket subscriptions?
Use subscriptions for anything that needs to react to on-chain events, and reserve polling for occasional checks. Subscriptions reduce request volume and usually feel faster, but they require reconnection handling.
When should I move from a public endpoint to a dedicated node?
When you see rate limiting, when getProgramAccounts or similar heavy reads time out, when you need many concurrent WebSocket subscriptions, or when you need historical state that standard nodes prune. A dedicated node gives you isolated capacity for those cases.