Finance OS / API

Merchant Balance

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

Зачем он нужен
  • Платные вызовы - AML-проверки списываются отсюда. Пустой счёт означает 402 на каждой проверке.
  • Выплаты клиентам - операция payout в Customer Wallets идёт за счёт мастер-счёта.
  • Ваш доход - надбавки, удержанные при выводах клиентов, и суммы charge зачисляются сюда.
Доступ не зависит от остальных сервисов
Раздел мастер-счёта работает, даже если у вашей учётной записи закрыты кошельки клиентов: иначе вы не смогли бы пополнить счёт, чтобы оплатить услугу. Чтение доступно любому ключу вашей учётной записи; операции с деньгами - только владельцу или администратору (см. Права).

Валюты счёта

На счёте по одной строке на валюту. Расчётная строка - USDT: именно с неё списываются услуги платформы, и она присутствует в ответе всегда, даже нулевая. USDT из любой сети падает в одну строку USDT - сеть фиксируется в записи леджера, но отдельной строки баланса не создаёт. Остальные монеты ведутся своим кодом: токен - {ASSET}_{NETWORK} (USDC_BSC), нативная монета - тикером (BTC).

Баланс

GET /api/v1/merchant/balance
Bearer Token

Строки всех валют мастер-счёта

Responses

{
  "data": [
    { "currency": "USDT",     "available": "312.5000000000", "locked": "0.0000000000" },
    { "currency": "USDC_BSC", "available": "40.0000000000",  "locked": "0.0000000000" }
  ]
}
{
  "data": [
    { "currency": "USDT", "available": "0", "locked": "0" }
  ],
  "sandbox": true
}

available - доступно к расходованию, locked - зарезервировано. В песочнице ответ синтетический и всегда содержит признак sandbox: true: реальных балансов тестовый ключ не касается.

Пополнение

Счёт пополняется двумя способами: переводом криптовалюты на выданный вам адрес и внутренним переводом со счёта финансирования владельца учётной записи.

Адрес пополнения

POST /api/v1/merchant/balance/deposit-address
Bearer Token

Выдать адрес пополнения мастер-счёта в указанной сети

Идемпотентно: повторный вызов с той же сетью возвращает тот же адрес. Доступно владельцу и администратору.

Body

chain string required
Сеть пополнения. Допустимы только сети, в которых вам доступны стейблкоины: TRON, ETHEREUM, BSC, POLYGON, BASE. Неподдерживаемая сеть - 422 coin_not_allowed.

Responses

{
  "data": {
    "network": "TRON",
    "address": "TJmVfKq3Zx8Yb1nQpR6cW4hS2dT7uL0aE9"
  }
}
Отправляйте только поддерживаемые монеты
Адрес принимает стейблкоины той сети, в которой он выдан. Перевод в другой сети или отправка неподдерживаемого токена на этот адрес не зачисляется. Зачисление 0-conf: баланс растёт сразу после появления транзакции в сети, о чём приходит вебхук merchant.balance.credited.

Перевод со счёта финансирования

POST /api/v1/merchant/balance/topup-from-treasury
Bearer Token

Пополнить мастер-счёт с личного счёта владельца

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

Заголовки

Idempotency-Key string required
Обязателен. Повтор с тем же ключом и теми же параметрами возвращает результат первой проводки с duplicate: true и денег не двигает; тот же ключ с другой суммой или сетью - 409 conflict.
, "+10", "10." отклоняются кодом 422.'], ['name' => 'chain', 'type' => 'string', 'default' => 'TRON', 'desc' => 'Сеть, из которой берутся средства владельца.'], ['name' => 'currency', 'type' => 'string', 'desc' => 'На пополнении игнорируется: мастер-счёт пополняется стейблом и зачисление всегда идёт в строку USDT.'], ]" />

Responses

{
  "data": {
    "direction": "topup",
    "network": "TRON",
    "currency": "USDT",
    "amount": "100",
    "balance": "412.5000000000",
    "wallet_balance": "900.00000000",
    "duplicate": false
  }
}
{
  "error": {
    "code": "insufficient_funds",
    "message": "Недостаточно средств для операции.",
    "request_id": "req_7Kp2Rt9Wx4Yz1Qb6Nm3Vc8L"
  }
}

Вывод с мастер-счёта

POST /api/v1/merchant/balance/transfer-to-treasury
Bearer Token

Перевести средства мастер-счёта на счёт финансирования владельца

