Перейти к содержанию

Инвойсы

Инвойс — это запрос на оплату, привязанный к одному заказу. При его создании за инвойсом закрепляется депозитный адрес в выбранной вами сети, и всё, что придёт на этот адрес, засчитывается именно ему и никакому другому.

Создание инвойса

curl -X POST https://api.pay.nullswap.com/api/v1/invoices \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
        "chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
        "tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
        "expectedAmount": "25500000",
        "expiresInSeconds": 3600,
        "externalId": "order-10241"
      }'
Поле Тип Обяз. Примечания
chainId UUID да Сеть, в которой будет платить покупатель. Берётся из GET /api/v1/chains
tokenId UUID да Должен относиться к сети chainId, иначе запрос вернёт 404 TOKEN_NOT_FOUND, и принимать платежи (srcStatus: ENABLED), иначе — 422 TOKEN_IS_DISABLED
expectedAmount десятичная строка да Строго положительная, в базовых единицах токена
expiresInSeconds целое число да 60–604800 (от 1 минуты до 7 дней)
externalId строка | null нет Ваш собственный номер заказа. Уникален в пределах мерчанта
acceptsAnyToken булево нет По умолчанию false. См. Альтернативные токены
autoSweep булево нет По умолчанию true. См. Перевод средств на баланс

Поля merchantId здесь нет: мерчанта уже определяет ваш API-ключ.

В ответе приходит сам инвойс:

{
  "id": "b2f4a6c8-0d1e-4f23-9a45-6b7c8d9e0f11",
  "merchantId": "9f1d2c3b-4a5e-4f60-8b71-2c3d4e5f6a7b",
  "depositWalletId": "7e2a9c14-8b3d-4c56-a789-0f1e2d3c4b5a",
  "depositAddress": "TQ5s...9Xk2",
  "depositTag": null,
  "depositXAddress": null,
  "chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
  "tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
  "expectedAmount": "25500000",
  "acceptsAnyToken": false,
  "autoSweep": true,
  "paidAmount": "0",
  "pendingAmount": "0",
  "externalId": "order-10241",
  "status": "WAITING",
  "amlStatus": null,
  "createdAt": "2026-01-01T12:00:00.000Z",
  "expiresAt": "2026-01-01T13:00:00.000Z",
  "updatedAt": "2026-01-01T12:00:00.000Z"
}

Покажите покупателю depositAddress и точную сумму expectedAmount, а после наступления expiresAt уберите их с экрана. На RIPPLE вместе с ними покажите depositTag с пометкой «обязательно»: там один адрес инвойс не определяет. См. Тег назначения на RIPPLE. На всех остальных сетях depositTag и depositXAddress равны null.

Всегда отправляйте externalId

Именно он не даёт одному заказу получить два депозитных адреса, если запрос на создание отвалился по таймауту и ваш клиент его повторил. Повторный запрос вернёт 409 INVOICE_EXTERNAL_ID_ALREADY_EXISTS вместо второго инвойса — чтобы восстановиться, найдите исходный через GET /api/v1/invoices?externalId=order-10241&limit=1. Так же его можно найти и позже, не сохраняя у себя наш идентификатор.

Ошибки

Проверки идут именно в таком порядке, так что вы получите ошибку по первому нарушенному правилу:

Статус message Причина
403 MERCHANT_BLOCKED Мерчант заблокирован. Проверяется по вашему ключу, ещё до чтения тела
400 INVALID_REQUEST_BODY Поля нет или у него неверный JSON-тип, например autoSweep — не булево
422 INVALID_INVOICE_EXPIRATION expiresInSeconds вне диапазона 60–604800
422 INVALID_INVOICE_AMOUNT expectedAmount — не строка с положительным целым числом базовых единиц
409 INVOICE_EXTERNAL_ID_ALREADY_EXISTS Этот externalId вы уже использовали
404 CHAIN_NOT_FOUND Неизвестный chainId
404 TOKEN_NOT_FOUND Неизвестный tokenId или токен относится к другой сети
422 TOKEN_IS_DISABLED Токен сейчас не принимает платежи: его srcStatus равен DISABLED. См. токены
502 INTERNAL_ERROR Не удалось выделить депозитный адрес. Запрос можно повторить

