Integrator Quote & Create Order
本文件面向 Any2Any 跨鏈聚合的集成商(Integrator),介紹以下介面:
| # | 介面 | 用途 |
|---|---|---|
| 1 | GET /api/v2/quote/stream | 即時詢價(SSE 串流回傳各通道報價) |
| 2 | POST /api/v1/integrator/orders | 集成商下單(在詢價結果上建立訂單) |
| 3 | POST /api/v1/integrator/orders/:orderNo/request_refund | 集成商對自己參與的訂單發起退款 |
下單時攜帶集成商 API Key 即可識別身分並完成分佣記帳,不影響一般使用者的下單流程。
0. 通用約定
0.1 Base URL
https://<aggregator-api-host>
0.2 鑑權
| 介面 | 鑑權方式 |
|---|---|
/api/v2/quote/stream | 無需鑑權(公開詢價) |
/api/v1/integrator/orders | X-Integrator-Api-Key: <api_key>(推薦)或 Authorization: ApiKey <api_key> |
/api/v1/integrator/orders/:orderNo/request_refund | X-Integrator-Api-Key: <api_key> |
API Key 由 Any2Any 後台分配,集成商可以透過集成商入口自助管理(最多 20 個)。每個 Key 都有獨立的
fee_rate(集成商抽佣費率,0.01% ~ 10.00%),下單時實際寫入的就是該 Key 的費率快照。命中 API Key 的訂單基礎手續費使用後台配置的 B 端階梯費率,使用者實際支付手續費比例 = B 端階梯費率 + 該 Key 的fee_rate。
0.3 一般回應包絡
非 SSE 介面統一回傳:
{
"code": 0,
"msg": "ok",
"data": {}
}
code = 0表示成功;HTTP 狀態碼:200成功;400參數錯誤;401API Key 無效;403集成商被停用;500/502服務端 / 上游異常。
0.4 金額表示
- 所有
amount_in、*_estimated_out_*、min_acceptable_out_amount等數量欄位,全部是目標 token 最小單位的整數字串(例如 USDT 6 位小數,"1000000"表示 1 USDT)。 slippage為小數百分比,取值範圍(0, 1),例如0.01表示 1%。- 費率欄位(
channel_fee、fee_rate、platform_fee_rate等)單位為百分比(例如0.3即 0.3%)。
1. 詢價:GET /api/v2/quote/stream
1.1 概述
詢價採用 SSE(Server-Sent Events) 串流回傳。服務端會邊詢問上游邊推送:每個通道(channel)的報價一回傳,就以 event: channel 形式立即下發,最後以 event: done 結束。
相較於一次性回傳,串流詢價的優勢:
- 首筆報價回應快(不必等到最慢的通道)
- 可中途取消(關閉連線即可)
- 錯誤隔離:單一通道失敗只發
event: error,不影響其他通道
1.2 請求
GET /api/v2/quote/stream?src_token_id=&dst_token_id=&amount_in=&slippage=&promo_code=&channel_id= HTTP/1.1
Accept: text/event-stream
| Query 參數 | 類型 | 必填 | 說明 |
|---|---|---|---|
src_token_id | int | Yes | 來源 token id(Any2Any 內部 ID,可透過 token 列表介面取得) |
dst_token_id | int | Yes | 目標 token id |
amount_in | string | Yes | 來源 token 數量,最小單位字串(例:"1000000" 表示 1 USDT) |
slippage | number | Yes | 滑點,(0, 1) 小數,例如 0.01 = 1% |
promo_code | string | No | 平台促銷碼(如有),命中後所有通道按折扣展示 |
channel_id | int | No | 指定單一通道詢價;不傳則詢價全部啟用通道 |
也支援
POST同名路徑,body 用 JSON 傳相同欄位,便於channel_id等數字類型。
1.3 回應
200 OK,Content-Type: text/event-stream; charset=utf-8,按 SSE 協議回傳若干事件。每個事件結構如下:
event: <event-name>
data: <json-payload>
1.3.1 事件類型
| event | 何時下發 | data |
|---|---|---|
channel | 每個通道有報價時 | 通道報價物件(見下) |
error | 某通道詢價失敗 | { channel_id?: number, msg: string } |
done | 全部完成 / 服務端關閉 | {} |
channel 事件 data 欄位:
{
"channel_id": 101,
"name": "Channel A",
"symbol": "USDT",
"channel_icon": "https://.../logo.png",
"estimated_out_str": "999500000",
"estimated_out_usd": "999.500000",
"channel_fee": 0.3,
"channel_fee_original": 0.3,
"promo_applied": false,
"discount_pct": null
}
| 欄位 | 說明 |
|---|---|
channel_id | 通道 ID,下單時必須原樣回傳 |
name | 通道名稱 |
symbol | 目標 token symbol |
channel_icon | 通道圖示 URL |
estimated_out_str | 扣除手續費後預估到帳數量(目標 token 最小單位字串),下單時 main_channel_estimated_out_amount 直接使用此值 |
estimated_out_usd | 預估到帳金額對應 USD(小數字串,僅展示) |
channel_fee | 實際生效費率(百分比),命中 promo 時為折後費率 |
channel_fee_original | 原始基礎費率(百分比) |
promo_applied | 是否命中促銷 |
discount_pct | 促銷折扣百分比,未命中時為 null |
1.4 失敗回應(HTTP 非 200)
詢價還沒開始建立 SSE 串流時的錯誤用一般 JSON 包絡回傳:
| HTTP | 情境 |
|---|---|
| 400 | 缺少必填參數(src_token_id/dst_token_id required 等) |
| 400 | token not found / chain not found / channel not found |
| 400 | no available channels |
| 502 | quote service timed out / quote service unavailable |
1.5 範例
請求:
curl -N \
"https://<host>/api/v2/quote/stream?src_token_id=1&dst_token_id=2&amount_in=1000000&slippage=0.01"
回應(節選):
event: channel
data: {"channel_id":101,"name":"Channel A","symbol":"USDT","channel_icon":"https://.../a.png","estimated_out_str":"999500000","estimated_out_usd":"999.500000","channel_fee":0.3,"channel_fee_original":0.3,"promo_applied":false,"discount_pct":null}
event: channel
data: {"channel_id":102,"name":"Channel B","symbol":"USDT","channel_icon":"https://.../b.png","estimated_out_str":"999200000","estimated_out_usd":"999.200000","channel_fee":0.3,"channel_fee_original":0.3,"promo_applied":false,"discount_pct":null}
event: error
data: {"channel_id":103,"msg":"upstream timeout"}
event: done
data: {}
1.6 瀏覽器 / Node.js 用戶端
EventSource(瀏覽器)
const url = new URL('https://<host>/api/v2/quote/stream');
url.searchParams.set('src_token_id', '1');
url.searchParams.set('dst_token_id', '2');
url.searchParams.set('amount_in', '1000000');
url.searchParams.set('slippage', '0.01');
const es = new EventSource(url.toString());
const channels = [];
es.addEventListener('channel', (event) => {
channels.push(JSON.parse(event.data));
});
es.addEventListener('error', (event) => {
// 單通道錯誤:data 可解析;網路層錯誤:data 可能為空。
console.warn('channel error', event.data);
});
es.addEventListener('done', () => {
es.close();
const best = channels.reduce((a, b) =>
BigInt(b.estimated_out_str) > BigInt(a.estimated_out_str) ? b : a
);
console.log('best quote', best);
});
Fetch + Streams(Node.js / Deno)
const res = await fetch(url, { headers: { Accept: 'text/event-stream' } });
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let idx;
while ((idx = buffer.indexOf('\n\n')) >= 0) {
const block = buffer.slice(0, idx);
buffer = buffer.slice(idx + 2);
let event = 'message';
let data = '';
for (const line of block.split('\n')) {
if (line.startsWith('event:')) event = line.slice(6).trim();
else if (line.startsWith('data:')) data = line.slice(5).trim();
}
if (event === 'channel') handleQuote(JSON.parse(data));
if (event === 'done') return;
}
}
1.7 注意事項
- 服務端預設上游逾時 60s,逾時後下發
event: error,再發event: done關閉。 - 同一詢價請求建議只發起一次連線;若需更新報價,先關閉舊連線再建立新連線,避免資料混淆。
estimated_out_str是扣完通道費 / 平台費 / 促銷折扣後的「淨到手」數量,可直接展示給使用者。
2. 集成商下單:POST /api/v1/integrator/orders
2.1 概述
在選定詢價結果後,集成商以服務端身分呼叫該介面建立訂單。請求體與公開的 POST /api/v1/orders 完全一致,額外透過 X-Integrator-Api-Key 頭識別集成商身分並寫入分佣上下文。
下單成功後服務端會回傳充值地址(deposit_address)和過期時間(expire_at)。使用者在 expire_at 之前向 deposit_address 轉入 amount_in 數量的來源 token,訂單會被自動執行並跨鏈送達 dst_address。
2.2 鑑權
請求需攜帶集成商 API Key,兩種寫法擇一:
X-Integrator-Api-Key: aaaabbbbccccddddeeeeffff00001111
或:
Authorization: ApiKey aaaabbbbccccddddeeeeffff00001111
降級行為:如果未傳 API Key、Key 不存在 / 已停用,或對應集成商被停用,介面不會回傳 401/403,而是降級為一般訂單建立(不綁定集成商,無分佣)。這樣可以避免誤刪 / 誤改 Key 導致下單完全中斷。集成方上線後請關注「訂單詳情中
integrator_id是否非空」來確認分佣鏈路是否正常。
2.3 請求
POST /api/v1/integrator/orders HTTP/1.1
Content-Type: application/json
X-Integrator-Api-Key: <api_key>
請求體:
{
"src_token_id": 1,
"dst_token_id": 2,
"amount_in": "1000000",
"dst_address": "0xRecipientAddress...",
"chosen_channel_id": 101,
"slippage": 0.01,
"main_channel_estimated_out_amount": "999500000",
"min_acceptable_out_amount": "989505000",
"channels": [
{
"channel_id": 101,
"estimated_out_str": "999500000",
"name": "Channel A",
"symbol": "USDT",
"channel_icon": "https://.../a.png",
"estimated_out_usd": "999.500000",
"channel_fee": 0.3
},
{
"channel_id": 102,
"estimated_out_str": "999200000",
"name": "Channel B"
}
],
"client_order_id": "your-internal-order-no-001",
"promo_code": "WELCOME"
}
2.3.1 必填欄位
| 欄位 | 類型 | 說明 |
|---|---|---|
src_token_id | int | 來源 token id(與詢價一致) |
dst_token_id | int | 目標 token id |
amount_in | string | 來源 token 數量(最小單位字串) |
dst_address | string | 使用者收款地址,需與目標鏈格式匹配(EVM / Solana / Tron / UTXO 等) |
chosen_channel_id | int | 使用者選定的通道 ID,來自詢價結果的 channel_id |
slippage | number | 滑點,與詢價時保持一致 |
main_channel_estimated_out_amount | string | 選定通道的預估到帳,直接傳詢價結果的 estimated_out_str |
min_acceptable_out_amount | string | 使用者能接受的最低到帳數量(最小單位字串)。通常 = main_channel_estimated_out_amount × (1 - slippage) |
channels | array | 本次詢價拿到的全部通道列表(用於服務端二次校驗和稽核) |
channels[] 元素結構(與詢價結果一一對應):
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
channel_id | int | Yes | 通道 ID |
estimated_out_str | string | Yes | 該通道預估到帳 |
name / symbol / channel_icon / estimated_out_usd / channel_fee | - | No | 詢價結果對應欄位,可全量原樣回傳 |
2.3.2 可選欄位
| 欄位 | 類型 | 說明 |
|---|---|---|
client_order_id | string | 集成商側業務訂單號;同一集成商下唯一,觸發冪等回傳(同 ID 重複下單只建立一次) |
client_id | string | 業務方自訂標識,僅日誌 / 風控用 |
partner_app_id | int | 合作夥伴 app id(如有) |
user_id | string | 集成商側終端使用者 ID(僅用於稽核與風控,不參與簽名) |
invite_code | string | 邀請碼(如有) |
promo_code | string | 平台促銷碼,與詢價時保持一致 |
is_use_wallet | boolean | 是否走集成商內部錢包流程(預設 false) |
fee_rate | number | 僅在合約允許時下發;通常不要傳,會按 API Key 內嵌的費率結算 |
min_quote_amount | string | 最小詢價金額校驗(保留欄位) |
2.4 回應
成功 200 OK:
{
"code": 0,
"msg": "ok",
"data": {
"order_id": 1234567,
"order_no": "OD2026052510000001",
"deposit_address": "0xDepositAddressGeneratedByPlatform",
"expire_at": "2026-05-25T10:30:00Z",
"fee_platform": "0",
"fee_partner": "0",
"fee_integrator": "3000",
"fee_platform_original": "3000",
"platform_fee_rate": "0",
"partner_fee_rate": "0",
"integrator_fee_rate": "0.0100"
}
}
欄位說明
| 欄位 | 類型 | 說明 |
|---|---|---|
order_id | int | 平台訂單 ID |
order_no | string | 平台訂單號,建議落庫 |
deposit_address | string | 充值地址,把 amount_in 數量的來源 token 轉入此地址即觸發跨鏈 |
expire_at | string | ISO8601 UTC,過期前必須完成充值,否則訂單會被取消 |
fee_platform | string | 平台費快照(來源 token 最小單位字串) |
fee_partner | string | 合作夥伴費(集成商訂單固定為 "0") |
fee_integrator | string | 集成商抽佣金額,即本筆訂單將分給該集成商的金額 |
fee_platform_original | string | 通道總費用(未扣集成商分成) |
platform_fee_rate / partner_fee_rate / integrator_fee_rate | string | 對應費率快照(小數字串,例如 "0.0100" 表示 1%) |
冪等情境:當本次請求的
client_order_id已經存在並匹配上既有訂單時,會回傳僅包含order_id / order_no / deposit_address / expire_at的簡化回應;如需完整費用欄位,請呼叫訂單詳情介面。
2.5 錯誤回應
集成商下單完全複用一般 POST /api/v1/orders 的校驗邏輯,常見錯誤:
| HTTP | code | msg | 說明 |
|---|---|---|---|
| 400 | 400 | invalid request body | JSON 解析失敗 |
| 400 | 400 | src_token_id/dst_token_id required | 必填欄位缺失 |
| 400 | 400 | token not found / chain not found | 內部 ID 不存在 |
| 400 | 400 | dst_address invalid | 目標地址格式不符合目標鏈 |
| 400 | 400 | chosen_channel_id not in channels | chosen_channel_id 不在 channels[] 中 |
| 400 | 400 | slippage out of range | 滑點不在 (0, 1) |
| 400 | 400 | min_acceptable_out_amount too low | 低於平台最小到帳約束 |
| 400 | 400 | quote amount mismatch | 服務端二次詢價後偏差超閾值(請重新整理詢價後重試) |
| 400 | 400 | amount_in below minimum | 低於最小下單金額 |
| 400 | 400 | duplicate client_order_id | 同 client_order_id 已有不同訂單 |
| 400 | 400 | split orders are not supported for integrator orders | 集成商訂單僅支援單通道,不接受拆單(傳入 split_count≥2 或 is_risk=true 時報此錯) |
2.5.1 單通道說明
集成商下單僅支援單通道,不接受拆單 / 多通道模式:
- 請求中不要傳
split_count,也不要把is_risk設為true;一旦傳入split_count ≥ 2或is_risk=true,介面會回傳split orders are not supported for integrator orders。 - 訂單按
chosen_channel_id選定的單一通道執行,main_channel_estimated_out_amount/min_acceptable_out_amount直接沿用詢價結果即可。 - 費用語義:
fee_platform_original為通道總費用(未扣集成商分成);fee_platform為扣除集成商分成後實際入帳平台的金額(集成商訂單中可能= 0);fee_integrator為本筆訂單分給集成商的金額。
2.6 範例
curl -X POST "https://<host>/api/v1/integrator/orders" \
-H "Content-Type: application/json" \
-H "X-Integrator-Api-Key: aaaabbbbccccddddeeeeffff00001111" \
-d '{
"src_token_id": 1,
"dst_token_id": 2,
"amount_in": "1000000",
"dst_address": "0xRecipient...",
"chosen_channel_id": 101,
"slippage": 0.01,
"main_channel_estimated_out_amount": "999500000",
"min_acceptable_out_amount": "989505000",
"channels": [
{ "channel_id": 101, "estimated_out_str": "999500000" },
{ "channel_id": 102, "estimated_out_str": "999200000" }
],
"client_order_id": "biz-order-20260525-001"
}'
3. 集成商退款:POST /api/v1/integrator/orders/:orderNo/request_refund
3.1 概述
當集成商訂單進入可退款狀態(充值已過期未執行、跨鏈失敗、滑點失敗等)時,集成商可以服務端身分呼叫該介面,為自己參與的訂單指定退款地址並發起退款。該介面與公開的 POST /api/v1/orders/:orderNo/request_refund 流程一致,僅多了兩點:
- 必須用集成商 API Key 鑑權,且只能退本集成商自己的訂單;
- 退款只在訂單未成功時退回本金,分成只在 swap 成功時結算:集成商訂單為單通道,swap 成功即正常計分成且不可退款;swap 失敗 / 充值過期則全額退回本金、不產生分成(詳見 §3.6)。
3.2 鑑權
該退款介面僅支援 X-Integrator-Api-Key 頭(不支援 Authorization: ApiKey 寫法):
X-Integrator-Api-Key: aaaabbbbccccddddeeeeffff00001111
與下單介面不同,退款介面不會降級:缺少 / 無效 API Key 直接回傳
401;集成商被停用回傳403;訂單不屬於該集成商回傳403。
3.3 請求
POST /api/v1/integrator/orders/{orderNo}/request_refund HTTP/1.1
Content-Type: application/json
X-Integrator-Api-Key: <api_key>
| 參數 | 位置 | 類型 | 必填 | 說明 |
|---|---|---|---|---|
orderNo | path | string | Yes | 下單時回傳的 order_no |
refund_address | body | string | Yes | 退款接收地址,需與來源鏈地址格式匹配 |
請求體:
{
"refund_address": "0xSourceChainRefundAddress..."
}
3.4 可退款狀態與處理方式
服務端會根據訂單目前狀態決定退款處理方式:
| 訂單狀態 | 處理方式 | 前置條件 |
|---|---|---|
Depositing / CancelledNoDeposit(充值過期) | 進入人工覆核(ManualReview) | 必須已過 expire_at 且未過 auto_cancel_at |
RefundAddressPending / ChainFailed(On-chain Failed) / SlippageFailed(Swap Failed) | 失敗後自動退回本金(扣 gas) | 必須已記錄執行明細,否則需人工確認 |
已終態的訂單(
Completed/Refunded/CancelledNoDeposit已結清 /FailedNoRefund)不可再退。
3.5 回應
成功 200 OK:
{
"code": 0,
"msg": "ok",
"data": "ok"
}
冪等:若該訂單已存在退款記錄且
refund_address與之前一致,重複呼叫直接回傳成功;若傳入的refund_address與已有退款記錄不一致,回傳refund_address_cannot_be_changed(退款地址不可變更)。
3.6 退款後的分成處理
集成商訂單為單通道,分成只在訂單結算且 swap 成功時寫入 integrator_commission_ledger(金額 = fee_integrator)。因此:
- swap 成功:訂單為終態、不可退款,集成商正常拿到
fee_integrator分成。 - swap 失敗 / 充值過期(無成功結算):結算階段不寫入分成,全額退回本金,集成商不產生分成。
- 退款流程不會回滾或改寫已結算的分成(包括已進入提現流程的
WithdrawRequested/Withdrawn)。
3.7 錯誤回應
| HTTP | msg | 說明 |
|---|---|---|
| 401 | unauthorized | 缺少 / 無效 API Key |
| 403 | integrator disabled | 集成商被停用 |
| 403 | order does not belong to integrator | 該訂單不是本集成商參與的訂單 |
| 400 | order no required | 路徑缺少 orderNo |
| 400 | refund_address required | 缺少退款地址 |
| 400 | order not found | 訂單不存在 |
| 400 | order_already_finalized | 訂單已終態,不可退 |
| 400 | order_not_in_refundable_state | 目前狀態不支援退款 |
| 400 | order_not_expired_yet | 充值尚未過期,暫不能退 |
| 400 | order_auto_cancel_window_passed | 已過自動取消視窗 |
| 400 | refund_requires_manual_confirmation | 無執行明細記錄,需人工確認 |
| 400 | no_amount_to_refund | 無可退金額 |
| 400 | refund_address_cannot_be_changed | 已有退款記錄且地址不一致 |
| 400 | order_state_changed_retry | 狀態被併發修改,請重試 |
3.8 範例
curl -X POST "https://<host>/api/v1/integrator/orders/OD2026052510000001/request_refund" \
-H "Content-Type: application/json" \
-H "X-Integrator-Api-Key: aaaabbbbccccddddeeeeffff00001111" \
-d '{
"refund_address": "0xSourceChainRefundAddress..."
}'
4. 端到端流程
┌──────────────────┐
│ 集成商服務端 │
└────────┬─────────┘
│ ① GET /api/v2/quote/stream (SSE)
│ src_token_id / dst_token_id / amount_in / slippage
▼
┌──────────────────┐
│ Any2Any API │ ──► event: channel × N (即時推送)
└────────┬─────────┘ event: done
│
│ ② 在收到的通道中選出最優 quote (estimated_out_str 最大)
│ 計算 min_acceptable_out_amount = floor(estimated_out_str * (1 - slippage))
│
│ ③ POST /api/v1/integrator/orders
│ X-Integrator-Api-Key: <api_key>
│ body: { ..., chosen_channel_id, channels[], main_channel_estimated_out_amount, min_acceptable_out_amount }
▼
┌──────────────────┐
│ Any2Any API │ ──► { order_no, deposit_address, expire_at, fee_integrator, ... }
└────────┬─────────┘
│
│ ④ 把 deposit_address 展示給終端使用者 / 由集成商錢包轉帳
│
│ ⑤ 使用者在 expire_at 之前完成充值
▼
┌────────────┐
│ 鏈上結算 │ ──► 自動跨鏈 → 使用者的 dst_address
└────────────┘
│
│ ⑥ 訂單終態後,集成商抽佣自動進入集成商入口「可提現餘額」
▼
Integrator Portal → 提現
5. 常見問題(FAQ)
Q1:詢價的 estimated_out_str 已經扣過通道費了,下單時還要再扣一次嗎?
不會。estimated_out_str 是對外展示口徑,已經包含通道費 / 平台費 / 促銷折扣的淨到手金額;min_acceptable_out_amount 直接基於它乘以 (1 - slippage) 即可。
Q2:channels[] 必須傳全部詢價結果嗎?
建議傳整次詢價收到的全部 channel 事件對應的物件,至少要包含 chosen_channel_id 對應的那一條。服務端會用它來做滑點 / 通道選擇的二次校驗,少傳可能觸發 chosen_channel_id not in channels。
Q3:API Key 外洩怎麼辦?
立即在集成商入口裡刪除該 Key(同時新建一個新的 Key),刪除後立即失效。已經建立的訂單不受影響(費率快照已落表)。
Q4:是否需要每次都重新詢價?
是。estimated_out_str 是基於上游即時報價的,下單時會做二次詢價 + 偏差校驗,超出閾值會報 quote amount mismatch。建議詢價 → 下單間隔不超過 10s。
Q5:下單後多久過期?
由 expire_at 欄位決定(通常 15~30 分鐘),過期前必須充值,否則訂單被取消,分佣不會產生。
Q6:抽佣金額如何到帳?
訂單終態(Settled)後,對應 fee_integrator 金額會按訂單建立時的來源幣價格快照,直接進入集成商的「可提現餘額(Available)」,可在集成商入口發起提現。
6. 相關連結
- 集成商入口、API Key 管理、佣金 / 提現等完整說明:Integrator API Integration Guide
- Postman 集合:
aggregator-api.postman_collection.json中的Quotes/Orders/Integrator Portal (Public)分組