Обратная операция к пополнению: деньги уходят на личный счёт владельца учётной записи, откуда он распоряжается ими обычным порядком. Доступно владельцу и администратору.
, "+10", "10." отклоняются кодом 422.'], ['name' => 'currency', 'type' => 'string', 'default' => 'USDT', 'desc' => 'Валюта строки мастер-счёта: USDT, USDC_BSC, BTC и т.д. Выйти можно любой валютой, которая есть на счёте.'], ['name' => 'chain', 'type' => 'string', 'default' => 'TRON', 'desc' => 'Нужна только для агрегированной строки USDT - она одна на все сети, и сеть выхода надо указать. Составной код (USDC_BSC) и нативные монеты несут сеть в себе.'], ]" />

Заголовки

Idempotency-Key string required
Обязателен, правила те же, что у пополнения.

Responses

{
  "data": {
    "direction": "transfer_out",
    "network": "TRON",
    "currency": "USDT",
    "amount": "50",
    "balance": "362.5000000000",
    "wallet_balance": "950.00000000",
    "duplicate": false
  }
}
{
  "error": {
    "code": "service_balance_insufficient",
    "message": "Недостаточно средств на сервисном счёте. Пополните мастер-счёт.",
    "request_id": "req_M9n8B7v6C5x4Z3a2S1d0F1gH",
    "currency": "USDT",
    "required": "50",
    "available": "12.4000000000"
  }
}

Поля ответа обоих переводов

direction enum optional
topup - деньги пришли на мастер-счёт, transfer_out - ушли с него.
network string optional
Сеть, в которой прошёл внутренний перевод.
currency string optional
Валюта строки мастер-счёта.
amount string optional
Реально проведённая сумма. При повторе ключа возвращается сумма первой проводки, а не переданная в повторе.
balance string optional
Остаток мастер-счёта после операции.
wallet_balance string optional
Остаток счёта финансирования владельца после операции.
duplicate bool optional
true - это повтор по уже использованному ключу, движения денег не было.
Он-чейн вывода с мастер-счёта нет
Мастер-счёт выводится только внутренним переводом на счёт финансирования владельца. Отправить средства мастер-счёта напрямую на внешний блокчейн-адрес через API нельзя.
Поведение в песочнице
Тестовым ключом все эндпоинты раздела отвечают синтетикой с признаком sandbox: true и реальных балансов не касаются: баланс всегда нулевой, леджер пуст. Форма ответа внутренних переводов при этом совпадает с боевой - приходят и wallet_balance, и duplicate, только значения синтетические (оба баланса "0", duplicate: false), поэтому строгий разбор ответа можно отлаживать прямо в песочнице. Тариф (service-pricing) в обоих окружениях отдаёт одни и те же действующие условия.

Леджер

GET /api/v1/merchant/balance/transactions
Bearer Token

Движения по мастер-счёту (курсорная выборка)

Записи отдаются от новых к старым. Пагинация курсорная: передавайте starting_after из поля meta.next_cursor, пока meta.has_more равно true.

Query parameters

type string optional
Фильтр по типу операции (см. таблицу ниже). Неизвестное значение - 422 invalid_request.
currency string optional
Фильтр по валюте строки, например USDT.
from date optional
Начало периода включительно (с начала суток).
to date optional
Конец периода включительно (до конца суток).
per_page integer optional
Размер страницы, 1..100.
Default: 25
starting_after string optional
Курсор: id последней полученной записи. Продолжает выборку строго после неё.

Типы операций (поле type)

topup_deposit credit optional
Пополнение переводом на адрес мастер-счёта.
topup_internal credit optional
Пополнение со счёта финансирования владельца.
charge credit optional
Списание с клиента в вашу пользу.
withdraw_markup credit optional
Ваша надбавка, удержанная при выводе клиента. Фиксированная сумма по монете, в единицах этой монеты.
aml_fee_refund credit optional
Возврат платы за проверку, которая не дала результата.
aml_fee debit optional
Плата за AML-проверку.
payout debit optional
Выплата клиенту.
transfer_out debit optional
Перевод на счёт финансирования владельца.
adjustment credit / debit optional
Корректировка, проведённая оператором платформы. В description указано основание.

Responses

