SwapZilla does not host invoices — it gives you the two calls an invoice is made of. Quote by the amount the invoice asks for, open the order, and the payer gets a deposit address that settles straight into the payee's wallet. Nothing is held by us or by you.
- The payer sends any listed asset; the payee receives the one they billed in.
- The rate can be locked for the life of the offer with
rate_type=fixed. - Your invoice id travels with the order as
client_order_id. - Settlement is non-custodial: payer → exchange provider → payee.
How a crypto payment link works
-
The payee states what they are owed
An asset, a network and an address —
500 USDTonTRC20, paid to their own wallet. That is your invoice record; SwapZilla never needs to know about it until the payer shows up. -
The payer opens the link and picks a coin
GET /v1/assetsis the honest list of what can be paid with right now — every asset and network with at least one provider behind it. Render it as the picker. -
Quote by the amount due, not by the amount sent
This is the call that makes an invoice an invoice: ask
amount_to=500and the offer answers with the exactFromAmountthe payer has to send. Addrate_type=fixedand that number stays true while they pay. -
Open the order
POST /v1/partner/orderswith the offer, the payee's address asto_address, the payer's address asrefund_addressand your invoice number asclient_order_id. Passexpected_to_amountand the order is refused rather than created if the payout drifted. -
Show the address, then watch the order
DepositAddressandAmountFromare the whole payment page — as text, as a QR, with the network named. PollGET /v1/partner/orders/{id}every 5–10 seconds untilDONE, and the receipt isAmountToReceivedplusTxID.
The two calls, in full
Quote a €500-sized invoice denominated in USDT, paid in BTC:
curl -sG "https://api.swapzilla.io/v1/partner/quotes" \ -H "X-API-Key: $API_KEY" \ --data-urlencode "from_asset=BTC" \ --data-urlencode "from_network=BITCOIN" \ --data-urlencode "to_asset=USDT" \ --data-urlencode "to_network=TRC20" \ --data-urlencode "amount_to=500" \ --data-urlencode "rate_type=fixed"
The first offer is the best one. Turn it into a payable address:
curl -s -X POST "https://api.swapzilla.io/v1/partner/orders" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"offer_id": "0b0c8f0e-2c1a-4f3f-8f57-2a1d1c4e77aa",
"to_address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
"refund_address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
"client_order_id": "INV-2026-0413",
"expected_to_amount": 500
}'
Then a loop that turns statuses into invoice states:
const terminal = ["DONE", "TIME_EXPIRED", "FAILED", "REFUNDED"];
async function watch(orderID) {
for (;;) {
const r = await fetch(`https://api.swapzilla.io/v1/partner/orders/${orderID}`, {
headers: { "X-API-Key": process.env.API_KEY },
});
const order = await r.json();
await onInvoiceState(order); // your own bookkeeping
if (terminal.includes(order.Status)) return order;
await new Promise((s) => setTimeout(s, 7000)); // 5–10 s is the right cadence
}
}
Quote when the link is opened, not when it is issued. An offer is valid for five minutes. An invoice emailed on Monday and paid on Friday is quoted on Friday — the link is a page of yours, the offer is created the moment someone lands on it.
Invoice states, mapped from order statuses
| Order status | What the invoice says | What you do |
|---|---|---|
| NEW WAIT_DEPOSIT | Awaiting payment | Show the address and the countdown. |
| CONFIRMING | Payment seen on-chain | Stop the countdown; the payer is done. |
| EXCHANGING SENDING | Settling | Nothing — the provider is paying out. |
| DONE | Paid | Mark it paid on AmountToReceived; file TxID as the receipt. |
| TIME_EXPIRED | Link expired unpaid | Re-quote and issue a fresh address. |
| FAILED REFUNDED | Not paid | The provider returns the coins to refund_address — which is why you asked the payer for one. |
What this does not do
| Not included | What that means for you |
|---|---|
| A hosted invoice page or PDF | The link, the layout and the emails are yours. The API supplies the amount, the address and the status. |
| Webhooks | There are none yet — poll the order every 5–10 seconds until a terminal status. |
| Fiat | Crypto in, crypto out. An invoice denominated in EUR is your own price feed converting to a stablecoin amount before you quote. |
| Partial or repeated payment | One order accepts one deposit of one amount. Split invoices are several orders. |
| Refund initiation | Refunds happen at the provider, to refund_address, when a swap fails — there is no API call that reverses a settled payment. |
Who bills this way
- Freelancers invoicing across borders. The client pays in whatever they hold; the invoice is settled in USDT on the network the freelancer's wallet actually uses.
- Studios and agencies issuing milestone invoices, each one its own order and its own
client_order_id. - Platforms that bill on behalf of their users — the payee address is the user's, so the platform never holds client money and never becomes the party that owes it.
- Anyone billing a payer on another chain: the payer's BTC and the payee's TRC20 USDT never have to meet on the same network.
Questions
Can I create a payment link that never expires?
The link can live forever; the offer behind it cannot. Offers expire five minutes after they are quoted, so a durable link points at your own page, which quotes and opens the order when the payer arrives. The deposit address is created at that moment, not when the invoice is issued.
Does SwapZilla hold the money in between?
No. The swap is non-custodial: the payer sends to the provider's deposit address and the provider pays out to the payee's address. Neither SwapZilla nor your platform is ever in possession of the funds, which is also why there is no payout call to make.
How do I make sure the payee receives exactly the invoiced amount?
Quote with amount_to instead of amount_from, ask for rate_type=fixed, and pass expected_to_amount when you create the order — the order is refused rather than created if the recalculated payout no longer matches what the invoice promised.
What happens if the payer sends the wrong amount?
The provider handles it under its own rules: a shortfall is usually refunded to refund_address, and an overpayment is either exchanged at the current rate or returned. This is why the payer's refund address should always be collected before the order is opened.
Which coins can an invoice be paid in?
Whatever GET /v1/assets lists at that moment — the top-100 assets by market cap, each with the networks and providers currently able to serve them. The payee's settlement asset comes from the same list.
Do I need a key to try it?
Yes for quotes and orders; /v1/assets, /v1/providers and /v1/validate-address answer without one. A key is issued by a SwapZilla admin and used as X-API-Key from your backend, never from a browser.
What else the same key builds
- Crypto payment processing and acquiringCheckout for shops and SaaS: accept 100+ assets, settle in one, reconcile by your own order id.
- Crypto exchange API, SDK and swap widgetEmbed swaps in a wallet, bot, aggregator or website — streaming quotes, one order call, your own UI.
- Crypto payroll for contractors and remote teamsRecurring payouts to a contractor roster: each person's own asset and network, exact net amounts, per-run reconciliation.
- Multi-send: batch payouts to many addressesOne-off fan-out to hundreds of addresses: cross-chain, per-recipient status, safe retries.
- Your own MCP server for AI agentsThe published swapzilla-mcp server in one line — or your own, on your partner key, with allow-lists and spending caps.
- Streaming quotes, live rates and price alertsServer-sent quote streaming, an always-fresh rate feed and order tracking you can turn into alerts.
Getting a key
Register in the partner cabinet and your partner id and the api_key that goes in X-API-Key are issued at once. The key starts working once it is activated: write to @swapzilla_support_bot with a sentence about what you are building. Every endpoint, field and status is in the partner API reference.