Docs

SolanaProx is a pay-per-call AI API paid with prepaid USDC on Solana. It speaks the Anthropic format at /v1/messages and the OpenAI format at /v1/chat/completions, with tool calling and streaming on both. It serves Claude and GPT models.

Auth for agents

A wallet address is not a credential. You prove you own a wallet by signing a message, then use the token you get back. Signing moves no funds.

  1. Challenge: POST /v1/auth/challenge with your wallet returns a nonce, a message and the same thing as sign_in_input.
  2. Sign: sign the exact bytes of message with the wallet's Ed25519 key. It expires in 5 minutes and works once.
  3. Token: POST /v1/auth/token with the nonce and signature (base58 or base64) returns a solx_ token.

The message is a standard Sign-In With Solana message. Treat it as opaque: sign what you receive rather than building your own.

solanaprox.com wants you to sign in with your Solana account:
YOUR_WALLET

Create a SolanaProx API token. This moves no funds. Token lifetime: 720 hours. Daily spend cap: $25.00.

URI: https://solanaprox.com
Version: 1
Chain ID: solana:mainnet
Nonce: f626cb228ce33ca14c2fb4275135e0f1
Issued At: 2026-11-06T20:44:50Z
Expiration Time: 2026-11-06T20:49:50Z

Browser wallets can pass sign_in_input to the Wallet Standard solana:signIn feature instead. Send the bytes the wallet signed as signed_message (base64) along with the signature. Every field must match what was issued.

Send the token as Authorization: Bearer, x-api-key or X-Spend-Token. It defaults to 24 hours, up to 720, with a daily spend cap of $25 unless you choose another. Both are in the message you sign. Tokens expire; balances don't. USDC you sent before signing in is claimed automatically and listed in the response as auto_claimed_deposits.

Don't give an agent your main wallet's key. Sign once at Sign in and give it a capped token, or use a dedicated low-value wallet.

curl

curl -X POST https://solanaprox.com/v1/auth/challenge -H 'Content-Type: application/json' \
  -d '{"wallet":"YOUR_WALLET","ttl_hours":720,"daily_cap_usd":25}'
# {"nonce":"...","message":"...","expires_in":300}

# Sign the message bytes with your wallet, then:
curl -X POST https://solanaprox.com/v1/auth/token -H 'Content-Type: application/json' \
  -d '{"nonce":"...","signature":"BASE58_SIGNATURE"}'
# {"token":"solx_...","wallet":"...","expires_at":"...","daily_cap_usd":25,
#  "balance_usdc":"0.000000","auto_claimed_deposits":[],"auto_claimed_usdc":"0.000000"}

Python

# pip install pynacl base58 requests
import base58, requests
from nacl.signing import SigningKey

API = "https://solanaprox.com"
key = SigningKey(bytes.fromhex(YOUR_32_BYTE_SEED_HEX))  # a dedicated wallet
wallet = base58.b58encode(bytes(key.verify_key)).decode()

c = requests.post(f"{API}/v1/auth/challenge", json={"wallet": wallet}).json()
signature = key.sign(c["message"].encode()).signature
t = requests.post(f"{API}/v1/auth/token", json={
    "nonce": c["nonce"], "signature": base58.b58encode(signature).decode(),
}).json()
print(t["balance_usdc"])
token = t["token"]  # a secret: don't log it

JavaScript

// npm install tweetnacl bs58
import nacl from "tweetnacl";
import bs58 from "bs58";

const API = "https://solanaprox.com";
const pair = nacl.sign.keyPair.fromSeed(YOUR_32_BYTE_SEED); // a dedicated wallet
const wallet = bs58.encode(pair.publicKey);
const post = (path, body) => fetch(API + path, { method: "POST",
  headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }).then(r => r.json());

const c = await post("/v1/auth/challenge", { wallet });
const signature = nacl.sign.detached(new TextEncoder().encode(c.message), pair.secretKey);
const { token } = await post("/v1/auth/token", { nonce: c.nonce, signature: bs58.encode(signature) });

Deposits

Only USDC on Solana is credited, minimum 0.10 USDC. SOL isn't credited. Credit is closed-loop, with no withdrawals. A deposit credits once it's finalized, usually within a minute.

Wallet address: FjGCr4WojWt1dHbUaCbkFgSrrXBYvbNqY6TWsePyqDFX
USDC token account: 7B65QQ41bRvk3Musz3mLCP1wBMW54T7M4TibjZ7NudSq