{
  "data": [
    {
      "id": "0b9a7c31-58d2-4e6f-9a11-c4d8e2f60b73",
      "currency": "USDT",
      "type": "aml_fee",
      "direction": "debit",
      "amount": "0.2000000000",
      "balance_after": "312.3000000000",
      "description": "AML-проверка адреса",
      "created_at": "2026-08-17T13:02:44+00:00"
    },
    {
      "id": "f41c8de0-2b57-4a90-8c63-71e5a9d04b28",
      "currency": "USDT",
      "type": "charge",
      "direction": "credit",
      "amount": "12.5000000000",
      "balance_after": "312.5000000000",
      "description": "Списание с клиента 5f8d7a3c-1234-4567-89ab-cdef01234567 - Абонентская плата за август",
      "created_at": "2026-08-17T12:40:55+00:00"
    }
  ],
  "meta": {
    "per_page": 25,
    "returned": 2,
    "has_more": false,
    "next_cursor": null
  }
}

Поля записи

id string optional
Идентификатор записи (UUID). Используется как курсор.
currency string optional
Валюта строки счёта.
type enum optional
Смысл операции (таблица выше).
direction enum optional
credit - зачисление, debit - списание. Знак несёт именно это поле, amount всегда положительна.
amount string optional
Сумма операции.
balance_after string optional
Остаток строки счёта сразу после операции.
description string|null optional
Человекочитаемое описание. Для charge и payout включает переданный вами reason.
created_at datetime optional
Время операции (ISO 8601).

Тариф и лимиты

GET /api/v1/merchant/service-pricing
Bearer Token

Действующие цены, комиссии и лимиты вашей учётной записи

Единая витрина условий: цена платного вызова, комиссии вывода по каждой доступной монете, ваша надбавка по каждой монете, лимиты и порог предупреждения о низком остатке. Значения актуальны на момент запроса.

Responses

{
  "data": {
    "currency": "USDT",
    "aml": {
      "check_price": "0.2",
      "billing_enabled": true,
      "exempt": false,
      "charged": true
    },
    "withdraw": {
      "fees": [
        { "asset": "USDT", "network": "TRON",     "fee": "5",  "min_withdraw": "10.000000000000000000", "min_deposit": "1.000000000000000000",  "native": false },
        { "asset": "USDT", "network": "BSC",      "fee": "1",  "min_withdraw": "10.000000000000000000", "min_deposit": "1.000000000000000000",  "native": false },
        { "asset": "USDT", "network": "ETHEREUM", "fee": "10", "min_withdraw": "20.000000000000000000", "min_deposit": "10.000000000000000000", "native": false },
        { "asset": "BTC",  "network": "BITCOIN",  "fee": "0",  "min_withdraw": "0.000300000000000000",  "min_deposit": "0.000100000000000000",  "native": true }
      ],
      "limits": {
        "max_per_tx": "10000",
        "daily_per_customer": "30000"
      },
      "markup": [
        { "coin_id": 1, "label": "USDT / TRON",     "asset": "USDT", "network": "TRON",     "native": false, "fee": "5",  "markup": "1.0000000000", "max": "10.0000000000", "editable": true },
        { "coin_id": 2, "label": "USDT / BSC",      "asset": "USDT", "network": "BSC",      "native": false, "fee": "1",  "markup": "0.5000000000", "max": "10.0000000000", "editable": true },
        { "coin_id": 3, "label": "USDT / ETHEREUM", "asset": "USDT", "network": "ETHEREUM", "native": false, "fee": "10", "markup": "0.0000000000", "max": "10.0000000000", "editable": true },
        { "coin_id": 4, "label": "BTC / BITCOIN",   "asset": "BTC",  "network": "BITCOIN",  "native": true,  "fee": "0",  "markup": "0.0000000000", "max": "0.0000000000",  "editable": false }
      ]
    },
    "balance": {
      "low_threshold": "5"
    }
  }
}

Комиссии, лимиты и минимумы выше - пример ответа, а не ваш тариф: действующие значения учётной записи отдаёт этот же вызов, его и опрашивайте перед показом условий клиенту.

Надбавка задаётся суммой по каждой монете
Ваша надбавка на вывод - фиксированная сумма в единицах выводимой монеты, отдельная для каждой пары монета+сеть, а не процент от суммы: работа платформы и сети на выводе 10 и 10 000 USDT одинакова. Верхнюю границу по каждой монете устанавливает платформа (поле max); у монет с нулевой границей надбавка недоступна - editable: false. На пополнение надбавки нет: зачисление бесплатно. AML-проверки - ваш расход, а не товар: перепродавать их с надбавкой нельзя.

Поля витрины

