Bot & Agent Onboarding Guide
Nodius is purpose-built for automated systems. This guide covers the recommended onboarding flow, authentication method selection, endpoint-profile behavior, and hot-path optimization tips for bots, trading systems, and AI agents.
How Identity & Credits Work
Your Solana keypair is your identity. Credits are tied to your public key โ no signup, no email, no account creation step. Three things that sound interchangeable are actually separate:
| What you want to do | What it requires |
|---|---|
| Authenticate (prove you own the keypair) | A valid wallet signature. Always works, regardless of balance. |
Call account-info endpoints (GET /account/info, POST /account/api-key) |
An account record, which is created automatically on first deposit or by calling POST /account/api-key (zero balance). |
Make billable RPC calls (getSlot, getBalance, sendTransaction, etc.) |
A positive credit balance. Credits come from USDC deposits. |
If you call a billable method before funding, you'll get 402 Payment Required with deposit instructions. The signature is valid โ the credits aren't there yet.
Recommended Onboarding Flow
- Generate or load a Solana keypair (Ed25519)
- Generate an API key via
generateApiKey()(TypeScript) orgenerate_api_key()(Python). This creates the account record and auto-stores the key for subsequent calls. - Get your deposit address via
getBillingAccount()/get_billing_account()and send USDC (SPL) on Solana mainnet from that wallet (minimum $0.01). Credits appear within seconds. - Start making RPC calls. Wallet-sig auth works from the start; the API key avoids per-request signing overhead.
Before funding: The account record exists (created in step 2) with zero balance.
GET /account/infoworks and returns your deposit address. Billable RPC calls return402until the deposit lands. With x402 auto-pay enabled (autoPay: true/auto_pay=True), the first402triggers an automatic micro-deposit and the call is retried.
Minimal TypeScript flow
import { NodiusClient } from "@nodius/sdk";
// No @solana/web3.js import needed โ pass your base58 secret key directly
const rpc = NodiusClient.fromSecretKey("https://rpc.nodius.xyz", "your-base58-secret-key");
// Step 1: Generate an API key (also creates the account record):
const result = await rpc.generateApiKey();
console.log("API key:", result.api_key);
// Step 2: Get your deposit address and fund with USDC (SPL):
const info = await rpc.getBillingAccount();
console.log("Deposit USDC to:", info.deposit_address);
// Credits appear within ~30 seconds of on-chain confirmation.
// Step 3: Make billable RPC calls:
const slot = await rpc.call("getSlot");
console.log("Slot:", slot);
Minimal Python flow
from nodius import NodiusClient
# Accepts 32-byte seeds and 64-byte expanded keys in base58
client = NodiusClient.from_base58("https://rpc.nodius.xyz", secret_key="your-base58-secret-key")
# Step 1: Generate an API key (also creates the account record):
result = client.generate_api_key()
print("API key:", result["api_key"])
# Step 2: Get your deposit address and fund with USDC (SPL):
info = client.get_billing_account()
print("Deposit USDC to:", info.get("deposit_address"))
# Credits appear within ~30 seconds of on-chain confirmation.
# Step 3: Make billable RPC calls:
slot = client.call("getSlot")
print("Slot:", slot)
Alternative: explicit Keypair object (TypeScript)
import { Keypair } from "@solana/web3.js";
import { NodiusClient } from "@nodius/sdk";
const keypair = Keypair.generate();
const rpc = new NodiusClient("https://rpc.nodius.xyz", { keypair });
await rpc.generateApiKey(); // also auto-stores API key
API keys are auto-stored by generateApiKey() / generate_api_key(). The POST /account/api-key endpoint generates an API key and returns it. Manual key management is rarely needed:
Auth Methods Comparison
| Method | Latency | Security | Best For |
|---|---|---|---|
API Key (X-Api-Key header or ?api-key= URL) |
Lowest โ no per-request signing | Medium (secret storage) | Latency-sensitive bots, drop-in with standard Solana libraries |
| Per-request wallet sig | Low โ Ed25519 sign + SHA-256 hash + nonce check per request | Highest (no API key secret) | Security-critical automated systems |
| Session token (Bearer) | Low | Medium (1h expiry) | WebSocket connections, dApps |
All three modes work for every request type. The difference is latency and convenience. Wallet signatures are the zero-setup default โ the SDK handles signing automatically. API keys avoid per-request signing entirely and enable drop-in compatibility with standard Solana libraries via ?api-key= in the URL.
Recommendation for bots: wallet signatures are fully supported for keyless autonomous operation and must remain available. Use API keys when the bot operator is comfortable managing a generated secret and wants the absolute lowest auth overhead.
Available Methods
The default Frankfurt endpoint serves all standard Solana RPC methods. Heavy methods like getTokenAccountsByOwner and getBlock are available and cost 25 credits per call. getTransaction and getSignaturesForAddress are served directly by the hot node.
Some historical methods (getEnrichedTransaction, explainTransaction, getConfirmedTransaction) are not available and return -32004 at no charge. getProgramAccounts is not available.
Hot-Path Optimization Tips
-
Choose auth mode deliberately. Wallet signatures are the zero-setup default โ the SDK signs every request automatically. API keys avoid per-request signing and are the lowest-latency option. If you are latency-sensitive, generate an API key early. Session tokens are useful for long-lived dApp or WebSocket clients.
-
Hold persistent connections. A cold TLS handshake adds ~60-80ms to the first request. Reuse HTTP connections via
requests.Session()(Python), a sharedAgent(Node.js), or a connection pool. Nodius keeps server-side connections alive with a 30s idle timeout โ a request every 30s keeps a connection warm. For real-time data, a single WebSocket subscription avoids per-request HTTP overhead entirely. -
Batch requests when possible. A batch of 10 getBalance calls = 1 HTTP round-trip, billed as 10 credits. Rate limit counts as 1 request.
-
Use getMultipleAccounts instead of N ร getAccountInfo. 1 credit per account, single round-trip. Max 100 accounts per call.
-
Cache getLatestBlockhash locally. Blockhash is valid for ~60 seconds. Cache client-side, refresh every 30s.
-
Prefer confirmed over finalized commitment. Finalized adds ~13 seconds of latency. Use confirmed for reads, finalized only when required.
-
Monitor X-Credits-Remaining header. Included in every response. Automate reloads when credits drop below threshold. Unused deposit credits expire after 10 days.
-
Use bulk endpoints for multi-account queries. POST /bulk/getBalances (up to 100 pubkeys), POST /bulk/getTokenBalances (up to 50 owners), POST /bulk/getTransactions (up to 50 signatures).
-
For streaming data, use WebSocket or Yellowstone gRPC. WS: 5 credits per subscription + 1 per notification. Yellowstone: 60 credits/minute (flat rate, unlimited notifications). Better than polling for real-time account changes.
Machine-Readable Error Codes
| HTTP | RPC Code | Constant | Credits? | Retry? | SDK error class |
|---|---|---|---|---|---|
| 402 | -32010 | INSUFFICIENT_CREDITS |
No | After deposit | InsufficientCreditsError |
| 402 | -32002 | PAYMENT_REQUIRED |
No | After deposit | InsufficientCreditsError |
| 429 | -32005 | RATE_LIMITED |
No | Yes (Retry-After) |
RateLimitError |
| 429 | -32005 | CONCURRENCY_LIMITED |
No | Yes (1s + jitter) | RateLimitError |
| 502 | -32003 | BACKEND_UNAVAILABLE |
No | Yes (backoff) | BackendUnavailableError |
| 503 | -32003 | SERVICE_TEMPORARILY_UNAVAILABLE |
No | Yes (backoff) | BackendUnavailableError |
| 403 | -32001 | METHOD_NOT_ALLOWED |
No | No | MethodNotAllowedError |
| 200 | -32004 | SERVICE_PROFILE_DISABLED |
No | No | ServiceProfileDisabledError |
| 403 | โ | NONCE_REUSED |
No | No (new nonce) | AuthenticationError |
| 401 | -32000 | AUTH_FAILED |
No | No (fix creds) | AuthenticationError / ExpiredSessionError |
| 400 | -32600 | INVALID_REQUEST |
No | No (fix request) | NodiusError |
| 200 | -32601 | METHOD_NOT_FOUND |
No | No | MethodNotAllowedError / RpcError |
Credits are never consumed on any error response โ billing happens only after the upstream node returns a successful result. For the full error reference including upstream Solana node codes and disambiguation guidance, see Error Reference.
Response headers on every request: - X-Credits-Remaining: current credit balance - X-Credits-Low: true (when balance < 1000) - Retry-After: seconds (on 429)
Error Handling Best Practice
Use SDK error classes for production bots. This keeps wallet-only auth intact and avoids hand-written signing bugs.
import {
BackendUnavailableError,
InsufficientCreditsError,
NodiusClient,
RateLimitError,
ServiceProfileDisabledError,
} from "@nodius/sdk";
async function rpcCall(rpc: NodiusClient, method: string, params: unknown[] = []) {
try {
return await rpc.call(method, params);
} catch (err) {
if (err instanceof RateLimitError) {
await new Promise((resolve) => setTimeout(resolve, err.retryAfter * 1000));
return rpcCall(rpc, method, params);
}
if (err instanceof InsufficientCreditsError) {
throw new Error("Out of credits - deposit USDC from the registered wallet");
}
if (err instanceof ServiceProfileDisabledError) {
throw new Error(`Method not available โ use an archive provider for ${method}`);
}
if (err instanceof BackendUnavailableError) {
throw new Error("Backend unavailable - retry with jitter or fail over");
}
throw err;
}
}