Skip to main content
A conversion swaps one currency for another inside your ledger — the funds you’ve already been credited on the platform. Reading quotes is cheap and safe. Executing a conversion moves real balance, so it needs the elevated conversions:execute API-key scope, or the conversions:manage team permission plus a fresh MFA factor from the dashboard.
A conversion is not necessarily instant. Depending on the execution mode, a 201 can mean “the source balance has been debited and the swap is running” — not “done”. Drive your integration off the conversion status, never off the HTTP code.

Two execution modes, one API

The platform resolves the mode at quote and execute time — you can’t choose it, and a pair the external route can’t handle silently falls back to internal mode. The quote tells you which one you got via executionMode. Internal mode (executionMode absent or "internal") — an instant ledger swap against platform inventory. The debit, the credit and the completion commit atomically: either all of it happened or none of it did. The 201 body already carries status: "completed". External mode (executionMode: "external") — the swap runs asynchronously over an external liquidity route:
  1. The full source amount is debited immediately and the conversion is persisted as status: "processing". This is what the 201 returns. No credit exists yet.
  2. The swap executes over the external route.
  3. A background monitor drives the conversion to a terminal state: on a verified fill it credits creditAmountStr of toCurrency and moves the row to completed; if the swap could not complete, it credits the full source amount back (commission included) and moves the row to failed. The credit is normally at least the quoted minimum (toAmountStr), but in rare shortfall cases — a late deposit that the route filled at a worse rate — the conversion still completes crediting the amount that actually arrived on-chain, which can be below the quoted minimum. Always reconcile on the final creditAmountStr, never on the quote.
  4. Positive slippage passes through to you. When the fill lands above the quoted minimum, the extra is credited to you up to the route’s expected (mid) output — the credited figure is what creditAmountStr reads after completion, and the extra is reported separately as bonusToMerchantStr. You carry the downside band of the quote, so the symmetric upside within it is yours; only the excess above the expected output is platform margin.
  5. Crediting is proven on-chain before it happens, and success and refund credits are mutually exclusive by construction — a double credit is impossible.
So: 201 does not mean finished. In external mode, poll Get a conversion until status is completed or failed.

Status lifecycle

Rows that stall in initializing/pending are automatically rolled back; they are never left dangling.

How the commission is computed

The commission is the maximum of several independent floors:
  1. Configured rate — the platform’s configured commission rate, floored at 40 bps (0.4 %).
  2. Base floor (always applied) — 0.4 % × fromAmount + \$0.10. The two parts are additive: the percentage prices risk and volume, the flat $0.10 prices per-conversion handling.
  3. Execution-cost floor — the base floor plus the real cost of physically moving the funds on-chain for that route. The fee reflects measured execution cost, and it only gets cheaper as the platform measures reality on your source currency.
If the execution cost can’t be priced at quote time, the quote fails closed with Conversion temporarily unavailable — please try again shortly rather than quoting an unpriced conversion. Rounding is asymmetric on purpose: commission rounds up, net and credited amounts round down. A conversion whose commission would swallow the whole amount is refused with a concrete suggested minimum (see below).

Fee breakdown (feeBreakdown)

When the effective fee lands above the 0.4 % base, the quote carries a display decomposition:
feeBreakdown
  • liddieFeePct — always 0.4, the platform’s own margin, in percent.
  • providerFeePct — the excess over that base, covering the execution cost of actually moving the funds.
The field is omitted when the effective rate is the base, so a rounding hair never renders a “0.00 %” line. liddieFeePct + providerFeePct reconstructs commissionRate × 100.

Minimums, maximums, and the learned minimum behaviour

A pair has a real minimum order size that moves with network fees and liquidity. The platform doesn’t hardcode one — it continuously re-learns each pair’s boundary, and a transient refusal can never raise a pair’s floor forever. What that means for you:
  • An amount below the floor is refused up front with a concrete suggestion, e.g. Conversion amount too small — the minimum for USDT_TRC20 is about 25.4 USDT_TRC20.
  • When the commission floors exceed the whole amount you get Conversion amount too small to cover execution costs — the minimum for X is about N X.
  • Routes also have a ceiling: Conversion amount too large — the maximum convertible right now is about N X, or Insufficient liquidity for this conversion pair right now.
  • If your ledger balance exceeds what is physically convertible right now, the quote refuses with Insufficient funds available to convert — the maximum amount convertible right now is about N X.
Do not cache minimums. Re-quote; the numbers move.

Error responses

Most conversion errors use the standard envelope:
Error envelope
Three responses are exceptions that send a flat body — handle both shapes:
  • CONVERSIONS_DISABLED on Get a quote and Execute a conversion → { "error": "CONVERSIONS_DISABLED", "message": "…" } (on /best-route and /cheaper-targets the same condition uses the envelope).
  • 409 RATE_DRIFT → { "error", "message", "driftPct", "newQuote" } — see Execute a conversion.
  • A failed MFA factor → { "error": "…" } with the verification status code (including 429 when the account is locked out).

Idempotency conflicts

Reuse an idempotencyKey (your unique retry token) and you get a 409 in the standard envelope — never a bare 400. The code tells you which case you’re in: The status code is the reliable signal: 409 = your key collided, nothing new was executed by this request. A 400 CONVERSION_FAILED is a genuine domain failure.

Message passthrough

The only failure text you should ever render is merchantMessage on the conversion record: every message is filtered so internal details can never reach your users, and anything that isn’t safe collapses to a generic fallback ("Quote failed" / "Conversion failed"). Compliance refusals (This conversion cannot be processed for compliance reasons — contact support) do pass through, because contacting support is the actionable answer.

Integration checklist

  1. Get a quote → show toAmountStr, commissionRate and, when present, feeBreakdown.
  2. Optionally call Get best route (or Get cheaper targets) first and offer the cheaper option.
  3. Execute echoing fromAmountStr, quotedRate, quotedToAmount and your own idempotencyKey. On a 409 CONVERSION_IN_PROGRESS, retry the same key after a backoff; on a 409 IDEMPOTENCY_KEY_CONSUMED, stop — funds already moved.
  4. Handle 409 RATE_DRIFT by re-presenting newQuote (with a fresh passkey assertion if you used one).
  5. If the 201 body has status: "processing", poll Get a conversion until completed or failed. Reconcile on creditAmountStr, and render merchantMessage on failure.
  6. Never persist minimums, and never rely on fields that aren’t documented on the conversion record — provider details are not returned.

See also