Z Zise Developers

预付资产内部调剂(原子 · 不可逆 · 无中间态)

POST /v1/merchant/conversions scope: merchant:write
商户自身 x-idempotency-key 动钱 · 你的预付:扣 from 资产 / 增 to 资产(同一名下,逐资产各自平)
这个端点会动钱

失败处置见下方响应表。超时(504)用同一把幂等键重试——我方可能已经处理完;业务失败要换新键,同键会原样返回那次失败。

把你的一种预付资产换成另一种。只动你自己的备付金 ——

会员的余额、你的托管声明(custody)一分都不碰。

原子、不可逆、无中间态:要么这一次请求就成交了,要么它失败。

没有「处理中」,也没有撤销、没有追回、没有重试 —— 错账只能走

我方资金管理的双人审批冲正。所以下单前把 min_to_amount 想清楚。

幂等有两层,行为不同

  • 24 小时内同键 + 同一份 body:我方把首次那份响应原样回放

201、逐字相同的 body,另带 X-Idempotent-Replay: true 响应头),

处理器一行都不执行。

  • 超过 24 小时那把键被当成新请求,但资金层仍然认得它

(库级唯一约束,没有 TTL):处理器会拿回原来那一单并回

200 + duplicated: true。这一档下同样没有第二次成交

⚠ 所以 duplicated 只在第二种情况下出现:正常成交与 24 小时内的

回放都没有这个键。if (res.duplicated === false) 永远为假,请判真值;

要判「这次是不是重放」,最稳的是 X-Idempotent-Replay 响应头。

拿到 service_unavailable 时用同一把键重试是被支持的 ——

我方不缓存这一档 400(它会因为你充值预付、或我方总闸恢复而自行解除)。

其余业务失败会被原样回放,那几种要换新键。

四个会当天返工的点

  • quote_id 一律 400 quote_lock_unsupported,不是静默忽略。

静默忽略的后果是「你以为锁了价而实际没有」,差额由你自己吃且全程

零提示。要防价格移动用 min_to_amount

  • 滑点没达到时回的是 amount_out_of_range,不是一个专门的滑点码。

同一个码还覆盖「低于起兑额」「高于单笔上限」「金额非正」——

想在自己这边分开,先读 GET /v1/merchant/conversion-pairs 的限额。

  • 余额闸减掉了已排队额。 排在 pending_merchant_funds 里的订单

已经把那部分预付许诺出去了(只是还没冻)。所以你会在「余额看起来

够」的情况下拿到 merchant_insufficient_funds —— 那是对的,

不减的话你一次「把 USDT 全换成 USDC」会让那一队订单永远推不动。

去看 GET /v1/merchant/pending

  • product_not_available 不等于「暂时不支持」。 只有托管模型是

merchant_hosted 的主体才有预付池;平台自营主体上「换你的预付」

这句话不成立。方向未启用、方向不在价目表里也归这个码。

定价取我方价目表那一行,不是你给你的会员定的零售价 ——

这条路径上我方是真实的对手方,拿零售价成交等于让对手方自己报价。

手续费落在 fee_side 那一侧的资产上,出参 fee_asset / fee_amount

明说是哪一侧。

金额一律十进制串from_amount 的小数位不得超过 from_asset

ledger_scale(超出的尾数全为 0 时放行),否则 400 invalid_request

同币种方向(from_asset == to_asset)同样是 400 invalid_request

这个端点不发任何 Webhook。 成交结果就在这次响应里,

别在这里等一条不会来的事件。

前置条件

  • 你的 custody_modelmerchant_hosted(自营主体没有预付池)
  • 该方向在我方价目表里存在且已启用
  • 两个资产都已启用,且都在你的资产白名单内(没配白名单 = 不限制)
  • 该资产可用余额 减去已排队额from_amount
字段类型必填说明
x-idempotency-key string 必填 UUID v4。必填;同键 24 小时内重放拿回同一单。 换了请求体还用同一把键 → 409 idempotency_key_reused

请求体

字段类型必填说明
from_asset string 必填 付出的资产代码,大小写不敏感(服务端转大写)
to_asset string 必填 换到的资产代码,大小写不敏感。与 from_asset 相同一律 400
from_amount string 必填 付出多少。十进制串,小数位不得超过 from_assetledger_scale。必须为正。USDT(scale 6):"5000.000000"
min_to_amount string 可选 滑点护栏:实收低于它就整笔拒(amount_out_of_range), 判在过账之前。省略 = 不设护栏,按成交那一刻的价成交。 位数按 to_assetledger_scale
quote_id string 可选 不接受。请求体里出现这个键就拒(含 ""null), 回 400 quote_lock_unsupported —— 判据是 quote_id !== undefined,不是「值非空」。 不用它就整个字段不要发。 ⚠ 这一条专门写给用强类型 SDK 或固定模板序列化请求体的接入方: 把未使用的字段发成 "" / null 是很自然的写法, 而那样每一笔都会失败,错误信息还指向一个你根本没用的功能。 本端点在成交那一刻现算价,锁价能力没有就是没有。