Тег назначения на RIPPLE

На RIPPLE, то есть в XRP Ledger, депозитный адрес принадлежит не только вам. Все инвойсы RIPPLE оплачиваются на один и тот же depositAddress, а к своему инвойсу платёж привязывается по тегу назначения (destination tag), который он несёт, и больше ни по чему. Этот тег инвойс называет в двух дополнительных полях:

Поле Тип Примечания
depositTag целое число | null Тег назначения, с которым должен прийти платёж, 0–4294967295. Теги не последовательны: показывайте полученный, никогда не вычисляйте его сами
depositXAddress строка | null depositAddress и depositTag, упакованные в один X-адрес (X…), — для кошельков, которые принимают адрес без тега. Платёж на него — то же самое, что платёж на пару
{
  "id": "b2f4a6c8-0d1e-4f23-9a45-6b7c8d9e0f11",
  "depositWalletId": "7e2a9c14-8b3d-4c56-a789-0f1e2d3c4b5a",
  "depositAddress": "rHsMGQEkVNJmpGWs8XUBoTBiAAbwxZN5v3",
  "depositTag": 959725,
  "depositXAddress": "XVMFQQBMhdouRqhPMuawgBMN1AVFToG5NjwwoxoAYuiUyDF",
  "expectedAmount": "25000000",
  "status": "WAITING"
}

Покажите покупателю адрес, тег — подписанный как обязательный, а не как необязательная заметка, — и точную сумму. Можно показать и depositXAddress как альтернативу паре; именно его и стоит закладывать в QR-код: кошелёк, который его отсканирует, получит адрес и тег вместе. Оба поля есть и в data каждого вебхука invoice.*.

Без нужного тега платёж до инвойса не дойдёт

Платёж на depositAddress без тега назначения сеть отклоняет, и кошелёк покупателя показывает его как неудавшийся. Платёж с тегом, отличным от depositTag, этому инвойсу не засчитывается: тег, который мы не выдавали, удерживается и автоматически ни одному инвойсу не засчитывается — обратитесь в поддержку с хешем транзакции. В формах вывода на биржах поле тега часто подписано как «необязательное»; предупредите покупателя, что это не так.

Засчитывается то, что платёж доставил на адрес, а не сумма, которая в нём указана: в XRP Ledger платёж можно отправить как частичный (partial payment), и считается только то, что дошло. Дальше недоплата и переплата работают как в любой другой сети — см. Недоплата и переплата.

Чтение инвойсов

Метод Путь Назначение
GET /api/v1/invoices/{invoiceId} Один инвойс
GET /api/v1/invoices Список с фильтрами и пагинацией
GET /api/v1/invoices/{invoiceId}/payments Отдельные ончейн-депозиты — см. Платежи по инвойсу

GET /api/v1/invoices фильтрует по id, chainId, tokenId, status и externalId — каждый из них принимает несколько значений — плюс offset, limit, order и sortBy (CREATED_AT, EXPIRES_AT, ID).

GET /api/v1/invoices?status=PAID&status=PARTIALLY_PAID&limit=100&sortBy=EXPIRES_AT&order=ASC

Опрос — это запасной вариант, а не интеграция

Подпишитесь на вебхуки, а опрос держите как задачу сверки, которую запускают после простоя вашего эндпоинта. Ни long-polling, ни потокового эндпоинта здесь нет.

Жизненный цикл статусов

