Swapnoir
EN

Public API · v1 · Free

Swapnoir API

Live exchange rates, supported currencies and order tracking for your wallet, bot or website. Read only, no API key, no account.

Base URL
https://swapnoir.com/api/v1
Authentication
None
Format
JSON, UTF-8
Errors
RFC 9457

Overview

The Swapnoir API gives your app the same live data the swapnoir.com exchange uses. It is a small REST API over HTTPS with three endpoints, returns JSON and is described in an OpenAPI 3.1 file you can load into Postman, Insomnia, Scalar or a code generator.

Show live rates

Price widgets, calculators and bots. Estimates already include all fees for the payout.

Send users to an exchange

Open a prefilled exchange on swapnoir.com from your app with one link.

Track orders

Follow an exchange by its order ID and notify your users when it completes.

Quickstart

No signup needed. Run these in a terminal; each one works on its own.

  1. List what you can exchange. Use the id of each currency in other calls.
    curl "https://swapnoir.com/api/v1/currencies"
  2. Get a live estimate. How much XMR does 0.01 BTC buy, after fees?
    curl "https://swapnoir.com/api/v1/estimate?from=BTC&to=XMR&amount=0.01"
  3. Send your user to the exchange and track it. The user finishes on swapnoir.com and gets an order page. Its ID lets you follow the order.
    curl "https://swapnoir.com/api/v1/orders/K7M2QX9PRT4B"

Try it live

GET /api/v1/estimate?from=BTC&to=XMR&amount=0.01&rate=float

Requests and data

Base URLhttps://swapnoir.com/api/v1. HTTPS only.
MethodsAll endpoints are GET. Parameters go in the query string or path.
Responsesapplication/json, UTF-8. Errors use application/problem+json.
AmountsDecimal strings such as "0.01", so no precision is lost. Send amounts with a dot and at most the currency’s decimals.
Currency IDsCurrency plus network, for example BTC, USDT-TRC20. Not case sensitive in requests; always upper case in responses.
TimesISO 8601 in UTC, for example 2026-10-05T12:00:00.000Z.
BrowsersCORS is open (Access-Control-Allow-Origin: *), so you can call the API from a web page. No cookies are used.
TorAlso available over Tor at http://swapnoqyyp3mfh3hoypzmvpnau7gymt7c32tybx2qew3s7sw7bzz7aqd.onion/api/v1, with the same endpoints and limits.
VersioningThe version is in the path. Within v1 we only add things: new endpoints, new optional parameters, new response fields and new currencies, so ignore fields you don’t know. Anything that would break an integration ships as a new version, and v1 keeps working alongside it.

Authentication

None. There are no API keys, accounts or tokens, and requests are not linked to anyone. Never send credentials, seed phrases or private keys to the API; it will never ask for them.

Rate limits

Limits are counted per connection over a rolling minute.

EndpointRequests per minute
GET /currencies60
GET /estimate90
GET /orders/{id}30

Every response carries the standard RateLimit-Policy (for example 90;w=60) and RateLimit (for example limit=90, remaining=89, reset=60) headers. Over the limit you get 429 with a Retry-After header in seconds.

  • Cache /currencies for at least 5 minutes; it rarely changes.
  • While a user types an amount, wait about half a second after the last keystroke before asking for an estimate.
  • Poll an order at most every 10 seconds, and stop when final is true.

Errors

Errors use the standard RFC 9457 problem details format. Check the HTTP status, then branch on code, which never changes. detail is a plain-English message you can show to your user, and type links to the matching row below.

422 Example
{
  "type": "https://swapnoir.com/api#error-amount_too_small",
  "title": "Amount below minimum",
  "status": 422,
  "detail": "The minimum for this pair is 0.00005799 BTC.",
  "code": "amount_too_small",
  "min_amount": "0.00005799"
}
CodeHTTPMeaning
invalid_parameter400A query parameter is missing or malformed. param names it.
same_currency400from and to are the same currency.
receive_not_supported400The to currency is send only (Lightning).
amount_too_small422The amount is below the minimum for this pair. min_amount holds the minimum.
pair_not_supported422This pair of currencies and networks is not offered.
pair_unavailable503No rate is available for this pair right now. Retry later or try another amount.
not_found404No order with this ID, or the order has been deleted.
rate_limited429You hit the rate limit. Wait the number of seconds in Retry-After.
internal_error500Something went wrong on our side. Retry later.

