# Hermes: paid swap API for AI agents (x402) Hermes is a paid swap API over the routhex routing engine. It aggregates Solana swap providers (Jupiter, DFlow, OKX DEX and others), returns ranked quotes, builds unsigned transactions, and can broadcast signed ones. Payment is per request via the x402 protocol (HTTP 402): no accounts, no API keys, no sign-up. You pay a fraction of a cent in USDC on Solana (or Base) for each paid call. The payment itself is gasless for you: the facilitator's fee payer covers the network fee, so your wallet needs only USDC, no SOL. Two guarantees: - Failed requests are never charged. Upstream errors (4xx/5xx) pass through without settlement. - Every paid 200 carries an on-chain settlement receipt in the X-PAYMENT-RESPONSE header. No receipt means you were not charged and the result must not be trusted as paid. Base URL: https://hermes.swap.io ## Endpoints Machine-readable discovery: GET https://hermes.swap.io/v1/info returns every endpoint with its current base price and the full x402 payment requirements (payTo, asset, maxAmountRequired, feePayer). Prices are parameter-aware; the authoritative price for a request is maxAmountRequired in its own 402 response. | Method | Path | Payment | Purpose | |--------|---------------------|---------|------------------------------------------------| | POST | /v1/swap/quote | x402 | Ranked multi-provider quotes | | POST | /v1/swap/quote-2.0 | x402 | Quotes plus a built and simulated tx per provider | | POST | /v1/swap/build-tx | x402 | Unsigned transaction for a chosen quote | | POST | /v1/swap/submit-tx | x402 | Broadcast your signed transaction | | GET | /v1/swap/status | free | Broadcast status by hash (?chainKey=sol&id=HASH) | | GET | /v1/info | free | Machine-readable service description | Typical paid-call price is $0.001 to $0.003 (atomic USDC, 6 decimals: "1000" = $0.001). Cost-driving request flags: enrich, autoSlippage, gasless, the providers filter. ## The flow 1. POST /v1/swap/quote with your swap parameters. Handle the 402, pay, get ranked quotes. quotes[0] is the best route. 2. POST /v1/swap/build-tx with the chosen quote. CRITICAL: the quoteResponse field must be the chosen quote's raw providerQuote object, NOT the whole quote wrapper. This is the most common integration mistake: { "provider": best.provider.key, "userAddress": ..., "chainKey": "sol", "quoteResponse": best.providerQuote } 3. Sign the returned base64 transaction with the userAddress key locally. 4. Either broadcast it yourself with any Solana RPC, or POST it to /v1/swap/submit-tx (paid) and poll the free /v1/swap/status until the status field is "finalized" or "failed". Quotes are short-lived: build and send promptly. ## Seeing the price first (no client library needed) An unpaid request returns HTTP 402 with the exact price and payment terms: curl -s -X POST https://hermes.swap.io/v1/swap/quote \ -H 'Content-Type: application/json' \ -d '{ "input": {"token": "So11111111111111111111111111111111111111112", "amount": "1000000000", "chainKey": "sol"}, "output": {"token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "chainKey": "sol"}, "userAddress": "YOUR_WALLET_ADDRESS", "slippageBps": 50 }' The 402 body lists accepts[]: each entry has scheme "exact", network ("solana"), maxAmountRequired (price in atomic USDC), payTo (recipient), asset (USDC mint) and extra.feePayer (the facilitator wallet that pays the network fee of your payment). To pay, sign a USDC transfer transaction per the x402 spec and retry the same request with the X-PAYMENT header. In practice, use an x402 client library that does this loop for you. ## Paying with a client library (recommended) npm install x402-solana @solana/web3.js import { createX402Client } from 'x402-solana/client'; import { Keypair, VersionedTransaction } from '@solana/web3.js'; import { readFileSync } from 'node:fs'; const payer = Keypair.fromSecretKey( new Uint8Array(JSON.parse(readFileSync(process.env.PAYER_KEYPAIR, 'utf8'))), ); const client = createX402Client({ wallet: { address: payer.publicKey.toString(), signTransaction: async (tx) => { tx.sign([payer]); return tx; }, }, network: 'solana', rpcUrl: 'https://api.mainnet-beta.solana.com', }); // 1. Paid quote const quoteResp = await client.fetch('https://hermes.swap.io/v1/swap/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: { token: 'So11111111111111111111111111111111111111112', amount: '1000000000', chainKey: 'sol' }, output: { token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', chainKey: 'sol' }, userAddress: payer.publicKey.toString(), slippageBps: 50, }), }); const { quotes } = await quoteResp.json(); const best = quotes[0]; // 2. Paid build: pass the raw providerQuote, not the wrapper const buildResp = await client.fetch('https://hermes.swap.io/v1/swap/build-tx', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ provider: best.provider.key, userAddress: payer.publicKey.toString(), chainKey: 'sol', quoteResponse: best.providerQuote, }), }); const { transaction } = await buildResp.json(); // base64, unsigned // 3. Sign locally, then submit (paid) and poll status (free) const unsigned = VersionedTransaction.deserialize(Buffer.from(transaction, 'base64')); unsigned.sign([payer]); const signedTransaction = Buffer.from(unsigned.serialize()).toString('base64'); const submitResp = await client.fetch('https://hermes.swap.io/v1/swap/submit-tx', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ signedTransaction, provider: best.provider.key, chainKey: 'sol' }), }); const { transactionHash } = await submitResp.json(); // GET https://hermes.swap.io/v1/swap/status?chainKey=sol&id= // until status is "finalized" or "failed" (bound the loop, e.g. 15s). The official @x402/fetch with @x402/svm works the same way. ## Verifying you actually paid (and were served) Trust a paid 200 only when the X-PAYMENT-RESPONSE header is present and decodes to a successful settlement: const receipt = resp.headers.get('x-payment-response'); const settled = receipt ? JSON.parse(Buffer.from(receipt, 'base64').toString()) : null; if (resp.status !== 200 || !settled?.success) throw new Error('not settled'); // settled.transaction is the on-chain payment signature (your receipt). A 200 without a receipt means the call was not charged; a 4xx/5xx is never charged. submit-tx settles when the network accepts the transaction and returns a transactionHash, not when the swap is confirmed; use the free status endpoint for confirmation. ## Wallet and key handling for agents - Use a DEDICATED wallet for API payments. Fund it with a few dollars of USDC and nothing else. It needs no SOL: x402 payments are gasless for the payer. - Load the keypair from a file path given in the PAYER_KEYPAIR environment variable (standard Solana JSON array format). Never print, log, echo or transmit the secret key material anywhere. - The wallet that PAYS for API calls and the wallet that OWNS the swap (userAddress) may be the same or different. The swap transaction itself is a normal Solana transaction: its fee payer is userAddress, which does need SOL for the network fee if you broadcast a swap. - Spending expectation: a quote plus build-tx round trip costs well under one cent. If you are an autonomous agent, surface the quoted swap route and price to your operator before broadcasting any swap transaction. ## Request parameters worth knowing - slippageBps: explicit slippage. When omitted and autoSlippage is on (default), routhex computes it (costs slightly more). - providers: filter which providers are queried, e.g. ["jupiter","dflow"]. Fewer providers is cheaper. - enrich: adds USD metadata to the response (costs more, default off). - gasless: opt-in flow where the backend fronts the swap's gas and recoups it from the swap output. Default off. Send "gasless": true only when the user explicitly wants it. ## Errors and limits - Unpaid paths are rate limited (60 requests/minute per IP). Paid, settled traffic is not throttled. - Request body cap: 1 MB. - Upstream 4xx (bad token, no route, insufficient balance in simulation) pass through unchanged and are never charged. - If a payment settles but the upstream fails afterwards, you get the error and no charge: settlement only happens after a successful upstream response. ## More - Claude Code users: a ready-made skill wraps this whole flow in a CLI. Install with: claude plugin marketplace add swap-dot-io/agent-skills claude plugin install hermes-swap@swap-io - MCP hosts (Claude Desktop, ChatGPT desktop, Cursor): a local MCP server wraps this flow as tools; the model never sees the payer key. https://github.com/swap-dot-io/hermes-mcp - Human documentation: https://docs.swap.io (Agent API section). - Machine-readable discovery: https://hermes.swap.io/v1/info - x402 protocol: https://www.x402.org - Client libraries: x402-solana (npm), @x402/fetch + @x402/svm (npm).