stateDiagram-v2
    direction LR
    [*] --> WAITING

    WAITING --> PAYMENT_DETECTED
    WAITING --> PARTIALLY_PAID
    WAITING --> PAID
    WAITING --> EXPIRED

    PAYMENT_DETECTED --> WAITING
    PAYMENT_DETECTED --> PARTIALLY_PAID
    PAYMENT_DETECTED --> PAID
    PAYMENT_DETECTED --> EXPIRED

    PARTIALLY_PAID --> WAITING
    PARTIALLY_PAID --> PAYMENT_DETECTED
    PARTIALLY_PAID --> PAID
    PARTIALLY_PAID --> EXPIRED

    PAID --> WAITING
    PAID --> PAYMENT_DETECTED
    PAID --> PARTIALLY_PAID

    WAITING --> SWAPPING
    PAYMENT_DETECTED --> SWAPPING
    PARTIALLY_PAID --> SWAPPING
    PAID --> SWAPPING
    SWAPPING --> COMPLETED
    SWAPPING --> FAILED
    FAILED --> SWAPPING
    FAILED --> COMPLETED

    COMPLETED --> [*]
    EXPIRED --> [*]
Статус Что означает
WAITING Создан, в сети пока ничего не видно
PAYMENT_DETECTED Транзакция видна, но ещё не подтверждена и не прошла скрининг
PARTIALLY_PAID Пришли прошедшие проверку деньги, но paidAmount всё ещё меньше expectedAmount
PAID paidAmount >= expectedAmount — именно по нему нужно исполнять заказ
SWAPPING Свип депозитного адреса начался, и его деньги идут на ваш баланс
COMPLETED Ни один свип депозитного адреса не идёт и не провалился: деньги на вашем балансе
FAILED Свип депозитного адреса сдался. Деньги всё ещё на адресе, и их заберёт следующий свип
EXPIRED expiresAt наступил без достаточной оплаты

Четыре детали на диаграмме легко не заметить:

  • Статус может откатываться назад, в том числе из PAID. Транзакция, вытесненная реоргом, забирает свою сумму с собой: инвойс возвращается из PARTIALLY_PAID обратно в WAITING, а инвойс в PAID — в WAITING, PAYMENT_DETECTED или PARTIALLY_PAID, смотря по оставшимся суммам. Поэтому обработчик должен считать тело каждого вебхука снимком текущего состояния, а не приращением к предыдущему.
  • Вперёд из PAID ведёт единственный путь — в SWAPPING. Пока инвойс оплачен, он не истечёт, а деньги, пришедшие в это время, запишутся как неожиданный платёж и на paidAmount не повлияют. Вывести инвойс из PAID может только откат платежа; после начала свипа откат платежа статус назад уже не двигает.
  • SWAPPING, COMPLETED и FAILED следуют за свипами, а не за оплатой. Автоматический свип переводит инвойс из PAID в SWAPPING, когда свип принят; частично оплаченный инвойс продолжает ждать остаток. Ваш собственный вызов эндпоинта свипа переводит инвойс в SWAPPING, как только вызов отправил деньги в путь, — из WAITING, PAYMENT_DETECTED и PARTIALLY_PAID так же, как из PAID. В обоих случаях инвойс становится COMPLETED, когда ни один свип адреса не идёт и не провалился; оставшуюся на нём пыль никто не ждёт.
  • FAILED — не конец. Когда свип инвойса в SWAPPING сдаётся после всех повторов, инвойс становится FAILED, и вы получаете invoice.failed. Деньги всё ещё на депозитном адресе: следующий свип — автоматический или запрошенный вами — возвращает инвойс в SWAPPING или сразу в COMPLETED, когда зачисляется.

invoice.paid может быть отозван

Реорганизация сети может откатить платёж уже после отправки invoice.paid. Тогда инвойс выходит из PAID, вы получаете invoice.payment_failed (и invoice.partially_paid, если он попадает в PARTIALLY_PAID), а при повторной оплате invoice.paid придёт снова. Если то, что вы отдаёте по invoice.paid, нельзя вернуть, перед выдачей убедитесь, что инвойс всё ещё PAID.

COMPLETED не значит «оплачен»

