Swap API for AI agents
Connect over MCP in one line, or call the key-less HTTPS API directly. Either way your agent can quote, create and track an XMR/USDC swap end to end.
Discovery
/llms-full.txt- everything an LLM needs to run a swap, in one file./openapi.json- OpenAPI 3.1 spec (basehttps://xmrtousdc.xyz/api/v1)./api/v1/agent/quickstart- signed quickstart JSON./.well-known/agent.json,/.well-known/mcp/server-card.json,/.well-known/ai-plugin.json,/.well-known/jwks.json
Fastest: connect over MCP
Public MCP endpoint https://xmrtousdc.xyz/mcp (Streamable HTTP, stateless, no auth, no API key). Three calls: get_quote → create_swap → get_swap_status. A quote needs only amount; direction defaults to XMR→USDC and chain to Arbitrum.
| Client | Setup |
|---|---|
| Claude Code | claude mcp add --transport http swapper https://xmrtousdc.xyz/mcp |
| Claude Desktop / claude.ai | Settings -> Connectors -> Add custom connector -> https://xmrtousdc.xyz/mcp |
| Cursor (~/.cursor/mcp.json) | { "mcpServers": { "swapper": { "url": "https://xmrtousdc.xyz/mcp" } } } |
| VS Code (.vscode/mcp.json) | { "servers": { "swapper": { "type": "http", "url": "https://xmrtousdc.xyz/mcp" } } } |
| Windsurf (~/.codeium/windsurf/mcp_config.json) | { "mcpServers": { "swapper": { "serverUrl": "https://xmrtousdc.xyz/mcp" } } } |
| Older clients (stdio bridge) | { "mcpServers": { "swapper": { "command": "npx", "args": ["-y", "mcp-remote", "https://xmrtousdc.xyz/mcp"] } } } |
| Tool | Required | Notes |
|---|---|---|
get_quote | amount | Step 1, read-only. direction defaults to x2u (XMR->USDC), chain to Arbitrum. |
create_swap | amount, to | Step 2. Creates the order; pass idempotencyKey. includeQuote:true quotes and orders in one call. Returns orderId + deposit {address, amount, expiresAt}. |
get_swap_status | orderId | Step 3, read-only. Poll every next.pollIntervalSec until terminal:true. |
plan_swap | none | Optional. Validates partial intent, returns the next question to ask. |
get_capabilities | none | Optional. Directions, chains, defaults, rules. |
create_support_ticket | message | For failed or stuck swaps (include orderId). |
Then ask your agent: "Swap 0.5 XMR to USDC on Arbitrum to 0xYourAddress." Every tool result carries structuredContent with userMessage and a next hint. Full client guide: docs/user/mcp-integration.md on the docs site.
REST endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/agent/quickstart | Discovery, trust metadata, example calls (always reachable). |
| GET | /api/v1/agent/capabilities | Directions, live chains, default chain, validation rules. |
| POST | /api/v1/agent/swap/plan | Validate partial intent; returns state + guidance.questionsToAsk. |
| POST | /api/v1/agent/swap/quote | Price a swap: {direction, amount, chain?}. |
| POST | /api/v1/agent/swap/create | Create order (send idempotency-key); returns fundingInstructions. |
| POST | /api/v1/agent/swap/execute | Quote + create in one call (send idempotency-key). |
| GET | /api/v1/agent/swap/status/{orderId} | Normalized order state; poll until terminal. |
| POST | /api/v1/agent/support/tickets | Open a support ticket: {message, orderId?}. |
| GET | /api/v1/chains | Chain list incl. minSwapUsd per chain. |
| GET | /api/v1/health | Service health (status ok | degraded). |
Inputs: direction is x2u (XMR→USDC) or u2x (USDC→XMR); amount is a decimal string in source-token units; to is an EVM address for x2u or a Monero address for u2x; chain is optional (auto-filled to Arbitrum while it is the only live chain).
1. Plan
curl -sS https://xmrtousdc.xyz/api/v1/agent/swap/plan \
-H "content-type: application/json" \
-d '{"direction":"x2u","amount":"0.8"}'
Returns state (needs_input, needs_destination, invalid_input, ready_to_execute) and data.guidance.questionsToAsk.
2. Execute (quote + create)
curl -sS https://xmrtousdc.xyz/api/v1/agent/swap/execute \
-H "content-type: application/json" \
-H "idempotency-key: swap_user123_20261007_01" \
-d '{"direction":"x2u","amount":"0.8","to":"0xYOUR_ARBITRUM_ADDRESS"}'
Returns state: "waiting_for_funding", data.order.orderId, data.swapDetails and data.fundingInstructions (depositAddress, depositAmount, depositTokenSymbol, expiresAt). Reusing the same idempotency-key replays the same order; a different body with the same key returns 409.
3. Poll status
curl -sS https://xmrtousdc.xyz/api/v1/agent/swap/status/ord_xxx
| state | Meaning |
|---|---|
waiting_for_funding | Order created; user must send the exact depositAmount to depositAddress before expiresAt. |
deposit_detected | Deposit seen on-chain, waiting for confirmations. |
swap_processing | Deposit confirmed; swap executing (or refund in progress). |
completed | Terminal success. Funds sent to the destination. |
terminal_failure | Terminal failure (failed, expired or refunded). Open a support ticket. |
Full loop (JavaScript)
const API = "https://xmrtousdc.xyz/api/v1/agent";
const post = (p, body, headers = {}) => fetch(API + p, {
method: "POST",
headers: { "content-type": "application/json", ...headers },
body: JSON.stringify(body)
}).then((r) => r.json());
// 1) plan -> ask the user only for data.guidance.questionsToAsk
const plan = await post("/swap/plan", { direction: "x2u", amount: "0.8" });
// 2) execute (quote + create) with a stable idempotency key
const ex = await post("/swap/execute",
{ direction: "x2u", amount: "0.8", to: "0xYOUR_ARBITRUM_ADDRESS" },
{ "idempotency-key": "swap_user123_20261007_01" });
// 3) show ex.data.swapDetails.userChecklist + ex.data.fundingInstructions to the user
const { orderId } = ex.data.order;
// 4) poll until terminal
let st;
do {
await new Promise((r) => setTimeout(r, 30_000));
st = await fetch(API + "/swap/status/" + orderId).then((r) => r.json());
} while (!st.terminal);
System prompt for your agent
You execute XMR<->USDC swaps via the Swapper Agent API (https://xmrtousdc.xyz/api/v1/agent).
1) POST /swap/plan with known fields; ask ONE question at a time from data.guidance.questionsToAsk.
2) POST /swap/quote (or go straight to /swap/execute) and show data.swapDetails to the user.
3) After the user confirms, POST /swap/execute or /swap/create with an idempotency-key.
4) Show data.swapDetails.userChecklist and data.fundingInstructions exactly as returned.
5) Poll GET /swap/status/{orderId} until terminal (completed | terminal_failure).
6) On terminal_failure, POST /support/tickets with the orderId.
Never invent addresses or amounts. Never ask the user to fund before fundingInstructions exist.
Rules and limits
- No API key. Rate limited per IP: honor
429+Retry-Afterwith exponential backoff. - Restricted regions (including the US) get
451on trading endpoints; quickstart stays reachable. - Always show
data.swapDetails.userChecklistto the user before they fund. - Integration help: agent@xmrtousdc.xyz.