测试环境 STAGING · Staging environment for acceptance testing. Uses separate test data; do not enter real client information.
Developer docs中文

TopUP API

Version 1. Every field in the API reference is checked automatically against the implementation, so the reference and the API cannot drift apart.

Overview

  • Network and token: USDT on TRON (TRC20) only. Amounts are decimal strings with up to 6 decimals, e.g. "100.5".
  • Base URL: https://{BASE_URL}/api/v1/ — every path ends with "/". Requests without the trailing slash are redirected, which some HTTP clients do not follow for POST.
  • Collect by customer: each of your customers gets one permanent address (deposit callbacks). With order mode on, create orders for a customer and deposits are matched to them automatically (order-matched callbacks).
  • A deposit counts after 19 confirmations (about 1 minute). Callbacks are normally sent within 2 minutes of payment.
  • Every withdrawal is reviewed manually. Working hours are 8:00–23:00 (UTC+8): requests in working hours are processed within 1 hour; others at the start of the next working period.
EndpointPurpose
POSTorders/Create an order
GETorders/Get an order
GETorders/list/List orders
POSTorders/match/Match a deposit to an order
POSTorders/unmatch/Unmatch a deposit
POSTcustomers/Create or update a customer
GETcustomers/Get a customer
GETcustomers/list/List customers
GETcustomers/stats/Customer statistics
GETbalance/Balance
GETledger/Ledger
GETdeposits/Deposits
POSTwithdrawals/Request a withdrawal
GETwithdrawals/Get a withdrawal
POSTwithdrawals/cancel/Cancel a withdrawal
POSTpayouts/batch/Submit a batch payout
GETpayouts/batch/Get a batch payout

Authentication

Every request carries three headers. The Secret is shown once in the dashboard (API settings); keep it on your server only.

HeaderValue
X-TopUP-KeyYour API Key
X-TopUP-TimestampUnix time in seconds; must be within 5 minutes of server time
X-TopUP-Signaturehex( HMAC-SHA256( Secret, timestamp + "\n" + METHOD + "\n" + path_and_query + "\n" + hex(SHA-256(body)) ) ) — use an empty body for GET
Idempotency-KeyRequired on every POST (8–128 characters: letters, digits and _ . : -; a UUID works). Retrying with the same key and body returns the first result instead of creating a duplicate (kept 24 hours). A request that failed does not use up the key.

Node.js

import crypto from 'node:crypto';

const BASE = process.env.TOPUP_BASE || 'https://{BASE_URL}/api/v1/';
const KEY = process.env.TOPUP_KEY, SECRET = process.env.TOPUP_SECRET;

async function topup(method, path, body) {
  const ts = Math.floor(Date.now() / 1000).toString();
  const raw = body ? JSON.stringify(body) : '';
  const bodyHash = crypto.createHash('sha256').update(raw).digest('hex');
  const url = new URL(path, BASE);
  const toSign = [ts, method, url.pathname + url.search, bodyHash].join('\n');
  const sig = crypto.createHmac('sha256', SECRET).update(toSign).digest('hex');
  const res = await fetch(url, {
    method,
    headers: {
      'content-type': 'application/json',
      'X-TopUP-Key': KEY, 'X-TopUP-Timestamp': ts, 'X-TopUP-Signature': sig,
      ...(method === 'POST' ? { 'Idempotency-Key': crypto.randomUUID() } : {}),
    },
    body: raw || undefined,
  });
  return res.json();
}

const order = await topup('POST', 'orders/', { customer_id: 'user_88213', merchant_order_no: `ACME-${Date.now()}`, amount: '100.00' });
console.log(order.pay_url);

Python

import hashlib, hmac, json, os, time, uuid, requests

BASE = os.environ.get("TOPUP_BASE_URL", "https://{BASE_URL}")
KEY, SECRET = os.environ["TOPUP_KEY"], os.environ["TOPUP_SECRET"].encode()

