Public API

Crypto exchange API: the same endpoints this site runs on

Not a product built to look open — the JSON that renders the pages you have just visited. No key, no sign-up, no plan. Every endpoint below has a console that calls it for real, with parameters you can change. Machine-readable: OpenAPI 3.1 — and llms.txt for assistants.

Base URL
https://btcswap.io
  • No authentication
  • JSON, UTF-8
  • 1,283 asset · network pairs
  • CORS open on reads
GET /api/tape
{
  "ok": true,
  "at": 1789926237,
  "rows": [
    {
      "t": "BTC",
      "q": "USDT",
      "rate": 80347.46835,
      "fmt": "80,347.47"
    },
    {
      "t": "ETH",
      "q": "USDT",
      "rate": 2593.023444,
      "fmt": "2,593.02"
    },
    {
      "t": "SOL",
      "q": "USDT",
      "rate": 108.25998159999999,
      "fmt": "108.26"
    },
    {
      "t": "XMR",
      "q": "USDT",
      "rate": 534.945079,
      "fmt": "534.9451"
    }
  ]
}

HTTP 200rendered with this pagepress Run for a fresh one

A swap, in five calls

The endpoints are listed in the order an integration uses them.

  1. 01 List /api/currencies the assets and their networks, once
  2. 02 Quote /api/quote a pair and an amount, as often as you type
  3. 03 Validate /api/validate the payout address against its network
  4. 04 Create /api/create the exchange — you get an order ID
  5. 05 Follow /api/status the order until it is finished
Authentication
None

There is no key to obtain, because there is no account system to attach one to. An Authorization header is ignored.

Errors
A refusal is still HTTP 200

“Amount below the minimum” is a valid answer to a valid question: it comes back as {"ok": false, "code": "…"}. Keep your HTTP error handling for the transport.

Asset IDs
The legacy ticker

Every endpoint identifies an asset by l from the catalogue — it carries the network, so usdttrc20 can never be confused with usdterc20.

Cross-origin
Reads only

currencies, quote and tape send Access-Control-Allow-Origin: *. The three that carry an address or an order ID are same-origin.

List the assets

GET/api/currencies

  • CORS open
  • no key
  • cache: public, 30 min

The whole catalogue in one file: every asset on every network the desk can route, already sorted (most traded first, then stablecoins, then alphabetical). It is what the asset picker of this site is built from. It is large — fetch it once and cache it.

Query parameters

None.

Response

vstring
Build identifier. When it changes, the shape of the rows may have changed too.
statsobject
assets, networks, entries (asset · network pairs) and fixed (entries that can be rate-locked).
popstring[]
Legacy tickers of the most traded assets, in display order.
items[].lstring
Legacy ticker — the ID used by every other endpoint. It carries the network: usdttrc20 and usdterc20 are two entries.
items[].t · nstring
Ticker (usdt) and full name.
items[].w · wn · wlstring
Network code, network name, and the badge to display — wl is empty when the network adds nothing (BTC on Bitcoin).
items[].istring|null
Icon file name, served from /assets/coins/.
items[].f · s · x0|1
Can be quoted at a fixed rate · is a stablecoin · uses a memo / destination tag.
items[].b · e0|1
Can be received · can be sent. An entry with b: 0 is send-only.
GET /api/currencies
// Press Run — the real response appears here.
// Or open it directly: /api/currencies

not called yetCtrl + Enter runs it

Get a live quote

GET/api/quote

  • CORS open
  • no key
  • cache: no-store

One pair, one amount: what arrives, at what rate, within which limits, with both network fees and an arrival forecast. This is the call the exchange module makes on every keystroke.

Query parameters

fromstringrequired
Legacy ticker of the asset you send.
tostringrequired
Legacy ticker of the asset you receive.
amountdecimalrequired
In the from asset. A dot or a comma both parse.
flowenumoptional
standard (floating, default) or fixed-rate. Falls back to standard when the pair cannot be locked — read the flow that comes back.
lock0|1optional
Fixed rate only. 0 returns the locked price without reserving it — use it for display; a real reservation is made when the order is created.

Response

okboolean
false is a business answer (amount too small, pair paused), still HTTP 200.
toAmount · ratenumber
What arrives, and the effective rate. Both network fees and the margin are already inside.
fromAmountFmt · toAmountFmt · rateFmtstring
The same numbers, formatted for display.
min · maxnumber|null
Live limits of the pair, in the from asset. Filled in even when ok is false — that is what lets an interface offer “use the minimum”.
depositFee · withdrawalFeenumber|null
Network fee on the deposit side (in from) and on the payout side (in to). Informational: do not subtract them again.
speedstring|null
End-to-end forecast in minutes, as a range: "10-60".
flow · fixedOkstring · boolean
The rate type actually quoted, and whether this pair can be locked at all.
validUntilISO 8601|null
Only on a locked fixed-rate quote: when the reservation lapses.
from · toobject
Both sides, ready to display: l, t, n, w, wl, i (icon URL).
code · messagestring
On refusal: a stable machine code and a sentence written for a human.
GET /api/quote?from=btc&to=xmr&amount=0.1&flow=standard
// Press Run — the real response appears here.
// Or open it directly: /api/quote?from=btc&to=xmr&amount=0.1&flow=standard

not called yetCtrl + Enter runs it

Validate an address

GET/api/validate

  • same-origin
  • no key
  • cache: no-store

Checks an address against one asset’s network — checksum included — before anyone commits to it. This is the call worth wiring into your own interface: it is the difference between catching a wrong-network address while someone is typing and catching it never.

Query parameters

