Finance OS / API

Проверка адреса

Синхронно проверяет блокчейн-адрес, сохраняет запись и возвращает полный обезличенный отчёт в теле ответа 201. Идентификатор проверки (id) используйте для повторного получения отчёта и выгрузки PDF - см. Отчёты и PDF.

POST /api/v1/aml/checks
Bearer Token

Проверить блокчейн-адрес

Один запрос выполняет всю оценку и возвращает готовый отчёт. Очереди и опроса результата нет.

Body

address string required
Блокчейн-адрес. Максимум 120 символов.
network string optional
Сеть адреса: tron, ethereum, bitcoin, litecoin и т.д. Максимум 20 символов. Если не указана - сеть определяется по формату адреса; для адресов, валидных в нескольких сетях (например EVM-совместимых), указывайте явно.

Заголовки

Idempotency-Key string optional
Необязательный ключ повтора (до 80 символов). Запрос с уже использованным ключом возвращает ту же проверку и не выполняет её повторно. Ключ действует в пределах вашей учётной записи и окружения (live и sandbox не пересекаются).
Безопасный ретрай
Сетевой таймаут не означает, что проверка не выполнена. Повторяйте запрос с тем же Idempotency-Key - в ответ придёт ранее выполненная проверка. Без заголовка каждый запрос считается новым вызовом.

Responses

{
  "data": {
    "id": "b7e4c1a2-3f5d-4e8a-9c1b-2d6f7a8e0b3c",
    "address": "TXYZ1234567890abcdefGHIJKLmnop",
    "network": "tron",
    "risk_score": 64,
    "risk_level": "high",
    "report": {
      "risk_score": 64,
      "risk_level": "high",
      "recommendation": "reject",
      "signals": {
        "sanctioned": false,
        "watchlisted": false,
        "watchlist_category": null,
        "community_reports": 4,
        "high_risk_counterparties": 1,
        "fatf_country": true,
        "is_stablecoin_contract": false,
        "defi_exposure_score": 24
      },
      "factors": [
        { "code": "cluster_risk",    "label": "Кластерный риск",                        "score": 4  },
        { "code": "direct_exposure", "label": "Прямая экспозиция к рисковым адресам",    "score": 14 },
        { "code": "anomaly_ml",      "label": "Поведенческие ML-аномалии",               "score": 64 }
      ],
      "networks": [
        {
          "network": "tron",
          "native_balance": 0,
          "tx_count": 231,
          "first_tx_at": null,
          "last_tx_at": null,
          "is_contract": false
        }
      ],
      "balances": {
        "USDT": 0,
        "USDC": 0,
        "tx_count": 231,
        "account_age_days": 512
      },
      "checked_at": "2026-07-13T10:24:00+00:00",
      "sandbox": true
    },
    "price": null,
    "pricing": { "markup_pct": 0, "price": null, "currency": "USDT" },
    "report_url": "https://fin-os.io/api/v1/aml/checks/b7e4c1a2-3f5d-4e8a-9c1b-2d6f7a8e0b3c/report.pdf",
    "created_at": "2026-07-13T10:24:00+00:00"
  }
}
{
  "error": {
    "code": "service_balance_insufficient",
    "message": "Недостаточно средств на сервисном счёте. Пополните мастер-счёт.",
    "request_id": "req_3Nd8Vc1Xz5Bm7Kq0Lp2Rt6W",
    "currency": "USDT",
    "required": "0.2",
    "available": "0.0500000000"
  }
}

Поле report.sandbox присутствует и равно true только в sandbox-ответах; в live его нет.

Вердикт: score → level → recommendation

risk_score (0-100) платформа сама отображает в risk_level, а risk_level - в готовую рекомендацию recommendation. Пороги перевода балла в уровень - настройка платформы: они пересматриваются по мере накопления данных и в публичном контракте не фиксируются. Поэтому в своей логике опирайтесь на risk_level и recommendation и не сравнивайте risk_score с собственными константами - иначе после очередной калибровки ваши правила разойдутся с решением платформы.

Уровень и рекомендация (связь жёсткая)

low allow optional
Пропустить операцию.
medium review optional
Отдать на ручную проверку.
high reject optional
Отклонить операцию.
critical block optional
Заблокировать.
В песочнице скоринг синтетический
Под ключом sk_test_ балл и уровень вычисляются детерминированно по самому адресу и служат только для проверки формата ответа и вашей обработки. С боевыми значениями по тому же адресу они не совпадают, поэтому пороговые правила, «подобранные» на песочнице, в бою работать не будут.
Санкции и критический риск
При попадании в санкционные списки (signals.sanctioned = true) и при risk_level: critical рекомендация всегда block. Такие адреса блокируйте жёстко на своей стороне - не полагайтесь только на «мягкие» пороги.

signals

Плоский набор булевых и числовых сигналов - самые «читаемые» поля для быстрых правил на вашей стороне.

report.signals

sanctioned bool optional
Адрес найден в санкционных списках.
watchlisted bool optional
Адрес найден в watchlist.
watchlist_category string|null optional
Категория watchlist-совпадения (например high_risk_entity) или null.
community_reports int optional
Число публичных жалоб сообщества на адрес.
high_risk_counterparties int optional
Число контрагентов с высоким риском.
fatf_country bool optional
Связь с юрисдикцией из индикаторов FATF.
is_stablecoin_contract bool optional
Адрес является контрактом стейблкоина.
defi_exposure_score int|null optional
Оценка экспозиции к DeFi (0-100) или null.

factors