From your own wallet

Send from the wallet you sign in with, before or after signing in. Credit goes to the wallet that signs the transfer. A deposit that lands during sign-in can be claimed with POST /v1/deposits/claim, which only works for transfers your own wallet signed.

From an exchange

An exchange signs from its own wallet, so ask for a unique exact amount, valid for 1 hour. No signing needed:

curl -X POST https://solanaprox.com/v1/deposit/intent -H 'Content-Type: application/json' -d '{"amount_usd":10}'
# {"intent_id":"...","amount_usdc":"10.004217","deposit_address":"...","expires_at":"...","token":"solx_..."}

The amount that arrives must match to the last digit. Exchange fees can change it: if yours takes its fee out of the amount you enter, add that fee. Other amounts aren't credited automatically, so contact [email protected] with the transaction signature. Save the token: it's shown once, and it's the only way to reach that balance. To top up later, send the same token with a new intent.

Calling the API

curl

curl https://solanaprox.com/v1/messages \
  -H "x-api-key: $SOLANAPROX_TOKEN" -H "Content-Type: application/json" \
  -d '{"model":"claude-haiku-5-5","max_tokens":256,"messages":[{"role":"user","content":"Hello"}]}'

Python (OpenAI SDK)

from openai import OpenAI

client = OpenAI(base_url="https://solanaprox.com/v1", api_key=SOLANAPROX_TOKEN)
reply = client.chat.completions.create(
    model="claude-haiku-5-5", max_tokens=256,
    messages=[{"role": "user", "content": "Hello"}],
)
print(reply.choices[0].message.content)

JavaScript (Anthropic SDK)

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({ baseURL: "https://solanaprox.com", apiKey: process.env.SOLANAPROX_TOKEN });
const reply = await client.messages.create({
  model: "claude-haiku-5-5", max_tokens: 256,
  messages: [{ role: "user", content: "Hello" }],
});

The current models and prices are at /v1/models, with per-token detail at /api/capabilities. Retired model IDs are mapped to their closest replacement, reported in the X-Model-Remapped header.

Endpoints

MethodPathAuthPurpose
POST/v1/messagesTokenAnthropic format
POST/v1/chat/completionsTokenOpenAI format
GET/v1/modelsNoneModels with live prices
POST/v1/auth/challengeNoneMessage to sign. Optional ttl_hours, daily_cap_usd, purpose
POST/v1/auth/tokenSignatureExchange the signature for a token
DELETE/v1/auth/tokenTokenRevoke this token
POST/v1/auth/revoke-allSignatureRevoke all of a wallet's tokens. Use a challenge with "purpose":"revoke_all"
GET/v1/balanceTokenBalance, spend today, cap, claimable deposits
GET/v1/account/activityTokenRecent usage and credits
POST/v1/deposit/intentNone or tokenExact amount for an exchange deposit
POST/v1/deposits/claimWallet tokenClaim a transfer your wallet signed
GET/api/capabilities, /healthNoneDiscovery and status

Billing

Each request is quoted up front: estimated input tokens times the input rate, plus max_tokens times the output rate, with a 20% markup and a 0.001 USDC minimum. If your balance covers the quote, the call runs, and you're credited back down to what the provider reports. So max_tokens affects the balance you need, not what you finally pay.

Failed, empty and thinking-only replies cost nothing, and a provider outage returns 503 provider_unavailable with no charge. If a stream breaks after delivering content, the content delivered is charged. Replies aren't cached.

Errors

StatusCodeMeaning
400unknown_modelNot a served model. See /v1/models.
401wallet_signature_requiredThe request named a wallet with no valid token. Sign in.
401invalid_tokenToken invalid, expired or revoked. Your balance is unaffected.
402authentication_requiredNo token sent.
402insufficient_balanceThe balance doesn't cover the quote. Lower max_tokens or deposit.
403identity_blockedThis address belongs to SolanaProx or is blocked, so it can't sign in or hold an account. Use your own wallet.
429daily_spend_cap_reachedThis token's cap. Resets at 00:00 UTC, or sign in with a higher one.
429Rate limit per account, or per IP without a token. See Retry-After.
502upstream_errorThe provider returned an error. No charge.
503provider_unavailableThe provider can't serve this model now. No charge.
503service_spend_cap_reachedThe service-wide daily limit. No charge.