Skip to main content

Integrator Quote & Create Order

This guide is for Any2Any aggregator integrators. It covers these endpoints:

#EndpointPurpose
1GET /api/v2/quote/streamReal-time quotes returned as an SSE stream
2POST /api/v1/integrator/ordersCreate an integrator order from a selected quote
3POST /api/v1/integrator/orders/:orderNo/request_refundRequest a refund for an order that belongs to the integrator

Passing an integrator API key when creating an order identifies the integrator and records the commission context. It does not affect the normal user order flow.


0. Common Conventions

0.1 Base URL

https://<aggregator-api-host>

0.2 Authentication

EndpointAuthentication
/api/v2/quote/streamNo authentication required
/api/v1/integrator/ordersX-Integrator-Api-Key: <api_key> (recommended)
or Authorization: ApiKey <api_key>
/api/v1/integrator/orders/:orderNo/request_refundX-Integrator-Api-Key: <api_key>

API keys are issued by the Any2Any backend and can be managed in the integrator portal. Each key has its own fee_rate (integrator commission rate, 0.01% to 10.00%). The order stores a snapshot of the rate attached to the API key used at creation time. For API-key orders, the user-facing fee rate is the B2B tier fee configured by the backend plus the key's fee_rate.

0.3 Response Envelope

Non-SSE endpoints return:

{
"code": 0,
"msg": "ok",
"data": {}
}
  • code = 0 means success. HTTP status codes: 200 success; 400 invalid parameters; 401 invalid API key; 403 integrator disabled; 500/502 server or upstream error.

0.4 Amounts and Rates

  • Amount fields such as amount_in, *_estimated_out_*, and min_acceptable_out_amount are integer strings in the token's smallest unit. For example, "1000000" means 1 USDT for a 6-decimal token.
  • slippage is a decimal percentage in (0, 1). For example, 0.01 means 1%.
  • Rate fields such as channel_fee, fee_rate, and platform_fee_rate are percentages. For example, 0.3 means 0.3%.

1. Quote: GET /api/v2/quote/stream

1.1 Overview

Quotes are returned through Server-Sent Events (SSE). The server queries upstream channels and pushes each channel quote as soon as it is available with event: channel. The stream ends with event: done.

Streaming quotes provide:

  • Faster first quote because the client does not wait for the slowest channel.
  • Cancelable requests by closing the connection.
  • Error isolation because a single channel failure is sent as event: error and does not stop other channels.

1.2 Request

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 parameterTypeRequiredDescription
src_token_idintYesSource token ID in Any2Any
dst_token_idintYesDestination token ID
amount_instringYesSource token amount as a smallest-unit string
slippagenumberYesSlippage in (0, 1), for example 0.01 = 1%
promo_codestringNoPlatform promo code, if any
channel_idintNoQuote only one channel. If omitted, all enabled channels are quoted

The same path also supports POST with the same fields in a JSON body, which is useful for numeric fields such as channel_id.

1.3 Response

200 OK with Content-Type: text/event-stream; charset=utf-8. Each event follows the SSE format:

event: <event-name>
data: <json-payload>

1.3.1 Event Types

eventWhen it is sentdata
channelWhen a channel quote is availableChannel quote object
errorWhen one channel quote fails{ channel_id?: number, msg: string }
doneWhen all quotes are finished or the server closes the stream{}

channel event 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
}
FieldDescription
channel_idChannel ID. Return this unchanged when creating the order
nameChannel name
symbolDestination token symbol
channel_iconChannel icon URL
estimated_out_strEstimated received amount after fees, in the destination token's smallest unit. Use this directly as main_channel_estimated_out_amount
estimated_out_usdEstimated USD value for display
channel_feeEffective percentage fee, after promo discount if applied
channel_fee_originalOriginal base percentage fee
promo_appliedWhether a promo discount was applied
discount_pctPromo discount percentage, or null

1.4 Failure Responses Before the Stream Starts

If the SSE stream has not started yet, errors are returned as normal JSON envelopes:

HTTPScenario
400Missing required parameters such as src_token_id/dst_token_id required
400token not found / chain not found / channel not found
400no available channels
502quote service timed out / quote service unavailable

1.5 Example

Request:

curl -N \
"https://<host>/api/v2/quote/stream?src_token_id=1&dst_token_id=2&amount_in=1000000&slippage=0.01"

