Crypto swap API and MCP server
Use xswap.to from your own app or from an AI assistant. The REST API and the MCP server do the same four things: find a currency, quote a swap, create a swap and check its status. You need no account and no API key. Answers are JSON over HTTPS.
Rates float. A quote is an estimate, and the final amount can differ a little. An order only reserves a deposit address: nothing moves until the user sends coins from their own wallet.
Quick start
Base URL: https://xswap.to/api/v1/. A swap takes four calls:
- Find the currency ids:
GET /api/v1/currencies?q=usdt tron - Get a quote and the minimum:
GET /api/v1/quote?from=btc&to=usdttrc20&amount=0.01 - Create the swap with the payout address:
POST /api/v1/swaps. The answer has the deposit address. - The user sends the amount to the deposit address. Follow it with
GET /api/v1/swaps/{id}.
curl "https://xswap.to/api/v1/quote?from=btc&to=eth&amount=0.1"
Currency ids and networks
Each currency id is one coin on one network. btc is Bitcoin, usdttrc20 is USDT on TRON and usdterc20 is USDT on Ethereum. A coin sent on the wrong network can be lost, so always pass the id of the exact network.
A plain symbol works when the coin is listed on one network only. For a symbol on several networks, such as usdt, the API answers with ambiguous_currency and lists the ids to choose from.
GETList and search currencies
GET /api/v1/currencies returns every listing. Add q to search.
| Parameter | Description |
|---|---|
q | Optional. Words to match against the id, symbol, name or network. Every word must match, so usdt tron finds USDT on TRON. |
limit | Optional. Most results to return. |
Example response:
{
"total": 1,
"currencies": [
{
"id": "usdttrc20",
"ticker": "USDT",
"name": "Tether (TRC20)",
"network": "trx",
"networkName": "TRON",
"tokenContract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"hasExtraId": false,
"canSend": true,
"canReceive": true
}
]
}
hasExtraId is true when addresses on that network can need a memo or tag (XRP and Stellar, for example). canSend and canReceive tell you which side of a swap the listing can be on right now.
GETGet a quote
GET /api/v1/quote?from=btc&to=eth&amount=0.1
| Parameter | Description |
|---|---|
from | Currency id to send. |
to | Currency id to receive. |
amount | Amount of from to send, as a decimal like 0.1. |
Example response (live values change; from and to are currency objects as above, shortened here):
{
"from": { "id": "btc", "ticker": "BTC", "network": "btc", ... },
"to": { "id": "eth", "ticker": "ETH", "network": "eth", ... },
"amount": "0.1",
"estimatedAmount": "3.433438",
"minAmount": "0.000016",
"maxAmount": null,
"depositFee": "0.000011",
"payoutFee": "0.00006",
"etaMinutes": "10-60",
"rateType": "floating",
"warnings": []
}
Amounts are decimal strings. depositFee (in from) and payoutFee (in to) are already taken out of estimatedAmount. A maxAmount of null means the pair has no upper limit. etaMinutes is the exchange partner's own time forecast. warnings lists notes to show the user, for example when an id like usdc means the Ethereum listing of a coin that other networks carry too.
POSTCreate a swap
POST /api/v1/swaps with a JSON body:
| Field | Description |
|---|---|
from | Currency id to send. |
to | Currency id to receive. |
amount | Amount of from the user will send. |
address | Wallet address that receives to. It must be on the network of to. |
extraId | Optional. Memo or tag of the payout address, when the receiving wallet needs one. |
curl -X POST "https://xswap.to/api/v1/swaps" \
-H "Content-Type: application/json" \
-d '{"from":"btc","to":"eth","amount":"0.1","address":"0xYourEthereumAddress"}'
The payout address is checked before the order exists, so a mistyped or wrong-network address returns invalid_address instead of an order. Answer, with HTTP 201:
{
"id": "a1b2c3d4e5f6a7",
"from": { "id": "btc", ... },
"to": { "id": "eth", ... },
"amount": "0.1",
"depositAddress": "bc1q-one-time-deposit-address",
"depositExtraId": null,
"payoutAddress": "0xYourEthereumAddress",
"payoutExtraId": null,
"trackUrl": "https://xswap.to/transaction.php?id=a1b2c3d4e5f6a7",
"warnings": []
}
Show the user depositAddress and the exact amount. When depositExtraId is set, the deposit must include it as the memo or tag. Each order has its own deposit address, so do not reuse it for a later swap.
GETSwap status
GET /api/v1/swaps/{id}
{
"id": "a1b2c3d4e5f6a7",
"status": "exchanging",
"from": "btc",
"to": "eth",
"expectedAmountFrom": "0.1",
"expectedAmountTo": "3.43",
"amountFrom": "0.1",
"amountTo": null,
"depositAddress": "bc1q-one-time-deposit-address",
"depositExtraId": null,
"payoutAddress": "0xYourEthereumAddress",
"payoutExtraId": null,
"depositHash": "f0e1d2...",
"payoutHash": null,
"createdAt": "2026-11-10T09:12:44.000Z",
"updatedAt": "2026-11-10T09:31:02.000Z",
"trackUrl": "https://xswap.to/transaction.php?id=a1b2c3d4e5f6a7"
}
| Status | Meaning |
|---|---|
new, waiting | Order created, no deposit seen yet. |
confirming | Deposit found, waiting for network confirmations. |
exchanging | The swap is in progress. |
sending | The payout is on its way to the payout address. |
finished | Done. payoutHash is the payout transaction. |
verifying | Paused for a check by the exchange partner. Contact support with the id. |
failed, refunded, expired | The swap did not complete. Contact support with the id if a deposit was sent. |
Errors
Errors use a normal HTTP status and a JSON body with a stable code and a readable message:
{
"error": {
"code": "amount_too_small",
"message": "The minimum for this pair is 0.000016 BTC.",
"minAmount": "0.000016",
"maxAmount": null
}
}
| Code | HTTP | When |
|---|---|---|
bad_request | 400 | A field is missing or has the wrong format. |
unknown_currency | 400 | No listing has this id. |
ambiguous_currency | 400 | The symbol is on several networks. The message lists the ids. |
pair_unavailable | 400 | One side cannot be sent or received right now. |
amount_too_small, amount_too_large | 400 | Outside the pair limits, which come with the error. |
invalid_address | 400 | The payout address is not valid for the receiving network. |
not_found | 404 | Unknown endpoint or swap id. |
method_not_allowed | 405 | Wrong HTTP method for the endpoint. |
rate_limited | 429 | Too many calls. Wait for the seconds in Retry-After. |
deposit_memo_unknown | 502 | The order exists (its id comes with the error) but its deposit memo could not be read yet. Get the swap status and use depositExtraId before sending. |
upstream_error, unavailable | 502, 503 | The exchange service did not answer. Try again in a minute. |
Other 400 codes come straight from the exchange partner, for example pair_is_inactive.
Rate limits
Limits apply per IP address (IPv6 per /56 block). Quotes, status checks and new swaps also share a limit across all callers:
| Calls | Per IP |
|---|---|
| Currency list and search, MCP setup | 120 per minute |
| Quotes and status checks | 30 per minute |
| New swaps | 10 per hour |
The currency list changes slowly, so cache it on your side for an hour. If you need higher limits for an integration, message @xswap_to on Telegram.
MCP server for AI agents
The MCP server lets an AI assistant such as Claude, Cursor or GitHub Copilot in VS Code quote and set up swaps in a chat. It runs at https://xswap.to/mcp over Streamable HTTP and needs no sign-in.
Claude Code
claude mcp add --transport http xswap https://xswap.to/mcp
Claude (web and desktop): Settings, Connectors, Add custom connector, then paste https://xswap.to/mcp.
Cursor, in ~/.cursor/mcp.json:
{ "mcpServers": { "xswap": { "url": "https://xswap.to/mcp" } } }
VS Code, in .vscode/mcp.json:
{ "servers": { "xswap": { "type": "http", "url": "https://xswap.to/mcp" } } }
Other clients work too if they support remote MCP servers over Streamable HTTP. The server has four tools:
| Tool | What it does |
|---|---|
search_currencies | Finds coins by symbol, name or network and returns their ids. |
get_quote | Estimate, minimum and maximum for a pair and an amount. |
create_swap | Creates the order and returns the one-time deposit address. |
get_swap_status | Status of an order by its id. |
Try a prompt like: "Swap 50 USDT on TRON to XRP. My XRP address is r... with tag 12345." The assistant looks up the ids, gets a quote, asks you to confirm, then gives you the deposit address. It can set up the order but it cannot send funds. The deposit always comes from your own wallet.
llms.txt
/llms.txt gives language models a short map of the site, the API and the MCP server, with the facts they need to describe swaps correctly.
Good to know
- Network fees are taken out of the amount received. The sender pays their own network fee to send the deposit.
- xswap.to never asks for private keys or seed phrases and holds no balances. Each order gets a one-time deposit address from the exchange partner.
- No account is needed to start. In rare cases the exchange partner's AML check can pause a swap (
verifying) and ask for verification. - Send the coin of
fromon its network only, and includedepositExtraIdas the memo when it is set. - Support: @xswap_to on Telegram, with the swap id. By using the API you accept the Terms of Service.