currency string optional
Расчётная валюта услуг платформы.
aml.check_price string optional
Цена одной платной AML-проверки.
aml.billing_enabled bool optional
Тарификация проверок включена на платформе.
aml.exempt bool optional
Ваша учётная запись освобождена от оплаты проверок.
aml.charged bool optional
Итог: будет ли списана плата за ваш следующий боевой вызов проверки.
withdraw.fees[] array optional
Строка на каждую доступную монету: fee - фиксированная комиссия платформы за вывод клиента, min_withdraw и min_deposit - минимальные суммы, native - нативная ли монета сети. Минимумы меняются платформой - не зашивайте их в код, читайте отсюда.
withdraw.limits.max_per_tx string optional
Лимит на одну операцию вывода, в эквиваленте USDT.
withdraw.limits.daily_per_customer string optional
Суточный лимит вывода на одного клиента, в эквиваленте USDT. Окно скользящее - последние 24 часа, а не календарные сутки.
withdraw.markup[] array optional
Ваша надбавка на выводы клиентов - строка на каждую доступную монету. Это фиксированная сумма в единицах монеты, а не процент.
withdraw.markup[].coin_id integer optional
Внутренний идентификатор монеты в справочнике платформы.
withdraw.markup[].label string optional
Человекочитаемое имя монеты, например USDT / TRON.
withdraw.markup[].asset string optional
Тикер монеты.
withdraw.markup[].network string optional
Сеть.
withdraw.markup[].native bool optional
Нативная ли монета сети.
withdraw.markup[].fee string optional
Комиссия платформы за вывод в этой сети - та же, что в withdraw.fees[].
withdraw.markup[].markup string optional
Ваша действующая надбавка по этой монете, в единицах монеты. Именно она попадает в fee_merchant расчёта вывода.
withdraw.markup[].max string optional
Верхняя граница надбавки по этой монете. Устанавливает платформа; сохранённое значение выше границы автоматически урезается до неё - и при сохранении, и при каждом расчёте.
withdraw.markup[].editable bool optional
false, если граница равна нулю: по такой монете надбавка недоступна, пока платформа не разрешит её отдельно.
balance.low_threshold string optional
Порог остатка USDT, ниже которого приходит вебхук merchant.balance.low.

Предупреждение о низком остатке

Как только доступный остаток строки USDT опускается ниже порога balance.low_threshold, на ваш подписанный эндпоинт уходит вебхук merchant.balance.low с полями currency, balance и threshold. Предупреждение отправляется не чаще одного раза в сутки, поэтому оно не заменяет мониторинг: опрашивайте GET /api/v1/merchant/balance перед пиковой нагрузкой. На остатке ровно в порог вебхука нет.

Вебхук merchant.balance.credited приходит при зачислении перевода на адрес мастер-счёта - используйте его, чтобы не опрашивать баланс в ожидании пополнения. Оба события отправляются только в боевом окружении. Подробности - Webhooks.

Права

Чтение баланса, леджера и тарифа любой ключ optional
Доступно всем ключам вашей учётной записи.
Выдача адреса пополнения владелец, администратор optional
Иначе 403 forbidden.
Перевод на счёт финансирования владелец, администратор optional
Деньги уходят владельцу учётной записи.
Пополнение со счёта финансирования только владелец optional
Списываются личные средства владельца, поэтому распоряжаться ими может только он сам.

Лимиты частоты

Чтение (баланс, леджер, тариф) - 120 запросов в минуту, своей корзиной. Выдача адреса пополнения (30 в минуту) и внутренние переводы мастер-счёта - пополнение и вывод (20 в минуту) - делят одну общую корзину записи: их лимиты считаются от единого счётчика, а не независимо друг от друга. Частый опрос баланса переводам при этом не мешает - читающая корзина отдельная.

Коды ошибок раздела

forbidden 403 optional
У ключа нет нужной роли, либо учётная запись не связана с мерчантом.
idempotency_key_required 400 optional
Во внутреннем переводе не передан заголовок Idempotency-Key.
invalid_request 422 optional
Некорректная сумма, неизвестный type в фильтре леджера или иная ошибка валидации.
coin_not_allowed 422 optional
Сеть или валюта недоступны вашей учётной записи.
conflict 409 optional
Idempotency-Key уже использован с другими параметрами, либо счёт финансирования владельца недоступен.
insufficient_funds 402 optional
На счёте финансирования владельца не хватает средств для пополнения.
service_balance_insufficient 402 optional
Не хватает средств на самом мастер-счёте. Ответ дополнен полями currency, required, available.
service_unavailable 503 optional
Мастер-счёт временно недоступен или адрес пополнения не удалось выдать - повторите позже.

Полный реестр и формат конверта - Error Codes.

Примеры кода

KEY="sk_live_..."
B="https://fin-os.io/api/v1"