Response excerpt:

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 Browser / Node.js Clients

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) => {
// Channel-level errors include data; network-level errors may not.
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

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 Notes

  • The default upstream timeout is 60 seconds. Timeout errors are sent as event: error, followed by event: done.
  • Use one connection per quote request. If a quote needs to be refreshed, close the old connection first.
  • estimated_out_str is the net received amount after channel fees, platform fees, and promo discounts.

2. Integrator Order: POST /api/v1/integrator/orders

2.1 Overview

After selecting a quote, the integrator calls this endpoint as a server-side client to create an order. The request body is the same as the public POST /api/v1/orders endpoint. The extra X-Integrator-Api-Key header identifies the integrator and records commission information.

After the order is created, the server returns a deposit address (deposit_address) and an expiration time (expire_at). The user transfers the amount_in source token amount to deposit_address before expire_at, and the order is then executed automatically to deliver funds to dst_address.

2.2 Authentication

Send the integrator API key in one of these forms:

X-Integrator-Api-Key: aaaabbbbccccddddeeeeffff00001111

or:

Authorization: ApiKey aaaabbbbccccddddeeeeffff00001111

Fallback behavior: if no API key is sent, the key does not exist, the key is disabled, or the integrator is disabled, this endpoint does not return 401/403. It falls back to normal order creation without integrator binding or commission. After launch, integrators should verify that integrator_id is present in order details to confirm the commission flow.

2.3 Request

POST /api/v1/integrator/orders HTTP/1.1
Content-Type: application/json
X-Integrator-Api-Key: <api_key>

Request body:

{
"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 Required Fields

FieldTypeDescription
src_token_idintSource token ID, same as the quote request
dst_token_idintDestination token ID
amount_instringSource token amount as a smallest-unit string
dst_addressstringUser receiving address. It must match the destination chain format
chosen_channel_idintSelected channel_id from the quote result
slippagenumberSlippage, same as the quote request
main_channel_estimated_out_amountstringThe selected channel's estimated_out_str
min_acceptable_out_amountstringMinimum acceptable received amount. Usually main_channel_estimated_out_amount × (1 - slippage)
channelsarrayAll channel quote results received in this quote request, used for validation and audit

channels[] object:

FieldTypeRequiredDescription
channel_idintYesChannel ID
estimated_out_strstringYesEstimated received amount for this channel
name / symbol / channel_icon / estimated_out_usd / channel_fee-NoOptional fields copied from the quote result

2.3.2 Optional Fields

FieldTypeDescription
client_order_idstringIntegrator-side business order number. Unique per integrator and used for idempotency
client_idstringIntegrator-defined identifier for logs and risk controls
partner_app_idintPartner app ID, if any
user_idstringIntegrator-side end-user ID for audit and risk controls
invite_codestringInvite code, if any
promo_codestringPlatform promo code, same as the quote request
is_use_walletbooleanWhether to use the integrator internal wallet flow. Defaults to false
fee_ratenumberOnly pass this if permitted by contract. Usually omit it and settle by the API key rate
min_quote_amountstringReserved minimum quote amount check

2.4 Response

Success 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"
}
}

Field Reference

FieldTypeDescription
order_idintPlatform order ID
order_nostringPlatform order number. Store it on the integrator side
deposit_addressstringDeposit address. Transfer the amount_in source token amount to this address to trigger execution
expire_atstringISO 8601 UTC. The deposit must be completed before this time
fee_platformstringPlatform fee snapshot in the source token's smallest unit
fee_partnerstringPartner fee. Always "0" for integrator orders
fee_integratorstringCommission amount for this integrator order
fee_platform_originalstringTotal channel fee before deducting integrator commission
platform_fee_rate / partner_fee_rate / integrator_fee_ratestringFee rate snapshots

Idempotency: if client_order_id already exists and matches an existing order, the response may contain only order_id, order_no, deposit_address, and expire_at. Call the order detail endpoint if full fee fields are needed.

2.5 Error Responses

Integrator order creation reuses the validation logic of POST /api/v1/orders. Common errors:

HTTPcodemsgDescription
400400invalid request bodyJSON parse failed
400400src_token_id/dst_token_id requiredRequired field missing
400400token not found / chain not foundInternal ID does not exist
400400dst_address invalidDestination address format does not match the destination chain
400400chosen_channel_id not in channelschosen_channel_id is not present in channels[]
400400slippage out of rangeSlippage is not in (0, 1)
400400min_acceptable_out_amount too lowBelow platform minimum output requirements
400400quote amount mismatchServer-side quote check exceeds tolerance. Refresh the quote and retry
400400amount_in below minimumBelow minimum order amount
400400duplicate client_order_idThe same client_order_id is already used by a different order
400400split orders are not supported for integrator ordersIntegrator orders only support one channel

2.5.1 Single-Channel Orders

Integrator orders only support one channel:

  • Do not pass split_count, and do not set is_risk to true. Passing split_count ≥ 2 or is_risk=true returns split orders are not supported for integrator orders.
  • The order executes through the single channel selected by chosen_channel_id.
  • fee_platform_original is the total channel fee before commission deduction, fee_platform is the platform's actual retained amount after deducting commission, and fee_integrator is the commission amount.

2.6 Example

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. Integrator Refund: POST /api/v1/integrator/orders/:orderNo/request_refund

3.1 Overview

When an integrator order enters a refundable state, such as deposit expired without execution, cross-chain failure, or slippage failure, the integrator can call this endpoint server-side to specify a refund address and request a refund for an order that belongs to the integrator.

This endpoint follows the public POST /api/v1/orders/:orderNo/request_refund flow with two differences:

  1. It requires integrator API key authentication and only allows refunds for orders that belong to that integrator.
  2. Refunds return principal only when the order did not succeed. Commission is settled only after a successful swap.