Запрошенный вами свип переводит инвойс в SWAPPING, сколько бы по нему ни заплатили, поэтому у инвойса в COMPLETED paidAmount может быть меньше expectedAmount или равен "0", если свипнут был только депозит в другом токене. После свипа статус уже не говорит, оплачен ли заказ: решайте это по paidAmount до того, как вызывать свип.

COMPLETED и EXPIRED — терминальные состояния. Суммы после этого ещё могут уточняться, поэтому запоздавшая транзакция по истёкшему инвойсу будет записана, а не потеряна, — но статус уже не изменится.

Две суммы

pendingAmount и paidAmount меняются независимо друг от друга и означают разное.

Учитывает деньги, которые… Сравнивается с expectedAmount?
pendingAmount видны в сети, но ещё не подтверждены и не прошли скрининг нет
paidAmount подтверждены и прошли скрининг да

На обе суммы влияют только платежи в той сети и в том токене, которые указаны в инвойсе. Депозиты в другом токене — никогда, что бы вы потом с ними ни решили делать. Статус выводится уже из этих двух величин:

paidAmount >= expectedAmount   -> PAID
paidAmount > 0                 -> PARTIALLY_PAID
pendingAmount > 0              -> PAYMENT_DETECTED
иначе                          -> WAITING

pendingAmount — это не деньги, которые у вас есть

Они пока только видны в сети. Вашими они становятся, когда переходят в paidAmount. Инвойс никогда не достигнет PAID за счёт средств, не прошедших скрининг: помеченные деньги вычитаются из pendingAmount и никогда не добавляются в paidAmount.

Недоплата и переплата

Неверную сумму инвойс не отклоняет — он записывает ровно то, что пришло.

Недоплата. Инвойс остаётся в PARTIALLY_PAID. Покупатель может доплатить до expiresAt, несколько депозитов складываются. Если срок вышел, а суммы так и не хватило, инвойс закрывается с уже записанным paidAmount: деньги не потеряны, но инвойс закрыт, и что делать дальше — решать вам.

Переплата. Инвойс переходит в PAID с paidAmount > expectedAmount. Верхней границы нет, статуса OVERPAID нет, автоматического возврата тоже. Хотите вернуть разницу — сравнивайте эти две суммы сами.

Проверяйте сумму, а не только статус

PAID значит «пришло не меньше ожидаемого». Если от точной суммы зависит выдача заказа, читайте paidAmount.

Истечение срока

Инвойс истекает по наступлении expiresAt — но не тогда, когда деньги ещё в пути. Срок не сработает, если у инвойса есть хотя бы один депозит, ждущий подтверждения, или хотя бы один подтверждённый депозит в альтернативном токене. Поэтому покупатель, заплативший незадолго до дедлайна, не потеряет платёж из-за того, что подтверждение пришло уже после него.

Подтверждённый депозит в альтернативном токене отменяет истечение навсегда

Правило не смотрит, разрешили ли вы платёж. Инвойс, получивший подтверждённый платёж ALTERNATIVE, остаётся в своём статусе и после expiresAt и никогда не отправит invoice.expired.

А вот инвойс в статусе PARTIALLY_PAID истечь может. Частичная оплата останавливает часы только на то время, пока что-то ещё подтверждается.

Альтернативные токены

Поставьте acceptsAnyToken: true — и депозит в другом токене совместимой сети будет записан как платёж ALTERNATIVE, а не признан ошибкой. В paidAmount он всё равно не попадёт: expectedAmount выражен в собственном токене инвойса, так что сколько такой депозит стоит и оплачен ли заказ — решаете вы.

Оставите false (значение по умолчанию) — тот же депозит станет платежом UNEXPECTED. Деньги записываются и там, и там; флаг меняет только их классификацию и то, спросят ли у вас решение по ним. См. Платежи по инвойсу.

Перевод средств на баланс

Деньги, оплаченные по инвойсу, приходят на его депозитный адрес. На ваш баланс — и в число того, что можно вывести, — они попадают, только когда их свипнут с этого адреса. Свип забирает всю чистую сумму одного токена на адресе разом, так что несколько платежей в одном токене переезжают вместе.

