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).
affiliateIdis 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.affiliateIdis 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 affiliate during registration.affiliateId: 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": "AFFILIATE_NOT_FOUND",
"message": "Affiliate does not exist",
"details": {}
}
}Common error codes:
INVALID_ARGUMENTAFFILIATE_ALREADY_EXISTSAFFILIATE_NOT_FOUNDSWAP_NOT_FOUNDRATE_LIMITEDINTERNAL_ERROR
6) REST endpoints
6.1 Register affiliate
- Method/Path:
POST /affiliates - Purpose: register new affiliate by
evmWalletand generateaffiliateId.
Request body:
{
"evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
}Example to register a new affiliate by EVM wallet:
BASE_URL="https://api-affiliate.pactswap.io/api/v1"
curl -sS -X POST "$BASE_URL/affiliates" \
-H "Content-Type: application/json" \
-d '{
"evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
}'Response 201:
{
"affiliateId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
}Possible errors:
400 INVALID_ARGUMENT(invalid wallet format)409 AFFILIATE_ALREADY_EXISTS(wallet already registered)
6.2 Get affiliate by wallet
- Method/Path:
GET /affiliates/by-wallet/{evmWallet} - Purpose: resolve existing
affiliateIdby source EVM wallet.
Example to resolve affiliateId by partner wallet:
BASE_URL="https://api-affiliate.pactswap.io/api/v1"
EVM_WALLET="0x1234567890abcdef1234567890abcdef12345678"
curl -sS "$BASE_URL/affiliates/by-wallet/$EVM_WALLET"Response 200:
{
"affiliateId": "0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
}Possible errors:
404 AFFILIATE_NOT_FOUND
6.3 Get fee balance
- Method/Path:
GET /affiliates/{affiliateId}/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"
AFFILIATE_ID="0x9a8b7c6d5e4f3210abcdeffedcba012345678901"
curl -sS "$BASE_URL/affiliates/$AFFILIATE_ID/fee-balance"Response 200:
{
"balanceUsd": "1234.560000"
}Possible errors:
404 AFFILIATE_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/getSwapQuotesByAmountFromonhttps://api.pactswap.io - Purpose: retrieve multiple swap quotes for a given
amountFrom(compare rates from different routes).
Query params:
from(string, required) — L2 C1 contract id for the source token.to(string, required) — L2 C2 contract id for the destination token.amountFrom(number, required) — amount in smallest units.fromType(string, optional) — source chain/type (for example,eth,btc,bnb).toType(string, optional) — destination chain/type.affiliates(array, optional) — affiliate fee recipients. In query strings use indexed fields, for exampleaffiliates[0][affiliateId]andaffiliates[0][bips]. Use youraffiliateIdfrom registration.slippageBips(number, optional) — slippage tolerance in basis points (1 bp = 0.01%, so100= 1%). Minimum50when provided. Quotes includeminAmountToderived from this.
Contract ids and presets are deployment-specific. Replace the placeholders below with values from your environment before calling production.
Example: request quotes to swap 1 ETH to BNB:
PACTSWAP_BASE_URL="https://api.pactswap.io"
# ETH (source): C1 base + destination C2 maker for BNB — replace with your deployment ids
FROM_C1="0x3f4031b087cb09891360a0b7aada49359eaabd09713979c190effc5b7ff928f6"
TO_C2="0x20ada6149b7480fc734c0ac2482d139e1febe1c5c01f9d7708b32ea986f691f7"
curl -sS -g "$PACTSWAP_BASE_URL/pactswap_cm/getSwapQuotesByAmountFrom" \
--get \
--data-urlencode "from=$FROM_C1" \
--data-urlencode "to=$TO_C2" \
--data-urlencode "fromType=eth" \
--data-urlencode "toType=bnb" \
--data-urlencode "amountFrom=1000000000000000000" \
--data-urlencode "slippageBips=100" \
--data-urlencode "affiliates[0][affiliateId]=0x9a8b7c6d5e4f3210abcdeffedcba012345678901" \
--data-urlencode "affiliates[0][bips]=25" \
--data-urlencode "affiliates[1][affiliateId]=0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12" \
--data-urlencode "affiliates[1][bips]=40"-g tells curl not to treat [ / ] in affiliates[0][...] as URL glob
characters.
Response 200 (example):
[
{
"amountTo": 50000000,
"minAmountTo": 49500000,
"route": "0xC0ffee254729296a45a3885639AC7E10F9d54979",
"lp": "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. route/lp— liquidity provider identifier from the quote (hex without0xprefix). Pass it to compose asrouteorlp.minAmountTo— lower bound on the destination amount implied by the quotedamountToand yourslippageBips(same smallest units asamountTo).estimatedSwapTimeSeconds— approximate expected swap duration in seconds (not a hard guarantee).
6.6 Compose swap transaction by send amount (compose)
- Method/Path:
GET /pactswap_cm/composeSwapTxByAmountFromonhttps://api.pactswap.io - Purpose: compose an unsigned L1 transaction based on
amountFrom. The affiliate/user must sign this transaction with their wallet, then broadcast it to the source chain.
Query params (required for EVM swaps):
fromType,toType(string)fromWalletAddress,toWalletAddress(string) — valid addresses for the respective chainsfromC1ContractId,fromC2ContractId,toC1ContractId,toC2ContractId(string) — L2 contract ids for source/destination tokensamountFrom(number)preset(number) — swap routing preset for the destination chain and address format (deployment-specific)routeorlp(string) — liquidity provider id from the quote response (hex without0x)
Query params (optional):
affiliates(array) — same indexed query format as in § 6.5slippageBips(number, optional) — same meaning as for quotes (≥50when provided); see § 6.5
Query params (UTXO-based source chains only):
fromPublicKey(string, required for BTC/LTC/DOGE)utxos(string, required for BTC/LTC/DOGE) — JSON string with UTXOs
Example: compose an unsigned EVM transaction to swap 1 ETH to BNB (use route
from § 6.5):
PACTSWAP_BASE_URL="https://api.pactswap.io"
FROM_C1="0x3f4031b087cb09891360a0b7aada49359eaabd09713979c190effc5b7ff928f6"
FROM_C2="0xe2dcfe88387f5dbc8ec583727cf4280462ceb356be3199d320ed818089f8a693"
TO_C1="0x6f20d1802288c90d2a044d3f7afae73fda261b27d85c0e01fdaaf3199b2c0e85"
TO_C2="0x20ada6149b7480fc734c0ac2482d139e1febe1c5c01f9d7708b32ea986f691f7"
ROUTE="0xC0ffee254729296a45a3885639AC7E10F9d54979" # from quotes[].route or quotes[].lp
PRESET="9007199254740257" # deployment-specific; depends on toType + toWalletAddress format
curl -sS -g "$PACTSWAP_BASE_URL/pactswap_cm/composeSwapTxByAmountFrom" \
--get \
--data-urlencode "fromType=eth" \
--data-urlencode "toType=bnb" \
--data-urlencode "fromWalletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \
--data-urlencode "toWalletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \
--data-urlencode "amountFrom=1000000000000000000" \
--data-urlencode "fromC1ContractId=$FROM_C1" \
--data-urlencode "fromC2ContractId=$FROM_C2" \
--data-urlencode "toC1ContractId=$TO_C1" \
--data-urlencode "toC2ContractId=$TO_C2" \
--data-urlencode "preset=$PRESET" \
--data-urlencode "route=$ROUTE" \
--data-urlencode "slippageBips=100" \
--data-urlencode "affiliates[0][affiliateId]=0x9a8b7c6d5e4f3210abcdeffedcba012345678901" \
--data-urlencode "affiliates[0][bips]=25" \
--data-urlencode "affiliates[1][affiliateId]=0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12" \
--data-urlencode "affiliates[1][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 affiliate/user wallet flow (outside this API endpoint).
7) TypeScript interfaces for affiliate clients
export type AddressLike = string;
export type DecimalString = string;
export interface RegisterAffiliateRequest {
evmWallet: AddressLike;
}
export interface RegisterAffiliateResponse {
affiliateId: AddressLike;
}
export interface GetAffiliateResponse {
affiliateId: 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 /affiliateswith partnerevmWallet.
BASE_URL="https://api-affiliate.pactswap.io/api/v1"
curl -sS -X POST "$BASE_URL/affiliates" \
-H "Content-Type: application/json" \
-d '{
"evmWallet": "0x1234567890abcdef1234567890abcdef12345678"
}'- Store returned
affiliateIdas primary identifier for all next API calls.
# Example of storing returned affiliateId for next requests
AFFILIATE_ID="0x9a8b7c6d5e4f3210abcdeffedcba012345678901"- For accounting UI, periodically call:
GET /affiliates/{affiliateId}/fee-balance
curl -sS "$BASE_URL/affiliates/$AFFILIATE_ID/fee-balance"- For swap audit/reconciliation:
- call
GET /swaps/{l1txid}
- call
L1TXID="0xaaaabbbbccccddddeeeeffff1111222233334444555566667777888899990000"
curl -sS "$BASE_URL/swaps/$L1TXID"- Get swap quotes on Pact Swap API (pick
route/lpfrom the response):GET https://api.pactswap.io/pactswap_cm/getSwapQuotesByAmountFrom- pass your
affiliateIdfrom step 2 inaffiliates; setslippageBipsforminAmountToon quotes
PACTSWAP_BASE_URL="https://api.pactswap.io"
FROM_C1="0x3f4031b087cb09891360a0b7aada49359eaabd09713979c190effc5b7ff928f6"
TO_C2="0x20ada6149b7480fc734c0ac2482d139e1febe1c5c01f9d7708b32ea986f691f7"
# `amountFrom` is in smallest units (wei/satoshi/etc), not whole coins.
curl -sS -g "$PACTSWAP_BASE_URL/pactswap_cm/getSwapQuotesByAmountFrom" \
--get \
--data-urlencode "from=$FROM_C1" \
--data-urlencode "to=$TO_C2" \
--data-urlencode "fromType=eth" \
--data-urlencode "toType=bnb" \
--data-urlencode "amountFrom=1000000000000000000" \
--data-urlencode "slippageBips=100" \
--data-urlencode "affiliates[0][affiliateId]=$AFFILIATE_ID" \
--data-urlencode "affiliates[0][bips]=25" \
--data-urlencode "affiliates[1][affiliateId]=0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12" \
--data-urlencode "affiliates[1][bips]=40"- Compose swap tx by selected route on Pact Swap API:
GET https://api.pactswap.io/pactswap_cm/composeSwapTxByAmountFrom- pass
route(orlp) from step 5, all four contract ids,preset,affiliates, andslippageBips(same values as step 5) - endpoint returns an unsigned tx payload; sign it in wallet flow and then broadcast
PACTSWAP_BASE_URL="https://api.pactswap.io"
FROM_C1="0x3f4031b087cb09891360a0b7aada49359eaabd09713979c190effc5b7ff928f6"
FROM_C2="0xe2dcfe88387f5dbc8ec583727cf4280462ceb356be3199d320ed818089f8a693"
TO_C1="0x6f20d1802288c90d2a044d3f7afae73fda261b27d85c0e01fdaaf3199b2c0e85"
TO_C2="0x20ada6149b7480fc734c0ac2482d139e1febe1c5c01f9d7708b32ea986f691f7"
ROUTE="0xC0ffee254729296a45a3885639AC7E10F9d54979"
PRESET="9007199254740257"
curl -sS -g "$PACTSWAP_BASE_URL/pactswap_cm/composeSwapTxByAmountFrom" \
--get \
--data-urlencode "fromType=eth" \
--data-urlencode "toType=bnb" \
--data-urlencode "fromWalletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \
--data-urlencode "toWalletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" \
--data-urlencode "amountFrom=1000000000000000000" \
--data-urlencode "fromC1ContractId=$FROM_C1" \
--data-urlencode "fromC2ContractId=$FROM_C2" \
--data-urlencode "toC1ContractId=$TO_C1" \
--data-urlencode "toC2ContractId=$TO_C2" \
--data-urlencode "preset=$PRESET" \
--data-urlencode "route=$ROUTE" \
--data-urlencode "slippageBips=100" \
--data-urlencode "affiliates[0][affiliateId]=$AFFILIATE_ID" \
--data-urlencode "affiliates[0][bips]=25" \
--data-urlencode "affiliates[1][affiliateId]=0x5D3e2B7C9a1F4c8E0b6A7D2C1E9f3A4b8C6d0F12" \
--data-urlencode "affiliates[1][bips]=40"Operational recommendations:
- Treat
POST /affiliatesas idempotent byevmWalleton client side (retry-safe logic). - Use exponential backoff for temporary
5xxfailures.
9) Operational guidance for affiliates
9.1 Idempotency and retries
- For
POST /affiliates, 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 (
affiliateIdresolution) 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
affiliateIdas 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?