To pay OpenAI API with Stellar USDC, point your client at https://apiserver.mpprouter.dev/v1/services/openai/chat instead of OpenAI, send the request with no API key, and answer the 402 Payment Required it returns by signing a USDC payment from a Stellar wallet. MPP Router verifies the payment, pays the merchant in USDC on Tempo, and hands back the model response. The same four steps work for Anthropic, Perplexity and DeepSeek today, each confirmed by a real paid call rather than by catalog presence.
What the router is, in one line. A payment proxy. It does not resell tokens, does not hold a balance for you, and does not act as an x402 facilitator for third parties. One request, one on-chain payment, one receipt.
Prerequisites
- A Stellar mainnet wallet holding USDC. The asset is Circle’s USDC on Stellar; in Soroban terms the contract id is
CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75(see x402 on Stellar). - No XLM needed. The router sponsors network fees. Every 402 it issues carries
feePayer: true(MPP dialect) andareFeesSponsored: true(x402 dialect), so your wallet only ever spends USDC. - Node 18+ and one of two client stacks:
@x402/core+@x402/stellar(recommended), ormppx+@stellar/mpp(legacy MPP dialect).
If you are wiring this into an agent rather than hand-rolling a client, the shortcut is the wallet skill: npx skills add mpprouter/stellar-agent-wallet-skill. It discovers services and settles 402s for you. Details on /how-it-works.
Step 1 — find the service
Discovery is a plain unauthenticated GET. Search by keyword and read the public_path and price fields.
curl "https://apiserver.mpprouter.dev/v1/services/search?q=openai"
# each hit looks like:
# { "id": "openai_chat",
# "public_path": "/v1/services/openai/chat",
# "price": "dynamic",
# "status": "active",
# "methods": { "stellar": { "intents": ["charge"] },
# "stellar_x402": { "scheme": "exact", "network": "stellar:pubnet" } } }Two things matter here. price: "dynamic" means the merchant quotes per request, usually based on the model you name in the body, so never cache an amount. And if a route has no methods.stellar block at all, the router has decided it is not safe to pay through Stellar and will not issue a challenge for it.
Step 2 — the first request comes back 402
Send the normal OpenAI-shaped body with no Authorization header. The router forwards the body upstream to obtain a live quote and returns a 402 with two headers carrying the same price: WWW-Authenticate (MPP) and Payment-Required (x402 v2). Decoded, the x402 header is:
{
"x402Version": 2,
"resource": { "url": "https://apiserver.mpprouter.dev/v1/services/openai/chat" },
"accepts": [{
"scheme": "exact",
"network": "stellar:pubnet",
"amount": "20000",
"asset": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75",
"payTo": "G...",
"maxTimeoutSeconds": 300,
"extra": { "areFeesSponsored": true }
}]
}amount is in Stellar base units with 7 decimals, so "20000" is 0.002 USDC. payTo is the router’s own Stellar account, not the merchant. The probe body is a no-op; nothing was sent to the model yet.
Step 3 — pay with a vanilla x402 v2 client
This is unmodified @x402/core code. There is nothing router-specific in it; the same client works against other x402 v2 servers.
import { x402Client, x402HTTPClient } from "@x402/core/client";
import { createEd25519Signer } from "@x402/stellar";
import { ExactStellarScheme } from "@x402/stellar/exact/client";
const signer = createEd25519Signer(process.env.STELLAR_SECRET!, "stellar:pubnet");
const core = new x402Client().register(
"stellar:*",
new ExactStellarScheme(signer, { url: "https://soroban-rpc.mainnet.stellar.gateway.fm" }),
);
const client = new x402HTTPClient(core);
const url = "https://apiserver.mpprouter.dev/v1/services/openai/chat";
const body = JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Explain HTTP 402 in one sentence." }],
});
// 1. probe — no auth header, expect 402
const probe = await fetch(url, {
method: "POST",
headers: { "content-type": "application/json" },
body,
});
// 2. decode the quote, 3. sign a Soroban auth entry
const required = client.getPaymentRequiredResponse((n) => probe.headers.get(n), undefined);
const payload = await client.createPaymentPayload(required);
const payHeaders = client.encodePaymentSignatureHeader(payload);
// 4. retry with Payment-Signature
const paid = await fetch(url, {
method: "POST",
headers: { "content-type": "application/json", ...payHeaders },
body,
});
console.log(await paid.json());
console.log(paid.headers.get("x-payment-tx")); // Stellar tx hashPrefer the MPP dialect? Swap the client for Mppx.create({ methods: [stellar.charge({ keypair })] }) from mppx/client and @stellar/mpp/charge/client, then call mppx.fetch(url, ...). It reads the WWW-Authenticate header instead and replies with Authorization: Payment .... Both dialects hit one verification path and pay the same amount. The full MPP flow is in Stellar’s MPP Charge Guide.
Step 4 — read the receipt
A successful call returns the merchant’s body plus receipt headers: X-Payment-Tx (the Stellar transaction hash), X-Payment-Method: stellar.x402 and X-Payment-Settle-Status: settled. On the MPP dialect the same data arrives as a base64url Payment-Receipt header. Keep the hash; it is publicly auditable on any Stellar explorer and is the only reconciliation record you need.
Send X-Request-Id if you retry on timeouts. Repeats with the same id return the cached response with X-Idempotent: true for 24 hours.
Pay Anthropic API with USDC? Status per provider
We only list a provider as working after a real paid mainnet call through the router returned a real completion. Catalog presence is not verification. Here is where the five providers people ask about most stand.
| Provider | Route | Status through the router | Last paid check |
|---|---|---|---|
| OpenAI | /v1/services/openai/chat | Working. Chat completion returned. | 2026-08-09 |
| Perplexity | /v1/services/perplexity/perplexity_chat | Working. Live sonar completion for 0.00922 USDC. | 2026-08-01 |
| DeepSeek | /v1/services/deepseek/chat | Working. Completion returned for $0.004. | 2026-08-10 |
| Anthropic | /v1/services/anthropic/messages | Working. Native completion returned for $0.001 on claude-haiku-4-5. The sibling /v1/services/anthropic/chat_completions route was paid and delivered in the same minute. | 2026-09-02 |
| Groq | /v1/services/groq/chat | Not working. Two models paid $0.008 each, then returned model_not_found; refunds queued. | 2026-08-24 |
Across the whole catalog: 88 services and roughly 489 paid endpoints are reachable; as of 2026-08-18, 20 distinct services had passed a paid check and 446 routes were open for payment. The live list on /services is authoritative; the snapshot in llms.txt can lag it.
What it costs
- You pay exactly the merchant’s quote. The router adds nothing to the
amountin the challenge, and the challenge is the only price that counts. - Chat routes are token-priced and quoted per request. Documented samples: DeepSeek $0.004 (2026-08-10), Perplexity 0.00922 USDC (2026-08-01), fal FLUX.1 schnell $0.003 (2026-04-12).
- Network fees: zero for you. The router’s sponsor account pays.
- If the merchant fails after your payment settled, the router returns
502and queues a refund to your wallet. We triggered this on purpose on 2026-09-02 by asking Anthropic for a retired model id: the payment settled, the merchant answered404 not_found_error, and the ledger moved the order torefunded. Refunds in our probes have landed between 25 seconds and a few minutes. The money is not lost either way.
Troubleshooting
402 loops
Every credential is single-use. If your retry comes back 402 with challenge_already_used, you replayed a header. Probe again, sign again. If it says invalid_challenge, the amount, asset or recipient you signed does not match what the router issued; this happens when a client caches a quote from a cheaper request. Do not hand-edit the amount.
Wrong network
Production is mainnet only, stellar:pubnet. A testnet signer will produce a signature the router cannot verify. If you use @stellar/mpp on mainnet, note that its exported mainnet SAC constants are currently StrKey-invalid (stellar-mpp-sdk issue #68); pass the USDC contract id from the 402 challenge yourself.
Insufficient USDC
The Soroban simulation fails before anything is submitted, so no fee is burned and no payment is made. Top up USDC on Stellar and retry the probe; the old challenge will have expired (maxTimeoutSeconds is 300).
400 before any 402
Some merchants validate the body before quoting. A 400 is issued before payment and costs nothing; fix the field names and retry.
502 with a merchant 404 on the model name
This is the most common way a healthy route looks broken. Model ids retire, and a provider answers a retired one with 404 not_found_error after your payment has already settled, so you see a 502 from the router and a refund. The route is fine; the id is not. Retry with a current id before concluding a provider is down. We hit exactly this against Anthropic with claude-3-5-haiku-20241022, then got a normal completion from claude-haiku-4-5 one minute later.
Try it
Pick a route on /services, follow the agent or manual setup on /how-it-works, and keep apiserver.mpprouter.dev/llms.txt open for the header formats. Once your client can pay OpenAI API with Stellar USDC, it can pay the rest of the catalog without another line of integration code.