Internal prepaid-asset conversion: available directions and current reference prices
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
{
"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
}insufficient_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→USDandUSD→USDTare independent records. Rates need not be symmetric, and only one direction may be enabled. Seeing A→B does not imply B→A exists. - ⚠
rateis a reference price, not a locked price. It is recalculated at execution using periodically refreshed valuation rates. Supplymin_to_amountwhen ordering to protect against price movement. This business line does not support price locking; supplyingquote_idalways returns 400. - ⚠ An empty
ratemeans 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_limitis an integer, with0likewise 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.
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.
{
"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
}