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.
- Challenge:
POST /v1/auth/challengewith your wallet returns anonce, amessageand the same thing assign_in_input. - Sign: sign the exact bytes of
messagewith the wallet's Ed25519 key. It expires in 5 minutes and works once. - Token:
POST /v1/auth/tokenwith the nonce and signature (base58 or base64) returns asolx_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
| Method | Path | Auth | Purpose |
|---|---|---|---|
| POST | /v1/messages | Token | Anthropic format |
| POST | /v1/chat/completions | Token | OpenAI format |
| GET | /v1/models | None | Models with live prices |
| POST | /v1/auth/challenge | None | Message to sign. Optional ttl_hours, daily_cap_usd, purpose |
| POST | /v1/auth/token | Signature | Exchange the signature for a token |
| DELETE | /v1/auth/token | Token | Revoke this token |
| POST | /v1/auth/revoke-all | Signature | Revoke all of a wallet's tokens. Use a challenge with "purpose":"revoke_all" |
| GET | /v1/balance | Token | Balance, spend today, cap, claimable deposits |
| GET | /v1/account/activity | Token | Recent usage and credits |
| POST | /v1/deposit/intent | None or token | Exact amount for an exchange deposit |
| POST | /v1/deposits/claim | Wallet token | Claim a transfer your wallet signed |
| GET | /api/capabilities, /health | None | Discovery 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
| Status | Code | Meaning |
|---|---|---|
| 400 | unknown_model | Not a served model. See /v1/models. |
| 401 | wallet_signature_required | The request named a wallet with no valid token. Sign in. |
| 401 | invalid_token | Token invalid, expired or revoked. Your balance is unaffected. |
| 402 | authentication_required | No token sent. |
| 402 | insufficient_balance | The balance doesn't cover the quote. Lower max_tokens or deposit. |
| 403 | identity_blocked | This address belongs to SolanaProx or is blocked, so it can't sign in or hold an account. Use your own wallet. |
| 429 | daily_spend_cap_reached | This token's cap. Resets at 00:00 UTC, or sign in with a higher one. |
| 429 | Rate limit per account, or per IP without a token. See Retry-After. | |
| 502 | upstream_error | The provider returned an error. No charge. |
| 503 | provider_unavailable | The provider can't serve this model now. No charge. |
| 503 | service_spend_cap_reached | The service-wide daily limit. No charge. |