Разложение скоринга по факторам. Каждый элемент - { code, label, score }, где score - вклад фактора (0-100). Состав массива зависит от адреса и может пополняться, поэтому обрабатывайте его как список, а не как фиксированный набор ключей. Возможные коды:

report.factors[].code

cluster_risk optional
Кластерный риск.
direct_exposure optional
Прямая экспозиция к рисковым адресам.
community_reports optional
Публичные жалобы сообщества.
balance_risk optional
Риск по балансу и активности.
fatf_indicators optional
Юрисдикционные индикаторы (FATF).
historical_coin_risk optional
Исторический риск актива.
anomaly_ml optional
Поведенческие ML-аномалии.
loo_toxicity optional
Токсичность окружения.
multihop_trace optional
Многоступенчатая трассировка.
bridge_contamination optional
Контаминация через кросс-чейн мосты.
defi_exposure optional
DeFi-экспозиция.
time_of_day_pattern optional
Временной паттерн активности.
velocity_decay optional
Затухание скорости операций.
token_mix_entropy optional
Энтропия набора токенов.
realtime_security optional
Реал-тайм проверка безопасности контракта.
intelligence_verdicts optional
Сводные вердикты разведданных.
deprecated_stablecoins optional
Устаревшие стейблкоины.

networks и balances

report.networks[] - агрегаты активности по каждой затронутой сети.

report.networks[]

network string optional
Сеть: tron, ethereum, bitcoin, litecoin и т.д.
native_balance number optional
Баланс нативной монеты сети.
tx_count int optional
Число транзакций в этой сети.
first_tx_at datetime|null optional
Первая транзакция (ISO 8601) или null.
last_tx_at datetime|null optional
Последняя транзакция (ISO 8601) или null.
is_contract bool optional
Адрес - смарт-контракт.

report.balances - сводные балансы и метрики по адресу.

report.balances

USDT number optional
Баланс USDT.
USDC number optional
Баланс USDC.
tx_count int optional
Совокупное число транзакций.
account_age_days int|null optional
Возраст адреса в днях или null.

price и pricing

Каждый ответ несёт стоимость выполненной проверки. Пример выше - sandbox, поэтому оба поля null: песочница не тарифицируется.

price string|null optional
Сумма, фактически списанная с мастер-счёта за эту проверку. null - вызов был бесплатным (песочница, освобождение по договору или обязательная проверка платформы).
pricing.price string|null optional
То же значение внутри объекта тарифа.
pricing.currency string optional
Валюта списания - всегда USDT.
pricing.markup_pct number optional
Историческое поле, всегда 0. Проверка - ваш расход, а не товар для перепродажи: надбавки у неё нет.

Цена фиксируется в момент проверки и в сохранённом отчёте больше не меняется, даже если тариф впоследствии пересмотрят. Сумма списания видна и в леджере мастер-счёта - строкой типа aml_fee (см. Merchant Balance). Если проверка не дала вердикта, там же появится парная строка aml_fee_refund.

Не хватило средств - 402
Списание идёт до оценки, поэтому пустой мастер-счёт останавливает проверку кодом 402 service_balance_insufficient. Ответ содержит currency, required и available - показывайте нехватку пользователю как есть и пополняйте счёт.

Примеры кода

# Проверить адрес (sandbox - по префиксу sk_test_)
curl -X POST https://fin-os.io/api/v1/aml/checks \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"address": "TXYZ1234567890abcdefGHIJKLmnop", "network": "tron"}'
const FINOS = 'https://fin-os.io/api/v1';
const KEY   = process.env.FINOS_SECRET_KEY; // sk_test_ (sandbox) или sk_live_ (live)

async function checkAddress(address, network) {
  const res = await fetch(`${FINOS}/aml/checks`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ address, network }),
  });
  const { data } = await res.json();

  // Готовое решение по единому вердикту:
  if (data.report.recommendation === 'block' || data.report.recommendation === 'reject') {
    console.warn(`Отклонить: ${address} - ${data.risk_level} (${data.risk_score})`);
  }
  console.log('PDF-отчёт:', data.report_url);
  return data;
}

checkAddress('TXYZ1234567890abcdefGHIJKLmnop', 'tron');
use Illuminate\Support\Facades\Http;

class FinosAml
{
    private string $base = 'https://fin-os.io/api/v1';

    public function __construct(private string $secretKey) {}

    public function check(string $address, ?string $network = null): array
    {
        return Http::withToken($this->secretKey)
            ->post("{$this->base}/aml/checks", array_filter([
                'address' => $address,
                'network' => $network,
            ]))
            ->json('data');
    }
}

$aml   = new FinosAml(config('services.finos.secret_key'));
$check = $aml->check('TXYZ1234567890abcdefGHIJKLmnop', 'tron');

if (in_array($check['report']['recommendation'], ['reject', 'block'], true)) {
    // задержать операцию / отправить на ручной комплаенс
}
import requests, os

FINOS = 'https://fin-os.io/api/v1'
KEY   = os.environ['FINOS_SECRET_KEY']  # sk_test_ (sandbox) или sk_live_ (live)
H     = {'Authorization': f'Bearer {KEY}'}

def check_address(address, network=None):
    payload = {'address': address}
    if network:
        payload['network'] = network
    r = requests.post(f'{FINOS}/aml/checks', headers=H, json=payload)
    r.raise_for_status()
    return r.json()['data']

data = check_address('TXYZ1234567890abcdefGHIJKLmnop', 'tron')
print(data['risk_level'], data['risk_score'], data['report']['recommendation'])