Documentation
Partner Exchange API
REST JSON API for embedding crypto exchange into your site, bot, or service: asset catalog → quote → create exchange → track status.
Base URL
https://snapex.pro/api/v1/partner
1. Overview
The Partner Exchange API lets you run crypto exchanges from your application with plain HTTP requests. You send JSON — the API returns the rate, a deposit address, and the exchange status. This section explains the overall flow; each step is covered in detail below.
How an exchange works
Every exchange follows the same four-step flow:
- Catalog (GET /assets). Find out which assets are available and within what limits. The catalog provides the short asset codes — usdt, btc, eth, xmr, etc.
- Quote (POST /quotes). You ask: "how much BTC will the client get for 20 USDT?" The API answers with the rate and exact amounts. A quote is non-binding and stays valid for 2 minutes.
- Create exchange (POST /exchanges). You confirm the quote — the API opens an order and returns a deposit address the client must send funds to.
- Status (GET /exchanges/{quote_id}). You poll the API to learn whether the deposit arrived and the payout was sent.
What you need to get started
- A partner cabinet account.
- An approved API access request (submitted in the cabinet, reviewed by an administrator).
- An API key starting with spx_… — issued in the cabinet after approval. The key is shown only once when issued or reissued: store it somewhere safe right away.
Methods
| Method | Path | Success HTTP code | Purpose |
|---|---|---|---|
| GET | /assets | 200 | Asset catalog and limits |
| POST | /quotes | 201 | Quote (rate and amounts) |
| POST | /exchanges | 201 | Create exchange, obtain a deposit address |
| GET | /exchanges/{quote_id} | 200 | Current quote or exchange status |
Data format
- All requests and responses are JSON, UTF-8.
- All monetary amounts are strings: "20", "0.000312". The decimal separator is a dot. Strings are intentional: floating-point numbers lose precision on crypto amounts.
- Dates use ISO-8601 with a timezone, e.g. 2026-07-27T13:02:00+00:00.
Headers for every request
X-API-Key: spx_your_key
Content-Type: application/json (POST requests only)
Accept: application/json
Key rules
- A quote is valid for 2 minutes (the expires_at field) — the exchange must be created before it expires.
- At most 1 request per IP address every 7 seconds across all methods; exceeding this returns HTTP 429 (see "Errors and limits").
- Retrying exchange creation with the same parameters is safe: no duplicate is created — the same exchange is returned.
2. Authentication
Every API request must include the X-API-Key header with your key. No tokens, signatures, or sessions — just this one header.
GET https://snapex.pro/api/v1/partner/assets
X-API-Key: spx_your_key
Accept: application/json
Where to get the key
Partner cabinet → "API" tab → "Issue key". The key starts with spx_ and is shown once. Reissuing immediately invalidates the old key.
Authentication errors
| HTTP | error | Cause | What to do |
|---|---|---|---|
| 401 | unauthorized | X-API-Key header missing or key invalid | Check that the header is sent and the key was copied in full |
| 403 | api_access_pending | Access request still under review | Wait for admin approval |
| 403 | api_not_approved | API access not approved | Contact support |
3. Asset catalog — GET /assets
The directory of available assets and send-amount limits. Integration starts here: it provides the asset codes for quotes and the allowed amount ranges. Success response — HTTP 200.
The API accepts only the short codes from the code field of this catalog (usdt, btc, eth, xmr, etc.). Raw internal blockchain identifiers are not accepted. The catalog changes rarely — you can cache it on your side for 5–10 minutes.
Request example
GET https://snapex.pro/api/v1/partner/assets
X-API-Key: spx_your_key
Accept: application/json
Response example
{
"assets": [
{
"code": "usdt",
"network": "TRON",
"symbol": "USDT",
"name": "USDT TRON",
"min_amount": "10",
"max_amount": "5000",
"min_usd": "10",
"max_usd": "5000"
},
{
"code": "btc",
"network": "Bitcoin",
"symbol": "BTC",
"name": "BTC",
"min_amount": "0.000085",
"max_amount": "0.0425",
"min_usd": "10",
"max_usd": "5000"
}
]
}
Response fields
| Field | Description |
|---|---|
| assets[].code | Short asset code. This is exactly what you pass as from_asset_id and to_asset_id |
| assets[].network | Blockchain network (TRON, Bitcoin, Ethereum, etc.) |
| assets[].symbol | Asset ticker (USDT, BTC…) |
| assets[].name | Human-readable name |
| assets[].min_amount / max_amount | Minimum and maximum send amount in asset units (converted from the USD limits at the current rate) |
| assets[].min_usd / max_usd | The same limits in US dollars |
USD limits by route type
Limits depend on whether Monero (XMR) is part of the exchange. The check applies to the USD value of the amount the client sends.
| Route | Min USD | Max USD |
|---|---|---|
| Regular exchange (TOKEN ↔ TOKEN) | $10 | $7000 |
| Sending XMR (XMR → TOKEN) | $10 | $1000 |
| Receiving XMR (TOKEN → XMR) | $10 | $1000 |
Current catalog
The table below shows the same data GET /assets returns, for quick reference.
| API code | Network | Asset | Min send | Max send | Min USD | Max USD |
|---|---|---|---|---|---|---|
aave |
APTOS | AAVE | 0.05951319 | 41.65922752 | $10 | $7000 |
btc_aptos |
APTOS | BTC | 0.00012061 | 0.08442686 | $10 | $7000 |
doge |
APTOS | DOGE | 116.6997316 | 81689.81211343 | $10 | $7000 |
eth |
APTOS | ETH | 0.00399422 | 2.79595146 | $10 | $7000 |
link |
APTOS | LINK | 0.77041603 | 539.29121726 | $10 | $7000 |
sol |
APTOS | SOL | 0.09101666 | 63.71165923 | $10 | $7000 |
uni |
APTOS | UNI | 1.31926122 | 923.4828496 | $10 | $7000 |
xrp |
APTOS | XRP | 7.142858 | 5000 | $10 | $7000 |
zec |
APTOS | ZEC | 0.0081793 | 5.72550303 | $10 | $7000 |
avax |
Avalanche | AVAX | 0.96711799 | 676.98259188 | $10 | $7000 |
cfi |
Base | CFI | 15268.34109475 | 10687838.76631804 | $10 | $7000 |
btc |
Bitcoin | BTC | 0.00012061 | 0.08442686 | $10 | $7000 |
aster |
BNB Chain | ASTER | 14.09715478 | 9868.00833988 | $10 | $7000 |
aurora |
BNB Chain | AURORA | 162.22705299 | 113558.93708835 | $10 | $7000 |
bnb |
BNB Chain | BNB | 0.01337614 | 9.36329588 | $10 | $7000 |
usdc |
HYPERCORE | USDC | 10.00292086 | 7002.04459702 | $10 | $7000 |
mon |
Monad | MON | 399.36485015 | 279555.39509963 | $10 | $7000 |
xmr |
Monero | XMR | 0.01934563 | 1.93456265 | $10 | $1000 |
op |
Optimism | OP | 72.03676757 | 50425.73729632 | $10 | $7000 |
usdt0 |
Optimism | USDT0 | 10.009349 | 7006.544112 | $10 | $7000 |
pol |
Polygon | POL | 98.27526903 | 68792.68831998 | $10 | $7000 |
rhea |
Solana | RHEA | 117.61246693 | 82328.72684505 | $10 | $7000 |
strk |
Solana | STRK | 97.88662771 | 68520.63939545 | $10 | $7000 |
zec_solana |
Solana | ZEC | 0.0081793 | 5.72550303 | $10 | $7000 |
xlm |
Stellar | XLM | 51.0394178 | 35727.5924196 | $10 | $7000 |
gram |
TON | GRAM | 6.84931507 | 4794.52054795 | $10 | $7000 |
usdt |
TRON | USDT | 10.008398 | 7005.877932 | $10 | $7000 |
okb |
X Layer | OKB | 0.07932102 | 55.5247085 | $10 | $7000 |
4. Quote — POST /quotes
Calculates the rate and exact exchange amounts. Creates nothing and commits you to nothing: no deposit address is allocated, no funds are reserved. Success response — HTTP 201. A quote is valid for 2 minutes.
Two modes: side=send and side=receive
The side parameter defines which of the two amounts is fixed:
side=send — "the client sends exactly X, how much will they get?" The amount is given in the send (from) asset.
side=receive — "the client wants to receive exactly Y, how much must they send?" The amount is given in the receive (to) asset.
| Mode | amount — in which asset | amount_usd |
|---|---|---|
| side=send | In the from_asset_id asset (what the client sends) | Allowed: the send amount in USD instead of amount |
| side=receive | In the to_asset_id asset (what the client receives) | Not allowed — amount only |
Pass either amount or amount_usd — one of the two. Sending both returns a 422 error.
Request example
POST https://snapex.pro/api/v1/partner/quotes
Content-Type: application/json
X-API-Key: spx_your_key
{
"from_asset_id": "usdt",
"to_asset_id": "btc",
"side": "send",
"amount": "20",
"recipient_address": "bc1qexampleaddress"
}
Request parameters
| Field | Required | Description |
|---|---|---|
| from_asset_id | Yes | Code of the asset the client sends (from the GET /assets catalog) |
| to_asset_id | Yes | Code of the asset the client receives. Must differ from from_asset_id |
| side | Yes | send or receive — see the table above |
| amount | Yes, unless amount_usd is used | Amount as a string, e.g. "20" or "0.0005". For send — in the from asset, for receive — in the to asset |
| amount_usd | No | side=send only: the send amount in US dollars, e.g. "100". Mutually exclusive with amount |
| recipient_address | Yes | The client wallet address on the receive (to) asset network. Needed already at the quote stage — it validates the route. Alias: recipient |
Response example
{
"quote_id": "pq_abc123",
"status": "quoted",
"side": "send",
"from_symbol": "USDT",
"to_symbol": "BTC",
"from_network": "TRON",
"to_network": "Bitcoin",
"request_amount": "20",
"send_amount": "20",
"receive_amount": "0.000312",
"amount_in_usd": "20",
"amount_out_usd": "19.85",
"expires_at": "2026-07-27T13:02:00+00:00"
}
Response fields
| Field | Description |
|---|---|
| quote_id | Quote identifier (pq_…). Needed to create the exchange and check its status |
| status | quoted — the quote is active, no exchange created yet |
| side | The amount-fixing mode from your request |
| from_symbol / to_symbol | Send and receive asset tickers |
| from_network / to_network | Asset networks |
| request_amount | The amount from your request |
| send_amount | How much the client must send |
| receive_amount | How much the client will receive |
| amount_in_usd / amount_out_usd | USD estimates of the send and receive amounts |
| request_amount_usd | Present only if the request used amount_usd |
| expires_at | Quote expiry moment (ISO-8601). After it, this quote_id can no longer be used to create an exchange |
5. Create exchange — POST /exchanges
Turns a quote into a real exchange: an order is opened and a deposit address is returned for the client transfer. Call it before the quote expires (2 minutes). Success response — HTTP 201.
Request example
POST https://snapex.pro/api/v1/partner/exchanges
Content-Type: application/json
X-API-Key: spx_your_key
{
"quote_id": "pq_abc123",
"recipient_address": "bc1qexampleaddress"
}
Request parameters
| Field | Required | Description |
|---|---|---|
| quote_id | Yes | Identifier from the POST /quotes response |
| recipient_address | Yes | Client payout address. Usually the same one used in the quote. Alias: recipient |
What happens on this call
- The API finds your quote and checks it has not expired.
- The rate is re-checked against the market. If it moved against the quote by more than 2%, no exchange is created and 422 is returned (simply request a new quote).
- An order is opened and a deposit address is allocated.
- The response includes the deposit block — the address and amount the client must transfer.
Response example
{
"quote_id": "pq_abc123",
"status": "awaiting_deposit",
"side": "send",
"from_symbol": "USDT",
"to_symbol": "BTC",
"from_network": "TRON",
"to_network": "Bitcoin",
"request_amount": "20",
"send_amount": "20",
"receive_amount": "0.000312",
"amount_in_usd": "20",
"amount_out_usd": "19.85",
"expires_at": "2026-07-27T13:02:00+00:00",
"deposit": {
"asset": "USDT",
"amount": "20",
"address": "TXyz…",
"amount_usd": "20"
},
"exchange": {
"public_id": "ex_…",
"status": "awaiting_deposit",
"recipient_address": "bc1qexampleaddress"
}
}
New fields compared to the quote
| Field | Description |
|---|---|
| status | Usually awaiting_deposit — waiting for the client transfer. quoting means the deposit address is still being prepared: poll the status again shortly |
| deposit.asset | The asset the client must transfer |
| deposit.amount | The exact transfer amount |
| deposit.address | The deposit address — show it to the client |
| deposit.amount_usd | USD equivalent (when available) |
| exchange.public_id | Public exchange identifier |
| exchange.status | Order status (matches the top-level status) |
| exchange.recipient_address | Client payout address |
Retries (idempotency)
If the connection dropped and you never got a response — just repeat the request with the same quote_id and recipient_address. No duplicate is created: the API returns the already existing exchange. The recipient_address cannot be changed after creation — attempting to do so returns 422.
Possible errors
- 409 quote_expired — the quote expired (more than 2 minutes passed). Request a new one via POST /quotes.
- 422 validation_error — invalid parameters; an attempt to change recipient_address on retry; or the rate moved by more than 2% — request a new quote.
- 404 not_found — the quote_id does not exist or belongs to another partner.
- 503 upstream_unavailable — temporary liquidity provider failure. Retry later with the same quote (if still valid) or a new one.
6. Status — GET /exchanges/{quote_id}
Returns the current state of a quote or exchange. The response format is identical to the create response. Success response — HTTP 200. Poll at most once every 7 seconds.
Request example
GET https://snapex.pro/api/v1/partner/exchanges/pq_abc123
X-API-Key: spx_your_key
Accept: application/json
status values
| Value | Meaning | What to do |
|---|---|---|
| quoted | Only a quote exists, no exchange created | Create the exchange before expires_at or request a new quote |
| quoting | The exchange is being created, deposit address being prepared | Poll the status again in a few seconds |
| awaiting_deposit | Waiting for the client transfer to the deposit address | Show the client the address and amount from the deposit block |
| processing | Deposit received, exchange in progress | Keep polling |
| completed | Exchange finished, funds sent to the client | Terminal status — stop polling |
| error | Exchange failed | Terminal status. Contact support with the exchange.public_id |
| cancelled | Exchange cancelled | Terminal status |
| expired | The quote expired, no exchange was created | Terminal status. Request a new quote |
Terminal statuses: completed, error, cancelled, expired. The state no longer changes after them — further polling is unnecessary.
When status=completed, the exchange block may include partner_api_earning_usd — your earnings on the deal in USD.
Response example
{
"quote_id": "pq_abc123",
"status": "completed",
"side": "send",
"from_symbol": "USDT",
"to_symbol": "BTC",
"from_network": "TRON",
"to_network": "Bitcoin",
"request_amount": "20",
"send_amount": "20",
"receive_amount": "0.000312",
"amount_in_usd": "20",
"amount_out_usd": "19.85",
"expires_at": "2026-07-27T13:02:00+00:00",
"deposit": {
"asset": "USDT",
"amount": "20",
"address": "TXyz…",
"amount_usd": "20"
},
"exchange": {
"public_id": "ex_…",
"status": "completed",
"recipient_address": "bc1qexampleaddress",
"partner_api_earning_usd": "0.20"
}
}
7. Errors and limits
All errors share a single JSON format. The HTTP status code mirrors the meaning of the error field — rely on either one.
{
"ok": false,
"error": "validation_error",
"message": "Human-readable explanation in English"
}
Error reference
| HTTP | error | Cause | What to do |
|---|---|---|---|
| 401 | unauthorized | Missing or invalid X-API-Key | Check the header and the key |
| 403 | api_access_pending | Access request under review | Wait for approval |
| 403 | api_not_approved | API access not approved | Contact support |
| 404 | not_found | Unknown or foreign quote_id | Check the identifier |
| 409 | quote_expired | The quote is older than 2 minutes | Request a new quote |
| 422 | validation_error | Invalid parameters, limit violation, or rate move > 2% | Read the message, fix the request, or get a fresh quote |
| 429 | rate_limited | Request rate exceeded | Wait and retry |
| 503 | upstream_unavailable | Temporary liquidity provider failure | Retry later |
Rate limits
| Scope | Limit |
|---|---|
| All methods, per IP address | 1 request every 7 seconds |
| POST /quotes, per partner | 15 requests per minute |
| POST /exchanges, per partner | 10 requests per minute |
| GET /assets and GET /exchanges/{quote_id}, per partner | 120 requests per minute |
8. Example: an exchange from start to finish
Full scenario: the client sends 20 USDT (TRON network) and receives BTC at their Bitcoin address. Replace YOUR_KEY with your API key.
Step 1. Request a quote
curl -sS -X POST 'https://snapex.pro/api/v1/partner/quotes' \
-H 'X-API-Key: YOUR_KEY' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"from_asset_id": "usdt",
"to_asset_id": "btc",
"side": "send",
"amount": "20",
"recipient_address": "bc1qexample"
}'
The response contains a quote_id (pq_…) and the amounts. Show the client receive_amount — that is how much BTC they will get. You have 2 minutes for the next step.
Step 2. Create the exchange
curl -sS -X POST 'https://snapex.pro/api/v1/partner/exchanges' \
-H 'X-API-Key: YOUR_KEY' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"quote_id": "pq_FROM_STEP_1",
"recipient_address": "bc1qexample"
}'
The response now includes the deposit block with an address and amount. Show the client deposit.address and deposit.amount — they must transfer exactly that amount to that address.
Step 3. Track the status
curl -sS 'https://snapex.pro/api/v1/partner/exchanges/pq_FROM_STEP_1' \
-H 'X-API-Key: YOUR_KEY' \
-H 'Accept: application/json'
Repeat the request with a 7–10 second pause. Once the client transfers the funds, the status changes to processing, then to completed — the payout has been sent. Terminal statuses: completed, error, cancelled, expired.
The same scenario in Python
import time
import requests
API = "https://snapex.pro/api/v1/partner"
HEADERS = {
"X-API-Key": "YOUR_KEY",
"Content-Type": "application/json",
"Accept": "application/json",
}
# Step 1. Quote: the client sends 20 USDT, receives BTC
quote = requests.post(f"{API}/quotes", headers=HEADERS, json={
"from_asset_id": "usdt",
"to_asset_id": "btc",
"side": "send",
"amount": "20",
"recipient_address": "bc1qexample",
}).json()
print("Client receives:", quote["receive_amount"], quote["to_symbol"])
# Step 2. Create the exchange (within 2 minutes of the quote)
exchange = requests.post(f"{API}/exchanges", headers=HEADERS, json={
"quote_id": quote["quote_id"],
"recipient_address": "bc1qexample",
}).json()
deposit = exchange["deposit"]
print("Client must transfer:", deposit["amount"], deposit["asset"])
print("To address:", deposit["address"])
# Step 3. Poll the status until terminal
while True:
time.sleep(10) # at most 1 request every 7 seconds
state = requests.get(
f"{API}/exchanges/{quote['quote_id']}", headers=HEADERS
).json()
print("Status:", state["status"])
if state["status"] in ("completed", "error", "cancelled", "expired"):
break
Common beginner mistakes
- Sending the amount as a number instead of a string. Correct: "amount": "20", not "amount": 20.
- With side=receive, giving the amount in the send asset. For receive, amount is in the receive (to) asset.
- Creating the exchange more than 2 minutes after the quote — you get 409 quote_expired. Request the quote right before creating.
- Sending requests more often than once every 7 seconds — you get 429. Add a pause between requests.