3.2 Authentication

This refund endpoint supports only the X-Integrator-Api-Key header. It does not support the Authorization: ApiKey form:

X-Integrator-Api-Key: aaaabbbbccccddddeeeeffff00001111

Unlike order creation, the refund endpoint does not fall back. Missing or invalid API keys return 401; disabled integrators return 403; orders that do not belong to the integrator return 403.

3.3 Request

POST /api/v1/integrator/orders/{orderNo}/request_refund HTTP/1.1
Content-Type: application/json
X-Integrator-Api-Key: <api_key>
ParameterLocationTypeRequiredDescription
orderNopathstringYesorder_no returned by order creation
refund_addressbodystringYesRefund receiving address. It must match the source chain format

Request body:

{
"refund_address": "0xSourceChainRefundAddress..."
}

3.4 Refundable States

The server chooses the refund handling flow by current order state:

Order stateHandlingPreconditions
Depositing / CancelledNoDeposit (deposit expired)Enter manual review (ManualReview)Must be after expire_at and before auto_cancel_at
RefundAddressPending / ChainFailed (On-chain Failed) / SlippageFailed (Swap Failed)Automatically refund principal after failure, minus gasExecution details must exist; otherwise manual confirmation is required

Final states such as Completed, Refunded, settled CancelledNoDeposit, and FailedNoRefund cannot be refunded again.

3.5 Response

Success 200 OK:

{
"code": 0,
"msg": "ok",
"data": "ok"
}

Idempotency: if the order already has a refund record and the refund_address is the same, repeated calls return success. If the new refund_address differs from the existing record, the endpoint returns refund_address_cannot_be_changed.

3.6 Commission After Refund

Integrator orders are single-channel orders. Commission is written to integrator_commission_ledger only after the order is settled and the swap succeeds. Therefore:

  • Swap succeeds: the order is final, not refundable, and the integrator receives fee_integrator.
  • Swap fails or deposit expires: no commission is written during settlement; the principal is refunded; the integrator receives no commission.
  • The refund flow does not roll back or modify already settled commissions, including commissions in WithdrawRequested or Withdrawn states.

3.7 Error Responses

HTTPmsgDescription
401unauthorizedMissing or invalid API key
403integrator disabledIntegrator is disabled
403order does not belong to integratorThe order does not belong to this integrator
400order no requiredMissing orderNo path parameter
400refund_address requiredMissing refund address
400order not foundOrder does not exist
400order_already_finalizedOrder is final and cannot be refunded
400order_not_in_refundable_stateCurrent state does not support refunds
400order_not_expired_yetDeposit has not expired yet
400order_auto_cancel_window_passedAuto-cancel window has passed
400refund_requires_manual_confirmationMissing execution details; manual confirmation required
400no_amount_to_refundNo refundable amount
400refund_address_cannot_be_changedExisting refund address cannot be changed
400order_state_changed_retryOrder state changed concurrently. Retry the request

3.8 Example

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. End-to-End Flow

┌──────────────────┐
│ Integrator Server │
└────────┬─────────┘
│ ① GET /api/v2/quote/stream (SSE)
│ src_token_id / dst_token_id / amount_in / slippage

┌──────────────────┐
│ Any2Any API │ ──► event: channel × N
└────────┬─────────┘ event: done

│ ② Select the best quote (largest estimated_out_str)
│ Calculate 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, ... }
└────────┬─────────┘

│ ④ Show deposit_address to the user or transfer from the integrator wallet

│ ⑤ User deposits before expire_at

┌────────────┐
│ Settlement │ ──► Cross-chain delivery to dst_address
└────────────┘

│ ⑥ After the order reaches a final state, commission enters the integrator portal balance

Integrator Portal → Withdrawal

5. FAQ

Q1: estimated_out_str already includes channel fees. Should I deduct fees again when creating the order?

No. estimated_out_str is the display amount after channel fees, platform fees, and promo discounts. Calculate min_acceptable_out_amount directly from it with (1 - slippage).

Q2: Must channels[] include all quote results?

It is recommended to pass every channel event object from the quote request. At minimum, include the selected chosen_channel_id. The server uses this list for channel and slippage validation.

Q3: What should I do if an API key leaks?

Delete the key in the integrator portal immediately and create a new one. Deleted keys become invalid immediately. Existing orders are not affected because they already store fee snapshots.

Q4: Do I need to quote again every time?

Yes. estimated_out_str is based on real-time upstream quotes. Order creation performs a server-side quote check, and stale quotes can return quote amount mismatch. Keep the time between quote and order creation under 10 seconds when possible.

Q5: When does an order expire?

Use the expire_at field. The deposit must be completed before that time. Otherwise the order is canceled and no commission is generated.

Q6: How is commission credited?

After the order reaches the settled final state, the fee_integrator amount enters the integrator's available balance in the integrator portal based on the source token price snapshot at order creation time.


  • Integrator portal, API key management, commission, and withdrawal guide: Integrator API Integration Guide
  • Postman collection: the Quotes, Orders, and Integrator Portal (Public) folders in aggregator-api.postman_collection.json