# Machine Payments Protocol (MPP) Router > MPP Router (Machine Payments Protocol Router) is a Stellar-to-Tempo > payment proxy. Agents pay the router once in USDC on Stellar using > either the legacy mppx 402 flow OR the standard x402 v2 protocol, > and the router pays the underlying merchant in USDC on Tempo. One > Stellar wallet lets an agent reach every Tempo-only merchant. ## Why MPP Router 88 API services (OpenAI, Anthropic, OpenRouter, fal.ai, Perplexity, Stability AI, Replicate, Suno, Grok, and more) accept MPP payments — but only on Base and Tempo, which locks Stellar-native agents out. MPP Router bridges the gap: pay USDC once on Stellar and the router settles with the merchant in under 5 seconds, with no bridging, no gas, and no extra wallet. The catalog mirrors mpp.dev — currently ~489 paid POST endpoints across 88 services, all reachable from a single Stellar wallet. ## Supported wallets and protocols - **Stellar mppx** — any Stellar USDC wallet using `@stellar/mpp/charge/client` (the legacy MPP flavor). Send `Authorization: Payment `. - **Stellar x402 v2** — any spec-compliant x402 client built on `@x402/core/client` + `@x402/stellar/exact/client`. Send `Payment-Signature: `. This is the recommended path for new clients — it works against any x402 v2 server, not just this router. Verified end-to-end on Stellar mainnet 2026-04-12. - **Base / Tempo wallets (and other schemes)** — forward directly to the upstream merchant as a transparent proxy The router emits a **dual-format 402** on the no-credential probe (both `WWW-Authenticate` and `Payment-Required` headers in the same response), so each client reads the format it understands without any router-specific code. The public base URL is `https://apiserver.mpprouter.dev`. Agents must only talk to this host. Upstream merchant hostnames, settlement keys, and internal routing are not part of the public contract and may change at any time. ## Discovery endpoints All GET, no auth required: - `/v1/services/catalog` — full catalog (~489 entries) - `/v1/services/search?q=&category=&status=&limit=&offset=` — search/filter - `/openapi.json` — OpenAPI 3.1 spec - `/.well-known/ai-plugin.json` — AI plugin manifest (ChatGPT etc.) - `/x402/supported` — x402 SupportedResponse - `/health` — router health check - `/llms.txt` — this file (also at https://mpprouter.dev/llms.txt) Search parameters: - `q` — keyword search across id, name, description - `category` — filter by category (ai, media, search, blockchain, etc.) - `status` — "active" (has llms_txt docs) or "limited" (use with caution) - `limit` — max results (default 20, max 100) - `offset` — pagination offset ## Quick start for agents (mppx, legacy) 1. `GET /v1/services/catalog` — list available services (id, path, method, price, docs link). No auth, no payment. 2. `POST /v1/services/{service}/{operation}` with your request body and no `Authorization` header. The router forwards the body to the upstream merchant to get a live quote, then returns `402 Payment Required` with a Stellar `WWW-Authenticate` challenge. 3. Parse the challenge, sign a Stellar USDC transfer for the exact `amount`, `currency` (SAC contract address), and `recipient` (the router's pool address) shown in the challenge. 4. Retry the same `POST` with `Authorization: Payment ` built from your signed transaction or the on-chain tx hash. The router verifies the credential, pays the upstream merchant on Tempo, and returns the merchant's `200` response with a `Payment-Receipt` header. If you send the body twice, the router reads your second body — the first probe is a no-op and only used to learn the price. Do not assume the probe produced side effects on the merchant. ## Quick start for agents (x402 v2, recommended) This works against any x402 v2 server with no router-specific code. 1. `GET /x402/supported` — confirm the router accepts the `exact` scheme on `stellar:pubnet`. Returns a standard `SupportedResponse`. 2. `POST /v1/services/{service}/{operation}` with your request body and no auth header. Router returns `402` with both `WWW-Authenticate` (mppx) AND `Payment-Required` (x402) headers. 3. Decode the `Payment-Required` header via `x402HTTPClient.getPaymentRequiredResponse(getHeader)`. You get a standard `PaymentRequired` object with the router's `STELLAR_X402_PAY_TO` recipient, the merchant's quote (in Stellar USDC 7-decimal base units), and `extra.areFeesSponsored: true`. 4. Sign a Soroban auth entry via `x402HTTPClient.createPaymentPayload(paymentRequired)` — this uses `@x402/stellar/exact/client` under the hood. 5. Encode the payload with `encodePaymentSignatureHeader` and retry the same POST with `Payment-Signature: `. Router dispatches into its `stellar.x402` branch, runs facilitator verify, pays the downstream merchant, and on a successful merchant 2xx submits the agent's signed Soroban invoke on chain. 6. Read the merchant body plus `X-Payment-Tx`, `X-Payment-Method: stellar.x402`, and `X-Payment-Settle-Status: settled` headers from the response. The vanilla x402 client is the same one used against Coinbase's weather paywall demo — pointing it at the router needs zero config changes. ## Bring-your-own-wallet (non-Stellar agents) The router only **represents** Stellar agents. If you already have another wallet or payment credential, send it directly — the router will detect that it is not a Stellar MPP credential and forward the whole request (including your `Authorization` header) to the upstream merchant untouched. Supported passthrough cases: - `Authorization: Bearer ` — standard HTTP bearer auth. - `Authorization: Basic ` — basic auth. - `Authorization: Payment ... method="tempo" ...` — EVM x402 / Tempo credential. The merchant will verify it natively. - `Authorization: Payment ... method="stripe" ...` — Stripe credential. - Any other scheme the router does not recognise as Stellar MPP. In passthrough mode the router is a **transparent proxy**. It does not spend from its Tempo pool, does not attach a `Payment-Receipt` header, and does not implement replay protection — that is the merchant's problem with the agent, not the router's. You still get the benefit of a single hostname (`apiserver.mpprouter.dev`) and unified service discovery via `GET /v1/services/catalog`. To opt into the Stellar flow, simply send **no** `Authorization` header on your first call; the router will return a signed Stellar `402` challenge for you to pay. ## Hard rules - **One credential = one call.** Credentials are single-use. Replaying the same `Authorization` header returns `402` with an `invalid_challenge` error. Get a new challenge for every call. - **Amount, currency, and recipient are HMAC-bound** to the challenge the router issued. You cannot reuse a credential obtained for a cheap route on an expensive one — verification will reject it. - **Use fee-sponsored (pull) mode if you do not hold XLM.** The router runs a gas sponsor, so your transaction source can be the all-zeros placeholder. The router rebuilds and signs the envelope before submission. - **Do not hardcode upstream hostnames.** Always dispatch by public path (`/v1/services/{service}/{operation}`). Upstream routing can change without notice. - **Treat the 402 challenge as authoritative on price.** Do not cache prior amounts; merchants may re-price dynamically (e.g. OpenRouter chat pricing depends on the model field in the body). ## Endpoints ### `GET /health` Router + pool health. Unauthenticated. Use for readiness probes. ### `GET /v1/services/search` Search and filter the service catalog. Parameters: - `?q=openai` — keyword search across id, name, description - `?category=ai` — filter by category - `?status=active` — only services with llms_txt docs - `?limit=20&offset=0` — pagination Returns `{ total, limit, offset, services: [...] }`. ### `GET /openapi.json` OpenAPI 3.1 specification for all public endpoints. ### `GET /.well-known/ai-plugin.json` Standard AI plugin manifest for ChatGPT and agent platforms. ### `GET /services` ### `GET /v1/services/catalog` Public service catalog. Same payload under two paths. Top-level shape: ```json { "version": 1, "base_url": "https://apiserver.mpprouter.dev", "supported_payment_methods": [ { "scheme": "stellar.mpp", "network": "stellar:pubnet" }, { "scheme": "stellar.x402", "network": "stellar:pubnet" } ], "services": [ /* ~489 entries */ ] } ``` Each entry: ```json { "id": "fal_flux_schnell", "name": "fal.ai – FLUX.1 [schnell] - Fast text-to-image (1-4 steps)", "category": "ai", "categories": ["ai", "media"], "description": "FLUX.1 [schnell] - Fast text-to-image (1-4 steps)", "public_path": "/v1/services/fal/flux_schnell", "method": "POST", "price": "$0.003/request", "payment_method": "stellar", "network": "stellar-mainnet", "asset": "USDC", "status": "active", "docs_url": "https://apiserver.mpprouter.dev/docs/integration#fal-flux-schnell", "methods": { "stellar": { "intents": ["charge"] }, "stellar_x402": { "scheme": "exact", "network": "stellar:pubnet", "pay_to": "G...", "asset": "USDC" }, "tempo": { "intents": ["charge"], "role": "upstream" } }, "verified_mode": "charge" } ``` Fields to note: - `status`: `"active"` (has llms_txt — agent can learn request format) or `"limited"` (no llms_txt — use with caution). When `"limited"`, `status_note` explains why. - `docs.llms_txt`: URL to the upstream service's llms.txt. Read this to learn how to construct the request body. - `categories`: array (e.g. `["ai", "media"]`). Legacy `category` field is `categories[0]` for backward compat. - `price`: human-readable string from upstream's actual payment amount. ### `GET /x402/supported` Standard x402 `SupportedResponse` for spec-compliant client discovery. Returns the schemes/networks the router accepts plus the recipient address and asset. ### `POST /v1/services/{service}/{operation}` The universal paid-proxy endpoint. Body and query string are forwarded to the upstream merchant unchanged. The `Authorization` or `Payment-Signature` header is consumed by the router and not forwarded. Verified end-to-end services (operator-tested on mainnet): - `POST /v1/services/parallel/search` — general web search - `POST /v1/services/exa/search` — AI web search - `POST /v1/services/firecrawl/scrape` — web page scraping - `POST /v1/services/openrouter/chat` — chat completions (session) - `POST /v1/services/openai/chat` — OpenAI chat (session) - `POST /v1/services/gemini/generate` — Google Gemini (session) - `POST /v1/services/alchemy/rpc` — Ethereum mainnet RPC - `POST /v1/services/tempo/rpc` — Tempo L2 RPC - `POST /v1/services/storage/upload` — object storage - `POST /v1/services/fal/flux_schnell` — FLUX.1 schnell text-to-image ($0.003), verified 2026-04-12 via stellar.x402 The full catalog has ~489 paid POST endpoints across 88 services including OpenAI, Anthropic, Stability AI, Replicate, Suno, Grok, Brave Search, Mistral, Perplexity, Deepgram, etc. Always call `GET /services` at runtime for the live list; this llms.txt snapshot may lag the live catalog. ### `GET /v1/services/{service}/jobs/{jobId}/challenge` ### `GET /v1/services/{service}/jobs/{jobId}` For asynchronous upstream services (image/video generation, etc.) the paid POST returns `202` with `X-Job-Id` and `X-Job-Poll-Url`, or a `200` body containing `{ jobId, status: "queued" | "pending" | ... }`. Either way the router binds the job to the paying Stellar G address. Polling is a challenge-response flow, NOT a reused payment credential: 1. `GET .../jobs/{jobId}/challenge` with header `X-Stellar-Owner: G...` → `{ "nonce": "", "expiresAt": "..." }` (2-min TTL, single-use) 2. Sign the hex-decoded nonce bytes with the Stellar secret key (`Keypair.fromSecret(S).sign(bytes)`, Ed25519). 3. `GET .../jobs/{jobId}` with headers: - `X-Stellar-Owner: G...` - `X-Stellar-Nonce: ` - `X-Stellar-Signature: ` The router verifies with `Keypair.fromPublicKey(G).verify(nonce, sig)`, checks that G owns the job, then proxies the upstream status body. Polling is free. Job records expire after 24 hours. `401` means the nonce is missing / expired / wrong signature; `403` means the signer is not the job owner. The router handles upstream SIWX/SIWE auth internally using its Tempo EVM wallet — agents do NOT need SIWE. ## 402 challenge format `WWW-Authenticate` header follows the MPP payment scheme: ``` Payment id="...", realm="apiserver.mpprouter.dev", method="stellar", intent="charge", request="", expires="..." ``` `request` is base64url-encoded JSON with: ```json { "amount": "10000", "currency": "CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75", "recipient": "G...", "methodDetails": { "network": "stellar:pubnet", "feePayer": true } } ``` - `amount` is in USDC base units (7 decimals on Stellar SAC). - `currency` is the USDC Soroban SAC contract address. Pubnet and testnet use different contracts — the router picks the right one per its `STELLAR_NETWORK` setting. - `recipient` is the router pool Stellar address. Pay this, not the merchant's EVM address. - `methodDetails.feePayer: true` means the router will sponsor the Stellar network fee; use an all-zeros transaction source. ## Building the credential Two modes are supported: - **Pull mode (`type: "transaction"`)** — send a signed XDR. Required when `feePayer: true`. The router rebuilds the envelope with its own source account, signs it, and submits. - **Push mode (`type: "hash"`)** — submit the transaction yourself, then send the on-chain tx hash. Not allowed with `feePayer: true`. Use the Stellar MPP client SDK (`@stellar/mpp/charge/client`) for a reference implementation. It handles the XDR construction, DID source tag, and Authorization header serialization. ## Receipts A successful call returns the merchant body plus a `Payment-Receipt` header: ``` Payment-Receipt: ``` The decoded receipt contains: ```json { "method": "stellar", "reference": "", "status": "success", "timestamp": "2026-04-10T...Z" } ``` Keep the receipt for reconciliation. The `reference` is the on-chain Stellar tx hash you can look up on Horizon / Stellar Expert. ## Errors - `400` — unknown public route (you hit a path not in the catalog). - `402` — payment required, or credential rejected. Check the `application/problem+json` body for the error code (`invalid_challenge`, `payment_expired`, `challenge_already_used`, `malformed_credential`, etc.). **Do not retry with the same credential on `challenge_already_used`** — get a fresh challenge. - `500` — router internal error; safe to retry with a fresh challenge. - `502` — merchant refused payment or returned an error. The router's payment to the merchant failed; your Stellar payment was still settled. Contact the operator if this persists. - `429` — unused at the moment. Future per-agent rate limiting. ## Idempotency Send an `X-Request-Id` header (any stable string, 128+ bits of entropy is plenty). Repeat requests with the same id and a cached response return the cached body with `X-Idempotent: true` for 24 hours. Idempotency is keyed purely by the header — body changes are not detected — so rotate the id when the request changes. ## Things the router does NOT do - It does not hold your funds. Each credential settles on-chain before the merchant is paid. - It does not mix Stellar networks. Mainnet-only at production. - It does not guarantee merchant availability. Upstream 4xx/5xx responses are proxied as `502` with a `detail` field. ## Full human docs https://mpprouter.dev/integration.md ## Support File issues at the public repo or contact the operator listed in the landing page footer at https://mpprouter.dev. ## Credits Powered by ROZO.AI (https://rozo.ai). - Supported by Stellar Community Fund (SCF) - Supported by Base Grants - Circle Alliance member