def topup(method, path, body=None):
    ts = str(int(time.time()))
    raw = json.dumps(body, separators=(",", ":")) if body else ""
    body_hash = hashlib.sha256(raw.encode()).hexdigest()
    to_sign = "\n".join([ts, method, "/api/v1/" + path, body_hash])
    sig = hmac.new(SECRET, to_sign.encode(), hashlib.sha256).hexdigest()
    headers = {"content-type": "application/json", "X-TopUP-Key": KEY,
               "X-TopUP-Timestamp": ts, "X-TopUP-Signature": sig}
    if method == "POST":
        headers["Idempotency-Key"] = str(uuid.uuid4())
    return requests.request(method, BASE + "/api/v1/" + path, data=raw or None, headers=headers).json()

print(topup("GET", "balance/"))

Orders

POST /api/v1/orders/
{
  "customer_id": "user_88213",          // required: your own ID for this customer
  "customer_name": "Lin Trading",       // optional, used only if the customer is new
  "customer_email": "lin@lintrading.example",
  "merchant_order_no": "ACME-88513",
  "amount": "100.00"
}
{
  "order_no": "ORD-20260929-0413",
  "merchant_order_no": "ACME-88513",
  "customer_id": "user_88213",
  "amount": "100.00",
  "matched": "0.00",
  "address": "TMa7yK2d9Qp4XvB8sN1cF6rH3wZ5jL0eUt",   // the customer's permanent address
  "network": "TRON",
  "token": "USDT",
  "status": "pending",
  "created_at": "2026-09-29T09:00:00.000Z",
  "expires_at": "2026-09-29T09:30:00.000Z",
  "deposits": [],
  "pay_url": "https://{BASE_URL}/pay/ORD-20260929-0413/"
}
  • customer_id is required. A new customer_id creates the customer and its permanent address. merchant_order_no must be unique in your account.
  • The expiry time and matching thresholds are set for your account by the platform (shown in API settings). Orders require order mode; otherwise the endpoint returns 403 order_mode_disabled.
  • Send your customer to pay_url, or show the address yourself.
  • The response can already show status completed if earlier deposits from this customer matched when the order was created. See Matching rules.

Customers

POST /api/v1/customers/
{ "customer_id": "user_88213", "name": "Lin Trading", "email": "lin@lintrading.example" }

→ {
  "customer_id": "user_88213", "name": "Lin Trading", "email": "lin@lintrading.example", "remark": "",
  "address": "TXk8fQ2m9sR7ZcY1hN4vB6pW3aLd5Z3wQe",
  "total": "0.00", "count": 0, "fees": "0.00", "unmatched": "0.00",
  "last_payment_at": null, "created_at": "2026-09-11T02:10:00.000Z"
}
  • Each customer has one permanent address. Every payment to it belongs to that customer, with or without an order. customer_id cannot be changed.
  • Payments under 1 USDT are not credited, not matched, trigger no callback and do not appear in your deposits or exports (they are usually spam "dust" transfers).
  • customers/stats/ returns received, count, fees and unmatched for a period.

Matching rules

Matching only links deposits to orders; it never changes your balance. It happens only within the same customer, in both directions:

  • Deposit first reaches an open order: the deposit goes to the open order where the running total lands between the lower (L) and upper (H) thresholds; otherwise to the oldest open order.
  • Order created after deposits: unmatched deposits from the look-back window are added from the oldest; if the total reaches L without exceeding H they are matched, otherwise none are.
  • Status: total < L = partial (keeps waiting), L ≤ total ≤ H = completed, total > H = overpaid; at expiry an order below L becomes expired (nothing received) or expired_partial.
  • Payments after expiry stay unmatched; match them manually with orders/match/ if needed. Avoid two open orders with similar amounts for the same customer.

Balance & ledger

GET balance/ returns { "available": "48215.40", "frozen": "1002.00" }. The ledger is append-only; each entry has type deposit, fee_in, freeze, unfreeze, withdraw or fee_out. Lists are paginated with cursor.

Withdrawals