Reference

List currencies

GET/api/v1/currencies

Every currency and network Swapnoir supports. A token on another network is a separate currency, for example USDT-TRC20 and USDT-ERC20. The list changes rarely; cache it for a few minutes.

The example shows three of the currencies. Response fields below are per currency.

Request

curl "https://swapnoir.com/api/v1/currencies"
const res = await fetch("https://swapnoir.com/api/v1/currencies");
const { currencies } = await res.json();

// Currencies a user can receive
const receivable = currencies.filter((c) => c.receive);
import requests

r = requests.get("https://swapnoir.com/api/v1/currencies", timeout=10)
r.raise_for_status()
currencies = r.json()["currencies"]

Response

200 OK
{
  "currencies": [
    {
      "id": "BTC",
      "ticker": "BTC",
      "name": "Bitcoin",
      "network": "Bitcoin",
      "decimals": 8,
      "send": true,
      "receive": true,
      "memo": null,
      "icon_url": "https://swapnoir.com/assets/coins/btc.svg"
    },
    {
      "id": "BTC-LN",
      "ticker": "BTC",
      "name": "Bitcoin",
      "network": "Lightning",
      "decimals": 8,
      "send": true,
      "receive": false,
      "memo": null,
      "icon_url": "https://swapnoir.com/assets/coins/btc.svg"
    },
    {
      "id": "XRP",
      "ticker": "XRP",
      "name": "XRP",
      "network": "XRP Ledger",
      "decimals": 6,
      "send": true,
      "receive": true,
      "memo": "Destination tag",
      "icon_url": "https://swapnoir.com/assets/coins/xrp.svg"
    }
  ]
}
Response fields
FieldTypeDescription
idstringCurrency and network.
tickerstring
namestring
networkstring
decimalsintegerMaximum decimals accepted in amounts.
sendbooleanCan be used as from.
receivebooleanCan be used as to.
memostring or nullName of the extra field some networks need with the address, such as Destination tag; null when not used.
icon_urlstring

Get an estimate

GET/api/v1/estimate

How much of to the user receives for amount of from, with all exchange and network fees for the payout already included. Estimates are live and are not reserved: the final amount is set when the exchange is created (fixed rate) or when the deposit is confirmed (floating rate).

Parameters

NameInDescription
fromrequiredqueryCurrency the user sends. Example: BTC.
torequiredqueryCurrency the user receives. BTC-LN is send only. Example: XMR.
amountrequiredqueryAmount of from, as a decimal with a dot and at most the currency’s decimals. Example: 0.01.
ratequeryfloat (default) follows the market until the deposit is confirmed. fixed locks the amount when the exchange is created. One of float, fixed. Example: float.

Request

curl "https://swapnoir.com/api/v1/estimate?from=BTC&to=XMR&amount=0.01&rate=float"
const params = new URLSearchParams({ from: "BTC", to: "XMR", amount: "0.01" });
const res = await fetch(`https://swapnoir.com/api/v1/estimate?${params}`);
const data = await res.json();

if (!res.ok) {
  // RFC 9457 problem, e.g. data.code === "amount_too_small"
  throw new Error(data.detail);
}
console.log(data.amount_to, data.to);
import requests

r = requests.get(
    "https://swapnoir.com/api/v1/estimate",
    params={"from": "BTC", "to": "XMR", "amount": "0.01"},
    timeout=10,
)
data = r.json()
if not r.ok:
    raise RuntimeError(data["code"] + ": " + data["detail"])
print(data["amount_to"], data["to"])

Response