响应

200资金层幂等命中:拿回原来那一单,本次没有真的成交。 响应体多一个 duplicated: true,其余字段与首次逐字相同。 (24 小时内的重放走的是另一条路 —— 原样回放那份 201,见上。)
201已成交(这一刻钱已经换完了)。金额是十进制串, 两侧位数分别看 ledger_scale_from / ledger_scale_to
{
  "id": "mcv_7a1e5c30-2b44-4c11-9f8e-31d0a7b62c45",
  "from_asset": "USDT",
  "to_asset": "USDC",
  "from_amount": "5000.000000",
  "to_amount": "4990.003750",
  "rate": "0.999500",
  "fee_asset": "USDC",
  "fee_amount": "7.496250",
  "ledger_scale_from": 6,
  "ledger_scale_to": 6,
  "created_at": "2026-08-13T04:12:55.108Z"
}
400quote_lock_unsupported 传了 quote_id · invalid_request body 非 JSON / 缺资产 / 同币种 / 金额格式或精度非法 · asset_not_allowed 资产已下架,或不在你的白名单里 · product_not_available 这个方向没开,或你的托管模型没有预付池 · amount_out_of_range 低于起兑额 / 高于单笔上限 / 滑点护栏没达到 · merchant_insufficient_funds 可用余额减去已排队额不够 · limit_exceeded 撞日累计金额或笔数上限(按方向计,阈值见价目表; ⚠ 这一个不带 limit_type / limit_scope)· service_unavailable 我方兑换总闸关闭、报不出价、或你这个资产的 账户处于非正常处置态
403insufficient_scopemerchant:write —— ⚠ 这把 scope 是受限的,须我方单独审批才能授出, 勾了不等于拿到了。只读 Key 在这里恒 403。
409idempotency_key_reused 同键不同 body · idempotency_in_progress
请求
curl -X POST 'https://api.zise.com/v1/merchant/conversions' \
  -H 'x-auth-token: Bearer $TOKEN' \
  -H 'x-idempotency-key: $IDEMPOTENCY_KEY' \
  -H 'content-type: application/json' \
  -d '{
    "from_asset": "USDT",
    "to_asset": "USDC",
    "from_amount": "5000.000000",
    "min_to_amount": "4990.000000"
  }'
const res = await fetch("https://api.zise.com/v1/merchant/conversions", {
  method: "POST",
  headers: {
    "x-auth-token": "Bearer $TOKEN",
    "x-idempotency-key": "$IDEMPOTENCY_KEY",
    "content-type": "application/json",
  },
  body: JSON.stringify({
    "from_asset": "USDT",
    "to_asset": "USDC",
    "from_amount": "5000.000000",
    "min_to_amount": "4990.000000"
  }),
});
// 金额按字符串读,别让它变成 number
const data = await res.json();
import requests

res = requests.post(
    "https://api.zise.com/v1/merchant/conversions",
    headers={
        "x-auth-token": "Bearer $TOKEN",
        "x-idempotency-key": "$IDEMPOTENCY_KEY",
        "content-type": "application/json",
    },
    json={
      "from_asset": "USDT",
      "to_asset": "USDC",
      "from_amount": "5000.000000",
      "min_to_amount": "4990.000000"
    },
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()
req, _ := http.NewRequest("POST", "https://api.zise.com/v1/merchant/conversions",
    strings.NewReader(`{
  "from_asset": "USDT",
  "to_asset": "USDC",
  "from_amount": "5000.000000",
  "min_to_amount": "4990.000000"
}`))
req.Header.Set("x-auth-token", "Bearer $TOKEN")
req.Header.Set("x-idempotency-key", "$IDEMPOTENCY_KEY")
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
// 金额字段用 string 接,不要 float64
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.zise.com/v1/merchant/conversions"))
    .header("x-auth-token", "Bearer $TOKEN")
    .header("x-idempotency-key", "$IDEMPOTENCY_KEY")
    .header("content-type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
{
  "from_asset": "USDT",
  "to_asset": "USDC",
  "from_amount": "5000.000000",
  "min_to_amount": "4990.000000"
}
"""))
    .build();
// 金额字段用 String / BigDecimal,不要 double
$ch = curl_init('https://api.zise.com/v1/merchant/conversions');
curl_setopt_array($ch, [
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'x-auth-token: Bearer $TOKEN',
    'x-idempotency-key: $IDEMPOTENCY_KEY',
    'content-type: application/json',
  ],
  CURLOPT_POSTFIELDS => <<<'JSON'
{
  "from_asset": "USDT",
  "to_asset": "USDC",
  "from_amount": "5000.000000",
  "min_to_amount": "4990.000000"
}
JSON,
]);
$res = curl_exec($ch);
// 金额用 bcmath / 字符串,不要 floatval
200
// spec 里还没有这个 operation 的响应示例