Swapping currencies

End-to-end walkthrough of executing a swap from quote to webhook confirmation.

This guide takes you through every step of swapping currencies, from quoting to receiving the completion webhook.

Prerequisites

  • An API key registered with a public Ed25519 key — see Authentication
  • A webhook URL registered — see Webhook setup
  • A non-zero balance in the currency_in you want to swap from

Flow

quote.get → (show user) → transaction.initiateSwap → webhook: transaction.updated (status COMPLETED)

1. Fetch a quote

POST /quote.get
Content-Type: application/json

{
    "currency_in":  { "value": "100000000", "code": "USDC", "decimals": 6, "chain": "ethereum" },
    "currency_out": { "value": "0", "code": "AUD", "decimals": 2 }
}

(On a quote, currency_out.value is just a placeholder — "0" asks for the output amount.)

{
    "data": {
        "bid": { "value": "154700", "code": "AUD", "decimals": 2 },
        "ask": { "value": "154700", "code": "AUD", "decimals": 2 },
        "direction": "ask",
        "fees": [
            { "type": "percentage", "value": { "value": "500", "code": "AUD", "decimals": 2 }, "label": "Service fee", "rate": 0.003 }
        ],
        "stablecoin": null
    }
}

If you're showing the rate to a user, refresh it every 5–10 seconds while they're on the confirmation screen so the displayed estimate doesn't drift.

2. Execute the swap

POST /transaction.initiateSwap
Content-Type: application/json

{
    "currency_in":  { "value": "100000000", "code": "USDC", "decimals": 6, "chain": "ethereum" },
    "currency_out": { "value": "154700", "code": "AUD", "decimals": 2 },
    "external_accounts": { "from": null, "to": null }
}
  • currency_in.value is the input in the smallest unit (here, 100 USDC = 100000000 at decimals 6).
  • currency_out.value is the output you expect — use the ask from the quote (154700 here). Both sides must be non-zero; the engine validates the implied rate against the live market within a small tolerance and re-quotes at execution, so a slightly stale estimate is fine.

Response — the new transaction id:

{ "data": "08a4d98b-07b1-4948-a6e7-7c0ee556d564" }

Fetch full details (status, amounts) via transaction.get with that id.

3. Wait for the webhook

When the swap settles, your webhook URL receives a delivery with the transaction in its terminal state. Verify the signature (Webhooks) and update your records.

4. Confirm

account.balance.getMany will now reflect the decreased USDC and increased AUD balance.

Error cases

Submission-time failures return { "error": { "code", "message" } } with code BAD_REQUEST:

  • Unsupported currencycurrency=<CODE> is not a payable currency / is not a receivable currency.
  • Unsupported pair — direct native-crypto ↔ native-crypto swaps are rejected: Direct <A> → <B> swaps are not supported. Bridge via USDC or USDT.
  • Below minimum — per-currency minimum trade sizes guard against dust trades: Swaps from <CODE> must be at least <min> <CODE>.
  • Stale rate — if the implied rate in your request drifts too far from the live market, the quote validation rejects with the reason (e.g. asking you to refresh the quote).
  • Invalid input — zero/negative amounts, identical currency_in/currency_out, a missing chain on crypto, or unknown fields are all rejected by schema validation.

A swap that is accepted can still fail afterwards (e.g. insufficient balance at execution) — that surfaces as the transaction reaching status: "FAILED", not as a submission error.