200 OK
{
  "from": "BTC",
  "to": "XMR",
  "rate_type": "float",
  "amount_from": "0.01",
  "amount_to": "1.57166029",
  "rate": "157.166029",
  "usd_value": "866.32",
  "quoted_at": "2026-10-05T12:00:00.000Z"
}
422 Below the minimum
{
  "type": "https://swapnoir.com/api#error-amount_too_small",
  "title": "Amount below minimum",
  "status": 422,
  "detail": "The minimum for this pair is 0.00005799 BTC.",
  "code": "amount_too_small",
  "min_amount": "0.00005799"
}
400 Invalid parameter
{
  "type": "https://swapnoir.com/api#error-invalid_parameter",
  "title": "Invalid parameter",
  "status": 400,
  "detail": "Unknown currency `DOG`. See /api/v1/currencies.",
  "code": "invalid_parameter",
  "param": "from"
}
Response fields
FieldTypeDescription
fromstringCurrency and network.
tostringAny currency except send-only ones.
rate_typestringRate type
amount_fromstringThe amount you asked about.
amount_tostringWhat the user receives, after all fees.
ratestringAmount of to per 1 from, after fees.
usd_valuestring or nullApproximate US dollar value of amount_from, when known.
quoted_atstring

Get an order

GET/api/v1/orders/{id}

Live status and details of an exchange. The order ID is the last part of the order page URL. Anyone with the ID can see the order, so treat it like a password. Poll at most every 10 seconds and stop when final is true. Orders are deleted 7 days after they finish.

Parameters

NameInDescription
idrequiredpathOrder ID, for example K7M2QX9PRT4B.

Request

curl "https://swapnoir.com/api/v1/orders/K7M2QX9PRT4B"
const res = await fetch(`https://swapnoir.com/api/v1/orders/${orderId}`);
if (res.status === 404) {
  // wrong ID, or the order was deleted
}
const order = await res.json();
console.log(order.status, order.final);
import time
import requests

while True:
    r = requests.get(f"https://swapnoir.com/api/v1/orders/{order_id}", timeout=10)
    if r.status_code == 404:
        break  # wrong ID, or the order was deleted
    order = r.json()
    print(order["status"])
    if order["final"]:
        break
    time.sleep(15)

Response