POST /api/v1/withdrawals/
{
  "kind": "payout",              // "payout" = to your customer, "cashout" = to your registered wallet
  "customer_id": "user_88213",   // optional, for payouts
  "to": "TXk8fQ2m9sR7ZcY1hN4vB6pW3aLd5Z3wQe",
  "amount": "1000.00",
  "merchant_ref": "PAYOUT-7781"
}

→ {
  "withdrawal_no": "WD-20260929-7F3A21", "kind": "payout", "customer_id": "user_88213",
  "to": "TXk8fQ2m9sR7ZcY1hN4vB6pW3aLd5Z3wQe", "amount": "1000.00", "fee": "2.00",   // 1002.00 is frozen
  "status": "pending", "txid": null, "reason": null, "merchant_ref": "PAYOUT-7781", "source": "api",
  "created_at": "2026-09-29T09:12:00.000Z"
}
  • API withdrawals require an IP whitelist (API settings). Without one, the endpoint returns 403 ip_not_allowed.
  • Amount + fee is frozen on submit and released if the request is rejected or cancelled.
  • Status: pending → approved → broadcast → completed, or rejected / cancelled / failed. Each change sends a withdrawal.updated callback.

Callbacks

POST https://your-server.example/callback
X-TopUP-Timestamp: 1790665800
X-TopUP-Signature: 5f1c…

{
  "id": "evt_9f2a02",
  "type": "order.matched",       // deposit | order.matched | withdrawal.updated
  "created_at": "2026-09-29T09:10:03Z",
  "data": {
    "order_no": "ORD-20260929-0409",
    "merchant_order_no": "ACME-88509",
    "customer_id": "user_88213",
    "status": "completed",       // pending | partial | completed | overpaid | expired | expired_partial
    "amount": "500.00",
    "matched": "500.00",
    "created_at": "2026-09-29T09:05:00.000Z",
    "expires_at": "2026-09-29T09:35:00.000Z",
    "deposits": [{ "id": "1042", "txid": "a3c1…", "amount": "500.00", "matched_by": "deposit", "time": "2026-09-29T09:10:00.000Z" }],
    "manual": false
  }
}

Signature: hex( HMAC-SHA256( Secret, timestamp + "\n" + raw_body ) ). Reply with any 2xx status. Otherwise we retry 1 min, 5 min, 30 min, 2 h, 6 h, 12 h and 24 h after the first attempt, then stop; you can resend from the dashboard. The same event can arrive more than once: deduplicate by id.

import crypto from 'node:crypto';

