Developer docs
Build on PRXVT
OpenAI-compatible chat completions, gated by USDC-funded API keys. Drop into any client that lets you override base_url and api_key.
Overview
One OpenAI-compatible endpoint — https://api.prxvt.ai/v1/chat/completions— backed by a prepaid USDC balance. Authenticate with a bearer API key; each call debits your balance at the model's real per-token rate. Point any OpenAI client at the base URL and it just works.
Fund a balance one of three ways:
- Manual transfer — send USDC to the server wallet. Auto-credited (details).
- x402 top-up — one signed payment, no signup and no gas for the payer (details).
- Confidential balance— already use PRXVT's private chat? Move funds from your confidential balance into a key.
Then mint bearer keys at /api-keys and send Authorization: Bearer prxvt-sk-… on every request.
No account at all? Pay per call with x402 per-call: one signature per request, one completion back. Zero state, zero key management.
Base URL
https://api.prxvt.aiOpenAI-compatible chat endpoint: https://api.prxvt.ai/v1/chat/completions
Authentication
Every request carries an API key as a bearer token. Mint one at /api-keys after connecting your wallet and topping up.
Authorization: Bearer prxvt-sk-<48 hex chars>Keys are sha256-hashed at rest. The plaintext is shown exactly once on mint; store it in a password manager or env var immediately.
Pricing
Per-model input / output rates, billed per million tokens. Live from /v1/models so the numbers always match what the server will actually charge.
Loading model catalog…
Debits are atomic and happen before the upstream call. If the upstream fails, the debit is refunded automatically.
prxvt-moa (multi-model) is the one exception: it runs several frontier models per call and a synthesizer fuses their best reasoning into one answer, so it is billed as the sum of the whole panel (quoted worst-case up front), not the single rate shown in the table. It works on the bearer and x402 per-call paths just like any other model id.
Models
List what's available so your agent doesn't hardcode model ids. OpenAI-compatible response shape (id, object,owned_by, created) plus PRXVT extras (contextLength, capabilities, pricing, privacy).
curl https://api.prxvt.ai/v1/modelsSingle model: GET https://api.prxvt.ai/v1/models/{id}. OpenAI SDK works too: await client.models.list(). Special id auto routes server-side to the cheapest model that handles your prompt.
Quick start: cURL
curl https://api.prxvt.ai/v1/chat/completions \
-H "Authorization: Bearer $PRXVT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [
{ "role": "user", "content": "Say hi in five words." }
]
}'OpenAI Node SDK
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.PRXVT_API_KEY,
baseURL: 'https://api.prxvt.ai/v1',
});
const res = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log(res.choices[0]?.message.content);Streaming works too: pass stream: true; the SSE wire format matches OpenAI exactly.
OpenAI Python SDK
from openai import OpenAI
client = OpenAI(
api_key=os.environ["PRXVT_API_KEY"],
base_url="https://api.prxvt.ai/v1",
)
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)Claude Code router
Point claude-code-router at PRXVT to drive Claude Code through your USDC balance.
{
"Providers": [
{
"name": "prxvt",
"api_base_url": "https://api.prxvt.ai/v1/chat/completions",
"api_key": "$PRXVT_API_KEY",
"models": ["anthropic/claude-sonnet-4-6", "anthropic/claude-haiku-4-5"]
}
],
"Router": {
"default": "prxvt,anthropic/claude-sonnet-4-6"
}
}MCP server
PRXVT speaks the Model Context Protocol over Streamable HTTP at https://api.prxvt.ai/v1/mcp. Any MCP-compatible client (Claude Desktop, Cursor, custom agents) can connect with the same prxvt-sk-… bearer key and call the built-in tools.
Tools exposed
prxvt_chat_completions— run a chat completion. Same routing, debit, and refund rules as the HTTP endpoint; the bearer's wallet pays.prxvt_list_models— live catalog with pricing, context, capabilities.prxvt_account_balance— USDC balance for the bearer's wallet. Useful as a pre-flight check before spending.prxvt_list_keys— keys owned by the bearer's wallet (no plaintext, just metadata).prxvt_topup_info— x402 endpoint + chain metadata so an agent can sign its own top-up.
Chat history and attached documents stay in the browser encrypted, so they aren't exposed over MCP.
Claude Desktop config (mcpServers section):
{
"mcpServers": {
"prxvt": {
"transport": {
"type": "http",
"url": "https://api.prxvt.ai/v1/mcp",
"headers": { "Authorization": "Bearer $PRXVT_API_KEY" }
}
}
}
}Custom MCP client with the official TypeScript SDK:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
const client = new Client({ name: 'my-agent', version: '0.1.0' });
const transport = new StreamableHTTPClientTransport(
new URL('/v1/mcp', 'https://api.prxvt.ai'),
{
requestInit: {
headers: { authorization: `Bearer ${process.env.PRXVT_API_KEY}` },
},
},
);
await client.connect(transport);
const { balanceUsd } = (
await client.callTool({ name: 'prxvt_account_balance', arguments: {} })
).structuredContent as { balanceUsd: number };
if (balanceUsd > 0.01) {
const reply = await client.callTool({
name: 'prxvt_chat_completions',
arguments: {
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello' }],
},
});
console.log(reply.content[0].text);
}Stateless transport: each call opens a fresh JSON-RPC exchange, so concurrent agents on the same key don't share session state. Chat tokens stream through MCP as notifications/progress messages. Pass an onprogress handler to callTool and read p.message to render text as it generates:
await client.callTool(
{
name: 'prxvt_chat_completions',
arguments: {
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Write a short poem.' }],
},
},
undefined,
{
onprogress: (p) => {
// p.message is the accumulated text so far. Render to your UI.
process.stdout.write('\r' + p.message);
},
},
);Endpoints
| Method | Path | Auth | Notes |
|---|---|---|---|
| POST | /v1/chat/completions | bearer | OpenAI-compatible. Streaming via stream: true. |
| POST | /v1/mcp | bearer | Model Context Protocol over Streamable HTTP. Tools: chat, balance, models, keys, topup. |
| GET | /v1/models | public | List live models (OpenAI shape + PRXVT extras). |
| GET | /v1/models/:id | public | Single model lookup. 404 for unknown / coming_soon. |
| GET | /v1/account | session | Balance + active key count for the signed-in wallet. |
| GET | /v1/keys | session | List keys for the signed-in wallet. |
| POST | /v1/keys | session | Mint a key. { label?, quotaUsdc? }. Plaintext returned once. |
| DELETE | /v1/keys/:id | session | Revoke a key. Add ?hard=1 to fully delete a revoked row. |
| POST | /v1/pool/account/topup | session / key | Fund an account from a PRXVT confidential balance (one-way). Body: pool withdraw proof. Credits the bearer's account by the withdrawn amount. |
| POST | /v1/credits/topup | x402 | Agent-facing topup ($5 USDC). Pay via the x402 protocol; balance credits on settle. Short alias: x402.prxvt.ai/topup. |
| POST | /v1/x402/chat/completions | x402 | Pay-per-call OpenAI-shape chat completion. Per-token pricing + 10% margin. Gasless EIP-3009. Short alias: x402.prxvt.ai/chat/completions. |
| POST | /v1/credits/rescan | public | Force the chain watcher to re-hydrate. Use when a deposit hasn't credited. |
| GET | /v1/credits/info | public | Server wallet, chain id, USDC contract, pricing. |
| GET | /v1/credits/debug | public | Watcher state + recent credit events for ?wallet=0x... |
Funding (manual transfer)
Direct USDC transfers go to the server wallet on Base Sepolia. The account credited is the sender of the on-chain Transfer event (the EOA that signed it). Smart-contract wallets credit the contract address, not the EOA controlling it, so prefer EOA-to-EOA for the manual rail.
Credits land within ~5 seconds of on-chain confirmation. Stuck? POST /v1/credits/rescan forces the watcher to re-scan. Inspect: GET /v1/credits/debug?wallet=0x…
Funding from your confidential balance
If you hold a PRXVT confidential balance — the private balance you deposit and spend in the chat app — you can move part of it into an API-key account. Your browser does one zero-knowledge pool spend that withdraws the amount; the server credits the account by exactly that, and the remainder stays confidential. One direction only: funds can't move back from a key into the pool.
- Open /api-keys and sign in.
- Under Fund from confidential balance, enter an amount and click Move to API key.
- The proof runs in your browser; your key balance updates when it settles.
Privacy: the withdrawal is anonymous (the server can't link the pool note to you), but the destination is not — funds in an API key are no longer confidential, and every call on the key is linked to it. The amount can't exceed your largest single deposit (one note is spent per move).
Programmatic: POST https://api.prxvt.ai/v1/pool/account/topupwith a pool withdraw proof, authing with a wallet session or an API key as the bearer. Most callers use the UI above — building the proof needs your note secrets, so it can't be done from the key alone.
x402 top-up (agent-native)
One-shot signed payment for agents. No prior signup, no wallet UI, no gas needed from the payer (the x402 facilitator pays gas in exchange for an EIP-3009 transferWithAuthorization signature). The wallet that signs the payment is the wallet that gets credited — no attribution surprises.
Protocol flow
POST /v1/credits/topupwith no body → 402 with apayment-requiredheader (base64 JSON describing scheme, network, asset, payTo, amount).- Sign EIP-3009 typed data with your EVM key (the official x402 SDK does this for you).
- Re-POST with the
PAYMENT-SIGNATUREheader (base64 of the signed payload). - Server calls the facilitator → on-chain settlement →
onAfterSettlecredits your wallet's balance. Tx hash returned in thepayment-responseheader. - Immediately sign in via SIWE with the same wallet and mint API keys against the new balance.
If you hand-roll x402 (not using the SDK), read this
- This is x402 v2. The payment header is
PAYMENT-SIGNATURE. The legacy v1 nameX-PAYMENTis also accepted as a fallback, butPAYMENTor other names are not read. - The requirements come in the
PAYMENT-REQUIREDresponse header (base64 JSON), not the body. - The signed payload (before base64) must be JSON with a top-level numeric
"x402Version": 2and a top-levelacceptedequal to the exactaccepts[]entry you chose, copied verbatim. Do not derive the version fromaccepts[]; do not omitaccepted. - Failed 402s now return a JSON body with
error(payment_requiredfor normal discovery vsinvalid_paymentwhen a header was present but rejected) and amessagechecklist, so you can tell "no payment" apart from "bad payment."
Canonical URL: https://api.prxvt.ai/v1/credits/topup, on the same api.prxvt.ai host as every other endpoint. The short alias https://x402.prxvt.ai/topup resolves to the same handler if you prefer it.
Easiest path: use the official @x402 client SDK, which builds the header and payload for you. Node example:
import { privateKeyToAccount } from 'viem/accounts';
import { x402Client } from '@x402/core/client';
import { x402HTTPClient } from '@x402/core/http';
import { registerExactEvmScheme } from '@x402/evm/exact/client';
const account = privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`);
const core = new x402Client();
registerExactEvmScheme(core, { signer: account });
const x402 = new x402HTTPClient(core);
// 1) Hit the topup endpoint; expect 402.
const first = await fetch('https://api.prxvt.ai/v1/credits/topup', { method: 'POST' });
const paymentRequired = x402.getPaymentRequiredResponse(
(name) => first.headers.get(name),
);
// 2) Build + sign the payment payload.
const payload = await x402.createPaymentPayload(paymentRequired);
const headers = x402.encodePaymentSignatureHeader(payload);
// 3) Retry with the signed payload.
const second = await fetch('https://api.prxvt.ai/v1/credits/topup', {
method: 'POST',
headers,
});
const settle = x402.getPaymentSettleResponse(
(name) => second.headers.get(name),
);
console.log('settled:', settle.success, 'tx:', settle.transaction);
// 4) Sign in via SIWE with the same wallet, mint a key, off you go.Networks: eip155:84532 (Base Sepolia, no-auth public facilitator) and eip155:8453 (Base mainnet, CDP-hosted facilitator). Default top-up is $5 USDC; the facilitator-reported amount must match the configured price exactly (defense against a compromised facilitator). Reference implementation: packages/sovereign-server/scripts/x402-burner-test.ts — run it against your dev server to verify the whole flow.
x402 per-call (zero state)
Canonical URL: https://api.prxvt.ai/v1/x402/chat/completions, on the same api.prxvt.ai host as the bearer path. The short alias https://x402.prxvt.ai/chat/completions resolves to the same handler if you prefer it. (The plain https://api.prxvt.ai/v1/chat/completions endpoint also accepts x402, so a single URL can serve both auth modes.)
Same x402 wire protocol as topup, but the payment buys exactly one chat completion instead of a credit balance. No PRXVT account, no API key, no IndexedDB. Built for short-lived agents that don't want a relationship with the server.
prxvt-moa works here too: the 402 quotes the worst-case panel sum (several models per call) up front, and your signed authorization caps at that amount. There is no per-call refund, so it suits a funded balance better than one-off x402 unless you specifically want the zero-state flow.
Protocol flow
POST /v1/x402/chat/completionswith the OpenAI-shaped body and no payment header, receive 402 with the quoted price.- Sign the EIP-3009 authorization (the official x402 SDK does this).
- Re-POST the same body with the
PAYMENT-SIGNATUREheader. - Server verifies the signature, forwards to the upstream model, settles on-chain just before the response pipes back. JSON or streaming SSE, your choice.
Node example using the official x402 client SDK:
import { privateKeyToAccount } from 'viem/accounts';
import { x402Client } from '@x402/core/client';
import { x402HTTPClient } from '@x402/core/http';
import { registerExactEvmScheme } from '@x402/evm/exact/client';
const account = privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`);
const core = new x402Client();
registerExactEvmScheme(core, { signer: account });
const x402 = new x402HTTPClient(core);
const body = {
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'one line summary of x402' }],
};
// 1) Ask without payment; expect 402.
const first = await fetch('https://api.prxvt.ai/v1/x402/chat/completions', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
});
const paymentRequired = x402.getPaymentRequiredResponse(
(name) => first.headers.get(name),
);
// 2) Sign + retry with PAYMENT-SIGNATURE.
const payload = await x402.createPaymentPayload(paymentRequired);
const second = await fetch('https://api.prxvt.ai/v1/x402/chat/completions', {
method: 'POST',
headers: {
'content-type': 'application/json',
...x402.encodePaymentSignatureHeader(payload),
},
body: JSON.stringify(body),
});
const completion = await second.json();
console.log(completion.choices[0].message.content);Pricing is per-token at the underlying model's rate plus a flat 10% margin. The 402 quote is computed live from your request body: (estimated input_tokens + max_output_tokens) × model rate × 1.10. Your EIP-3009 signature authorizes exactly that amount; settlement is for the same amount via the gasless x402 exact scheme (the facilitator pays the on-chain gas). No Permit2 approval, no ETH on the payer wallet, no on-chain setup required.
The quote is conservative: input tokens are over-estimated at ~chars/2.5 (vs. ~chars/4 for English under typical BPE tokenizers) and the output side charges max_tokens as a worst case. Sendmax_tokens tight to your actual expected length to keep the bill tight. Defaultmax_tokens is 1024; hard cap is 32k.
Failure semantics: a non-2xx upstream response, an upstream timeout, or any client-side validation error cancels settlement before any USDC moves, so the wallet is not charged. Streaming (stream: true) is passed through token-by-token from the upstream. Image generation is not available here; use a bearer key on /v1/chat/completions for that.
The Sakana Fugu models (fugu, fugu-ultra) reason and orchestrate internally for a minute or more before the first token. Pass stream: trueso you see progress right away; a non-streamed call still works but returns nothing until the whole answer is ready. Fugu is also given room to finish, so it won't truncate a long answer at the default max_tokens.
Errors
- 401 Unknown or revoked key, or expired session.
- 402 Insufficient balance or per-key quota exhausted. Top up.
- 429 IP rate limit hit. Respect the
Retry-Afterheader. - 502 / 504 Upstream model error or timeout. Debit is refunded automatically.