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):

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., solana-rpc(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., solana-rpc` subprotocols).

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