200 OK
{
  "id": "K7M2QX9PRT4B",
  "status": "confirming",
  "final": false,
  "rate_type": "float",
  "created_at": "2026-10-05T12:00:00.000Z",
  "updated_at": "2026-10-05T12:06:12.000Z",
  "from": {
    "currency": "BTC",
    "amount": "0.01"
  },
  "to": {
    "currency": "XMR",
    "amount": "1.57166029",
    "amount_is_final": false
  },
  "deposit": {
    "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
    "memo": null,
    "expires_at": null
  },
  "payout": {
    "address": "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H",
    "memo": null
  },
  "refund": null,
  "transactions": {
    "deposit": {
      "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "url": "https://mempool.space/tx/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    },
    "payout": null
  },
  "links": {
    "order_page": "https://swapnoir.com/order/K7M2QX9PRT4B",
    "receipt": "https://swapnoir.com/order/K7M2QX9PRT4B/receipt.txt"
  }
}
404 Not found
{
  "type": "https://swapnoir.com/api#error-not_found",
  "title": "Not found",
  "status": 404,
  "detail": "No order with this ID. Orders are deleted 7 days after they finish.",
  "code": "not_found"
}
Response fields
FieldTypeDescription
idstringOrder ID
statusstringOrder status
finalbooleanTrue for done, expired, refunded and failed: stop polling.
rate_typestringRate type
created_atstring
updated_atstring
fromobject
from.currencystringCurrency and network.
from.amountstringAmount to deposit.
toobject
to.currencystringCurrency and network.
to.amountstring or nullEstimated payout, or the final payout once amount_is_final is true.
to.amount_is_finalboolean
depositobject
deposit.addressstring or nullWhere the user sends the deposit; null while the status is preparing.
deposit.memostring or nullMemo or destination tag that must be included with the deposit, when set.
deposit.expires_atstring or nullSend before this time, when set.
payoutobject
payout.addressstring
payout.memostring or null
refundnull or object
refund.addressstring
refund.memostring or null
transactionsobject
transactions.depositnull or object
transactions.deposit.hashstring
transactions.deposit.urlstring or nullBlock explorer link, when one is known.
transactions.payoutnull or object
transactions.payout.hashstring
transactions.payout.urlstring or nullBlock explorer link, when one is known.
linksobject
links.order_pagestring
links.receiptstring

Guides

Create an exchange

Exchanges are created on swapnoir.com, so your user always gets their own order page and keeps control of it. Your app sends the user there with the coins and amount already filled in:

LinkOpens
https://swapnoir.com/exchange?from=BTC&to=XMR&amount=0.01&rate=floatThe exchange at the address step, with the live estimate shown. Best for “Exchange now” buttons.
https://swapnoir.com/?from=BTC&to=XMR&amount=0.01The home page with the exchange box filled in, so the user can still change coins and amount.

Both take the same from, to, amount and optional rate as Get an estimate. To open the page in another language, put the language code first, for example /de/exchange?….

<a href="https://swapnoir.com/exchange?from=BTC&to=XMR&amount=0.01&rate=float" rel="noopener">
  Exchange BTC to XMR on Swapnoir
</a>
  1. The user pastes their receiving address and an optional refund address, passes a quick security check, and presses Exchange now.
  2. They land on their order page, https://swapnoir.com/order/<ID>, with the deposit address and amount.
  3. If your app should follow the order, ask the user to paste the order link or ID, then poll Get an order.

Order statuses

A normal exchange moves through awaiting → confirming → exchanging → sending → done. Show status to your user in your own words and stop polling once final is true.

StatusFinalMeaning
preparingNoThe deposit address is being prepared. It usually appears within minutes.
awaitingNoWaiting for the deposit. Send only while the order is in this status and before deposit.expires_at.
confirmingNoThe deposit was seen and is waiting for network confirmations.
exchangingNoThe deposit is confirmed and is being exchanged.
sendingNoThe exchanged coins are being sent to the payout address.
processingNoIn progress. Used when a more specific status is not known.
doneYesComplete. The payout was sent; see transactions.payout.
expiredYesNo deposit arrived in time. A late deposit can still be completed or refunded through support.
holdNoPaused for a routine review. The user should contact support with the order ID.
attentionNoSomething needs checking, for example a deposit of the wrong amount. The user should contact support.
refundingNoThe deposit is being returned to the refund address.
refundedYesThe deposit was returned.
failedYesThe exchange could not be completed. The user should contact support.

Supported currencies

The same list List currencies returns. New currencies are added over time, so read the list from the API instead of hard-coding it.

IDCurrencyNetworkDecimalsReceiveMemo
BTCBitcoinBitcoin8Yes–
XMRMoneroMonero12Yes–
ETHEthereumEthereum8Yes–
USDT-TRC20TetherTron (TRC20)6Yes–
USDT-ERC20TetherEthereum (ERC20)6Yes–
USDT-SOLTetherSolana6Yes–
USDC-ERC20USD CoinEthereum (ERC20)6Yes–
LTCLitecoinLitecoin8Yes–
SOLSolanaSolana9Yes–
BTC-LNBitcoin LightningLightning8Send only–
TRXTronTron6Yes–
BNBBNBBNB Smart Chain (BEP20)8Yes–
DOGEDogecoinDogecoin8Yes–
ZECZcashZcash8Yes–
BCHBitcoin CashBitcoin Cash8Yes–
DASHDashDash8Yes–
XRPXRPXRP Ledger6YesDestination tag

Integration checklist

  1. Show the networkAlways show the network next to the coin, e.g. USDT on Tron. Sending on the wrong network can lose funds.
  2. Show the memoWhen deposit.memo is set, the deposit must include it. Display it as clearly as the address.
  3. Explain fixed ratesA fixed-rate exchange pays the quoted amount only if the user sends exactly from.amount before deposit.expires_at.
  4. Keep order IDs privateAnyone with an ID can see that order. Don’t put IDs in analytics, logs or public URLs.
  5. Handle errors by codeBranch on code, show detail, and respect Retry-After.
  6. Link to the real siteOnly link to https://swapnoir.com. Never ask users for seed phrases or private keys.

Changelog

  • v1 released. GET /currencies, GET /estimate, GET /orders/{id}, RFC 9457 errors, RateLimit headers and the OpenAPI 3.1 description.

Support

Questions, ideas or higher limits for your integration: send us a message or reach us at info@swapnoir.com. Use of the API is covered by our Terms and Privacy pages.

Last updated .