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 every admitted (rate-metered) response โ single calls and batches โ and on 429 responses, where Retry-After is also present. Requests rejected before admission (malformed JSON-RPC, unknown method) consume no rate units and carry no rate-window headers. Public endpoints (/health, /pricing, /capabilities) do not include credit or rate-limit headers.
Transaction v1 (SIMD-0385): Nodius fully supports Solana transaction v1. Pass maxSupportedTransactionVersion: 1 (integer) to getBlock, getTransaction, getBlocksWithLimit, and simulateTransaction to retrieve v1 transactions. Omitting it defaults to 0 and returns an error on blocks containing v1 transactions. V1 raises the transaction size limit from 1,232 to 4,096 bytes.
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 100 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, getEpochInfo, getEpochSchedule, getFeeForMessage, getFirstAvailableBlock, getGenesisHash, getHealth, getHighestSnapshotSlot, getIdentity, getInflationGovernor, getInflationRate, getLatestBlockhash, getLeaderSchedule, getMaxRetransmitSlot, getMaxShredInsertSlot, getMinimumBalanceForRentExemption, getMultipleAccounts, getRecentPerformanceSamples, getRecentPrioritizationFees, getSignatureStatuses, getSlot, getSlotLeader, getSlotLeaders, getStakeMinimumDelegation, getTokenAccountBalance, getTokenSupply, getTransactionCount, getVersion, getVoteAccounts, isBlockhashValid, minimumLedgerSlot, getPriorityFeeEstimate |
| READ_MEDIUM | 5 | simulateTransaction, getTransaction, getSignaturesForAddress, suggestPriorityFee, getClusterNodes |
| READ_HEAVY | 25 | simulateBundle, getBlock, getBlocks, getBlocksWithLimit, getTokenAccountsByOwner, getAssetsByOwner, getTransactionHistory โ getBlock methods served from hot-node retention (several hours of history; see the retention note below). Slots outside retention return Agave's native "cleaned up" error and are refunded. |
| 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, getStakeActivation, getTokenLargestAccounts |
getStakeActivation is not an Agave 4.3 RPC method (removed in Agave v2.0; see Solana migration guidance). This endpoint rejects it before billing; derive activation client-side from the stake account, StakeHistory sysvar, and current epoch. There is no single-method replacement, and a historical epoch requires archived state. getTokenLargestAccounts is a different incompatibility: Agave supports it, but this node lacks the SPL mint index; the unbounded scan can starve the RPC pool. It is disabled before billing, not offered as a heavy/slow read. Clients requiring mint-wide largest-account rankings need a separately indexed provider. Both return a local -32601 at no charge.
Hot-node retention: this endpoint serves at least the last several hours of history from a hot node (commonly 4โ8h; the window grows between ledger cleanup cycles). getSignaturesForAddress, getTransaction, and getTransactionHistory return empty/null results for older data. getBlock returns the node's native "cleaned up" error for slots outside retention โ those requests are refunded at no charge.
These are bounded findings, not a guarantee that every listed method or fixture is currently available. For example, an old slot can return Agave's cleaned-up error; an invalid token account or malformed transaction message can return -32602. getEnrichedTransaction and explainTransaction remain service-gated (archive_history: false) and return -32004 before billing on this endpoint.
โ 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โ 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.- Not served on this hot-node profile:
getTokenAccountsByDelegateneeds a mint or program-id index for either supported filter; neither is configured, so accepting it could trigger a full account scan.getInflationRewardneeds retained epoch-boundary blocks, which this non-archive ledger cannot reliably provide. Both are rejected before billing (-32601). 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": ["<writable-curve-or-market-account>"]}]) 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.8 /nodiusxyzโฅ 0.2.8. 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
Note:
/accountendpoints are not per-account rate-metered (they are un-billed management reads/writes); IP-level limits apply to these routes.
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.
Optional ref_code field. If you were given a referral code, pass it here so the account is linked to it. The code is captured once, at account creation, and cannot be changed afterward โ pass it the first time you create the key.
{ "ref_code": "your-code" }
Unknown or expired codes are ignored silently (signup never fails on a bad code). Once linked, every USDC deposit from that wallet automatically earns its bonus credits โ no per-deposit action needed.
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..."}'
x402 Payment Endpoints
POST /x402/settle
Self-serve deposit settlement for x402 clients and agents. When a billable call returns 402 Payment Required, the body and the base64 payment-required header advertise USDC payment terms (minimum deposit $0.01). Build a partially-signed USDC transfer that pays those terms and submit it here โ Nodius verifies it and broadcasts it to the network. This endpoint is unauthenticated by design: it is the onboarding path for agents that have no account or credits yet.
Request body:
{
"transaction": "<base64-encoded, partially-signed transaction (legacy or versioned)>"
}
The transaction must contain an SPL Token Transfer or TransferChecked that pays USDC to the Nodius deposit wallet's associated token account (ATA) โ NOT the wallet's default/standard token account (that account does not exist on-chain and settle rejects it). Derive the deposit ATA the same way the SDKs' deposit flows do: getAssociatedTokenAddress(depositWallet, USDC) using the payTo wallet pubkey from the 402 terms. A malformed settlement payload (not valid JSON) returns 400 Bad Request with a descriptive error field.
Verification (before broadcast):
- Payload decodes to a valid transaction โ legacy and versioned (v0+) formats are both accepted
- Transfer destination is the deposit ATA; mint is the accepted USDC mint
- Amount is at least 10,000 base units ($0.01)
Two settlement paths:
| Path | When | Behavior |
|---|---|---|
| Self-paid (default) | Fee payer is your wallet | Transaction is verified and submitted as-is |
| Sponsored (opt-in) | Fee payer is the Nodius signer wallet (advertised in extra.feePayer of the 402 terms) |
Strict whitelist: a single transferChecked instruction only, no ComputeBudget instructions. Nodius co-signs as fee payer and submits |
Response:
{
"success": true,
"transaction": "5xKp...base58 signature...",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"message": "Payment submitted. Credit your account via POST /account/confirm-deposit with this signature."
}
Credits are not assigned by this endpoint. After the transfer confirms, credit your account via POST /account/confirm-deposit with the returned signature โ crediting attributes by transfer authority (your wallet), preserving the same-wallet-pays-and-calls rule.
Errors:
| Status | Meaning |
|---|---|
400 |
Verification failed: bad base64, invalid transaction, wrong destination/mint, amount below minimum, no payment instruction found, sponsored-whitelist violation, or malformed (unparseable) request body |
502 |
Payment transaction rejected by the network |
503 |
Settlement not configured |
curl -X POST https://rpc.nodius.xyz/x402/settle \
-H "Content-Type: application/json" \
-d '{"transaction":"<base64 partially-signed transaction>"}'
With x402 auto-pay enabled in the SDK, the full handshake โ receive 402, settle, confirm deposit, retry โ runs automatically.
MCP
POST /mcp
Streamable-HTTP Model Context Protocol endpoint. Exposes Solana RPC as MCP tools so AI agents (Claude, Smithery, or any MCP client) can call JSON-RPC methods natively without a custom transport.
- Auth: identical to the JSON-RPC endpoint โ
X-Api-Keyheader,?api-key=, wallet signature, or session token. - Billing: identical to the JSON-RPC endpoint. Every admitted
tools/callis billed per the method's credit class and carries theX-Credits-Remaining/X-Ratelimit-*header trio. - Version negotiation:
initializeechoes the client's requestedprotocolVersionwhen supported (2025-03-26,2025-06-18); otherwise it replies with the latest supported version. - Tools:
initializelists the full tool catalog;tools/callwithmethod/paramsmaps 1:1 onto the JSON-RPC surface documented above.
curl -X POST https://rpc.nodius.xyz/mcp \
-H "Content-Type: application/json" \
-H "X-Api-Key: <your-key>" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"agent","version":"1.0"}}}'
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
sendSmartTransaction
Send a fully-signed transaction through the multi-leg coordinator (local Agave RPC + Jito Block Engine + TPU QUIC). Unlike sendAndConfirm, it returns immediately after broadcast โ you track confirmation yourself (getSignatureStatuses).
Cost: per send, as published by GET /pricing.
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "sendSmartTransaction",
"params": [
"<base64_or_base58_signed_transaction>",
{
"simulate": false,
"priority_fee_urgency": "medium"
}
]
}
Params:
| Field | Type | Required | Description |
|---|---|---|---|
params[0] |
string | yes | Base64 or base58 encoded signed transaction |
simulate |
boolean | no | Run a pre-send simulation first (default false). Failed simulations reject before the send and refund |
priority_fee_urgency |
string | no | low | medium | high | critical โ used for the fee suggestion |
Signatures are verified before billing; a corrupt or missing signature is rejected with -32602 at no charge.
Response: the transaction signature (base58 string), or an error object. The send is fire-and-forget โ poll getSignatureStatuses for landing.
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 published landing and base prices are returned by GET /pricing. Successful confirmation is assessed the landing price; a definitive failed execution is assessed the base fee. A requested preflight rejection is fully refunded. An unknown outcome keeps its charge pending until recovery proves the result; a timeout is not proof of non-broadcast. The response's charged_credits reports the assessed or pending charge for that response. Check the original signature and settled account balance before retrying.
Preflight: the default is skipPreflight: true. Set skipPreflight: false to require signature-verifying simulation before any broadcast leg starts. A rejected or unavailable requested preflight stops submission; it does not authorize a fallback broadcast. Preflight success is a point-in-time check, not a guarantee against subsequent state changes or slippage.
Outcome: status distinguishes confirmed (successful execution), failed (a definitive on-chain execution error), and unknown (not yet resolved). A landed failure still incurs Solana network fees. Preserve the signature and reconcile unknown outcomes through signature status and getSendStatus; do not build or submit a replacement merely because the HTTP request failed.
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. Estimates depend on the node's recent observations for those accounts. A valid window with no recorded fee demand can return zero; an upstream query failure returns an error rather than a zero estimate.
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": ["<writable-curve-or-market-account>"] }]
}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"suggested_fee_microlamports": 27390,
"urgency": "high",
"all_levels": { "low": 10000, "medium": 18000, "high": 27390, "veryHigh": 35000, "sample_count": 150, "paid_slot_count": 9, "basis": "paid_slots" },
"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 tells you which window was used. Account-scoped levels use nonzero paid slots when at least three sampled slots paid a fee; all_levels.basis, sample_count, and paid_slot_count expose that choice. Otherwise the estimate includes all slots and may be zero. These are historical observations, not a landing guarantee; bound the total fee (price ร compute-unit limit / 1,000,000) before sending.
Trade Execution
executeSnipe builds an unsigned pump.fun buy against the live bonding-curve state, fee-aware and slippage-guarded โ for both classic and v2 (buy_v2) bonding-curve launches. executeSell builds the matching unsigned sell for tokens you hold, including mayhem-mode launches and v2 curves (sell_v2); for WSOL-quoted v2 buys the builder funds and syncs a wrapped-SOL account for you. Non-SOL-quoted v2 curves settle in their quote asset and are rejected until supported. You sign both with your wallet and submit them. Nodius never takes custody of your keys.
Pricing: both methods charge a build fee plus a percentage of the trade's SOL notional, converted to credits at the live rate (GET /pricing gives current fees and conversion). The exact charge is returned in fee_credits. A large SOL budget therefore requires proportionally more credits; check your balance before building. If the build fails after billing, the charge is refunded automatically.
executeSnipe
Build an unsigned buy transaction for a pump.fun token, spending a fixed SOL budget. The quote is computed against the live curve state using the constant-product formula with protocol + creator fees netted out, so tokens_out matches what the on-chain buy_exact_sol_in will actually return.
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "executeSnipe",
"params": [{
"mint": "TokenMintAddress...",
"amount_sol": 0.1,
"slippage_bps": 500,
"priority_fee_micro_lamports": 100000
}]
}
Params:
| Field | Type | Required | Description |
|---|---|---|---|
mint |
string | yes | The token mint to buy |
amount_sol |
number | yes | SOL budget to spend (e.g. 0.1) |
slippage_bps |
number | no | Max slippage in basis points (default 500 = 5%) |
priority_fee_micro_lamports |
number | no | Priority fee in micro-lamports (default 100000; see GET /pricing) |
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"unsigned_tx_base64": "AQAAAA...",
"tokens_out": 123456789,
"max_sol_cost_lamports": 105000000,
"fee_credits": 7750,
"mint": "TokenMintAddress...",
"bonding_curve": "CurvePDA..."
}
}
Fields:
| Field | Type | Description |
|---|---|---|
unsigned_tx_base64 |
string | Unsigned v0 transaction (create-ATA + buy). Sign with your wallet, then submit |
tokens_out |
number | Expected tokens received (base units), after fees |
max_sol_cost_lamports |
number | Slippage-guarded max SOL cost in lamports |
fee_credits |
number | Total credits charged for this call (build fee plus flow fee) |
mint |
string | The mint sniped |
bonding_curve |
string | The bonding-curve PDA targeted |
executeSell
Build an unsigned sell transaction for pump.fun tokens you hold, quoting against the live curve state with the current FeeConfig fee tiers netted out. Works for standard pump.fun curves and mayhem-mode launches (Token-2022 mints). The sell fails closed before billing if your wallet does not hold the requested token amount.
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "executeSell",
"params": [{
"mint": "TokenMintAddress...",
"token_amount": 123456789,
"slippage_bps": 500,
"priority_fee_micro_lamports": 100000
}]
}
Params:
| Field | Type | Required | Description |
|---|---|---|---|
mint |
string | yes | The token mint to sell |
token_amount |
integer or decimal string | yes | Positive amount in raw token base units (integer or decimal string for full u64 precision). Use the SDK sell_all/sellAll helper to read inventory; the RPC does not accept "all" or fractions. |
slippage_bps |
number | no | Max slippage in basis points (default 500 = 5%) |
priority_fee_micro_lamports |
number | no | Priority fee in micro-lamports (default 100000; see GET /pricing) |
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"unsigned_tx_base64": "AQAAAA...",
"sol_out_lamports": 99000000,
"min_sol_output_lamports": 94050000,
"fee_credits": 7425,
"mint": "TokenMintAddress...",
"bonding_curve": "CurvePDA..."
}
}
Fields:
| Field | Type | Description |
|---|---|---|
unsigned_tx_base64 |
string | Unsigned v0 transaction (sell). Sign with your wallet, then submit |
sol_out_lamports |
number | Estimated SOL output at the quoted state; not a guaranteed payout |
min_sol_output_lamports |
number | Slippage-guarded minimum SOL output in lamports |
fee_credits |
number | Total credits charged for this call (build fee plus flow fee) |
mint |
string | The mint sold |
bonding_curve |
string | The bonding-curve PDA targeted |
Minimum sale size: sale fees are charged in basis points of the sale's gross SOL proceeds, so a sale fails when its gross SOL proceeds are so small that the bps fee rounds the net proceeds to zero. Such sales are rejected before billing with -32602, and the error names the exact lamports threshold (unbilled).
Bot flow: subscribeSignal identifies a mint โ executeSnipe builds an unsigned buy โ your wallet signs and submits it โ verify on-chain landing and inventory โ executeSell builds the unsigned exit for your position โ your wallet signs and submits it. Builders return unsigned transactions; your wallet signs, broadcasts, and verifies landing. Check live send-path availability and pricing before depending on sendAndConfirm.
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. Bundle submission is best-effort: landing depends on Jito and network conditions, and the bundle ID does not guarantee inclusion.
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. Pricing: 1 credit per address (live rate: GET /pricing).
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. Pricing: 5 credits per owner (live rate: GET /pricing).
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. Pricing: 5 credits per signature (live rate: GET /pricing). The response has results (each entry contains its input signature and a transaction, which is null when absent) and errors (each entry contains a signature and an error). Results may arrive out of input order because lookups run concurrently; match by signature, not array position. An absent transaction is a completed lookup and is charged; backend lookup errors receive a refund for the failed items.
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.
Every delivery is signed: the X-Webhook-Signature header carries sha256=<hex> where <hex> is the HMAC-SHA256 of the bytes "{event_id}.{timestamp}.{body}" (note the two separating dots; body is the raw JSON payload) keyed with the webhook's secret. X-Webhook-Timestamp is the unix-seconds signing time, X-Webhook-Event-Id identifies the delivery event (evt_{webhook_id}_{job_id}), and X-Webhook-Id identifies the subscription. Verify the signature before trusting a payload; both SDKs ship a verify_webhook_signature helper. Account/program (tx-watch) webhooks deliver successful transactions only โ failed transactions never fire a delivery.
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; availability in /capabilities |
slotsUpdatesSubscribe |
slotsUpdatesUnsubscribe |
Detailed slot status updates |
voteSubscribe |
voteUnsubscribe |
Vote notifications; availability in /capabilities |
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 5 credits per subscription, plus 1 credit per delivered event, billed from the same credit balance as RPC calls. signalUnsubscribe is free (0 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": "TokenMintAddress...",
"accounts": ["acct1...", "acct2..."],
"quote_asset": "SOL",
"curve_type": "classic",
"complete": false
}
}
}
quote_asset identifies the denomination (SOL for classic Pump or a quote-mint address, including wrapped SOL So11111111111111111111111111111111111111112 for v2); curve_type is classic or v2 for recognized Pump launches. complete: false describes a newly created Pump curve at the event slot, not its current state. Fields are null when they cannot be established from the event; verify live curve state before building a trade. The free /signals/recent preview uses the same metadata fields.
Close the subscription with signalUnsubscribe:
{ "jsonrpc": "2.0", "id": 2, "method": "signalUnsubscribe", "params": ["sig_sub_1"] }
import { NodiusClient, NodiusWebSocket } from "@nodiusxyz/sdk";
// The SDK ships a WebSocket transport (NodiusWebSocket).
const rpc = new NodiusClient("https://rpc.nodius.xyz", { keypair, auth: "session" });
const ws = NodiusWebSocket.forClient(rpc);
await ws.connectUntilOpen();
// Returns a STRING subscription id (e.g. "sig_sub_1"), unlike the
// numeric ids of the Solana-native subscriptions.
const subId = await ws.subscribeSignal({
kinds: ["newPair", "graduation"],
programs: ["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],
});
// Incoming messages with method "signalNotification" match SignalNotification.
// Later: await ws.unsubscribeSignal(subId);
from nodiusxyz import NodiusClient, NodiusWebSocket
# The SDK ships a WebSocket transport (NodiusWebSocket;
# requires the `websockets` extra: pip install nodiusxyz[ws]).
rpc = NodiusClient.from_base58("https://rpc.nodius.xyz", secret_key=SECRET)
with NodiusWebSocket.for_client(rpc) as ws:
# Returns a STRING subscription id (e.g. "sig_sub_1"), unlike the
# numeric ids of the Solana-native subscriptions.
sub_id = ws.subscribe_signal(
kinds=["newPair", "graduation"],
programs=["6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"],
)
# Later: ws.unsubscribe_signal(sub_id)
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.nodius.xyz:443 (TLS)
Authenticate every request via gRPC metadata: x-token: <session-token-or-api-key>. Each established request or subscription, including unary calls, costs 1 credits, plus 60 credits per subsequent 60-second interval while active. GET /pricing publishes the live rates.
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