A payroll run is one order per person, quoted by the amount they should receive. Everyone on the roster names their own asset, network and address; you fund each payout from your treasury; the API converts and delivers. Nothing is prefunded, nothing is pooled, and the money is yours until the moment each payout is sent.
- Quote with
amount_toso the net figure on the payslip is the figure that arrives. - An idempotency key per payout means a retried run never pays anyone twice.
- One person's failure — an amount under a provider's minimum — does not stop the run.
- There is no scheduler in the API: your cron decides when a run happens.
A payroll run, step by step
-
The roster
Per person: net amount, payout asset, network, address. Check each address with
GET /v1/validate-addresswhen it is added and again before a run — a typo found on payday is an expensive typo. -
Quote each line by the net amount
from_assetis your treasury asset,to_assetis theirs, andamount_tois what they are owed. The offer answers with theFromAmountthe run will cost you, so the whole run can be priced before a single coin moves. -
Open an order per person
client_order_idis yourrun + employeekey, and theIdempotency-Keyis derived from the same pair. Rerun a half-finished payroll and the orders already created come back unchanged instead of doubling. -
Fund the payouts
Each order returns its own
DepositAddressand exactAmountFrom. Your treasury sends to each one — this is the part the API cannot do for you, because it never holds your funds. -
Close the run
Poll each order to a terminal status.
DONEgives youAmountToReceivedandTxIDfor the payslip; anything else is a line to re-run, with a fresh quote.
Fund promptly. An offer is good for five minutes and the provider's
own deposit window is typically 30–60 minutes. Quote → create → fund should be
one automated sequence; a run that quotes on Monday and pays on Tuesday is a
run of TIME_EXPIRED orders.
The run loop
const BASE = "https://api.swapzilla.io";
const head = { "X-API-Key": process.env.SWAPZILLA_KEY, "Content-Type": "application/json" };
async function payout(run, person) {
const q = new URLSearchParams({
from_asset: "USDT", from_network: "TRC20",
to_asset: person.asset, to_network: person.network,
amount_to: String(person.net), rate_type: "fixed",
});
const { offers } = await fetch(`${BASE}/v1/partner/quotes?${q}`, { headers: head })
.then((r) => r.json());
if (!offers?.length) throw new Error(`no offer for ${person.id}`);
const order = await fetch(`${BASE}/v1/partner/orders`, {
method: "POST",
headers: { ...head, "Idempotency-Key": `${run}:${person.id}` }, // same key on every retry
body: JSON.stringify({
offer_id: offers[0].ID,
to_address: person.address,
refund_address: process.env.TREASURY_ADDRESS, // failures come home
client_order_id: `${run}:${person.id}`,
expected_to_amount: person.net,
}),
}).then((r) => r.json());
return order; // DepositAddress + AmountFrom — what the treasury must send
}
// Five at a time is plenty: providers throttle bursts, and a payroll run is not
// a race. Wrap each line so one failure is one failed payslip, not a failed run.
Run bookkeeping
| Question the finance team asks | Where the answer lives |
|---|---|
| What did this run cost us? | The sum of AmountFrom across the run's orders, known before funding. |
| Did this contractor get paid? | Their order's status; DONE carries AmountToReceived and TxID. |
| Which payslip is this order? | client_order_id — the run:employee pair you set. |
| Show me the whole run | GET /v1/partner/orders?page=1&limit=100, filtered on your side by the run prefix. |
| Someone is stuck — who do we ask? | The provider, quoting ProviderOrderID; POST /orders/{id}/refresh first, in case it has already moved. |
The honest limits
| Not included | What that means for the run |
|---|---|
| One funding transaction for the whole run | Each order has its own deposit address, so the treasury sends once per payout. Budget the network fees accordingly. |
| Scheduling | There is no "pay on the 1st" in the API. Your scheduler triggers the run; the API executes it. |
| Fiat payouts | Crypto to crypto only. A salary agreed in EUR is converted by your own price feed before quoting. |
| Contracts, invoices, tax forms | Not a payroll suite — SwapZilla moves and converts value; the paperwork stays in your system. |
| KYC, AML or sanctions screening of recipients | The API performs none. Whatever your compliance programme requires, it comes from elsewhere. |
Who pays this way
- Studios and agencies with remote contractors across a dozen countries, each preferring a different asset and network.
- Product teams paying in stablecoins from a single treasury asset, without operating a wallet per network.
- DAOs and open-source funds whose payouts have to be individually traceable to a
TxID. - Anyone who has outgrown a spreadsheet and a hot wallet but does not want a custodian holding the payroll.
Questions
Can I fund the whole run with one transfer?
No. Each order gets its own deposit address at the provider, so funding is one transfer per payout. It is the direct consequence of the swap being non-custodial: there is no SwapZilla balance to top up and therefore nothing to distribute from.
How do I stop a retry from paying someone twice?
Send an Idempotency-Key derived from the run and the person — for example 2026-08:emp-114 — and reuse it on every retry of that payout. A repeated request returns the same order instead of creating a second one.
Can each contractor be paid in a different coin?
Yes, and that is the point. The payout asset and network are per order, so one run can settle USDT on Tron, BTC and ETH from the same treasury asset — the provider does each conversion.
What happens if one payout fails?
Only that payout fails. Typical causes are an amount below the provider's MinFromAmount or a disabled asset; both surface as a 400 when the order is created, before any money moves. Re-quote that line, or pay it in another asset.
Does SwapZilla hold the payroll between the run and the payday?
Never. Funds leave your treasury only when you send them to a specific order's deposit address, and the provider pays out to the contractor directly. There is no float, no escrow and no balance page.
Is this SolarStaff or Deel for crypto?
Not as a product — those are payroll suites with contracts, invoices and compliance. This is the payment rail underneath one: quoting, converting and delivering value to each person's own wallet, with the roster, the paperwork and the schedule staying in your system.
What else the same key builds
- Crypto payment links and invoicesPay-by-link and invoicing for freelancers and contractors — quote by the amount due, settle straight to the payee's wallet.
- 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.
- 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.