Affiliate Service API Integration Guide
NOTE: API documentation is currently under development
1) Scope
AffiliateService - service for affiliate integration.
This API is dedicated to affiliate partners and designed for production integrations.
Key points:
- API is public/open (no auth headers required).
integratorIdis generated by the service. It may look like an EVM wallet address, but is not the same value as the original wallet used during registration.integratorIdis the primary identifier for all subsequent calls after registration.- When fee balance reaches 100 USD, the system automatically creates a withdrawal to the EVM wallet specified during registration.
2) Core terms
evmWallet: external EVM wallet provided by integrator during registration.integratorId: service-generated affiliate identifier (external-facing ID for API calls).l1txid: swap transaction identifier on L1 side.
3) Data format and transport conventions
- Base URL (example):
https://api-affiliate.pactswap.io/api/v1 - Content type:
application/json - Successful responses use
2xxstatus codes. - Error responses use
4xx/5xxstatus codes and unified error envelope. - Address/id fields are case-insensitive in requests, but response values should be treated as canonical.
- Monetary values are returned as string decimals to avoid precision loss.
- For swap endpoints, numeric token amounts (for example,
amountFrom) are in the smallest token units (not human-readable units): satoshis for BTC, wei for ETH/BNB/POL, etc. Example:amountFrom=100for BTC means100 satoshis, not100 BTC.
4) Versioning policy
- API version is part of URL:
/api/v1. - Backward-compatible additions may include new optional fields.
- Breaking changes are released under a new version path (for example,
/api/v2).
5) Error response format
Unified error envelope (recommended):
{
"error": {
"code": "INTEGRATOR_NOT_FOUND",
"message": "Integrator does not exist",
"details": {}
}
}Common error codes:
INVALID_ARGUMENTINTEGRATOR_ALREADY_EXISTSINTEGRATOR_NOT_FOUNDSWAP_NOT_FOUNDRATE_LIMITEDINTERNAL_ERROR
6) REST endpoints
6.1 Register integrator
- Method/Path:
POST /integrators - Purpose: register new integrator by
evmWalletand generateintegratorId.
Request body:
{
"evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
}Example to register a new integrator by EVM wallet:
BASE_URL="https://api-affiliate.pactswap.io/api/v1"
curl -sS -X POST "$BASE_URL/integrators" \
-H "Content-Type: application/json" \
-d '{
"evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
}'Response 201:
{
"integratorId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
}Possible errors:
400 INVALID_ARGUMENT(invalid wallet format)409 INTEGRATOR_ALREADY_EXISTS(wallet already registered)
6.2 Get integrator by wallet
- Method/Path:
GET /integrators/by-wallet/{evmWallet} - Purpose: resolve existing
integratorIdby source EVM wallet.
Example to resolve integratorId by partner wallet:
BASE_URL="https://api-affiliate.pactswap.io/api/v1"
EVM_WALLET="0x1234567890abcdef1234567890abcdef12345678"
curl -sS "$BASE_URL/integrators/by-wallet/$EVM_WALLET"Response 200:
{
"integratorId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
}Possible errors:
404 INTEGRATOR_NOT_FOUND
6.3 Get fee balance
- Method/Path:
GET /integrators/{integratorId}/fee-balance - Purpose: get current fee balance in USD.
Example to get current affiliate fee balance:
BASE_URL="https://api-affiliate.pactswap.io/api/v1"
INTEGRATOR_ID="0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
curl -sS "$BASE_URL/integrators/$INTEGRATOR_ID/fee-balance"Response 200:
{
"balanceUsd": "1234.560000"
}Possible errors:
404 INTEGRATOR_NOT_FOUND
6.4 Get swap info
- Method/Path:
GET /swaps/{l1txid}
Example to get swap info by L1 transaction id:
BASE_URL="https://api-affiliate.pactswap.io/api/v1"
L1TXID="0xaaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000"
curl -sS "$BASE_URL/swaps/$L1TXID"Response 200 (normal case):
{
"kind": "normal",
"feesAcquiredUsdt": "245.100000",
"feeBips": "30"
}Response 200 (reversed pact case):
{
"kind": "reversed_pact"
}Possible errors:
404 SWAP_NOT_FOUND
6.5 Get swap quotes by send amount (quotes)
- Method/Path:
GET /pactswap_cm/getSwapQuotesByAmountFrom - Purpose: retrieve multiple swap quotes for a given
amountFrom(compare rates from different route).
Query params:
amountFrom(number, required) โ amount in smallest unitsfromType(string, required) โ source chain/typetoType(string, required) โ destination chain/typeaffiliates(string, optional) โ JSON array of affiliate fee recipients:[{ "affiliateId": string, "bips": number }, ...]. Use yourintegratorIdfrom registration asaffiliateId.slippageBips(number, optional) โ slippage tolerance: maximum allowed deviation from the expected receive amount, in basis points (1 bp = 0.01%, so e.g.100โ 1%). If sent, the API enforces a minimum of50(0.5%). Quotes includeminAmountToderived from this (see response example).
Example how to request a Quote to swap 1 ETH to BNB:
BASE_URL="https://api.pactswap.io"
curl -sS "$BASE_URL/pactswap_cm/getSwapQuotesByAmountFrom" \
--get \
--data-urlencode "fromType=eth" \
--data-urlencode "toType=bnb" \
--data-urlencode "amountFrom=1000000000000000000" \
--data-urlencode "slippageBips=100" \
--data-urlencode 'affiliates=[{"affiliateId":"0x9a8b7c6d5e4f3210abcdeffedcba012345678901","bips":25},{"affiliateId":"0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12","bips":40}]'Response 200 (example):
[
{
"amountTo": 50000000,
"minAmountTo": 49500000,
"route": "0xC0ffee254729296a45a3885639AC7E10F9d54979",
"estimatedSwapTimeSeconds": 120
}
]Notes:
amountFromis always provided in the smallest units of the source token (for example, satoshis for BTC, wei for ETH/BNB/POL).- Example:
amountFrom=100for BTC means100 satoshis, not100 BTC. minAmountToโ lower bound on the destination amount implied by the quotedamountToand yourslippageBips(same smallest units asamountTo).estimatedSwapTimeSecondsโ next to the liquidity provider id in each quote, approximate expected swap duration in seconds (not a hard guarantee).
6.6 Compose swap transaction by send amount (compose)
- Method/Path:
GET /pactswap_cm/composeSwapTxByAmountFrom - Purpose: compose an unsigned L1 transaction based on
amountFrom. The integrator/user must sign this transaction with their wallet, then broadcast it to the source chain.
Query params (most common):
fromType(string, required)toType(string, required)fromWalletAddress(string, required)toWalletAddress(string, required)amountFrom(number, required)route(string, required)affiliates(string, optional) โ JSON array of affiliate fee recipients:[{ "affiliateId": string, "bips": number }, ...]. Use yourintegratorIdfrom registration asaffiliateId.slippageBips(number, optional) โ same meaning as for quotes: slippage in basis points (โฅ50when provided); see ยง 6.5.
Query params (UTXO-based chains only):
fromPublicKey(string, required for BTC/LTC/DOGE)utxos(string, required for BTC/LTC/DOGE) โ JSON string with UTXOs
Example how to request an unsigned EVM transaction to swap from 1 ETH to BTC:
BASE_URL="https://api.pactswap.io"
curl -sS "$BASE_URL/pactswap_cm/composeSwapTxByAmountFrom" \
--get \
--data-urlencode "fromType=eth" \
--data-urlencode "toType=btc" \
--data-urlencode "fromWalletAddress=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
--data-urlencode "toWalletAddress=bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh" \
--data-urlencode "amountFrom=1000000000000000000" \
--data-urlencode "route=0xC0ffee254729296a45a3885639AC7E10F9d54979" \
--data-urlencode "slippageBips=100" \
--data-urlencode 'affiliates=[{"affiliateId":"0x9a8b7c6d5e4f3210abcdeffedcba012345678901","bips":25},{"affiliateId":"0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12","bips":40}]'Response 200 (example):
{
"rawTx": "0x123..."
}Notes:
rawTxis an unsigned transaction payload returned by the API for client-side signing.- Signing and broadcasting are performed by the integrator/user wallet flow (outside this API endpoint).
7) TypeScript interfaces for integrator clients
export type AddressLike = string;
export type DecimalString = string;
export interface RegisterIntegratorRequest {
evmWallet: AddressLike;
}
export interface RegisterIntegratorResponse {
integratorId: AddressLike;
}
export interface GetIntegratorResponse {
integratorId: AddressLike;
}
export interface GetFeeBalanceResponse {
balanceUsd: DecimalString;
}
export interface SwapInfoNormal {
kind: "normal";
feesAcquiredUsdt: DecimalString;
feeBips: number;
}
export interface SwapInfoReversedPact {
kind: "reversed_pact";
}
export type GetSwapInfoResponse = SwapInfoNormal | SwapInfoReversedPact;
export interface ApiErrorResponse {
error: {
code: string;
message: string;
details?: Record<string, unknown>;
};
}8) Integration flow (recommended)
- Call
POST /integratorswith partnerevmWallet.
BASE_URL="https://api-affiliate.pactswap.io/api/v1"
curl -sS -X POST "$BASE_URL/integrators" \
-H "Content-Type: application/json" \
-d '{
"evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
}'- Store returned
integratorIdas primary identifier for all next API calls.
# Example of storing returned integratorId for next requests
INTEGRATOR_ID="0x9a8b7c6d5e4f3210abcdeffedcba012345678901"- For accounting UI, periodically call:
GET /integrators/{integratorId}/fee-balance
curl -sS "$BASE_URL/integrators/$INTEGRATOR_ID/fee-balance"- For swap audit/reconciliation:
- call
GET /swaps/{l1txid}
- call
L1TXID="0xaaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000"
curl -sS "$BASE_URL/swaps/$L1TXID"- Get swap quotes (pick ROUTE):
- call
GET /pactswap_cm/getSwapQuotesByAmountFrom
- call
# `amountFrom` is in smallest units (wei/satoshi/etc), not whole coins.
curl -sS "$BASE_URL/pactswap_cm/getSwapQuotesByAmountFrom" \
--get \
--data-urlencode "fromType=eth" \
--data-urlencode "toType=bnb" \
--data-urlencode "amountFrom=1000000000000000000"- Compose swap tx by selected ROUTE:
- call
GET /pactswap_cm/composeSwapTxByAmountFrom(passroute) - endpoint returns an unsigned tx payload; sign it in wallet flow and then broadcast
- call
ROUTE="020a89e0698443aaf4ecd613317d2fdf90ef73b99244182fc7bb3c6b85f375b876"
# `amountFrom` is in smallest units (wei/satoshi/etc), not whole coins.
curl -sS "$BASE_URL/pactswap_cm/composeSwapTxByAmountFrom" \
--get \
--data-urlencode "fromType=eth" \
--data-urlencode "toType=bnb" \
--data-urlencode "fromWalletAddress=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
--data-urlencode "toWalletAddress=0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" \
--data-urlencode "amountFrom=1000000000000000000" \
--data-urlencode "route=$ROUTE"Operational recommendations:
- Treat
POST /integratorsas idempotent byevmWalleton client side (retry-safe logic). - Use exponential backoff for temporary
5xxfailures.
9) Operational guidance for integrators
9.1 Idempotency and retries
- For
POST /integrators, client should implement retry-safe behavior keyed byevmWallet. - Recommended retry strategy for transient failures (
5xx, network timeout): exponential backoff with jitter.
9.2 Rate limits
- Default partner limit recommendation:
60 requests/minuteper IP. - If response is
429 RATE_LIMITED, retry after backoff. - Clients should cache stable lookup data (
integratorIdresolution) to reduce request volume.
9.3 Timeouts and polling
- Recommended client timeout per request:
10s. - For operational dashboards, polling interval for balance/history:
15-60sdepending on UI needs.
9.4 Production readiness checklist (partner side)
- Store
integratorIdas primary partner key. - Handle both swap variants:
normalandreversed_pact. - Implement structured handling for documented error codes.
- Add observability for request id, latency, non-2xx responses, and retry attempts.
Was this documentation helpful? Any suggestions?