Кто запускает свип, решает флаг autoSweep у инвойса:

autoSweep Что происходит с подтверждённым чистым платежом
true (по умолчанию) Свипается на ваш баланс автоматически, как только подтвердился и прошёл скрининг
false Остаётся на депозитном адресе, пока вы не свипнете его сами

Свипается каждый платёж, который подтвердился и прошёл скрининг, какого бы он ни был kind, — EXPECTED, ALTERNATIVE и UNEXPECTED одинаково, и независимо от того, разрешили вы его или нет. Деньги, не прошедшие скрининг, не свипаются никогда и до баланса не доходят: они остаются на депозитном адресе.

Свипы повторяются без какого-либо участия с вашей стороны. Свип, который не удалось запустить, запускается снова, пока его не примут, а провалившийся поднимается заново ограниченное число раз, так что временный сбой задерживает деньги, а не оставляет их на адресе. Если свип так и не прошёл, вызовите эндпоинт свипа сами.

Автоматический свип переводит инвойс из PAID в SWAPPING, когда его приняли, и в COMPLETED, когда деньги на вашем балансе. Инвойс, ещё не оплаченный полностью, сохраняет свой статус, пока свипаются его первые платежи, и двигается дальше только после того, как станет PAID. Свип, который сдался, переводит инвойс из SWAPPING в FAILED, пока деньги не унесёт следующий свип; см. жизненный цикл статусов.

Ставьте autoSweep: false, когда хотите сами выбирать момент, — например, чтобы платёж, который вы, возможно, разрешите как REFUNDED, не попадал на баланс, пока вы не решили. Свип — единственный способ увести деньги с депозитного адреса: NullSwap не отправляет возвраты, поэтому при false средства остаются ровно там, где лежат, пока вы их не свипнете.

Пыль остаётся на адресе

Сумма, которая стоит не больше сетевой комиссии за её перемещение, не свипается: свип обошёлся бы дороже, чем она стоит. Она остаётся на депозитном адресе и уходит вместе со следующим свипом того же токена с этого адреса. На адрес инвойса следующий платёж приходит редко, поэтому пыль там обычно так и остаётся.

Перемещение денег стоит сетевой комиссии, и платите её в любом случае вы. В нативной монете сети свип оплачивает свою сетевую комиссию из перемещаемой суммы, поэтому доходит немного меньше, чем было заплачено. Для токена в сети с моделью счетов сетевую комиссию в нативной монете платим мы, а её стоимость в токене по текущему курсу удерживаем из зачисления — вместе со стоимостью approve, которые нужны конвертирующему свипу, и нативной монеты, отправленной на адрес для оплаты перемещения. В UTXO-сетях и в Monero сверх того, что платит сам свип, ничего не удерживается. Из того, что дошло, удерживаются комиссия мерчанта и эта сетевая комиссия, а остальное зачисляется на ваш баланс.

Сетевой комиссии нет среди полей свипа

Свип сообщает amount и fee, но не сетевую комиссию, удержанную рядом с fee, поэтому на свипе токена amount − fee завышает зачисление. Сколько на самом деле дошло до баланса, показывает credited в списке удержанных комиссий.

Свип депозитного адреса

curl -X POST https://api.pay.nullswap.com/api/v1/wallets/7e2a9c14-8b3d-4c56-a789-0f1e2d3c4b5a/sweep \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
        "chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
        "tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908"
      }'

walletId в пути — это depositWalletId инвойса или id статического кошелька. Тело необязательно:

Поле Тип Обяз. Примечания
chainId UUID нет Свипнуть только токены этой сети
tokenId UUID нет Свипнуть только этот токен

Без тела свипаются все токены, у которых на адресе есть чистая сумма. Ответ — 202 со свипом на каждый токен:

