Z Zise Developers 简体中文

Internal prepaid-asset conversion: available directions and current reference prices

GET /v1/merchant/conversion-pairs scope: merchant:read
Merchant account

These endpoints convert your own reserve funds, not any member's money. Member exchanges use POST /v1/exchange/orders; the two paths share no funds.

Parameters

This endpoint takes no parameters: no path parameters, query parameters or request body. For the authentication header (x-auth-token) and on-behalf-of header (x-on-behalf-of), see Authentication and Signing.

Response

200OK
{
  "data": [
    {
      "from_asset": "USDT",
      "to_asset": "USDC",
      "rate": "0.999500",
      "rate_mode": "fixed",
      "min_amount": "100.000000",
      "max_amount": "200000.000000",
      "daily_amount_limit": "500000.000000",
      "daily_count_limit": 20,
      "fee_mode": "percent",
      "fee_side": "to",
      "fee_bps": 15,
      "fee_fixed": "0.000000",
      "ledger_scale_from": 6,
      "ledger_scale_to": 6
    }
  ],
  "next_cursor": null,
  "has_more": false
}
403insufficient_scope: missing merchant:read.

Additional Details

Why you may need this

Under merchant-hosted custody, you do not choose which prepaid asset an order consumes. That follows the order's payment asset, meaning what your member chose to pay with. If members pay in USDT today and USDC tomorrow, one prepaid pool can run dry while the other remains unused. A depleted pool causes member-facing service_unavailable, a generic Temporarily unavailable message without a reason, and queued orders accumulating in GET /v1/merchant/pending.

Previously, merchant.available had only two entry points: top-ups approved by two staff members and your withdrawals. Neither could move funds between assets.

Four points when reading this table

  • Configured per direction. USDT→USD and USD→USDT are independent records. Rates need not be symmetric, and only one direction may be enabled. Seeing A→B does not imply B→A exists.
  • ⚠ rate is a reference price, not a locked price. It is recalculated at execution using periodically refreshed valuation rates. Supply min_to_amount when ordering to protect against price movement. This business line does not support price locking; supplying quote_id always returns 400.
  • ⚠ An empty rate means no price is available now, because valuation rates have not refreshed; it is neither 0 nor free. That direction cannot currently be ordered, but others remain readable. One direction's configuration problem should not break the whole price list, so we return an empty string instead of making the endpoint return 500.
  • Amounts are decimal strings, padded to the relevant asset's ledger_scale. Unlimited is numeric zero: min_amount: "0.000000" means no minimum. daily_count_limit is an integer, with 0 likewise meaning unlimited.

Three enums to handle explicitly: rate_mode ∈ oracle / fixed; fee_mode ∈ none / percent / fixed / percent_plus_fixed; fee_side ∈ from / to. fee_side also determines fee_fixed precision: from uses ledger_scale_from, while to uses ledger_scale_to.

The list is our price list ∩ your asset allowlist. No allowlist rows means unrestricted access: it restricts rather than enables. Spread is not returned; rate already includes it, so you can determine how much you receive.

⚠ This table does not check your custody model. Entities without a prepaid pool, whose custody_model is not merchant_hosted, still see directions here but all orders return product_not_available. Check custody_model in GET /v1/merchant first.

Not paginated: next_cursor is always null; cursor / limit have no effect.

Request
curl -X GET 'https://api.zinfra.vip/v1/merchant/conversion-pairs' \
  -H 'x-auth-token: Bearer $TOKEN'
const res = await fetch("https://api.zinfra.vip/v1/merchant/conversion-pairs", {
  method: "GET",
  headers: {
    "x-auth-token": "Bearer $TOKEN",
  },
});
// Keep monetary amounts as strings, never numbers.
const data = await res.json();
import requests

res = requests.get(
    "https://api.zinfra.vip/v1/merchant/conversion-pairs",
    headers={
        "x-auth-token": "Bearer $TOKEN",
    },
)
# Use Decimal(str(...)) for amounts, not float.
data = res.json()
req, _ := http.NewRequest("GET", "https://api.zinfra.vip/v1/merchant/conversion-pairs",
    nil)
req.Header.Set("x-auth-token", "Bearer $TOKEN")
res, err := http.DefaultClient.Do(req)
// Decode amount fields as string, not float64.
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.zinfra.vip/v1/merchant/conversion-pairs"))
    .header("x-auth-token", "Bearer $TOKEN")
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();
// Use String / BigDecimal for amounts, not double.
$ch = curl_init('https://api.zinfra.vip/v1/merchant/conversion-pairs');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'GET',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'x-auth-token: Bearer $TOKEN',
  ],
]);
$res = curl_exec($ch);
// Use bcmath / strings for amounts, not floatval.
200
{
  "data": [
    {
      "from_asset": "USDT",
      "to_asset": "USDC",
      "rate": "0.999500",
      "rate_mode": "fixed",
      "min_amount": "100.000000",
      "max_amount": "200000.000000",
      "daily_amount_limit": "500000.000000",
      "daily_count_limit": 20,
      "fee_mode": "percent",
      "fee_side": "to",
      "fee_bps": 15,
      "fee_fixed": "0.000000",
      "ledger_scale_from": 6,
      "ledger_scale_to": 6
    }
  ],
  "next_cursor": null,
  "has_more": false
}