assetstringrequired
Legacy ticker. The network is implied by it, which is the entire point: usdttrc20 and usdterc20 validate differently.
addressstringrequired
The address to check, 200 characters at most. The example is the public BIP-173 test vector.

Response

validtrue|false|null
Treat null as “unknown”, never as “invalid”. It means the checker could not answer (engine unreachable, or the global cap below). Blocking a legitimate user because a checker was down is the worse failure.
messagestring
Empty when valid; otherwise a sentence you can show as is.
okboolean
false only when the check itself could not run.

Capped globally at a few hundred checks a minute, all callers together — over it, valid comes back null. Same-origin: no CORS header is sent. Prefer a page? The address validator is this endpoint with an interface.

GET /api/validate?asset=btc&address=bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
// Press Run — the real response appears here.
// Or open it directly: /api/validate?asset=btc&address=bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4

not called yetCtrl + Enter runs it

Create an exchange

POST/api/create

  • same-origin
  • no key
  • cache: no-store

The one endpoint with an effect. It opens a real exchange at the desk and returns an order ID — the receipt, and the only thing to keep. Body is application/x-www-form-urlencoded.

Body fields

from · to · amount · flowrequired
As in /api/quote. For fixed-rate, the price is reserved at this moment.
addressstringrequired
The payout address. Validated again server-side, whatever your interface did first. All whitespace is stripped.
extrastringoptional
Memo or destination tag, only where the receiving network uses one (x: 1 in the catalogue). Omit it rather than sending it empty.
refundstringoptional
Optional, strongly recommended: where the deposit returns if the exchange cannot complete.

Response

ok · idboolean · string
On success. The exchange then lives at /order/<id> and answers on /api/status.
codestring
On refusal: not_valid_address, deposit_too_small, deposit_too_big, pair_is_inactive, no_rate_id
messagestring
The reason, written for a human.
fieldstring|null
Set when one specific input is at fault — address today.

There is no Run button on purpose: calling this opens a real order and generates a deposit address. Nothing moves until a deposit is sent, and an unfunded order simply expires.

POST /api/create
curl -s -X POST 'https://btcswap.io/api/create' \
  -d from=btc -d to=xmr -d amount=0.1 -d flow=standard \
  -d address=bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4

not runnable here — it would open a real order

Follow an exchange

GET/api/status

  • same-origin
  • no key
  • cache: never cached

The live state of one exchange: status, both sides, addresses, and the transaction hashes once they exist. Never cached — a stale swap status is a lie with financial consequences. Poll it every ten seconds at most; the order page of this site does exactly that.

Query parameters

idstringrequired
The order ID: 8 to 40 letters and digits.

Response

statusstring
new · waitingconfirming · verifyingexchangingsendingfinished. Terminal: failed, refunded, expired.
payin · payinExtrastring · string|null
The deposit address, and its memo when the deposit network needs one — mandatory in that case.
payoutstring
Where the coins are going.
expectFrom · expectTonumber|null
What the order was created for.
amountFrom · amountTonumber|null
What was actually received and sent, once known.
payinHash · payoutHashstring|null
Transaction hashes, as soon as each leg is on-chain.
createdAt · validUntilISO 8601|null
Creation time, and the deposit deadline of a fixed-rate order.
notFoundtrue
Only with ok: false: no order carries that ID.

Same-origin, and treat the ID accordingly: an order ID is a bearer credential for reading that order.

GET /api/status
// Press Run — the real response appears here.
// Or open it directly: /api/status

not called yetCtrl + Enter runs it

Price strip

GET/api/tape

  • CORS open
  • no key
  • cache: edge, 60 s

A compact strip of major assets quoted in USDT — the one in the hero of the home page, and at the top of this one. Cheap to poll: cached for a minute at the edge.

Query parameters

None.

Response

atinteger
Unix time of the answer.
rows[].t · qstring
Asset and quote currency: BTC, USDT.
rows[].rate · fmtnumber · string
USDT received for one unit on a sizeable swap, network fees included — raw, and formatted.
GET /api/tape
// Press Run — the real response appears here.
// Or open it directly: /api/tape

not called yetCtrl + Enter runs it

Errors

Two layers, and they do not mix. The transport speaks HTTP: a 4xx or 5xx means the request itself failed. The desk speaks JSON: ok: false with a stable code and a message you can show to a person.

Business error codes
codeMeansWhat to do
deposit_too_smallThe amount is under the pair’s live minimum.Offer min from the same response.
deposit_too_bigOver the pair’s maximum.Offer max, or split the swap.
pair_is_inactiveThe desk has paused this pair.Retry later; try the same asset on another network.
unknown_asset · same_asset · bad_amountThe request does not describe a swap.Fix the input — these never resolve by retrying.
not_valid_addressOn create: the payout address failed the network check.Show message next to the field it names.
no_rate_idA fixed rate could not be reserved just now.Retry, or fall back to standard.
anything elseThe engine is momentarily unreachable.Keep the last known value on screen and retry with a back-off.

Ground rules

  1. 01

    Be gentle

    Quotes are cached for seconds and the catalogue for half an hour: polling faster returns the same bytes, it does not get you a fresher rate. /api/validate is capped globally.

  2. 02

    Never build your own address check

    Format-checking an address yourself is how wrong-network sends happen — the format is identical across chains, and the format is not the question. Use /api/validate.

  3. 03

    No SLA, and we say so

    This is our own front end’s API, published because a desk that hides its numbers is worth less. If you are going to depend on it, tell us so we warn you before changing it.

  4. 04

    If it breaks, say so

    Support reads API reports the same as any other. Include the URL you called and the response you got — both fit in one paste from the console above.