{
  "data": [
    {
      "id": "0b1c2d3e-4f50-4617-8a29-3b4c5d6e7f80",
      "walletId": "7e2a9c14-8b3d-4c56-a789-0f1e2d3c4b5a",
      "invoiceId": "b2f4a6c8-0d1e-4f23-9a45-6b7c8d9e0f11",
      "staticWalletId": null,
      "chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
      "tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
      "trigger": "MANUAL",
      "status": "SWEEPING",
      "amount": "25500000",
      "feeEstimate": null,
      "transferRequestId": "4d5e6f70-8192-4a3b-9c4d-5e6f708192a3",
      "error": null,
      "createdAt": "2026-01-01T12:10:00.000Z",
      "updatedAt": "2026-01-01T12:10:01.000Z",
      "feePercentage": "150",
      "fee": null,
      "feeUsd": null,
      "settledAt": null,
      "creditedTokenId": null,
      "dstTokenId": null,
      "amountIn": null,
      "amountOut": null,
      "conversionSkipReason": null
    }
  ]
}
Поле Значение
invoiceId, staticWalletId Какой адрес свипнут. Заполнено ровно одно из двух
trigger AUTO — запущен автоматически; MANUAL — запущен этим эндпоинтом
amount Базовые единицы tokenId, которые перемещаются, а после SWEPT — то, что дошло, в creditedTokenId. null, пока свип не принят
feeEstimate Сетевая комиссия в базовых единицах нативной монеты сети. Заполняется только для пыли
transferRequestId Наш идентификатор перевода, стоящего за свипом. Назовите его поддержке
error Почему свип провалился, если провалился, — кодом: SWEEP_SOURCE_NOT_SUPPORTED (этот адрес нельзя свипнуть), SWEEP_FUNDS_UNAVAILABLE (денег для свипа на адресе уже нет) или SWEEP_FAILED (всё остальное). Незнакомый код считайте SWEEP_FAILED
feePercentage Ставка комиссии мерчанта для этого свипа в базисных пунктах, зафиксированная при его открытии
fee Комиссия мерчанта, удержанная из amount, в базовых единицах creditedTokenId. null до SWEPT
feeUsd fee в USD, умноженная на 1018. null, пока не оценена
settledAt Когда свип перешёл в SWEPT. До этого null
creditedTokenId Токен, в котором выражены amount и fee после SWEPT: tokenId или dstTokenId, если свип был сконвертирован. До этого null
dstTokenId Целевой токен вашего правила автоконвертации, скопированный при открытии свипа. null, если правила нет
amountIn На сконвертированном свипе — проданные базовые единицы tokenId
amountOut На сконвертированном свипе — полученные базовые единицы dstTokenId
conversionSkipReason Почему свип, подпадающий под правило, прошёл без конвертации. null, если он сконвертирован или правила для него нет

Правила автоконвертации задаются в кабинете, в разделе Конвертация → Автоконвертация. Свип, подпадающий под такое правило, по пути на ваш баланс конвертируется в целевой токен правила, если это возможно. Если нет, он зачисляется в собственном токене, а conversionSkipReason объясняет почему:

conversionSkipReason Почему свип не сконвертирован
NO_VENUE Токен негде обменять
NO_QUOTE Для токена сейчас нет пригодной цены
BELOW_MINIMUM Сумма слишком мала для конвертации
ABOVE_LIQUIDITY_LIMIT Сумма больше, чем рынок принимает в пределах допустимого влияния на цену
GAS_TOO_HIGH Сетевая комиссия обмена заняла бы слишком большую долю суммы
SLIPPAGE Актуальная котировка оказалась ниже порога, допускаемого вашим проскальзыванием
PRICE_DEVIATION Актуальная котировка слишком далека от рыночной цены
SWAP_FAILED Обмен провалился в сети, и свип был повторён без конвертации
TARGET_NOT_HEDGE В целевой токен правила больше нельзя конвертировать
MERCHANT_BLOCKED Мерчант был заблокирован в момент открытия свипа
status Значение
PENDING Записан, но ещё не принят к переводу. Повторяется автоматически
SWEEPING Принят, деньги идут на ваш баланс
SWEPT Зачислено на ваш баланс: amount − fee, за вычетом сетевой комиссии на свипе токена. Терминальный
FAILED Перевод провалился или был отклонён, читайте error. Повторяется автоматически ограниченное число раз, если дело не в самом адресе
DUST Слишком мало, чтобы перемещать. Этот эндпоинт его не возвращает — в таком случае ответ 422
SUPERSEDED Свип FAILED или DUST, деньги которого переместил более поздний свип того же адреса и токена. Зачислил их тот свип, по этому не зачислено ничего, поэтому его amount никогда не учитывайте. Терминальный. Этот эндпоинт его не возвращает: в ответе только свипы, которые он запустил или застал активными

