POST /apiv2/screening
Заказ AML-проверки для блокчейн-адреса. Это контракт версии 2: единая структура ответа для каждого провайдера и любого состояния заказа, десятичные числа в виде строк и единый формат ошибок.
Он заменяет собой POST /apiv2/aml, который продолжает работать и не будет отключен без предварительного уведомления.
URL эндпоинта
POST https://netts.io/apiv2/screeningЗаголовки запроса
| Заголовок | Обязательный | Описание |
|---|---|---|
| Content-Type | Да | application/json |
| X-API-KEY | Да | Ваш API-ключ из панели управления Netts |
| X-Idempotency-Key | Нет | Ваш собственный ключ для безопасных повторных попыток. См. Идемпотентность |
Тело запроса
{
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic",
"wait_for_result": true
}Параметры
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
| address | string | Да | Адрес для проверки, от 10 до 128 символов |
| network | string | Да | Тикер сети. Тикеры, поддерживаемые каждым провайдером, возвращаются методом GET /apiv2/screening/providers; полная таблица сетей с их названиями находится здесь |
| provider | string | Да | elliptic или bitok. Значения по умолчанию нет |
| wait_for_result | boolean | Нет | true ожидает результат до 15 секунд. По умолчанию false |
| language | string | Нет | Язык отчета. Только en |
Неизвестные поля отклоняются. Тело запроса, содержащее поле, отсутствующее в таблице выше, возвращает 400 с кодом 4001. В версии 1 неизвестные поля молча игнорировались, и опечатка в wait приводила к тому, что вызывающая сторона ждала результат, который ни при каких условиях не вернулся бы синхронно.
Параметр provider обязателен и не имеет значения по умолчанию. В версии 1 пропущенный провайдер означал Elliptic, из-за чего клиент, не сделавший выбор, платил за провайдера, которого он явно не указывал.
provider в схеме является произвольной строкой, а не перечислением (enum). На сегодняшний день принимаются два значения; добавление третьего провайдера не должно ломать интеграцию у тех, кто валидирует ответы по схеме. Актуальный список, поддерживаемые каждым провайдером сети и шкалы их оценок возвращаются методом GET /apiv2/screening/providers.
Примеры запросов
cURL — ожидание результата
curl -X POST https://netts.io/apiv2/screening \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-d '{
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic",
"wait_for_result": true
}'cURL — принятие в обработку и последующий опрос
curl -X POST https://netts.io/apiv2/screening \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-d '{
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "bitok"
}'Ответ — 202 Accepted с заголовком Location, указывающим на заказ.
Python
import requests
from decimal import Decimal
url = "https://netts.io/apiv2/screening"
headers = {
"Content-Type": "application/json",
"X-API-KEY": "your_api_key",
}
payload = {
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic",
"wait_for_result": True,
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code in (200, 202):
body = response.json()
print("Order:", body["order"]["client_order_id"])
print("Status:", body["order"]["status"])
if body["risk"]["score"] is not None:
# Parse provider numbers as Decimal, never as float
print("Score:", Decimal(body["risk"]["score"]), "of", body["risk"]["scale"]["max"])
print("Level:", body["risk"]["level"], "-", body["risk"]["level_source"])
print("Charged:", body["billing"]["charged_amount"], body["billing"]["charged_currency"])
else:
problem = response.json()
print(problem["code"], problem["title"], "-", problem["detail"])Коды ответов
| Ситуация | Код | Заголовки |
|---|---|---|
| Заказ создан, проверка выполняется в фоновом режиме | 202 Accepted | Location: /apiv2/screening/{client_order_id} |
Результат возвращен в ответе (wait_for_result) | 200 OK | — |
| Результат переиспользован из недавней проверки, списания не было | 200 OK | — |
| У адреса нет активности в блокчейне, списания не было | 200 OK | — |
| Ошибка | см. Ошибки | Content-Type: application/problem+json |
Ответы, содержащие результат проверки, отправляются с заголовком Cache-Control: private, no-store.
Ответ
{
"schema_version": 2,
"order": {
"client_order_id": "A90D21F68C9AEA2",
"status": "completed",
"api_version": "v2",
"cache_hit": false,
"created_at": "2026-09-13T08:14:29.614988Z",
"started_at": "2026-09-13T08:14:30.288251Z",
"completed_at": "2026-09-13T08:14:34.993174Z"
},
"request": {
"address": "YOUR_ADDRESS_HERE",
"network": "trx",
"provider": "elliptic"
},
"billing": {
"charged": true,
"price_usdt": "0.98",
"base_amount": "2.882421",
"markup_amount": "0",
"charged_amount": "2.882421",
"charged_currency": "TRX",
"exchange_rate": "0.33999200",
"payment_status": "pending"
},
"precheck": {
"activity_checked": true,
"activity_status": "active",
"source": "tron-address-checker"
},
"check": {
"provider": "elliptic",
"provider_check_id": "b4903a5d-9f22-4075-b51b-beb40b419b97",
"checked_at": "2026-09-13T08:14:32.144000Z",
"status": "completed",
"provider_status": "complete"
},
"risk": {
"score": "0.9634087310611608",
"scale": { "min": 0, "max": 10 },
"level": "low",
"level_source": "computed",
"provider_level": null,
"policy": "netts-risk-v1",
"by_direction": {
"source": "0.12332156899576921",
"destination": "0.9634087310611608"
}
},
"sanctions": {
"self": false,
"self_entities": null,
"exposure": {
"entity": "HTX (formerly Huobi) - Post-Sanctions - UK Sanctions List - 26 May 2026",
"category": "Exchange",
"share_fraction": "0.004409451392423414",
"proximity": "indirect",
"hops": 3,
"direction": "source",
"rule_name": "Sanctioned, TF & CSAM"
},
"items": []
},
"exposure": [
{
"category": "Exchange",
"share_fraction": "0.1888065152442461",
"direction": "source",
"hops": 2,
"is_screened_address": false,
"entities": [
{ "name": "Binance", "category": "Exchange", "is_vasp": true,
"is_primary": true, "is_after_sanction_date": null }
]
}
],
"rules": [
{ "name": "Sanctioned, TF & CSAM", "type": "exposure", "level": null,
"score": "0.061426483114820525", "share_fraction": null, "category": null,
"proximity": null, "direction": "source", "detected_at": null }
],
"entities": [
{ "name": "Unknown", "category": "Unknown", "is_vasp": null,
"is_primary": true, "is_after_sanction_date": false }
],
"primary_entity": {
"name": "Unknown", "category": "Unknown", "is_vasp": null,
"is_primary": true, "is_after_sanction_date": false
},
"sanctioned_entities": [],
"wallet": {
"inflow_usd": "354663.6116669158",
"outflow_usd": "80248.63081808022"
},
"provider_data": { }
}Набор полей никогда не меняется
Каждый блок, перечисленный выше, присутствует в каждом ответе, независимо от провайдера и состояния заказа. То, что провайдер не предоставляет, принимает значение null; пустой список возвращается как [], а не null; блок, для которого еще нет данных, заполняется значениями null, а не опускается. Один и тот же обработчик может разобрать как только что принятую проверку, так и ту же самую проверку после ее завершения.
Два следствия для вашего кода:
- игнорируйте неизвестные вам поля. Новые поля добавляются в эти блоки без выпуска новой версии API. Ошибка из-за наличия неизвестного поля — это проблема на вашей стороне, а не на нашей;
provider_dataне является частью контракта. Его структура зависит от провайдера и меняется при смене провайдера. Все, что гарантируется контрактом, находится в описанных выше блоках.
order
| Поле | Тип | Описание |
|---|---|---|
| client_order_id | string | Идентификатор заказа, используется для получения результата позже |
| status | string | pending, processing, completed, skipped, failed |
| api_version | string | Версия контракта, создавшего заказ |
| cache_hit | boolean | true, если был повторно использован недавний результат и списания средств не было |
| created_at | string | RFC 3339, UTC, микросекунды |
| started_at | string | null | Время начала вызова провайдера. null для skipped |
| completed_at | string | null | Время получения результата |
| error | string | Только для failed: причина сбоя |
| reason | string | Только для skipped: address_inactive |
Все временные метки представлены в UTC, по стандарту RFC 3339, с суффиксом Z и точностью до микросекунд.
billing
| Поле | Тип | Описание |
|---|---|---|
| charged | boolean | Было ли списание средств |
| price_usdt | string | Базовая цена провайдера в USDT |
| base_amount | string | Стоимость заказа в валюте списания, без наценки субаккаунта |
| markup_amount | string | Наценка субаккаунта. "0" для прямого аккаунта |
| charged_amount | string | Сумма, фактически списанная с баланса |
| charged_currency | string | TRX |
| exchange_rate | string | null | Курс, использованный для конвертации |
| payment_status | string | paid, pending, failed, not_charged |
payment_status имеет значение pending в течение короткого времени после успешной проверки: списание сначала холдируется и подтверждается в течение часа. failed означает, что средства были возвращены. not_charged означает, что списание даже не создавалось — при повторном использовании результата или пропуске адреса.
precheck
Перед платной проверкой адрес проверяется на наличие активности в блокчейне. Адрес без активности не отправляется провайдеру, и средства не списываются.
| Поле | Тип | Описание |
|---|---|---|
| activity_checked | boolean | Выполнялась ли проверка. false для сетей, где она отсутствует |
| activity_status | string | active, inactive, unknown |
| source | string | null | Название механизма |
Значение unknown не останавливает платную проверку: если сервис проверки активности недоступен, адрес считается активным.
check
| Поле | Тип | Описание |
|---|---|---|
| provider | string | Провайдер, выполнивший проверку |
| provider_check_id | string | null | Собственный идентификатор провайдера — укажите его при оспаривании результата с ним |
| checked_at | string | null | Время, когда провайдер сформировал результат |
| status | string | См. таблицу ниже |
| provider_status | string | null | Исходный статус от провайдера, без изменений |
order.status | check.status | Значение |
|---|---|---|
pending | pending | Заказ принят, еще не запущен |
processing | running | Провайдер выполняет обработку |
completed | completed | Результат получен |
failed | failed | Отклонено до или во время вызова провайдера |
skipped | not_performed | У адреса нет активности; обращение к провайдеру не выполнялось, списания не было |
risk
| Поле | Тип | Описание |
|---|---|---|
| score | string | null | Собственная оценка провайдера в виде десятичной строки |
| scale | object | min и max для шкалы этого провайдера |
| level | string | none, low, medium, high, severe |
| level_source | string | computed — уровень рассчитан нами на основе оценки; provider — уровень указан провайдером |
| provider_level | string | null | Исходное обозначение уровня от провайдера, если оно возвращается |
| policy | string | Название политики пороговых значений, netts-risk-v1 |
| by_direction | object | Оценка с разделением на source и destination, если провайдер поддерживает такое разделение |
Оценка риска никогда не масштабируется. Шкала Elliptic составляет от 0 до 10, а BitOK — от 0 до 1, и значение 7 по одной шкале ни в каком смысле не эквивалентно 0.7 по другой. Шкала передается в ответе для того, чтобы интеграция, написанная под одного провайдера, не интерпретировала неверно данные другого после простого изменения конфигурации.
Уровень риска приведен к единому словарю во всем API. Если провайдер возвращает собственный уровень, мы передаем его и указываем это в level_source; если же нет, мы рассчитываем уровень по оценке с использованием порогов netts-risk-v1 и указываем соответствующий источник. Одно и то же слово используется в ответе API, панели управления и в PDF-отчете для одной и той же проверки.
exposure[], rules[], entities[]
exposure[] детализирует средства по категориям контрагентов. rules[] перечисляет сработавшие правила провайдера. entities[] содержит сущности, к которым принадлежит сам адрес; primary_entity выбирает одну из них по фиксированному правилу — сущность, которую провайдер отметил как основную, иначе первую из списка, иначе null. sanctioned_entities[] содержит те сущности из entities[], которые помечены как активные после даты введения санкций.
Доли выражаются дробями, а не процентами
Каждая доля в ответе представлена единым полем share_fraction — десятичной строкой в диапазоне от "0" до "1".
Elliptic reports 31.574732212596924 % -> "share_fraction": "0.31574732212596924"
BitOK reports 0.8488 -> "share_fraction": "0.8488"У провайдеров различаются единицы измерения: одна и та же треть связи приходит как 31.57 от одного и как 0.3157 от другого. Единое поле, содержащее оба варианта, было бы невозможно корректно разобрать без знания конкретного провайдера. Исходное число провайдера в его собственных единицах измерения сохраняется в provider_data.
Числа передаются строками
Каждое число, полученное от провайдера — оценки, доли, объемы в USD и каждая сумма в блоке billing — передается в виде десятичной строки.
"score": "0.9634087310611608"Разбор такого значения как числа JSON в JavaScript, Go или любом другом языке с двоичной плавающей запятой приведет к погрешности округления, и выводимое вами значение перестанет совпадать со значением, выданным провайдером. Разбирайте эти поля с помощью типа для точных десятичных вычислений: Decimal в Python, BigDecimal в Java, decimal.Decimal или оставляйте в виде строки в JavaScript.
Собственные поля сервиса (не от провайдера) — scale.min, scale.max, hops — передаются обычными числами JSON.
Повторное использование недавнего результата
При проверке того же адреса, сети и провайдера в течение 60 секунд возвращается предыдущий результат, и списание средств не производится.
Каждый запрос по-прежнему создает собственный заказ со своим client_order_id; повторно использованный заказ помечается как "cache_hit": true, а его блок billing содержит "charged": false со статусом "payment_status": "not_charged". Идентификатор заказа, из которого был взят результат, не раскрывается — он может принадлежать другому аккаунту.
Повторное использование действует только в рамках одного аккаунта. Результат проверки, выполненной кем-то другим, вам никогда не вернется.
Идемпотентность
Передавайте заголовок X-Idempotency-Key с собственным значением для безопасных повторных попыток: тот же ключ с тем же телом запроса возвращает сохраненный ответ вместо создания второго заказа на проверку.
| Ситуация | Код | Ответ |
|---|---|---|
| Первый запрос с этим ключом еще выполняется | 409 | 4090 |
| Тот же ключ, но другое тело запроса | 409 | 4093 |
| Тот же ключ, то же тело запроса, уже завершен | сохраненный код | сохраненный ответ |
Если заголовок не передан, ключ генерируется автоматически на основе API-ключа, адреса, провайдера и вашего IP-адреса в рамках двухсекундного окна. Это защищает от двойного клика и повторных запросов шлюза, но не от повторного запроса через минуту: такой запрос будет считаться новым полноценным заказом и будет тарифицирован.
Область действия ключей ограничена конкретным эндпоинтом. Одно и то же значение, отправленное в POST /apiv2/aml и в этот эндпоинт — это два независимых условия для двух разных запросов: тела различаются, как и ответы. Повторное использование вашего ключа при переводе интеграции с версии 1 на версию 2 безопасно: оно не вернет ответ версии 1 и не будет расценено как использование того же ключа с другим телом запроса.
Ошибки
Все ошибки приложения формируются по стандарту RFC 9457 с заголовком Content-Type: application/problem+json:
{
"type": "https://doc.netts.io/api/v2/errors/insufficient-funds",
"title": "Insufficient funds",
"status": 403,
"detail": "Insufficient funds. Required: 2.888742 TRX, Available: 1.203000 TRX",
"instance": "/apiv2/screening",
"code": 1004,
"required_trx": "2.888742",
"available_trx": "1.203000"
}type, title, status, detail и instance — стандартные поля. Числовой code сохранен как расширение, чтобы интеграции, написанные под версию 1, могли продолжать сопоставлять ошибки по нему. Дополнительные поля зависят от конкретной ошибки и должны игнорироваться, если они вам неизвестны.
| Код | HTTP | Значение |
|---|---|---|
4000 | 400 | Тело запроса не является валидным JSON |
4001 | 400 | Поле не прошло валидацию или передано неизвестное поле |
4002 | 403 | Провайдер недоступен для вашего аккаунта |
4003 | 400 | Некорректный формат идентификатора заказа |
4004 | 400 | Провайдер не поддерживает запрошенную сеть |
4010 | 401 | API-ключ не передан |
4011 | 401 | API-ключ или IP-адрес не приняты |
4040 | 404 | Заказ не найден |
4041 | 404 | Аккаунт не найден |
4090 | 409 | Запрос с этим ключом идемпотентности все еще выполняется |
4091 | 409 | Дублирующийся запрос |
4093 | 409 | Этот ключ идемпотентности был использован с другим телом запроса |
1004 | 403 | Недостаточно средств на балансе |
5000 | 500 | Внутренняя ошибка |
5001 | 500 | Не удалось провести списание |
5002 | 500 | Заказ не был создан |
5030 | 503 | Провайдер недоступен |
Ошибки другого формата
Некоторые сбои происходят на уровне шлюза до того, как запрос достигнет приложения, и они сохраняют исходную структуру шлюза. Любой ответ, у которого Content-Type не равен application/problem+json, следует считать одним из следующих случаев:
| Ситуация | HTTP | Тело ответа |
|---|---|---|
| API-ключ отсутствует или не принят | 401 | {"detail":{"code":-1,"msg":"Invalid or missing API key"}} |
| Превышен лимит запросов | 429 | {"message":"API rate limit exceeded"} |
| Неизвестный путь или метод, не поддерживаемый маршрутом | 404 / 405 | {"detail":"Method Not Allowed"} |
Ограничение частоты запросов
Лимит является общим с POST /apiv2/aml и другими AML-маршрутами: 5 запросов в секунду и 150 в минуту. Переход на этот эндпоинт не выделяет отдельный лимит.
Примечания
- Стоимость: Elliptic $0.98, BitOK $0.50 за проверку, списывается с баланса TRX по курсу на момент списания.
- Время обработки: большинство проверок завершается за несколько секунд; проверка адреса с длинной историей может занять до трех минут. Используйте асинхронный режим и получайте результат с помощью GET /apiv2/screening/{client_order_id}.
- Неактивные адреса возвращают статус
skipped, средства за них не списываются. - Исходный ответ провайдера никогда не возвращается. Поле
provider_data— это отфильтрованная проекция; данные, относящиеся к нашему аккаунту у провайдера, а не к проверяемому адресу, никому не раскрываются.
Отчеты
Эндпоинт возвращает только JSON и ничего больше. Форматы PDF и Markdown не поддерживаются.
Все составляющие отчета уже содержатся в ответе: унифицированный блок и provider_data. Генерация отчета на вашей стороне позволяет сформировать документ именно в том виде, который вам нужен — с вашим брендингом, языком и структурой, — что особенно актуально при перепродаже проверок, поскольку отчет с нашим брендом — не тот документ, который стоит передавать вашему конечному клиенту.
Если вам нужен отчет как подтверждающий документ для третьей стороны — банка, регулятора, контрагента, — обратите внимание: неподписанный PDF не имеет юридической силы, кто бы его ни создал: его можно отредактировать в текстовом редакторе за минуту. Для проверяемого документа требуется цифровая подпись или публичная страница верификации, а это отдельная функциональность. Если вам необходимо такое решение, сообщите нам о требованиях вашего контрагента.
В панели управления Netts доступны человекочитаемые PDF-отчеты по тем же самым проверкам на семнадцати языках.
См. также
- GET /apiv2/screening/{client_order_id} — получение результата проверки
- GET /apiv2/screening/history — список ваших проверок с курсорной пагинацией
- GET /apiv2/screening/providers — провайдеры, цены, сети, шкалы оценок
- GET /apiv2/screening/price — стоимость проверки у конкретного провайдера
- Санкции в AML-результате — значение блока
sanctionsи флагов провайдера