API Reference
Base URL: https://rpc.nodius.xyz
All authenticated endpoints require one of: per-request wallet signature headers (X-Pubkey, X-Signature, X-Timestamp, X-Nonce), X-Api-Key header, or Authorization: Bearer <token>. See Authentication for signing details.
Signing message format:
solana-nodius:v2:{METHOD}:{PATH}:{TIMESTAMP}:{NONCE}:{SHA256_BODY_HEX}โ signed with Ed25519, signature is base58-encoded. Body hash is SHA-256 hex of the raw request body bytes (empty string for GET).
RPC Endpoint
POST / (or /rpc)
The primary Solana RPC endpoint. Send JSON-RPC 2.0 requests for all Solana methods. Also available at POST /rpc.
Request body:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getSlot",
"params": []
}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": 285943216
}
All responses from authenticated RPC endpoints include X-Credits-Remaining and X-Request-Id headers. Rate-limit headers (X-Ratelimit-Limit, X-Ratelimit-Remaining, X-Ratelimit-Reset) are included on all authenticated responses. On 429 responses, Retry-After is also present. Public endpoints (/health, /pricing, /capabilities) do not include credit or rate-limit headers.
import { NodiusClient } from "@nodiusxyz/sdk";
const rpc = NodiusClient.fromSecretKey(
"https://rpc.nodius.xyz",
"your_base58_secret_key_here"
);
const slot = await rpc.call("getSlot");
console.log("Current slot:", slot);
const balance = await rpc.call("getBalance", [
"vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg",
]);
console.log("Balance:", balance.value);
const accountInfo = await rpc.call("getAccountInfo", [
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
{ encoding: "base64" },
]);
Use rpc.getAccountInfo(pubkey) as a typed wrapper for the Solana RPC method, or rpc.getBillingAccount() for your Nodius billing account (balance, deposit address, usage stats).
from nodiusxyz import NodiusClient
rpc = NodiusClient.from_base58(
"https://rpc.nodius.xyz",
secret_key="your_base58_secret_key_here",
)
slot = rpc.call("getSlot")
print("Current slot:", slot)
balance = rpc.call("getBalance", ["vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg"])
print("Balance:", balance)
account_info = rpc.call("getAccountInfo", [
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
{"encoding": "base64"},
])
Use rpc.get_account_info(pubkey) as a typed wrapper for the Solana RPC method, or rpc.get_billing_account() for your Nodius billing account (balance, deposit address, usage stats).
# See authentication.md for the full wallet signing algorithm.
# For API key auth:
import json, urllib.request
API_KEY = "srpc_live_your_api_key_here"
body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "getSlot"}).encode()
req = urllib.request.Request(
"https://rpc.nodius.xyz",
data=body,
headers={"Content-Type": "application/json", "X-Api-Key": API_KEY},
method="POST",
)
with urllib.request.urlopen(req) as resp:
print("Credits:", resp.headers.get("X-Credits-Remaining"))
print("Slot:", json.loads(resp.read())["result"])
// See authentication.md for wallet signing. For API key auth:
const API_KEY = "srpc_live_your_api_key_here";
const res = await fetch("https://rpc.nodius.xyz", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": API_KEY,
},
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getSlot" }),
});
const result = await res.json();
console.log("Slot:", result.result);
curl -X POST https://rpc.nodius.xyz \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{"jsonrpc":"2.0","id":1,"method":"getSlot"}'
Batch Requests
Send an array of JSON-RPC requests (max 20 per batch). Each method is billed individually.
[
{"jsonrpc": "2.0", "id": 1, "method": "getSlot"},
{"jsonrpc": "2.0", "id": 2, "method": "getBlockHeight"},
{"jsonrpc": "2.0", "id": 3, "method": "getBalance", "params": ["vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg"]}
]
const responses = await rpc.callBatch([
{ method: "getSlot" },
{ method: "getBlockHeight" },
]);
results = rpc.call_batch([
{"method": "getSlot"},
{"method": "getBlockHeight"},
])
body = json.dumps([
{"jsonrpc": "2.0", "id": 1, "method": "getSlot"},
{"jsonrpc": "2.0", "id": 2, "method": "getBlockHeight"},
]).encode()
# POST to https://rpc.nodius.xyz with X-Api-Key header
const body = JSON.stringify([
{ jsonrpc: "2.0", id: 1, method: "getSlot" },
{ jsonrpc: "2.0", id: 2, method: "getBlockHeight" },
]);
// POST to https://rpc.nodius.xyz with X-Api-Key header
curl -X POST https://rpc.nodius.xyz \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '[{"jsonrpc":"2.0","id":1,"method":"getSlot"},{"jsonrpc":"2.0","id":2,"method":"getBlockHeight"}]'
Supported Methods & Credits
| Category | Credits | Methods |
|---|---|---|
| READ_CHEAP | 1 | getAccountInfo, getBalance, getBlockCommitment, getBlockHeight, getBlockProduction, getBlockTime, getClusterNodes, getEpochInfo, getEpochSchedule, getFeeForMessage, getFirstAvailableBlock, getGenesisHash, getHealth, getHighestSnapshotSlot, getIdentity, getInflationGovernor, getInflationRate, getInflationReward, getLatestBlockhash, getLeaderSchedules, getMaxRetransmitSlot, getMaxShredInsertSlot, getMinimumBalanceForRentExemption, getMultipleAccounts, getRecentPerformanceSamples, getRecentPrioritizationFees, getSignatureStatuses, getSlot, getSlotLeader, getSlotLeaders, getStakeActivation, getStakeMinimumDelegation, getTokenAccountBalance, getTokenLargestAccounts, getTokenSupply, getTransactionCount, getVersion, getVoteAccounts, isBlockhashValid, minimumLedgerSlot, getPriorityFeeEstimate |
| READ_MEDIUM | 5 | simulateTransaction, getTransaction, getSignaturesForAddress, suggestPriorityFee |
| READ_HEAVY | 25 | simulateBundle, getBlock, getBlocks, getBlocksWithLimit, getTokenAccountsByOwner, getTokenAccountsByDelegate, getAssetsByOwner, getTransactionHistory โ getBlock methods served from hot-node retention (โ3.7h of history). Slots outside retention return Agave's native "cleaned up" error. |
| SEND | 2 | sendTransaction, sendSmartTransaction |
| SEND_AND_CONFIRM | outcome-priced | sendAndConfirm โ multi-leg send (local RPC + Jito + TPU QUIC) with confirmation polling and auto-resend. Returns landing slot, time-to-confirm, and retry count. Full landing price charged only when the transaction confirms; a failed/timed-out attempt pays just the small base fee (equal to SEND). Live pricing: GET /pricing. |
| SEND_BUNDLE | 10 | sendBundle |
| ARCHIVE_HISTORY | โ | getEnrichedTransaction, explainTransaction, getConfirmedTransaction, getConfirmedSignaturesForAddress2 โ not enabled on this endpoint; calls return an error at no charge |
| DENIED | โ | requestAirdrop, getProgramAccounts, getSupply |
โ getEnrichedTransaction and explainTransaction are not enabled on this endpoint (calls return an error at no charge). Other READ_MEDIUM methods are served by the hot node.
Method behaviors that trip up bots (verified against live prod):
getTokenAccountsByOwner/getTokenAccountsByDelegateโ if you omit the third config object, we inject{"encoding": "base64"}automatically. Solana's default (base58) rejects every token account (> 128 bytes). Pass{"encoding": "jsonParsed"}explicitly if you want parsed metadata.getAssetsByOwnerโ accepts the owner as a bare string (["<pubkey>"]) or an object in any common key style:[{"ownerAddress": "..."}],[{"owner_address": "..."}], or[{"owner": "..."}].suggestPriorityFeeโ the global fee window is legitimately ~0 when no fee market is active. Pass the writable accounts your tx touches ([{"urgency": "high", "accounts": ["<program_or_amm>"]}]) for an actionable per-account estimate. See the full section.- SDK signal helpers โ
subscribeSignalRequest(TS) /subscribe_signal_request(Python) andcreateSignalWebhook/create_signal_webhookbuild the payloads for you; they're in@nodiusxyz/sdkโฅ 0.2.6 /nodiusxyzโฅ 0.2.6. On 0.2.5 they shipped broken (stale build) โ upgrade. simulateBundleโ params are[{"transactions": ["<base64_tx>", ...]}](a config object holding atransactionsarray of base64-encoded serialized transactions). Both SDKs wrap this for you (simulateBundle(txs)/simulate_bundle(...)); if you call it raw, don't pass a bare array of strings.
Error Response
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Method not found"
}
}
See Error Reference for all error codes.
Account Management
POST /account/api-key
Generate an API key. Requires wallet signature auth. If no account record exists for your pubkey, it is created automatically with a zero balance โ so this endpoint works before any deposit.
Response:
{
"ok": true,
"api_key": "srpc_live_<base64url>",
"message": "Save this key โ it will not be shown again."
}
The API key is returned only once. Store it securely. Bots can skip this entirely and use per-request wallet signatures.
const result = await rpc.generateApiKey();
console.log("API key:", result.api_key);
// SDK auto-stores the key for subsequent calls
result = rpc.generate_api_key()
print("API key:", result["api_key"])
# SDK auto-stores the key for subsequent calls
# Requires wallet signature auth โ see authentication.md
# PATH = "/account/api-key", body = "{}"
// Requires wallet signature auth โ see authentication.md
// PATH = "/account/api-key", body = "{}"
# Requires wallet signature auth โ use an SDK or raw script
GET /account/info
Get account details: credit balance, deposit address, usage stats. Works with any auth mode. The deposit_address and funding fields are the canonical source for where to send USDC โ bots should fetch this at runtime rather than relying on static documentation.
Response:
{
"ok": true,
"pubkey": "YourPublicKeyBase58...",
"balance": 84521,
"deposit_address": "CQKviPv5QupKhE6MuhSuqP4uh1JfrL93nxtcx23tLMRk",
"funding": {
"asset": "USDC",
"network": "solana",
"deposit_address": "CQKviPv5QupKhE6MuhSuqP4uh1JfrL93nxtcx23tLMRk",
"credits_per_usdc": 10000,
"credit_expiry_seconds": 864000,
"credit_expiry_days": 10,
"attribution": "send USDC from the same wallet pubkey used to activate the account"
},
"status": "active",
"total_consumed_credits": 415479,
"created_at": 1705312200,
"last_deposit": {},
"credit_batches": []
}
The deposit_address field is where you send USDC (SPL) on Solana mainnet. Minimum deposit: $0.01 USDC. Do not send SOL or other tokens.
const info = await rpc.getBillingAccount();
console.log("Balance:", info.balance);
console.log("Deposit address:", info.deposit_address);
console.log("Total consumed:", info.total_consumed_credits);
info = rpc.get_billing_account()
print("Balance:", info["balance"])
print("Deposit address:", info.get("deposit_address"))
print("Total consumed:", info["total_consumed_credits"])
import json, urllib.request
API_KEY = "srpc_live_your_api_key_here"
req = urllib.request.Request(
"https://rpc.nodius.xyz/account/info",
headers={"X-Api-Key": API_KEY},
)
with urllib.request.urlopen(req) as resp:
info = json.loads(resp.read())
print("Balance:", info["balance"])
const res = await fetch("https://rpc.nodius.xyz/account/info", {
headers: { "X-Api-Key": "srpc_live_..." },
});
const info = await res.json();
console.log("Balance:", info.balance);
curl https://rpc.nodius.xyz/account/info \
-H "X-Api-Key: srpc_live_..."
GET /account/ledger
Get credit transaction history (deposits, debits, adjustments). Query params: offset, limit.
Response:
{
"entries": [
{
"type": "deposit",
"amount": 10000,
"balance": 10000,
"description": "USDC deposit: 1.0 USDC",
"timestamp": "2025-01-15T10:31:00Z"
}
]
}
const ledger = await rpc.getLedger(50, 0);
for (const entry of ledger.entries) {
console.log(entry.type, entry.amount, entry.description);
}
ledger = rpc.get_ledger(offset=0, limit=50)
for entry in ledger.get("entries", []):
print(entry["type"], entry["amount"], entry["description"])
import json, urllib.request
API_KEY = "srpc_live_your_api_key_here"
req = urllib.request.Request(
"https://rpc.nodius.xyz/account/ledger?offset=0&limit=50",
headers={"X-Api-Key": API_KEY},
)
with urllib.request.urlopen(req) as resp:
ledger = json.loads(resp.read())
const res = await fetch(
"https://rpc.nodius.xyz/account/ledger?offset=0&limit=50",
{ headers: { "X-Api-Key": "srpc_live_..." } }
);
const ledger = await res.json();
curl "https://rpc.nodius.xyz/account/ledger?offset=0&limit=50" \
-H "X-Api-Key: srpc_live_..."
GET /account/history
Get RPC usage history (method calls with credits consumed). Query params: offset, limit.
Response:
{
"entries": [
{
"method": "getSlot",
"credits": 1,
"timestamp": "2025-01-15T10:32:00Z"
}
]
}
const history = await rpc.getHistory(0, 50);
for (const entry of history.entries) {
console.log(entry.method, entry.credits, entry.timestamp);
}
history = rpc.get_history(offset=0, limit=50)
for entry in history.get("entries", []):
print(entry["method"], entry["credits"], entry["timestamp"])
import json, urllib.request
API_KEY = "srpc_live_your_api_key_here"
req = urllib.request.Request(
"https://rpc.nodius.xyz/account/history?offset=0&limit=50",
headers={"X-Api-Key": API_KEY},
)
with urllib.request.urlopen(req) as resp:
history = json.loads(resp.read())
const res = await fetch(
"https://rpc.nodius.xyz/account/history?offset=0&limit=50",
{ headers: { "X-Api-Key": "srpc_live_..." } }
);
const history = await res.json();
curl "https://rpc.nodius.xyz/account/history?offset=0&limit=50" \
-H "X-Api-Key: srpc_live_..."
POST /account/confirm-deposit
Confirm a USDC deposit by providing the transaction signature. Speeds up credit assignment if automatic detection hasn't processed it yet.
Request body:
{
"signature": "5xYz...base58..."
}
Response:
{
"ok": true,
"credits_added": 10000,
"new_balance": 10000,
"already_processed": false
}
const result = await rpc.confirmDeposit("5xYz...base58...");
console.log("Credits added:", result.credits_added);
result = rpc.confirm_deposit("5xYz...base58...")
print("Credits added:", result["credits_added"])
import json, urllib.request
API_KEY = "srpc_live_your_api_key_here"
body = json.dumps({"signature": "5xYz...base58..."}).encode()
req = urllib.request.Request(
"https://rpc.nodius.xyz/account/confirm-deposit",
data=body,
headers={"Content-Type": "application/json", "X-Api-Key": API_KEY},
method="POST",
)
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
const res = await fetch("https://rpc.nodius.xyz/account/confirm-deposit", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": "srpc_live_...",
},
body: JSON.stringify({ signature: "5xYz...base58..." }),
});
const result = await res.json();
curl -X POST https://rpc.nodius.xyz/account/confirm-deposit \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{"signature":"5xYz...base58..."}'
Auth Endpoints
POST /auth/challenge
Request a challenge for session token auth. No authentication required.
Request body:
{
"pubkey": "YourPublicKeyBase58..."
}
Response:
{
"challenge": "a1b2c3d4e5f67890abcdef1234567890a1b2c3d4e5f67890abcdef1234567890",
"expires_in": 300
}
The challenge is a 64-character hex string, valid for 300 seconds.
// The SDK handles the full challenge โ sign โ verify flow:
const token = await rpc.authenticate();
# The SDK handles the full challenge โ sign โ verify flow:
token = rpc.authenticate()
import json, urllib.request
body = json.dumps({"pubkey": "YourPublicKeyBase58..."}).encode()
req = urllib.request.Request(
"https://rpc.nodius.xyz/auth/challenge",
data=body,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
print("Challenge:", result["challenge"])
const res = await fetch("https://rpc.nodius.xyz/auth/challenge", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pubkey: "YourPublicKeyBase58..." }),
});
const { challenge } = await res.json();
curl -X POST https://rpc.nodius.xyz/auth/challenge \
-H "Content-Type: application/json" \
-d '{"pubkey":"YourPublicKeyBase58..."}'
POST /auth/verify
Submit a signed challenge to receive a session token. Sign the challenge hex string as UTF-8 bytes with your Ed25519 keypair (do not hex-decode it โ sign the 64-character hex text directly).
Request body:
{
"pubkey": "YourPublicKeyBase58...",
"challenge": "a1b2c3d4e5f67890...",
"signature": "Base58EncodedSignature..."
}
Response:
{
"token": "64-char-hex-session-token...",
"expires_in": 3600
}
The token expires after 3600 seconds (1 hour) with a sliding TTL. See Authentication for the full flow with code examples.
curl -X POST https://rpc.nodius.xyz/auth/verify \
-H "Content-Type: application/json" \
-d '{"pubkey":"...","challenge":"...","signature":"..."}'
POST /auth/logout
Revoke the current session token. Requires Authorization: Bearer <token>.
Response:
{
"status": "ok"
}
await rpc.authenticate(); // get a session token first
await rpc.logout();
rpc.authenticate() # get a session token first
rpc.logout()
import json, urllib.request
TOKEN = "your_session_token_here"
req = urllib.request.Request(
"https://rpc.nodius.xyz/auth/logout",
data=b"{}",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {TOKEN}",
},
method="POST",
)
with urllib.request.urlopen(req) as resp:
print(json.loads(resp.read()))
const res = await fetch("https://rpc.nodius.xyz/auth/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: "{}",
});
curl -X POST https://rpc.nodius.xyz/auth/logout \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your_token_here" \
-d '{}'
Premium RPC Methods
sendAndConfirm
Send a transaction via the multi-leg coordinator (local Agave RPC + Jito Block Engine + TPU QUIC) and poll for confirmation. If the transaction hasn't landed after 8 slots (~3.2s), it's automatically re-sent. Up to 2 re-send attempts before timing out at 30 slots (~12s).
Cost: outcome-priced. The full landing price (see GET /pricing) is charged only when the transaction confirms; a failed or timed-out attempt pays just the small base fee (equal to a plain sendTransaction). The response's charged_credits field reports exactly what was billed โ don't be surprised that it differs between a landed tx (full price) and a failed one (base fee only).
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "sendAndConfirm",
"params": ["<base64_transaction>", { "encoding": "base64" }]
}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"signature": "5xKp...",
"confirmed": true,
"slot": 285943216,
"confirmations": 3,
"landing_time_ms": 1240,
"legs_attempted": ["local_rpc", "jito"],
"retry_count": 0,
"error": null
}
}
Fields:
| Field | Type | Description |
|---|---|---|
signature |
string | Transaction signature (base58) |
confirmed |
bool | Whether the transaction was confirmed on-chain |
slot |
number|null | Slot in which the transaction was confirmed |
confirmations |
number|null | Number of confirmations |
landing_time_ms |
number|null | Time from send to confirmation in milliseconds |
legs_attempted |
string[] | Which send legs were used (local_rpc, jito, tpu_quic) |
retry_count |
number | Number of re-send attempts (0 = landed first try) |
charged_credits |
number | Credits actually billed for this call (full landing price when confirmed is true; base fee only otherwise) |
error |
string|null | On-chain error message if the transaction landed but failed |
getTransactionHistory
Fetch recent transactions for a Solana address, each enriched with decoded token transfers, SOL transfers, program interactions, and a natural language explanation. Batch-fetches and enriches concurrently for speed.
Cost: 25 credits
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransactionHistory",
"params": ["vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg", { "limit": 10 }]
}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"transactions": [
{
"signature": "5xKp...",
"found": true,
"slot": 285943200,
"block_time": 1721342400,
"fee": 5000,
"error": null,
"enriched": {
"token_transfers": [...],
"sol_transfers": [...],
"program_interactions": [...],
"summary": "Swapped 1.5 USDC for 0.0089 SOL via Jupiter v6"
},
"explanation": "Summary: Swapped 1.5 USDC for 0.0089 SOL via Jupiter v6\n..."
}
],
"count": 10
}
}
Params:
| Parameter | Type | Default | Description |
|---|---|---|---|
address |
string | required | Base58 Solana address |
limit |
number | 10 | Number of transactions to return (max 100) |
suggestPriorityFee
Suggest a priority fee (microlamports per compute unit) for a given urgency level. Cost: 5 credits.
Important: the global fee window is legitimately ~0 on mainnet when no fee market is active. For an actionable estimate on a congested account (Raydium/Jupiter AMM, pump.fun bonding curve), pass the writable accounts your transaction touches โ the estimate is then scoped to per-account contention.
Request (urgency only):
{ "jsonrpc": "2.0", "id": 1, "method": "suggestPriorityFee", "params": ["high"] }
Request (account-scoped โ recommended for bots):
{
"jsonrpc": "2.0",
"id": 1,
"method": "suggestPriorityFee",
"params": [{ "urgency": "high", "accounts": ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"] }]
}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"suggested_fee_microlamports": 27390,
"urgency": "high",
"all_levels": { "low": 0, "medium": 0, "high": 0, "veryHigh": 27390 },
"scoped_to_accounts": true
}
}
Params:
| Parameter | Type | Default | Description |
|---|---|---|---|
urgency |
string | "medium" |
low, medium, high, or critical (maps to the 25th/50th/75th/90th percentile) |
accounts |
string[] | (global window) | Writable accounts to scope the estimate to (max 128). Omit for the global 150-slot window |
scoped_to_accounts in the response tells you which window was used.
Jito Endpoints
POST /jito/sendBundle
Submit a Jito bundle for MEV-protected transaction submission. Cost: 10 credits. Bundles must include a tip instruction to a Jito tip account.
const bundleId = await rpc.jitoSendBundle(["base58tx1...", "base58tx2..."]);
bundle_id = rpc.jito_send_bundle(["base58tx1...", "base58tx2..."])
import json, urllib.request
API_KEY = "srpc_live_your_api_key_here"
body = json.dumps({
"jsonrpc": "2.0", "id": 1, "method": "sendBundle",
"params": [["base58tx1...", "base58tx2..."]],
}).encode()
req = urllib.request.Request(
"https://rpc.nodius.xyz/jito/sendBundle",
data=body,
headers={"Content-Type": "application/json", "X-Api-Key": API_KEY},
method="POST",
)
with urllib.request.urlopen(req) as resp:
print(json.loads(resp.read()))
const res = await fetch("https://rpc.nodius.xyz/jito/sendBundle", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": "srpc_live_..." },
body: JSON.stringify({
jsonrpc: "2.0", id: 1, method: "sendBundle",
params: [["base58tx1...", "base58tx2..."]],
}),
});
curl -X POST https://rpc.nodius.xyz/jito/sendBundle \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{"jsonrpc":"2.0","id":1,"method":"sendBundle","params":[["base58tx1...","base58tx2..."]]}'
POST /jito/getBundleStatuses
Check status of previously submitted bundles. Cost: 2 credits.
const statuses = await rpc.jitoGetBundleStatuses(["bundleId1...", "bundleId2..."]);
statuses = rpc.jito_get_bundle_statuses(["bundleId1...", "bundleId2..."])
body = json.dumps({
"jsonrpc": "2.0", "id": 1, "method": "getBundleStatuses",
"params": [["bundleId1...", "bundleId2..."]],
}).encode()
# POST to https://rpc.nodius.xyz/jito/getBundleStatuses with X-Api-Key
const res = await fetch("https://rpc.nodius.xyz/jito/getBundleStatuses", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": "srpc_live_..." },
body: JSON.stringify({
jsonrpc: "2.0", id: 1, method: "getBundleStatuses",
params: [["bundleId1...", "bundleId2..."]],
}),
});
curl -X POST https://rpc.nodius.xyz/jito/getBundleStatuses \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{"jsonrpc":"2.0","id":1,"method":"getBundleStatuses","params":[["bundleId1...","bundleId2..."]]}'
GET /jito/getTipAccounts
Get the list of valid Jito tip accounts. Cost: 1 credit.
const tipAccounts = await rpc.jitoGetTipAccounts();
tip_accounts = rpc.jito_get_tip_accounts()
req = urllib.request.Request(
"https://rpc.nodius.xyz/jito/getTipAccounts",
headers={"X-Api-Key": API_KEY},
)
with urllib.request.urlopen(req) as resp:
print(json.loads(resp.read()))
const res = await fetch("https://rpc.nodius.xyz/jito/getTipAccounts", {
headers: { "X-Api-Key": "srpc_live_..." },
});
curl https://rpc.nodius.xyz/jito/getTipAccounts \
-H "X-Api-Key: srpc_live_..."
GET /jito/tipFloor
Get the current Jito tip floor (minimum tip amount). Cost: 1 credit.
const tipFloor = await rpc.jitoGetTipFloor();
tip_floor = rpc.jito_get_tip_floor()
req = urllib.request.Request(
"https://rpc.nodius.xyz/jito/tipFloor",
headers={"X-Api-Key": API_KEY},
)
with urllib.request.urlopen(req) as resp:
print(json.loads(resp.read()))
const res = await fetch("https://rpc.nodius.xyz/jito/tipFloor", {
headers: { "X-Api-Key": "srpc_live_..." },
});
curl https://rpc.nodius.xyz/jito/tipFloor \
-H "X-Api-Key: srpc_live_..."
Bulk Endpoints
POST /bulk/getBalances
Batch balance lookups for multiple accounts.
Request body:
{
"addresses": ["pubkey1...", "pubkey2...", "pubkey3..."]
}
const result = await rpc.bulkGetBalances(["pubkey1...", "pubkey2..."]);
result = rpc.bulk_get_balances(["pubkey1...", "pubkey2..."])
body = json.dumps({"addresses": ["pubkey1...", "pubkey2..."]}).encode()
# POST to https://rpc.nodius.xyz/bulk/getBalances with X-Api-Key
const res = await fetch("https://rpc.nodius.xyz/bulk/getBalances", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": "srpc_live_..." },
body: JSON.stringify({ addresses: ["pubkey1...", "pubkey2..."] }),
});
curl -X POST https://rpc.nodius.xyz/bulk/getBalances \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{"addresses":["pubkey1...","pubkey2..."]}'
POST /bulk/getTokenBalances
Batch token balance lookups for multiple owners.
Request body:
{
"owners": ["pubkey1...", "pubkey2..."]
}
const result = await rpc.bulkGetTokenBalances(["pubkey1...", "pubkey2..."]);
result = rpc.bulk_get_token_balances(["pubkey1...", "pubkey2..."])
body = json.dumps({"owners": ["pubkey1...", "pubkey2..."]}).encode()
# POST to https://rpc.nodius.xyz/bulk/getTokenBalances with X-Api-Key
const res = await fetch("https://rpc.nodius.xyz/bulk/getTokenBalances", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": "srpc_live_..." },
body: JSON.stringify({ owners: ["pubkey1...", "pubkey2..."] }),
});
curl -X POST https://rpc.nodius.xyz/bulk/getTokenBalances \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{"owners":["pubkey1...","pubkey2..."]}'
POST /bulk/getTransactions
Batch transaction lookups.
Request body:
{
"signatures": ["sig1...", "sig2...", "sig3..."]
}
const result = await rpc.bulkGetTransactions(["sig1...", "sig2..."]);
result = rpc.bulk_get_transactions(["sig1...", "sig2..."])
body = json.dumps({"signatures": ["sig1...", "sig2..."]}).encode()
# POST to https://rpc.nodius.xyz/bulk/getTransactions with X-Api-Key
const res = await fetch("https://rpc.nodius.xyz/bulk/getTransactions", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": "srpc_live_..." },
body: JSON.stringify({ signatures: ["sig1...", "sig2..."] }),
});
curl -X POST https://rpc.nodius.xyz/bulk/getTransactions \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{"signatures":["sig1...","sig2..."]}'
Webhooks
POST /webhooks
Create a webhook subscription for account/program monitoring.
Request body:
{
"url": "https://your-server.com/webhook",
"filter_type": "account",
"filter_value": "pubkey1..."
}
Supported filter_type values: account, program, my_account (watches your own pubkey; filter_value optional), and lifecycle (fires on account-level events; requires a lifecycle_events array).
When filter_type is lifecycle, provide a lifecycle_events array selecting which events trigger webhooks:
{
"url": "https://your-server.com/webhook",
"filter_type": "lifecycle",
"lifecycle_events": ["low_balance", "deposit.credited", "credits.expiring", "api_key.rotated"]
}
Available lifecycle events:
| Event | Fires When |
|---|---|
low_balance |
Balance drops below 1,000 credits |
deposit.credited |
A deposit is processed and credits granted |
credits.expiring |
A credit batch is within 24h of expiry |
api_key.rotated |
API key is rotated via POST /account/api-key |
Only these four lifecycle events exist today. tx.confirmed / tx.failed / tx.received / token_transfer.received are not implemented โ subscribing with them returns unknown lifecycle event. To watch for an on-chain transaction to one of your own addresses, use filter_type: "my_account" (fires on signature activity on your pubkey) rather than a lifecycle event.
See Credits & Pricing for dedup behavior and payload details.
const webhook = await rpc.createWebhook(
"https://your-server.com/webhook",
"account",
"pubkey1..."
);
webhook = rpc.create_webhook(
url="https://your-server.com/webhook",
filter_type="account",
filter_value="pubkey1...",
)
body = json.dumps({
"url": "https://your-server.com/webhook",
"filter_type": "account",
"filter_value": "pubkey1...",
}).encode()
# POST to https://rpc.nodius.xyz/webhooks with X-Api-Key
const res = await fetch("https://rpc.nodius.xyz/webhooks", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": "srpc_live_..." },
body: JSON.stringify({
url: "https://your-server.com/webhook",
filter_type: "account",
filter_value: "pubkey1...",
}),
});
curl -X POST https://rpc.nodius.xyz/webhooks \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{"url":"https://your-server.com/webhook","filter_type":"account","filter_value":"pubkey1..."}'
GET /webhooks
List all webhook subscriptions for your account.
const webhooks = await rpc.listWebhooks();
webhooks = rpc.list_webhooks()
req = urllib.request.Request(
"https://rpc.nodius.xyz/webhooks",
headers={"X-Api-Key": API_KEY},
)
with urllib.request.urlopen(req) as resp:
print(json.loads(resp.read()))
const res = await fetch("https://rpc.nodius.xyz/webhooks", {
headers: { "X-Api-Key": "srpc_live_..." },
});
const webhooks = await res.json();
curl https://rpc.nodius.xyz/webhooks \
-H "X-Api-Key: srpc_live_..."
DELETE /webhooks/{id}
Delete a webhook subscription.
await rpc.deleteWebhook("abc123");
rpc.delete_webhook("abc123")
req = urllib.request.Request(
"https://rpc.nodius.xyz/webhooks/abc123",
headers={"X-Api-Key": API_KEY},
method="DELETE",
)
with urllib.request.urlopen(req) as resp:
print("Deleted:", resp.status)
const res = await fetch("https://rpc.nodius.xyz/webhooks/abc123", {
method: "DELETE",
headers: { "X-Api-Key": "srpc_live_..." },
});
console.log("Deleted:", res.ok);
curl -X DELETE https://rpc.nodius.xyz/webhooks/abc123 \
-H "X-Api-Key: srpc_live_..."
POST /webhooks/{id}/rotate-secret
Rotate the signing secret for a webhook.
Response:
{
"secret": "new_signing_secret..."
}
const result = await rpc.rotateWebhookSecret("abc123");
console.log("New secret:", result.secret);
result = rpc.rotate_webhook_secret("abc123")
print("New secret:", result["secret"])
req = urllib.request.Request(
"https://rpc.nodius.xyz/webhooks/abc123/rotate-secret",
data=b"{}",
headers={"Content-Type": "application/json", "X-Api-Key": API_KEY},
method="POST",
)
with urllib.request.urlopen(req) as resp:
result = json.loads(resp.read())
const res = await fetch("https://rpc.nodius.xyz/webhooks/abc123/rotate-secret", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": "srpc_live_..." },
body: "{}",
});
const result = await res.json();
curl -X POST https://rpc.nodius.xyz/webhooks/abc123/rotate-secret \
-H "Content-Type: application/json" \
-H "X-Api-Key: srpc_live_..." \
-d '{}'
Public Endpoints
GET /health
Public health check. No authentication required.
Response (healthy):
{
"status": "ok",
"paid_traffic_ready": true,
"agave_healthy": true,
"service_profile": {
"auth": {
"wallet_signature": true,
"api_key": true,
"session_token": true
},
"services": {
"hot_rpc": true,
"send": true,
"websocket": true,
"yellowstone_grpc": true,
"heavy_indexed_rpc": true
}
},
"timestamp": 1712000001,
"solanaSlot": 285943216,
"solanaBlockHeight": 265234567,
"uptime": 864000,
"nodeVersion": "4.1.0"
}
Response (degraded):
{
"status": "degraded",
"paid_traffic_ready": false,
"fallback_active": true,
"service_profile": { ... },
"timestamp": 1712000001
}
Response (starting โ proxy not yet ready):
Returns HTTP 503:
{
"status": "starting",
"paid_traffic_ready": false,
"agave_healthy": true,
"service_profile": { ... },
"timestamp": 1712000001
}
| Status | HTTP Code | Description |
|---|---|---|
ok |
200 | Healthy โ all systems operational |
degraded |
200 | Operational but a backend dependency is degraded; fallback RPC may be active |
starting |
503 | Proxy not yet ready to serve paid traffic |
Key fields:
- paid_traffic_ready โ whether the proxy will accept and bill RPC requests
- fallback_active โ present only when degraded; indicates requests are routing to the fallback RPC
- service_profile โ shows which services are enabled
const health = await rpc.health();
console.log("Status:", health.status);
health = rpc.health()
print("Status:", health["status"])
import json, urllib.request
with urllib.request.urlopen("https://rpc.nodius.xyz/health") as resp:
health = json.loads(resp.read())
print("Status:", health["status"])
const res = await fetch("https://rpc.nodius.xyz/health");
const health = await res.json();
console.log("Status:", health.status);
curl https://rpc.nodius.xyz/health
GET /pricing
Public endpoint returning the current credits-per-USDC rate. No authentication required.
Response:
{
"ok": true,
"credits_per_usdc": 10000
}
curl https://rpc.nodius.xyz/pricing
import json, urllib.request
with urllib.request.urlopen("https://rpc.nodius.xyz/pricing") as resp:
pricing = json.loads(resp.read())
print("Credits per USDC:", pricing["credits_per_usdc"])
const res = await fetch("https://rpc.nodius.xyz/pricing");
const pricing = await res.json();
console.log("Credits per USDC:", pricing.credits_per_usdc);
// No dedicated SDK method; fetch directly:
const res = await fetch("https://rpc.nodius.xyz/pricing");
const pricing = await res.json();
console.log("Credits per USDC:", pricing.credits_per_usdc);
# No dedicated SDK method; fetch directly:
import json, urllib.request
with urllib.request.urlopen("https://rpc.nodius.xyz/pricing") as resp:
pricing = json.loads(resp.read())
print("Credits per USDC:", pricing["credits_per_usdc"])
GET /capabilities
Public endpoint returning the endpoint capability contract. Useful for discovering which services are enabled.
Response:
{
"ok": true,
"service_profile": {
"auth": {
"wallet_signature": true,
"api_key": true,
"session_token": true
},
"services": {
"hot_rpc": true,
"send": true,
"websocket": true,
"yellowstone_grpc": true,
"heavy_indexed_rpc": true,
"archive_history": false
}
}
}
const caps = await rpc.capabilities();
console.log("Capabilities:", caps);
caps = rpc.capabilities()
print("Capabilities:", caps)
import json, urllib.request
with urllib.request.urlopen("https://rpc.nodius.xyz/capabilities") as resp:
caps = json.loads(resp.read())
print("Capabilities:", caps)
const res = await fetch("https://rpc.nodius.xyz/capabilities");
const caps = await res.json();
console.log("Capabilities:", caps);
curl https://rpc.nodius.xyz/capabilities
WebSocket
GET /ws
WebSocket endpoint for streaming subscriptions.
wss://rpc.nodius.xyz/ws
Authenticate via Authorization: Bearer *** header orSec-WebSocket-Protocol: auth.(both subprotocols must be sent together when using theSec-WebSocket-Protocol` option).
Connection lifecycle: the server sends a protocol-level Ping every 25 seconds, so an idle subscription is never reaped by the fronting proxy โ a healthy connection stays open indefinitely. Your client must answer Pongs (any standards-compliant WS library does this automatically). A 1006 abnormal close means the network path dropped, not an idle timeout โ just reconnect and re-subscribe. To never miss a signal across a reconnect, keep a small client-side buffer of recent signatures and de-dupe, or use the free GET /signals/recent snapshot as a gap-fill.
Supported subscriptions:
| Method | Unsubscribe | Description |
|---|---|---|
accountSubscribe |
accountUnsubscribe |
Account data changes |
logsSubscribe |
logsUnsubscribe |
Transaction logs |
programSubscribe |
programUnsubscribe |
Program account changes |
signatureSubscribe |
signatureUnsubscribe |
Transaction signature status |
slotSubscribe |
slotUnsubscribe |
Slot updates |
rootSubscribe |
rootUnsubscribe |
Root slot updates |
blockSubscribe |
blockUnsubscribe |
Block updates |
slotsUpdatesSubscribe |
slotsUpdatesUnsubscribe |
Detailed slot status updates |
voteSubscribe |
voteUnsubscribe |
Vote transaction notifications |
subscribeSignal |
signalUnsubscribe |
nodius.xyz Signals firehose โ decoded on-chain signal events (see below) |
See the Streaming Guide for detailed usage.
Signals Firehose
nodius.xyz Signals is a real-time firehose of decoded on-chain events, classified rule-based from the Geyser stream on a bare-metal Frankfurt node. A funded wallet gets instant access. Consume it two ways: the subscribeSignal WebSocket method, or signal webhooks (filter_type: "signal") for HTTP push delivery.
subscribeSignal (WebSocket)
Local (non-proxied) JSON-RPC method on wss://rpc.nodius.xyz/ws. Authenticate the connection the same way as any other WebSocket subscription (Authorization: Bearer *** header orauth.
Cost: connect charge โ credits per subscription, plus โ credit per delivered event, billed from the same credit balance as RPC calls. signalUnsubscribe is free (โ credits). Live pricing: GET /pricing.
Event types (valid kinds values):
| Kind | Fires when |
|---|---|
newPair |
A new pool/pair is created on a watched launch or DEX program (pump.fun create, Raydium AMM initialize, Orca Whirlpool). This is the event kind emitted today. |
The event taxonomy (graduation, walletActivity, largeSwap) is defined and additional kinds are added as their instruction-level classifiers land โ the filter accepts them, and the stream emits each kind only once its classifier is live.
Filter params โ all optional, combined with AND semantics (an absent filter matches everything):
| Param | Type | Description |
|---|---|---|
kinds |
string[] |
Event kinds to receive (default: all) |
mint |
string |
Only events for this token mint |
programs |
string[] |
Only events from these program IDs |
Subscribe example:
{
"jsonrpc": "2.0",
"id": 1,
"method": "subscribeSignal",
"params": [
{
"kinds": ["newPair", "graduation"],
"programs": ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"]
}
]
}
Response (result is the subscription id):
{ "jsonrpc": "2.0", "id": 1, "result": "sig_sub_1" }
signalNotification example โ one per matched event:
{
"jsonrpc": "2.0",
"method": "signalNotification",
"params": {
"subscription": "sig_sub_1",
"result": {
"kind": "newPair",
"program": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P",
"signature": "5j7s...",
"slot": 285943216,
"mint": "So111...",
"accounts": ["acct1...", "acct2..."]
}
}
}
Close the subscription with signalUnsubscribe:
{ "jsonrpc": "2.0", "id": 2, "method": "signalUnsubscribe", "params": ["sig_sub_1"] }
import { NodiusClient } from "@nodiusxyz/sdk";
// The SDK has no WS transport โ build the request and send it over
// your own WebSocket client after authenticating the connection.
const req = NodiusClient.subscribeSignalRequest({
kinds: ["newPair", "graduation"],
programs: ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],
});
ws.send(JSON.stringify(req));
// Incoming messages with method "signalNotification" match SignalNotification.
from nodiusxyz import NodiusClient
# The SDK has no WS transport โ build the request and send it over
# your own WebSocket client (e.g. websockets) after authenticating.
req = NodiusClient.subscribe_signal_request(
kinds=["newPair", "graduation"],
programs=["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],
)
await ws.send(json.dumps(req))
wscat -c "wss://rpc.nodius.xyz/ws" \
-H "Authorization: Bearer $SESSION_TOKEN"
> {"jsonrpc":"2.0","id":1,"method":"subscribeSignal","params":[{"kinds":["newPair"]}]}
Signal webhooks
Prefer HTTP push over a persistent WebSocket? Register a webhook with filter_type: "signal" and a signal_events array of the event kinds you want. Deliveries reuse the standard webhook pipeline: HMAC X-Webhook-Signature header, exponential-backoff retry, and per-delivery billing (see GET /pricing for the live rate).
Request body:
{
"url": "https://your-server.com/webhook",
"filter_type": "signal",
"signal_events": ["newPair", "graduation", "largeSwap"]
}
Each delivery POSTs the same SignalEvent shape shown in the signalNotification example above.
const webhook = await rpc.createSignalWebhook(
"https://your-server.com/webhook",
["newPair", "graduation", "largeSwap"]
);
webhook = rpc.create_signal_webhook(
url="https://your-server.com/webhook",
events=["newPair", "graduation", "largeSwap"],
)
curl -X POST https://rpc.nodius.xyz/webhooks \
-H "Content-Type: application/json" \
-H "X-Api-Key: *** \
-d '{"url":"https://your-server.com/webhook","filter_type":"signal","signal_events":["newPair","graduation"]}'
Yellowstone gRPC
High-performance streaming via Yellowstone geyser plugin.
grpc://rpc.nodius.xyz:10001
Authenticate via gRPC metadata: x-token: <session-token-or-api-key>. Cost: 60 credits per minute per active connection.
See the Yellowstone gRPC documentation for proto definitions and client libraries.
Response Headers
All authenticated endpoints include these headers on every response (including errors):
| Header | Description |
|---|---|
X-Credits-Remaining |
Current credit balance (after this request's deduction) |
X-Credits-Low |
true if balance < 1,000 credits |
X-Request-Id |
Unique request identifier for debugging |
X-Ratelimit-Limit |
Requests per second limit for this account |
X-Ratelimit-Remaining |
Remaining requests in current window |
X-Ratelimit-Reset |
Unix timestamp when the rate limit window resets |
On 429 responses, Retry-After (seconds to wait before retrying) is also present.
Public endpoints (/health, /pricing, /capabilities) do not include credit or rate-limit headers.
Content Types
- All request bodies must be
application/json - All responses are
application/json - The
/wsendpoint uses the WebSocket protocol - The gRPC endpoint uses Protocol Buffers