Payroll

Crypto payroll for contractors and remote teams

Everyone on the roster names an asset, a network and an address. A payroll run is one order per person, quoted by the net amount they should receive, funded from your treasury and reconciled by your own employee id.

  • Per payoutone order, one deposit
  • Exact net payquote with amount_to
  • Double-pay guardIdempotency-Key per payout
  • Treasurystays yours until each payout is funded

Built on/v1/validate-address/v1/partner/quotes/v1/partner/orders

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_to so 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

  1. The roster

    Per person: net amount, payout asset, network, address. Check each address with GET /v1/validate-address when it is added and again before a run — a typo found on payday is an expensive typo.

  2. Quote each line by the net amount

    from_asset is your treasury asset, to_asset is theirs, and amount_to is what they are owed. The offer answers with the FromAmount the run will cost you, so the whole run can be priced before a single coin moves.

  3. Open an order per person

    client_order_id is your run + employee key, and the Idempotency-Key is derived from the same pair. Rerun a half-finished payroll and the orders already created come back unchanged instead of doubling.

  4. Fund the payouts

    Each order returns its own DepositAddress and exact AmountFrom. Your treasury sends to each one — this is the part the API cannot do for you, because it never holds your funds.

  5. Close the run

    Poll each order to a terminal status. DONE gives you AmountToReceived and TxID for 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 asksWhere 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 runGET /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 includedWhat that means for the run
One funding transaction for the whole runEach order has its own deposit address, so the treasury sends once per payout. Budget the network fees accordingly.
SchedulingThere is no "pay on the 1st" in the API. Your scheduler triggers the run; the API executes it.
Fiat payoutsCrypto to crypto only. A salary agreed in EUR is converted by your own price feed before quoting.
Contracts, invoices, tax formsNot a payroll suite — SwapZilla moves and converts value; the paperwork stays in your system.
KYC, AML or sanctions screening of recipientsThe 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.

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.