// Verify a TopUP callback. rawBody must be the exact request body you received,
// not an object you parsed and serialised again.
export function verifyCallback(secret, timestamp, signature, rawBody) {
  const expected = crypto.createHmac('sha256', secret).update(`${timestamp}\n${rawBody}`).digest('hex');
  const a = Buffer.from(expected), b = Buffer.from(String(signature || ''));
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
  return fresh && a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express example:
// app.post('/callback', express.raw({ type: 'application/json' }), (req, res) => {
//   if (!verifyCallback(SECRET, req.get('X-TopUP-Timestamp'), req.get('X-TopUP-Signature'), req.body)) return res.sendStatus(401);
//   const event = JSON.parse(req.body); // the same event may arrive more than once: deduplicate by event.id
//   res.sendStatus(200);
// });

Callback body

FieldTypeDescription
idstringEvent ID; deduplicate by it
typestringdeposit, order.matched or withdrawal.updated
created_attimestampWhen the event was created
dataobjectEvent data, see below

deposit

A payment of 1 USDT or more to a customer address is credited. data fields:

FieldTypeDescription
customer_idstringYour customer ID
chainstringChain of the payment: eth, bsc or tron
addressstringReceiving address
txidstringOn-chain transaction ID
amountamountAmount received
feeamountCollection fee
creditedamountAmount added to your balance
order_nostringOrder it was matched to(may be null)
timetimestampBlock time of the payment

order.matched

An order changes because deposits were matched or unmatched. data fields:

FieldTypeDescription
order_nostringOrder number assigned by TopUP
merchant_order_nostringYour order number
customer_idstringYour customer ID
amountamountOrder amount
matchedamountTotal of the deposits matched to this order
statusstringpending, partial, completed, overpaid, expired or expired_partial (see Matching rules)
created_attimestampCreated at
expires_attimestampExpiry time
depositsarrayDeposits matched to this order (OrderDeposit objects)
manualbooleantrue when matched or unmatched manually

withdrawal.updated

A withdrawal is created or changes status. data fields:

FieldTypeDescription
withdrawal_nostringWithdrawal number
kindstringpayout (to your customer) or cashout (to your registered wallet)
chainstringChain it is paid on: eth, bsc or tron
batch_nostringBatch it belongs to (batch payouts); null for single withdrawals(may be null)
customer_idstringCustomer, for payouts(may be null)
tostringDestination address on that chain
amountamountAmount sent
feeamountWithdrawal fee (amount + fee is frozen on submit)
statusstringpending, approved, broadcast, completed, rejected, cancelled or failed
txidstringOn-chain transaction ID once broadcast(may be null)
reasonstringRejection or failure reason(may be null)
merchant_refstringYour reference(may be null)
sourcestringapi or web (merchant dashboard)
created_attimestampCreated at

API reference

Amounts are decimal strings in USDT (e.g. "100.50"); times are ISO 8601 in UTC. Lists return { items, next_cursor }: pass next_cursor as cursor to get the next page.

OrderDeposit

Used in the deposits array of an order.

FieldTypeDescription
idstringDeposit ID
txidstringOn-chain transaction ID
amountamountAmount received
matched_bystringHow it was matched: deposit (payment arrived after the order), order (order created after the payment) or manual
timetimestampBlock time of the payment

POST/api/v1/orders/ · Create an order

Request body (JSON)

FieldTypeRequiredDescription
customer_idstringYesYour ID for the customer, 1–128 characters: letters, digits and _ . @ -. A new ID creates the customer (no address yet: addresses are generated per chain when the customer makes a deposit)
merchant_order_nostringYesYour order number, 1–64 characters: letters, digits, _ and -. Unique in your account
amountamountYesOrder amount in USDT, up to 6 decimals, e.g. "100.5"
customer_namestringNoUsed only when the customer is new
customer_emailstringNoUsed only when the customer is new
chainstringNoeth or bsc: the payer can only pay on this chain, and the customer's address on this chain is generated now. Leave out to let the payer choose on the payment page (recommended); the address is generated when they choose

Response

FieldTypeDescription
order_nostringOrder number assigned by TopUP
merchant_order_nostringYour order number
customer_idstringYour customer ID
amountamountOrder amount
matchedamountTotal of the deposits matched to this order
statusstringpending, partial, completed, overpaid, expired or expired_partial (see Matching rules)
created_attimestampCreated at
expires_attimestampExpiry time
depositsarrayDeposits matched to this order (OrderDeposit objects)
addressstringAddress to pay to on the order's chain; null when no chain was given (the payer picks one on the payment page)(may be null)
networkstringNetwork of address: ETH, BSC or TRON; null when no chain was given(may be null)
chainstringChain given when the order was created (eth or bsc); null = the payer chooses on the payment page(may be null)
addressesarrayAddresses already generated for this customer: [{ chain, address }] (only the order's chain when one was given). ETH and BSC use the same address; pay only on the chain you choose(may be null)
tokenstringAlways USDT
pay_urlstringHosted payment page for this order

Errors: order_mode_disabled、duplicate_merchant_order_no、chain_not_enabled、invalid_param

GET/api/v1/orders/ · Get an order

Query parameters

FieldTypeRequiredDescription
order_nostringNoOrder number (either this or merchant_order_no)
merchant_order_nostringNoYour order number

Response

FieldTypeDescription
order_nostringOrder number assigned by TopUP
merchant_order_nostringYour order number
customer_idstringYour customer ID
amountamountOrder amount
matchedamountTotal of the deposits matched to this order
statusstringpending, partial, completed, overpaid, expired or expired_partial (see Matching rules)
created_attimestampCreated at
expires_attimestampExpiry time
depositsarrayDeposits matched to this order (OrderDeposit objects)
addressstringAddress to pay to on the order's chain; null when no chain was given (the payer picks one on the payment page)(may be null)
networkstringNetwork of address: ETH, BSC or TRON; null when no chain was given(may be null)
chainstringChain given when the order was created (eth or bsc); null = the payer chooses on the payment page(may be null)
addressesarrayAddresses already generated for this customer: [{ chain, address }] (only the order's chain when one was given). ETH and BSC use the same address; pay only on the chain you choose(may be null)
tokenstringAlways USDT
pay_urlstringHosted payment page for this order

Errors: not_found

GET/api/v1/orders/list/ · List orders

Query parameters

FieldTypeRequiredDescription
statusstringNoFilter by status
customer_idstringNoFilter by customer
cursorstringNonext_cursor from the previous page
limitintegerNoPage size, 1–100 (default 50)

Response: { items, next_cursor }; each item:

FieldTypeDescription
order_nostringOrder number
merchant_order_nostringYour order number
customer_idstringYour customer ID
amountamountOrder amount
matchedamountMatched total
statusstringOrder status
created_attimestampCreated at
expires_attimestampExpiry time

POST/api/v1/orders/match/ · Match a deposit to an order

Changes only the link between the deposit and the order; the balance does not change. Sends an order.matched callback with manual = true.

Request body (JSON)

FieldTypeRequiredDescription
order_nostringYesOrder number
deposit_idstringYesDeposit ID from deposits/ (same customer, not yet matched)

Response

FieldTypeDescription
order_nostringOrder number assigned by TopUP
merchant_order_nostringYour order number
customer_idstringYour customer ID
amountamountOrder amount
matchedamountTotal of the deposits matched to this order
statusstringpending, partial, completed, overpaid, expired or expired_partial (see Matching rules)
created_attimestampCreated at
expires_attimestampExpiry time
depositsarrayDeposits matched to this order (OrderDeposit objects)

Errors: not_found、cannot_match

POST/api/v1/orders/unmatch/ · Unmatch a deposit

Request body (JSON)

FieldTypeRequiredDescription
order_nostringYesOrder number
deposit_idstringYesA deposit currently matched to this order

Response

FieldTypeDescription
order_nostringOrder number assigned by TopUP
merchant_order_nostringYour order number
customer_idstringYour customer ID
amountamountOrder amount
matchedamountTotal of the deposits matched to this order
statusstringpending, partial, completed, overpaid, expired or expired_partial (see Matching rules)
created_attimestampCreated at
expires_attimestampExpiry time
depositsarrayDeposits matched to this order (OrderDeposit objects)

Errors: not_found、not_matched

POST/api/v1/customers/ · Create or update a customer

Fields you leave out are not changed. No address is created here: each chain's address is generated when the customer first makes a deposit on it, and never changes.

Request body (JSON)

FieldTypeRequiredDescription
customer_idstringYesYour ID for the customer (see Create an order). Cannot be changed later
namestringNoName, up to 200 characters
emailstringNoEmail
remarkstringNoNote, up to 500 characters

Response

FieldTypeDescription
customer_idstringYour customer ID
namestringName
emailstringEmail (stored encrypted)
remarkstringYour note
addressstringPermanent TRON address (older field; only customers who already had one, otherwise null. Use addresses)(may be null)
addressesarrayPermanent address on each chain used so far: [{ chain, address }], chain is eth, bsc or tron. Generated per chain when the customer makes a deposit (an order names the chain, or the payer picks it); empty for a new customer. ETH and BSC use the same address
totalamountTotal credited deposits
countintegerNumber of credited deposits
feesamountTotal collection fees
unmatchedamountCredited deposits not matched to any order
last_payment_attimestampTime of the last payment(may be null)
created_attimestampCreated at

Errors: invalid_param

GET/api/v1/customers/ · Get a customer

Query parameters

FieldTypeRequiredDescription
customer_idstringYesYour customer ID

Response

FieldTypeDescription
customer_idstringYour customer ID
namestringName
emailstringEmail (stored encrypted)
remarkstringYour note
addressstringPermanent TRON address (older field; only customers who already had one, otherwise null. Use addresses)(may be null)
addressesarrayPermanent address on each chain used so far: [{ chain, address }], chain is eth, bsc or tron. Generated per chain when the customer makes a deposit (an order names the chain, or the payer picks it); empty for a new customer. ETH and BSC use the same address
totalamountTotal credited deposits
countintegerNumber of credited deposits
feesamountTotal collection fees
unmatchedamountCredited deposits not matched to any order
last_payment_attimestampTime of the last payment(may be null)
created_attimestampCreated at

Errors: not_found

GET/api/v1/customers/list/ · List customers

Query parameters

FieldTypeRequiredDescription
qstringNoSearch by customer ID, name or email (partial, case-insensitive), or by exact address
cursorstringNonext_cursor from the previous page
limitintegerNoPage size, 1–100 (default 50)

Response: { items, next_cursor }; each item:

FieldTypeDescription
customer_idstringYour customer ID
namestringName
emailstringEmail (stored encrypted)
remarkstringYour note
addressstringPermanent TRON address (older field; only customers who already had one, otherwise null. Use addresses)(may be null)
addressesarrayPermanent address on each chain used so far: [{ chain, address }], chain is eth, bsc or tron. Generated per chain when the customer makes a deposit (an order names the chain, or the payer picks it); empty for a new customer. ETH and BSC use the same address
totalamountTotal credited deposits
countintegerNumber of credited deposits
feesamountTotal collection fees
unmatchedamountCredited deposits not matched to any order
last_payment_attimestampTime of the last payment(may be null)
created_attimestampCreated at

GET/api/v1/customers/stats/ · Customer statistics

Query parameters

FieldTypeRequiredDescription
customer_idstringYesYour customer ID
fromtimestampNoStart (ISO 8601); default: the beginning
totimestampNoEnd (ISO 8601); default: now

Response

FieldTypeDescription
customer_idstringYour customer ID
fromtimestampStart of the period
totimestampEnd of the period
totalamountCredited deposits in the period
countintegerNumber of credited deposits
feesamountCollection fees
unmatchedamountCredited deposits not matched to an order
last_payment_attimestampLast payment in the period(may be null)
payoutsamountCompleted payouts to this customer
payout_countintegerNumber of completed payouts

Errors: not_found、invalid_param

GET/api/v1/balance/ · Balance

Response

FieldTypeDescription
availableamountAvailable balance
frozenamountFrozen by pending withdrawals
currencystringAlways USDT

GET/api/v1/ledger/ · Ledger

Query parameters

FieldTypeRequiredDescription
customer_idstringNoFilter by customer
cursorstringNonext_cursor from the previous page
limitintegerNoPage size, 1–100 (default 50)

Response: { items, next_cursor }; each item:

FieldTypeDescription
idstringEntry ID; also the cursor for the next page
typestringdeposit, fee_in, freeze, unfreeze, withdraw or fee_out
amountamountChange: positive adds, negative subtracts (withdraw and fee_out change the frozen balance)
available_afteramountAvailable balance after this entry
frozen_afteramountFrozen balance after this entry
refstringRelated deposit ID or withdrawal number
customer_idstringRelated customer(may be null)
created_attimestampCreated at

GET/api/v1/deposits/ · Deposits

Query parameters

FieldTypeRequiredDescription
customer_idstringNoFilter by customer
matchedstringNo"false" = credited and not matched to an order; "true" = matched
cursorstringNonext_cursor from the previous page
limitintegerNoPage size, 1–100 (default 50)

Response: { items, next_cursor }; each item:

FieldTypeDescription
idstringDeposit ID (used by orders/match/)
customer_idstringYour customer ID
chainstringChain of the payment: eth, bsc or tron
addressstringReceiving address
txidstringOn-chain transaction ID
amountamountAmount received
feeamountCollection fee (never more than the amount)
creditedamountAmount added to your balance (amount − fee)
resultstringAlways credited. Payments under 1 USDT are not credited and are not listed
order_nostringMatched order(may be null)
matched_bystringdeposit, order or manual(may be null)
timetimestampBlock time of the payment

POST/api/v1/withdrawals/ · Request a withdrawal

Requires an IP whitelist. Amount + fee is frozen immediately. Every withdrawal is reviewed manually.

Request body (JSON)

FieldTypeRequiredDescription
kindstringYespayout (to your customer) or cashout (to a wallet registered in your onboarding form)
chainstringNoeth, bsc or tron (default tron)
tostringYesDestination address on that chain (ETH / BSC: 0x address, mixed case must match the EIP-55 checksum; TRON: Base58). For cashout it must be a wallet registered for that chain
amountamountYesAmount in USDT, up to 6 decimals
customer_idstringNoFor payouts: the customer being paid
merchant_refstringNoYour reference, up to 64 characters

Response

FieldTypeDescription
withdrawal_nostringWithdrawal number
kindstringpayout (to your customer) or cashout (to your registered wallet)
chainstringChain it is paid on: eth, bsc or tron
batch_nostringBatch it belongs to (batch payouts); null for single withdrawals(may be null)
customer_idstringCustomer, for payouts(may be null)
tostringDestination address on that chain
amountamountAmount sent
feeamountWithdrawal fee (amount + fee is frozen on submit)
statusstringpending, approved, broadcast, completed, rejected, cancelled or failed
txidstringOn-chain transaction ID once broadcast(may be null)
reasonstringRejection or failure reason(may be null)
merchant_refstringYour reference(may be null)
sourcestringapi or web (merchant dashboard)
created_attimestampCreated at

Errors: ip_not_allowed、invalid_address、address_not_registered、insufficient_balance、invalid_param

GET/api/v1/withdrawals/ · Get a withdrawal

Query parameters

FieldTypeRequiredDescription
withdrawal_nostringYesWithdrawal number

Response

FieldTypeDescription
withdrawal_nostringWithdrawal number
kindstringpayout (to your customer) or cashout (to your registered wallet)
chainstringChain it is paid on: eth, bsc or tron
batch_nostringBatch it belongs to (batch payouts); null for single withdrawals(may be null)
customer_idstringCustomer, for payouts(may be null)
tostringDestination address on that chain
amountamountAmount sent
feeamountWithdrawal fee (amount + fee is frozen on submit)
statusstringpending, approved, broadcast, completed, rejected, cancelled or failed
txidstringOn-chain transaction ID once broadcast(may be null)
reasonstringRejection or failure reason(may be null)
merchant_refstringYour reference(may be null)
sourcestringapi or web (merchant dashboard)
created_attimestampCreated at

Errors: not_found

POST/api/v1/withdrawals/cancel/ · Cancel a withdrawal

Request body (JSON)

FieldTypeRequiredDescription
withdrawal_nostringYesWithdrawal number; only pending withdrawals can be cancelled. Items of a batch payout cannot be cancelled one by one: cancel the batch (its not-yet-reviewed items) instead

Response

FieldTypeDescription
withdrawal_nostringWithdrawal number
kindstringpayout (to your customer) or cashout (to your registered wallet)
chainstringChain it is paid on: eth, bsc or tron
batch_nostringBatch it belongs to (batch payouts); null for single withdrawals(may be null)
customer_idstringCustomer, for payouts(may be null)
tostringDestination address on that chain
amountamountAmount sent
feeamountWithdrawal fee (amount + fee is frozen on submit)
statusstringpending, approved, broadcast, completed, rejected, cancelled or failed
txidstringOn-chain transaction ID once broadcast(may be null)
reasonstringRejection or failure reason(may be null)
merchant_refstringYour reference(may be null)
sourcestringapi or web (merchant dashboard)
created_attimestampCreated at

Errors: not_found、invalid_state、batch_item_cancel

POST/api/v1/payouts/batch/ · Submit a batch payout

Requires an IP whitelist. If any item is invalid the whole request is rejected with 422 invalid_rows and error.rows = [{ index, line, code, errors }] (index starts at 0, line = index + 1; code is the first problem of the item, errors lists all of them); nothing is frozen. Otherwise amount + fee of every item is frozen at once and each item is reviewed manually. Network fees are not charged separately. Body limit for this endpoint is 256 KB.

Request body (JSON)

FieldTypeRequiredDescription
itemsarrayYesUp to 500 items: [{ chain, to, amount, customer_id?, merchant_ref? }] (chain: eth, bsc or tron; amount in USDT, up to 6 decimals)

Response

FieldTypeDescription
batch_nostringBatch number
sourcestringapi or web (uploaded sheet)
statusstringpending (some items not reviewed yet), approved, partially_rejected, rejected, completed or cancelled
countintegerNumber of items accepted
totalamountTotal amount (fees not included)
created_attimestampCreated at
itemsarrayItems: [{ withdrawal_no, chain, to, amount, fee, status, txid, reason, customer_id, merchant_ref }], in the order submitted
serviceobjectProcessing hours (UTC+8 08:00–23:00): { open } and, outside the hours, next_open_at

Errors: ip_not_allowed、too_many_items、invalid_rows、insufficient_balance、invalid_param

GET/api/v1/payouts/batch/ · Get a batch payout

Query parameters

FieldTypeRequiredDescription
batch_nostringYesBatch number

Response

FieldTypeDescription
batch_nostringBatch number
sourcestringapi or web (uploaded sheet)
statusstringpending (some items not reviewed yet), approved, partially_rejected, rejected, completed or cancelled
countintegerNumber of items accepted
totalamountTotal amount (fees not included)
created_attimestampCreated at
itemsarrayItems: [{ withdrawal_no, chain, to, amount, fee, status, txid, reason, customer_id, merchant_ref }], in the order submitted

Errors: not_found

Errors

Errors return { "error": { "code": "…", "message": "…", "field": "…" } }.

HTTPcodeMeaning
400idempotency_key_requiredPOST without an Idempotency-Key (8–128 characters: letters, digits and _ . : -)
400invalid_paramThe request body is not valid JSON
401invalid_signatureMissing or wrong signature, or unknown API Key
401timestamp_expiredTimestamp more than 5 minutes from server time
403merchant_disabledAccount disabled
403order_mode_disabledOrder mode is not enabled for your account
403ip_not_allowedWithdrawal request not from a whitelisted IP (or no whitelist set)
404not_foundUnknown endpoint, or no such object in your account
409duplicate_merchant_order_nomerchant_order_no already used
409idempotency_conflictSame Idempotency-Key with a different request
409request_in_progressA request with this Idempotency-Key is still running; retry shortly
409cannot_matchThe deposit cannot be matched to this order (other customer, already matched, not credited, or order closed)
409not_matchedThe deposit is not matched to this order
409chain_not_enabledThis chain is not open for new addresses yet (TRON comes in a later release)
409invalid_stateNot allowed in the current state (e.g. cancelling a withdrawal that is no longer pending)
409batch_item_cancelThe withdrawal belongs to a batch payout and cannot be cancelled on its own; cancel the batch instead
413too_largeRequest body over 32 KB (256 KB for POST payouts/batch/)
422invalid_paramA parameter is missing or invalid; see error.field
422invalid_addressNot a valid address for the chain
422too_many_itemsMore than 500 items in one batch
422invalid_rowsSome batch items are invalid; see error.rows = [{ index, line, code, errors }]
422address_not_registeredcashout to a wallet that is not registered
422insufficient_balanceAmount + fee exceeds available balance
429rate_limitedMore than 20 requests per second; retry later
503wallet_not_initializedService not ready yet
500internal_errorUnexpected error; safe to retry with the same Idempotency-Key

Sandbox

A sandbox is not available yet. During the early-access period, integrate on production with small amounts (for example 1 USDT).