О чём это
Партнёрский API отдаёт сторонним сервисам — кошельку, боту, виджету, агрегатору — весь обмен SwapZilla под их собственным брендом. Один запрос собирает котировки всех подключённых обменников, второй открывает ордер. Сам обмен остаётся некастодиальным: средства пользователя не проходят через SwapZilla, они уходят прямо на депозитный адрес провайдера, а провайдер платит на указанный вами адрес.
Каждый вызов с вашим ключом помечается вашим PartnerID, и вы
видите только свои ордера.
Что на нём собирают партнёры — ссылки на оплату и инвойсы, крипто-эквайринг, payroll и массовые выплаты, MCP-сервер для ИИ-агентов, стриминг курсов — разобрано по эндпоинтам в разделе /ru/solutions/.
| Базовый URL | https://api.swapzilla.io — тот же сервис доступен и как https://swapzilla.io/api, но интегрируйтесь с API-хостом |
|---|---|
| Префикс | /v1/partner для всего, что требует ключа |
| Авторизация | X-API-Key: <ваш ключ> в каждом запросе |
| Формат | JSON на вход и выход; text/event-stream у двух стриминговых эндпоинтов |
| Рейт-лимиты | с нашей стороны нет — но провайдеры режут частые запросы котировок, поэтому кэшируйте на 5–10 секунд |
| Тестовая сеть | на проде нет; для детерминированных смоук-тестов попросите включить провайдера mock |
Получение ключа
Зарегистрируйтесь на swapzilla.io/ru/partners/ —
ключ выпускается сразу. Либо попросите админа SwapZilla выдать ключ вручную. В
обоих случаях у вас на руках два значения: id партнёра (UUID) и сам
api_key — около 43 символов base64url, например
BmPmSIZkY7d_JBmFBIPLC9N7V8I-gAS-7stbPlBQVvE.
Ключ при самостоятельной регистрации выдаётся неактивным. Он ваш и
виден в кабинете, но любой запрос к
/v1/partner/* будет отвечать 403 и
{"error":"api key is awaiting activation…"}, пока поддержка не
включит его. Напишите в
@swapzilla_support_bot, укажите
название и почту, с которыми регистрировались, и коротко опишите интеграцию.
Ключ, выданный админом вручную, работает сразу.
Относитесь к ключу как к паролю. Это предъявительский доступ: кто им
владеет, тот и торгует от вашего имени. Если ключ утёк — перевыпустите его:
кнопка в кабинете или POST /v1/partner/key/regenerate с тем самым
ключом, который вы меняете. Старый перестаёт работать уже на следующем
запросе, а уже созданные ордера сохраняют исторический partner id.
GET /v1/partner/me нужен ключ
Проверяет, что ключ рабочий, и говорит, кто вы. Сам ключ в ответе не возвращается.
curl -s https://api.swapzilla.io/v1/partner/me \ -H "X-API-Key: $API_KEY"
{
"ID": "12ec0f35-2f4a-4c0e-9f1b-7d0a9e5a1c33",
"Name": "Acme Inc",
"Enabled": true,
"CreatedAt": "2026-05-04T12:59:13Z",
"UpdatedAt": "2026-05-04T12:59:13Z"
}
Без заголовка — 401 и
{"error":"missing X-API-Key header"}; с неизвестным ключом —
401 и {"error":"invalid api key"}. Если ключ нам
известен, но работать ему нельзя — не активирован, отклонён или отозван — то
403, и в тексте сказано, что именно. Это разные вещи:
401 — «перепроверьте ключ», 403 — «ключ верный,
напишите в поддержку».
Держите ключ на своём сервере. API отвечает с
Access-Control-Allow-Origin: *, то есть браузер может
ходить в него напрямую — и тогда любой достанет ваш ключ из DevTools.
Проксируйте партнёрские вызовы через свой бэкенд и добавляйте заголовок
там.
Быстрый старт
Три вызова доводят пользователя от «хочу обменять» до оплаченного ордера.
-
Запросить котировки
Получите офферы и выберите один — обычно первый, они приходят отсортированными от лучшего.
curl -sG "https://api.swapzilla.io/v1/partner/quotes" \ -H "X-API-Key: $API_KEY" \ --data-urlencode "from_asset=BTC" \ --data-urlencode "from_network=BITCOIN" \ --data-urlencode "to_asset=USDT" \ --data-urlencode "to_network=TRC20" \ --data-urlencode "amount_from=0.05"
-
Создать ордер
Отправьте
IDоффера вместе с адресом получения. В ответе придётDepositAddress.curl -s -X POST "https://api.swapzilla.io/v1/partner/orders" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "offer_id": "0b0c8f0e-2c1a-4f3f-8f57-2a1d1c4e77aa", "to_address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE", "refund_address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq" }' -
Показать депозитный адрес и опрашивать статус
Пользователь отправляет
AmountFromисходного актива наDepositAddress. Дальше опрашивайте ордер раз в 5–10 секунд, пока он не придёт в терминальный статус.curl -s "https://api.swapzilla.io/v1/partner/orders/$ORDER_ID" \ -H "X-API-Key: $API_KEY"
Оффер живёт 5 минут. Создание ордера по протухшему
offer_id вернёт 400 — перезапросите котировки и
используйте свежий оффер. Курс двигается, и именно это ограничение не даёт
показанному пользователю числу разойтись с реальностью.
Котировки
GET /v1/partner/quotes нужен ключ
Опрашивает все включённые провайдеры параллельно и возвращает их офферы. Провайдер, который упал или не успел, просто отсутствует в списке.
Параметры запроса
| Параметр | Описание | |
|---|---|---|
from_asset | обязательный | Тикер исходного актива, например BTC, USDT. Регистр не важен. |
from_network | обязательный | Его сеть. Понимаются популярные алиасы: ERC20/ETH/ETHEREUM, TRC20/TRX, BEP20/BSC, BITCOIN/BTC. |
to_asset | обязательный | Тикер целевого актива. |
to_network | обязательный | Его сеть. |
amount_from | как правило | Сколько отправляет пользователь, в единицах from_asset. |
amount_to | опционально | Считать от суммы получения. Используйте что-то одно. |
rate_type | опционально | floating (по умолчанию) или fixed — второй оставляет только провайдеров с фиксированным курсом. |
Ответ
{
"offers": [
{
"ID": "0b0c8f0e-2c1a-4f3f-8f57-2a1d1c4e77aa",
"ProviderCode": "fixedfloat",
"ProviderName": "FixedFloat",
"RateType": "floating",
"FromAsset": "BTC",
"FromNetwork": "BTC",
"ToAsset": "USDT",
"ToNetwork": "TRX",
"FromAmount": 0.05,
"ToAmount": 3920.55,
"AmountFromUsd": 3940.15,
"AmountToUsd": 3919.57,
"Price": 78411.08,
"EstimatedMins": 6,
"KycRating": "B",
"DeviationPercent": 0,
"MinFromAmount": 0.00064584,
"MaxFromAmount": 7.10422758,
"CreatedAt": "2026-08-18T09:12:44Z"
}
]
}
Поля оффера, которые важны
| Поле | Что означает |
|---|---|
ID | То, что вы передадите как offer_id при создании ордера. Живёт 5 минут. |
ProviderName | Человекочитаемое имя — показывайте его, а не ProviderCode. |
RateType | floating следует за рынком до подтверждения депозита, fixed зафиксирован. |
EstimatedMins | Медиана времени выполнения по последним 100 успешным ордерам провайдера. |
KycRating | От A (минимум проверок) до D (агрессивный KYC). Пользователь имеет право знать это заранее. |
DeviationPercent | Насколько хуже лучшего оффера, в процентах. 0 — лучший. |
AmountFromUsd / AmountToUsd | Эквивалент в USD. Отсутствуют, если для актива нет цены. |
MinFromAmount / MaxFromAmount | Лимиты провайдера для этой пары в единицах FromAsset. Отсутствуют, пока провайдер их не сообщил. Проверяйте ввод по ним до создания ордера. |
IsPayment | Появляется у офферов из платёжного (amount-to) эндпоинта провайдера. Ничего делать не нужно — схема создания ордера та же. |
GET /v1/partner/quotes-sse нужен ключ
Тот же запрос, но потоком. Офферы приходят по мере ответа каждого провайдера, а не после самого медленного — первый обычно за 200–400 мс. Именно это нужно живому интерфейсу.
Параметры те же, что у /quotes, плюс
timeout_ms — по умолчанию и максимум 5000 мс. Стрим
всегда закрывается сам.
События
| Событие | Данные |
|---|---|
offer | Один оффер, в точности как элемент массива offers. |
private_route | Лучший приватный маршрут для той же пары, если был передан amount_from. См. приватные обмены. |
provider_error | {"provider":"…","error":"…"} — провайдер выпал. Логируйте, но не показывайте. |
ping | {"elapsed_ms":1000} раз в секунду, чтобы прокси не рвали соединение. |
done | {"total":7,"errors":1,"elapsed_ms":2140,"timed_out":false,"deviations":{"<offer_id>":0.16}}. Карта deviations — итоговый рейтинг, примените её к уже отрисованным офферам. |
// С вашего бэкенда, который добавляет ключ. В браузере — только через прокси.
const res = await fetch(url, { headers: { "X-API-Key": key, Accept: "text/event-stream" } });
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
// … делите по "\n\n", разбирайте строки "event:" / "data:", рисуйте каждый `offer` сразу
Ордера
POST /v1/partner/orders нужен ключ
Превращает оффер в реальный ордер у провайдера. Отвечает
201 и полным ордером.
Тело запроса
| Поле | Описание | |
|---|---|---|
offer_id | обязательное | ID выбранного оффера, не старше 5 минут. |
to_address | обязательное | Куда уйдёт полученный актив. Потом не меняется. |
refund_address | настоятельно | Куда вернуть исходный актив, если обмен не прошёл. Без него возврат может потребовать ручной работы на стороне провайдера. |
from_address | опционально | Адрес, с которого платит пользователь, если он вам известен. |
client_order_id | опционально | Ваш собственный идентификатор, сохраняется с ордером. |
expected_to_amount | опционально | Защита от слипаджа: если пересчитанная выплата отличается, ордер не создастся. |
expected_from_amount | опционально | Сумма, которую вы пообещали пользователю отправить; сохраняется как AmountFromExpected. |
idempotency_key | опционально | То же, что заголовок Idempotency-Key, для клиентов, которые не умеют ставить заголовки. |
Ответ
{
"ID": "7a0e2c31-4f9a-4b2e-9a77-9f0b2e5d1c02",
"ProviderCode": "fixedfloat",
"ProviderOrderID": "FF9K2LQ",
"OfferID": "0b0c8f0e-2c1a-4f3f-8f57-2a1d1c4e77aa",
"Status": "NEW",
"FromAsset": "BTC",
"ToAsset": "USDT",
"FromNetwork": "BTC",
"ToNetwork": "TRX",
"DepositAddress": "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
"PayoutAddress": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
"RefundAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
"AmountFrom": 0.05,
"AmountFromExpected": 0.05,
"AmountToExpected": 3920.55,
"AmountToReceived": 0,
"RateType": "floating",
"TxID": "",
"PartnerID": "12ec0f35-2f4a-4c0e-9f1b-7d0a9e5a1c33",
"CreatedAt": "2026-08-18T09:13:02Z",
"UpdatedAt": "2026-08-18T09:13:02Z"
}
Главное поле ответа — DepositAddress. Покажите его
пользователю вместе с точной суммой AmountFrom и сетью, в
которой нужно отправить. SwapZilla ничего не удерживает: средства идут от
пользователя сразу провайдеру, а провайдер платит на
PayoutAddress.
GET /v1/partner/orders/{id} нужен ключ
Текущее состояние вашего ордера. Чужой ордер отвечает 404, а
не 403.
Мы сами опрашиваем провайдера каждые 10 секунд по всем ордерам в нетерминальном статусе, поэтому вам достаточно опрашивать этот эндпоинт раз в 5–10 секунд — ходить к провайдеру напрямую не нужно.
GET /v1/partner/orders нужен ключ
Ваши ордера, новые сверху. page по умолчанию
1, limit — 20 и не больше
100.
curl -s "https://api.swapzilla.io/v1/partner/orders?page=1&limit=50" \ -H "X-API-Key: $API_KEY"
{ "orders": [ /* … */ ], "page": 1, "limit": 50, "has_more": true }
POST /v1/partner/orders/{id}/refresh нужен ключ
Синхронно ходит к провайдеру и возвращает обновлённый ордер — статус,
фактические суммы и TxID. Полезно сразу после того, как
пользователь сказал «я отправил». Не чаще раза в 5 секунд: фоновый поллер и
так делает эту работу.
Жизненный цикл ордера
| Статус | Что значит | |
|---|---|---|
| NEW | Создан, ждём депозит пользователя. | в процессе |
| WAIT_DEPOSIT | То же самое, но подтверждено провайдером явно. | в процессе |
| CONFIRMING | Депозит виден в сети, ждём подтверждений. | в процессе |
| EXCHANGING | Средства подтверждены, идёт обмен. | в процессе |
| SENDING | Провайдер отправляет результат на PayoutAddress. | в процессе |
| DONE | Завершён. AmountToReceived и TxID окончательные. | терминальный |
| TIME_EXPIRED | Депозит не пришёл вовремя. | терминальный |
| FAILED | Обмен не удался; в ErrorMessage — что сообщил провайдер. | терминальный |
| REFUNDED | Средства вернулись на RefundAddress. | терминальный |
TxID заполняется, когда провайдер отправляет выплату — обычно
на SENDING, к DONE всегда. В терминальном статусе
опрос можно прекращать.
Логируйте ProviderOrderID рядом со своим id ордера. Это
единственная ссылка, которую поймёт поддержка провайдера, если обмен придётся
разбирать вручную.
Приватные обмены
Приватный обмен разрывает on-chain связь между отправителем и получателем,
проводя средства через анонимный промежуточный актив — по умолчанию Monero. Под
капотом это два обычных ордера, сцепленных так, что выплата первого является
депозитом второго, и помеченных общим PrivateSwapID.
Маршруты, где обе ноги идут через одного провайдера, отбрасываются — они лишили бы смысла всю затею.
GET /v1/partner/private-quotes нужен ключ
Параметры пары те же, что у /quotes, плюс
max_routes (положительное целое). Возвращает
{"routes": [ … ]}; в каждом маршруте есть LegA и
LegB — оба обычные офферы — и собственные
AmountTo, EstimatedMins, KycRating,
DeviationPercent.
curl -sG "https://api.swapzilla.io/v1/partner/private-quotes" \ -H "X-API-Key: $API_KEY" \ --data-urlencode "from_asset=TRX" \ --data-urlencode "from_network=TRX" \ --data-urlencode "to_asset=USDT" \ --data-urlencode "to_network=ERC20" \ --data-urlencode "amount_from=100" \ --data-urlencode "max_routes=5"
Стриминговый близнец — /v1/partner/private-quotes-sse: те же
параметры плюс timeout_ms, события route,
provider_error, ping и done.
POST /v1/partner/private-orders нужен ключ
curl -s -X POST "https://api.swapzilla.io/v1/partner/private-orders" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"route_id": "9d2f1a76-6c3b-4a55-b0f1-2c9a7e0d4411",
"to_address": "0x750c44dB01899176f2e64bD25A2fabAC1140d8e9",
"refund_address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
"intermediate_refund_address": "48jLd5…"
}'
| Поле | Описание | |
|---|---|---|
route_id | обязательное | ID маршрута. Как и офферы, маршруты живут 5 минут. |
to_address | обязательное | Конечный адрес в целевом активе. |
refund_address | настоятельно | Адрес возврата для первой ноги, в исходном активе. |
intermediate_refund_address | настоятельно | Адрес возврата для второй ноги, в промежуточном активе (по умолчанию XMR-адрес). |
client_order_id | опционально | Ваш собственный идентификатор. |
Отвечает 201 и объектом PrivateSwap, внутри
которого FirstOrder и SecondOrder. Показывать
пользователю нужно FirstOrder.DepositAddress.
Здесь читается только заголовок Idempotency-Key —
в отличие от /orders, поля в теле для него нет.
GET /v1/partner/private-orders/{id} нужен ключ
Агрегированный статус и обе ноги целиком — этого хватает на подробный
таймлайн в интерфейсе. GET /v1/partner/private-orders отдаёт
список ваших с той же пагинацией page / limit, что
и обычные ордера.
| Статус | Что значит |
|---|---|
| NEW | Создан, ждём депозит на первой ноге. |
| STAGE_1 | Идёт первая нога (исходный актив → промежуточный). |
| STAGE_2 | Первая нога завершена, идёт вторая (промежуточный → целевой). |
| DONE | Обе ноги завершены. |
| FAILED | Одна из ног упала, протухла или вернула средства. Смотрите сами ноги. |
Эндпоинты без ключа
Эти публичные. С вашим ключом они тоже работают, просто он не нужен.
GET /v1/assets
Все доступные активы и сети с провайдерами, которые их поддерживают, — источник правды для селектора валют. Отфильтровано до топ-100 по капитализации, обновляется раз в несколько часов.
{ "assets": [ { "Code": "BTC", "Network": "BTC", "Providers": ["fixedfloat", "changee"] } ] }
GET /v1/providers
Зарегистрированные обменники с человекочитаемыми именами и признаком, включён ли провайдер сейчас.
{ "providers": [ { "code": "changee", "name": "Changee", "enabled": true } ] }
GET /v1/validate-address
Проверяет адрес по формату сети до того, как пользователь нажмёт
«отправить». Параметры network и address; без них
— 400.
curl -sG "https://api.swapzilla.io/v1/validate-address" \ --data-urlencode "network=ERC20" \ --data-urlencode "address=0x750c44dB01899176f2e64bD25A2fabAC1140d8e9"
{ "valid": true, "network": "ERC20", "known": true }
known: false значит, что паттерна для этой сети у нас нет и
проверка ничего не доказала — это «не проверено», а не «валидно».
GET /v1/export/rates.xml
Лучший курс по каждому направлению в XML-формате для мониторингов
(BestChange-style), пересобирается раз в несколько секунд и кэшируется.
На сайте опубликован как /export/rates.xml.
Коды валют — по конвенции мониторингов: USDT в сети Tron это
USDTTRC20.
<rates>
<item>
<from>BTC</from>
<to>USDTTRC20</to>
<in>1</in>
<out>63684.46973</out>
<minamount>0.00078071</minamount>
<maxamount>1.17106129</maxamount>
<param>floating</param>
</item>
</rates>
Идемпотентность
Оба создающих эндпоинта принимают заголовок Idempotency-Key.
Повтор запроса с тем же ключом вернёт тот же ордер, а не создаст
второй — ровно то, что нужно, когда сетевой таймаут не дал понять, дошёл ли
первый запрос.
Сгенерируйте UUID v4 перед первой попыткой, используйте его во всех ретраях
этой попытки и берите новый для действительно нового ордера.
POST /orders принимает его ещё и полем тела
idempotency_key; POST /private-orders читает только
заголовок.
Ошибки
Формат у всех ошибок один:
{ "error": "offer not found or expired" }
| Код | Когда |
|---|---|
200 | Успешное чтение. |
201 | Ордер или приватный обмен создан. |
400 | Неверные параметры или бизнес-ошибка: протухший оффер, отключённый актив, сумма ниже минимума провайдера. |
401 | Ключ отсутствует, неверен, отозван или отключён. |
404 | Ресурса нет — или он не ваш. |
5xx | Временная проблема у нас или у провайдера. Ретрайте с backoff. |
Ошибки, которые встретятся на практике
| Сообщение | Причина | Что делать |
|---|---|---|
offer not found / offer expired | С момента котировки прошло больше 5 минут. | Перезапросить котировки и создать ордер с новым offer_id. |
route not found or expired | Приватный маршрут протух, те же 5 минут. | Перезапросить /private-quotes. |
provider is disabled | Обменник выключили уже после котировки. | Взять другой оффер из списка. |
asset is disabled | Актив отключён. | Предложить пользователю другой актив. |
| сумма ниже минимума / выше максимума | Проксировано от провайдера. | Проверять по MinFromAmount / MaxFromAmount до отправки. |
Как работать с API хорошо
- Кэшируйте котировки на 5–10 секунд. Провайдеры начнут ограничивать вас задолго до того, как это заметят пользователи, а курс за секунду существенно не двигается.
- Всегда отправляйте ключ идемпотентности. Это одна строка кода и разница между ретраем и дублирующим обменом.
- Всегда спрашивайте адрес возврата. Неудавшийся обмен без него — в лучшем случае тикет в поддержку.
- Проверяйте сумму по
MinFromAmount/MaxFromAmount, а адрес — через/v1/validate-address, до создания чего-либо. - Показывайте
KycRatingиEstimatedMinsрядом с каждым провайдером: самый выгодный оффер не всегда тот, который нужен пользователю. - Стримьте котировки в интерфейс через
/quotes-sse, а потом пересортируйте по картеdeviationsиз событияdone. - Опрашивайте статус раз в 5–10 секунд и останавливайтесь на терминальном. Чаще — только лишняя нагрузка: наш поллер всё равно ходит раз в 10 секунд.
- Никогда не отдавайте ключ в браузер или в мобильное приложение. Между ними и API должен стоять ваш бэкенд.
Частые вопросы
Есть ли вебхуки об изменении статуса?
Пока нет, только поллинг. Если вашей интеграции они нужны — скажите, и мы поднимем приоритет.
Можно ли поменять адрес получения после создания ордера?
Нет. Создавайте новый ордер.
Что будет, если провайдер отвалится посреди обмена?
Ордер, не дошедший до DONE за отведённое провайдером время
(обычно 30–60 минут), перейдёт в TIME_EXPIRED или
FAILED. Если депозит уже сделан, провайдер вернёт средства на
RefundAddress.
Как сменить ключ?
Попросите новый. Он придёт с новым partner id, а ордера,
созданные старым ключом, останутся за старым — поэтому сначала мы выдаём новый
ключ, вы переключаетесь, и старый отзывается, когда его последний ордер
завершится. Сам отзыв срабатывает уже на следующем запросе.
Есть ли песочница?
На проде нет. Для детерминированных смоук-тестов мы можем включить для
вашего ключа провайдера mock, который отдаёт синтетические офферы
и ордера.
Вопросы, запрос ключа или расхождение этой страницы с тем, что реально делает API, — пишите в @swapzilla_support_bot.