# 1. Текущий баланс
curl -X GET $B/merchant/balance -H "Authorization: Bearer $KEY"

# 2. Адрес пополнения в TRON
curl -X POST $B/merchant/balance/deposit-address \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"chain": "TRON"}'

# 3. Пополнить со счёта финансирования владельца
curl -X POST $B/merchant/balance/topup-from-treasury \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: topup-2026-08-17-01" \
  -d '{"amount": "100", "chain": "TRON"}'

# 4. Расходы на проверки за август
curl -G $B/merchant/balance/transactions \
  -H "Authorization: Bearer $KEY" \
  --data-urlencode "type=aml_fee" \
  --data-urlencode "from=2026-08-01" \
  --data-urlencode "to=2026-08-31" \
  --data-urlencode "per_page=100"

# 5. Действующий тариф
curl -X GET $B/merchant/service-pricing -H "Authorization: Bearer $KEY"
const B   = 'https://fin-os.io/api/v1';
const KEY = process.env.FINOS_SECRET_KEY;
const H   = { 'Authorization': `Bearer ${KEY}`, 'Content-Type': 'application/json' };

// Остаток расчётной строки USDT
async function serviceBalance() {
  const { data } = await (await fetch(`${B}/merchant/balance`, { headers: H })).json();
  return data.find(r => r.currency === 'USDT')?.available ?? '0';
}

// Полный леджер за период: идём по курсору, пока has_more
async function ledger(params = {}) {
  const rows = [];
  let cursor = null;

  do {
    const q = new URLSearchParams({ per_page: '100', ...params });
    if (cursor) q.set('starting_after', cursor);

    const body = await (await fetch(`${B}/merchant/balance/transactions?${q}`, { headers: H })).json();
    rows.push(...body.data);
    cursor = body.meta.has_more ? body.meta.next_cursor : null;
  } while (cursor);

  return rows;
}

// Расход на проверки за месяц
const fees = await ledger({ type: 'aml_fee', from: '2026-08-01', to: '2026-08-31' });
console.log(fees.reduce((s, r) => s + Number(r.amount), 0));
use Illuminate\Support\Facades\Http;

$api = Http::withToken(config('services.finos.secret_key'))
    ->acceptJson()
    ->baseUrl('https://fin-os.io/api/v1');

// Хватит ли на пачку проверок
$pricing = $api->get('/merchant/service-pricing')->json('data');
$balance = collect($api->get('/merchant/balance')->json('data'))
    ->firstWhere('currency', 'USDT')['available'] ?? '0';

if (bccomp($balance, bcmul($pricing['aml']['check_price'], (string) $plannedChecks, 10), 10) < 0) {
    // Пополняем со счёта финансирования: ключ идемпотентности - ваш идентификатор операции
    $api->withHeaders(['Idempotency-Key' => "topup-{$invoiceId}"])
        ->post('/merchant/balance/topup-from-treasury', ['amount' => 100, 'chain' => 'TRON']);
}

// Леджер за период с курсорной пагинацией
$rows   = [];
$cursor = null;

do {
    $page = $api->get('/merchant/balance/transactions', array_filter([
        'type'           => 'aml_fee',
        'from'           => '2026-08-01',
        'to'             => '2026-08-31',
        'per_page'       => 100,
        'starting_after' => $cursor,
    ]))->json();

    $rows   = array_merge($rows, $page['data']);
    $cursor = $page['meta']['has_more'] ? $page['meta']['next_cursor'] : null;
} while ($cursor);
import os, requests
from decimal import Decimal

B   = 'https://fin-os.io/api/v1'
KEY = os.environ['FINOS_SECRET_KEY']
H   = {'Authorization': f'Bearer {KEY}'}

def service_balance():
    rows = requests.get(f'{B}/merchant/balance', headers=H).json()['data']
    return next((Decimal(r['available']) for r in rows if r['currency'] == 'USDT'), Decimal(0))

def ledger(**params):
    params.setdefault('per_page', 100)
    cursor = None
    while True:
        if cursor:
            params['starting_after'] = cursor
        body = requests.get(f'{B}/merchant/balance/transactions',
                            headers=H, params=params).json()
        yield from body['data']
        if not body['meta']['has_more']:
            return
        cursor = body['meta']['next_cursor']

# Расход на проверки за август
spent = sum(Decimal(r['amount']) for r in ledger(type='aml_fee',
                                                 **{'from': '2026-08-01', 'to': '2026-08-31'}))
print(service_balance(), spent)