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.

  1. Generate or load a Solana keypair (Ed25519)
  2. Send USDC (SPL) on Solana mainnet to the deposit address from that wallet (minimum $0.01) to buy credits โ€” the account record is created automatically and credits appear within seconds
  3. Start making RPC calls using wallet-sig auth โ€” the SDK handles signing automatically
  4. Generate an API key via generateApiKey() (TypeScript) or generate_api_key() (Python). This hits POST /account/api-key, auto-stores the returned key, and subsequent calls use it automatically. If you are latency-sensitive, do this early โ€” API keys avoid per-request signing overhead.

generateApiKey() before funding: Creates the account record (zero balance) and returns an API key. GET /account/info will work and return your deposit address. But billable RPC calls will still return 402 until the deposit lands. With x402 auto-pay enabled (autoPay: true / auto_pay=True), the first 402 triggers 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: Fund โ€” send USDC (SPL) from this wallet to the deposit address.
//         Credits appear within ~30 seconds of on-chain confirmation.
//         Get the deposit address from the 402 response or:
const info = await rpc.getBillingAccount();
console.log("Deposit USDC to:", info.deposit_address);

// Step 2: Wait for credits to land, then make billable RPC calls:
const slot = await rpc.call("getSlot");
console.log("Slot:", slot);

// Step 3 (optional): Generate an API key for lower-latency auth:
const result = await rpc.generateApiKey();
console.log("API key:", result.api_key);

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: Fund โ€” send USDC (SPL) from this wallet to the deposit address.
#         Credits appear within ~30 seconds of on-chain confirmation.
#         Get the deposit address from the 402 response or:
info = client.get_billing_account()
print("Deposit USDC to:", info.get("deposit_address"))

# Step 2: Wait for credits to land, then make billable RPC calls:
slot = client.call("getSlot")
print("Slot:", slot)

# Step 3 (optional): Generate an API key for lower-latency auth:
result = client.generate_api_key()
print("API key:", result["api_key"])

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 โ€” single Redis lookup 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.

Endpoint Profile

The default Frankfurt endpoint is a hot-node profile optimized for:

Archive-only methods such as getEnrichedTransaction, explainTransaction, and getConfirmedTransaction are enabled on dedicated endpoint profiles. When the archive profile is disabled, the request is rejected before billing with a deterministic service-profile error (-32004). Bots should treat -32004 with HTTP 200 as "route to an archive endpoint or skip this workflow." getTransaction and getSignaturesForAddress are served by the hot node directly; getProgramAccounts is not available.

Hot-Path Optimization Tips

  1. 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.

  2. Hold persistent connections. A cold TLS handshake adds 2-5s to the first request. Reuse HTTP connections via requests.Session() (Python), a shared Agent (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.

  3. Batch requests when possible. A batch of 10 getBalance calls = 1 HTTP round-trip, billed as 10 credits. Rate limit counts as 1 request.

  4. Use getMultipleAccounts instead of N ร— getAccountInfo. 1 credit per account, single round-trip. Max 100 accounts per call.

  5. Cache getLatestBlockhash locally. Blockhash is valid for ~60 seconds. Cache client-side, refresh every 30s.

  6. Prefer confirmed over finalized commitment. Finalized adds ~13 seconds of latency. Use confirmed for reads, finalized only when required.

  7. Monitor X-Credits-Remaining header. Included in every response. Automate reloads when credits drop below threshold. Unused deposit credits expire after 10 days.

  8. 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).

  9. 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(`Route ${method} to another endpoint profile or skip it`);
    }

    if (err instanceof BackendUnavailableError) {
      throw new Error("Backend unavailable - retry with jitter or fail over");
    }

    throw err;
  }
}