Three calls: quote → create → track. No SDK, no account for your users, and we never touch your keys.
https://cryptoexchanger.co/api/v1Create an API key — pick Test first (simulated swaps, no real funds). You'll get a key like cx_test_…. Send it as a bearer token on any authenticated call.
Best rate across all connected liquidity, plus min/max, in one call. No key needed for quotes.
# BTC -> ETH for 0.05 BTC curl "https://cryptoexchanger.co/api/v1/estimate?from=BTC&to=ETH&amount=0.05&flow=float"
{ "estimate": { "fromAmount": 0.05, "toAmount": 1.5564, "rate": 31.13,
"minAmount": 0.0004, "maxAmount": null, "flow": "float" } }
flow=float is the live market rate on arrival (default). flow=fixed locks the rate for the deposit window.
extraId; refund metadata belongs in refundExtraId on the send network. Supply both values on /estimate and /exchange when the respective wallets need them. Routes that cannot preserve a supplied refund tag are excluded; a compatible quote may be unavailable. XRP tag 0 is valid.Returns a deposit address to send the from coin to. The swapped coins are sent to address. Always include an Idempotency-Key so a retry never creates a second order.
curl -X POST https://cryptoexchanger.co/api/v1/exchange \ -H "Authorization: Bearer cx_test_..." \ -H "Idempotency-Key: order-001" \ -H "Content-Type: application/json" \ -d '{ "from": "BTC", "to": "ETH", "amount": 0.05, "address": "0xYourRecipientEthWallet", "refundAddress": "bc1qYourBtcRefund", "flow": "float" }'
{ "order": {
"id": "AKCQ8", "status": "waiting",
"payinAddress": "bc1q...send-exactly-0.05-BTC-here",
"payinExtraId": null,
"expectedFromAmount": 0.05, "expectedToAmount": 1.5564,
"depositWindow": 3600, "createdAt": 1790026280 } }
expectedFromAmount. If the coin needs a memo/tag, it comes back as payinExtraId — include it or funds can be lost. Deposit within depositWindow seconds; a funded order is never expired.Retry the same logical request. Restart-safe reservations prevent another allocation for the same key. A 503 with retrySameKey:true means creation is pending/uncertain or its safety store is unavailable. Keep the same key and body, check the original order, and contact support if it cannot be recovered. Do not silently create a replacement order.
Send the deposit from your wallet, then poll until the status is terminal. (Or set a webhook and skip polling.)
curl https://cryptoexchanger.co/api/v1/order/AKCQ8 \ -H "Authorization: Bearer cx_test_..."
Lifecycle: waiting → confirming → exchanging → sending → finished. Terminal-bad: failed, refunded, expired.
import requests, time H = {"Authorization": "Bearer cx_test_..."} B = "https://cryptoexchanger.co/api/v1" q = requests.get(f"{B}/estimate", params={"from":"BTC","to":"ETH","amount":0.05}).json() o = requests.post(f"{B}/exchange", headers={**H, "Idempotency-Key":"order-001"}, json={"from":"BTC","to":"ETH","amount":0.05, "address":"0xRecipient","refundAddress":"bc1qRefund"}).json()["order"] # -> send o["expectedFromAmount"] to o["payinAddress"] from your wallet while True: s = requests.get(f"{B}/order/{o['id']}", headers=H).json()["order"]["status"] if s in ("finished","failed","refunded","expired"): break time.sleep(15)
const B = "https://cryptoexchanger.co/api/v1", KEY = "cx_test_..."; const order = await fetch(`${B}/exchange`, { method:"POST", headers:{ "Authorization":`Bearer ${KEY}`, "Idempotency-Key":"order-001", "Content-Type":"application/json" }, body: JSON.stringify({ from:"BTC", to:"ETH", amount:0.05, address:"0xRecipient", refundAddress:"bc1qRefund" }) }).then(r => r.json());
cx_live_ key when you're ready.