Свип может прийти со статусом FAILED уже в самом ответе 202, если его отклонили в момент запуска. 202 значит, что вызов принят, а не что каждый свип в нём идёт: проверяйте status у каждого.

На один адрес и токен одновременно идёт не больше одного активного (PENDING или SWEEPING) свипа. Если такой уже идёт — автоматический или от вашего предыдущего вызова, — он возвращается как есть, а новый свип запускается только для токенов, у которых его нет. Это не ошибка: trigger: "AUTO" в ответе на ваш вызов значит, что деньги уже были в пути. Поэтому повторять вызов безопасно.

Прочитать свип в пути отдельным запросом нельзя

Нет эндпоинта, возвращающего свип по id, и нет вебхука о нём, если только под свип не подпадает правило автоконвертации — тогда его sweepId несёт conversion.completed или conversion.skipped. Когда ответ сказал SWEEPING, следите за балансом. Повторный вызов этого эндпоинта возвращает свип, пока он активен; когда он завершился, следующий вызов уже не найдёт, что перемещать. Зачисленный свип появляется под тем же id в списке удержанных комиссий; проваленный не появляется нигде.

Если на адресе нет ничего чистого сверх пыли, вызов отвечает 422 NOTHING_TO_SWEEP и добавляет объект details с оценкой, чтобы было видно, насколько сумме не хватает:

{
  "statusCode": 422,
  "message": "NOTHING_TO_SWEEP",
  "timestamp": "2026-01-01T12:10:00.000Z",
  "details": {
    "amount": "150000",
    "feeEstimate": "2800000",
    "dust": [
      {
        "chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
        "tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
        "amount": "150000",
        "feeEstimate": "2800000"
      }
    ]
  }
}

amount — в базовых единицах токена, feeEstimate — в базовых единицах нативной монеты сети; пара верхнего уровня повторяет первый элемент dust. Если чистой суммы нет совсем, dust пуст, amount равен "0", а feeEstimate — null.

Статус message Причина
400 INVALID_PATH_PARAMETER walletId — не UUID
400 INVALID_REQUEST_BODY chainId или tokenId передан, но это не UUID
403 MERCHANT_BLOCKED Мерчант заблокирован. Автоматические свипы уже полученных денег продолжаются; отклоняется только этот вызов
404 WALLET_NOT_FOUND Такого адреса нет или он принадлежит другому мерчанту
409 WALLET_HAS_NO_OWNER Адрес не закреплён ни за одним мерчантом, зачислять некуда. Обратитесь в поддержку; повтор не поможет
422 NOTHING_TO_SWEEP На адресе нет ничего чистого сверх пыли. В details — оценка
422 SWEEP_AMOUNT_TOO_SMALL Здесь не ожидается: на пыль отвечает NOTHING_TO_SWEEP. Если всё же встретится, обрабатывайте так же
502 INTERNAL_ERROR Не удалось прочитать, что лежит на адресе. Ничего не запущено; можно повторить
502 INTERNAL_ERROR Не удалось найти токены, лежащие на адресе. Ничего не запущено; можно повторить

В отличие от остального API, адрес другого мерчанта даёт 404, а не 403: ответ не подтверждает, что такой id существует.