Finance OS / API

Wallets

Казначейские крипто-кошельки мерчанта: реестр поддерживаемых монет, депозит-адреса для приёма средств, балансы и вывод на внешний адрес. Кошельки принадлежат самому мерчанту (это его казначейство), а не конкретному customer'у - поэтому endpoints живут под /api/v1/wallets, без привязки к {uuid}.

Мультичейн-монеты
Один и тот же ассет (например USDT) доступен в нескольких сетях (TRON, ETHEREUM, BSC, POLYGON, BASE). Параметр пути {coin} - это символ ассета (USDT, BTC, …). Для мультичейн-ассетов дополнительно указывайте network, иначе вернётся 422 invalid_request (неоднозначность). Сети - это блокчейны, которые нужны вашему клиенту, а не поставщики.
Окружение по префиксу ключа
Sandbox или live определяется префиксом ключа, а не заголовком: sk_test_ → sandbox, sk_live_ → live. В sandbox все балансы нулевые, депозит-адрес - детерминированный не-реальный адрес (не пополняйте его), а вывод только симулируется. См. Sandbox.

Реестр монет

GET /api/v1/wallets/coins
Bearer Token

Список доступных вам монет

Возвращает монеты, доступные вашему ключу (администратор Finance OS может ограничить набор per-merchant allowlist'ом). Для каждой монеты - сеть, тип, контракт токена, точность и минимумы.

Responses

{
  "data": [
    {
      "asset": "USDT",
      "symbol": "USDT",
      "name": "Tether USD",
      "network": "TRON",
      "kind": "token",
      "contract": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
      "decimals": 6,
      "min_deposit": "0.000000000000000000",
      "min_withdraw": "0.000000000000000000",
      "markup": { "deposit_pct": 0, "withdraw_pct": 0 }
    },
    {
      "asset": "USDT",
      "symbol": "USDT",
      "name": "Tether USD",
      "network": "ETHEREUM",
      "kind": "token",
      "contract": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
      "decimals": 6,
      "min_deposit": "0.000000000000000000",
      "min_withdraw": "0.000000000000000000",
      "markup": { "deposit_pct": 0, "withdraw_pct": 0 }
    },
    {
      "asset": "BTC",
      "symbol": "BTC",
      "name": "Bitcoin",
      "network": "BITCOIN",
      "kind": "native",
      "contract": null,
      "decimals": 8,
      "min_deposit": "0.000000000000000000",
      "min_withdraw": "0.000000000000000000",
      "markup": { "deposit_pct": 0, "withdraw_pct": 0 }
    }
  ]
}

Поля монеты

asset string optional
Символ ассета (используется как {coin} в пути).
symbol string optional
Тикер для отображения.
name string optional
Человекочитаемое имя.
network string optional
Блокчейн-сеть: TRON, ETHEREUM, BITCOIN, LITECOIN, BSC, POLYGON, BASE.
kind enum optional
native или token.
contract string? optional
Адрес контракта для token; null для native.
decimals integer optional
Точность ассета в сети.
min_deposit string optional
Минимальная сумма депозита (десятичная строка; настраивается администратором, может быть 0).
min_withdraw string optional
Минимальная сумма вывода (десятичная строка).
markup object optional
Историческое поле, deposit_pct и withdraw_pct всегда 0: это ваш собственный счёт финансирования, надбавки самому себе не бывает. Надбавка на вывод клиента задаётся фиксированной суммой по монете и видна в витрине тарифа GET /api/v1/merchant/service-pricing.

Балансы

GET /api/v1/wallets
Bearer Token

Балансы казначейства по всем монетам

Баланс каждой доступной монеты + оценка в USD. В sandbox все значения нулевые.

Responses

{
  "data": [
    { "asset": "USDT", "symbol": "USDT", "network": "TRON",     "balance": "1500.250000", "usd": 1500.25 },
    { "asset": "USDT", "symbol": "USDT", "network": "ETHEREUM", "balance": "0",           "usd": 0 },
    { "asset": "BTC",  "symbol": "BTC",  "network": "BITCOIN",  "balance": "0.05000000",  "usd": 3250.00 }
  ]
}

Баланс одной монеты

GET /api/v1/wallets/{coin}/balance
Bearer Token

Баланс по конкретной монете

Для мультичейн-ассета уточните сеть query-параметром ?network=TRON.

Query

network string optional
Сеть для мультичейн-ассета (макс. 20 символов). Без неё неоднозначный ассет вернёт 422.

Responses

{
  "data": {
    "asset": "USDT",
    "network": "TRON",
    "balance": "1500.250000",
    "usd": 1500.25
  }
}

Депозит-адрес

POST /api/v1/wallets/{coin}/address
Bearer Token

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

Возвращает адрес казначейства в выбранной сети. Отправляйте на него только указанный ассет в указанной сети - балансы обновятся после подтверждения транзакции в блокчейне. В sandbox возвращается детерминированный не-реальный адрес (флаг sandbox: true); реальных средств туда не отправляйте.

Body

network string optional
Сеть для мультичейн-ассета (макс. 20 символов).

Responses

{
  "data": {
    "asset": "USDT",
    "network": "TRON",
    "address": "TXYZ1234567890abcdef..."
  }
}
{
  "data": {
    "asset": "USDT",
    "network": "TRON",
    "address": "SBXTRON8F2A1C...",
    "sandbox": true
  }
}

Вывод

POST /api/v1/wallets/{coin}/withdraw
Bearer Token

Вывод на внешний адрес

Инициирует on-chain-вывод из казначейства на внешний адрес. Ответ 202 Accepted означает, что запрос принят в обработку; итоговый статус подтверждается по факту транзакции в сети.
Вывод по умолчанию выключен
Реальный вывод - дормантная операция за kill-switch'ем и включается администратором Finance OS индивидуально. Пока он выключен, live-запрос вернёт 503 service_unavailable - обрабатывайте это как «временно недоступно» и повторяйте позже. В sandbox вывод всегда симулируется (без движения средств, флаг sandbox: true).

Body

to string required
Внешний адрес-получатель (макс. 120 символов).
amount string required
Сумма в единицах ассета, десятичная строка до 18 знаков после точки (^\d+(\.\d{1,18})?$).
network string optional
Сеть для мультичейн-ассета (макс. 20 символов).

Responses

{
  "data": {
    "status": "accepted",
    "asset": "USDT",
    "network": "TRON",
    "amount": "25.5",
    "to": "TExternalRecipientAddr...",
    "tx_hash": "9f2b0c...",
    "pricing": { "markup_pct": 0 }
  }
}
{
  "data": {
    "status": "accepted",
    "asset": "USDT",
    "network": "TRON",
    "amount": "25.5",
    "to": "TExternalRecipientAddr...",
    "pricing": { "markup_pct": 0 },
    "sandbox": true
  }
}
{
  "error": {
    "code": "service_unavailable",
    "message": "Сервис временно недоступен. Попробуйте позже.",
    "request_id": "req_8f3ca1b209d74e55a1c0f2e7"
  }
}

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

Запросов в минуту

Выдача депозит-адреса (<code>POST /api/v1/wallets/{coin}/address</code>) 30 optional
Корзина изменяющих вызовов казначейских кошельков.
Вывод (<code>POST /api/v1/wallets/{coin}/withdraw</code>) 6 optional
Отдельная узкая корзина: вызов двигает деньги в сеть.

Чтение реестра монет и балансов отдельной корзиной не ограничено. При превышении приходит 429 rate_limited с заголовком Retry-After: спите указанное число секунд и повторяйте с экспоненциальной задержкой.

Коды ошибок

Ошибки отдаются в едином конверте { "error": { "code", "message", "request_id" } }. При ошибке валидации добавляется поле fields с детализацией по параметрам.

Introduction.'], ['name' => 'not_found', 'type' => '404', 'desc' => 'Монета недоступна для этого ключа.'], ['name' => 'insufficient_funds', 'type' => '402', 'desc' => 'Недостаточно средств в казначействе для вывода.'], ['name' => 'service_unavailable', 'type' => '503', 'desc' => 'Вывод временно недоступен (дормант) либо инфраструктура недоступна. Повторите позже.'], ]" />

Примеры кода

# Реестр монет
curl -X GET https://fin-os.io/api/v1/wallets/coins \
  -H "Authorization: Bearer sk_test_..."

# Все балансы казначейства
curl -X GET https://fin-os.io/api/v1/wallets \
  -H "Authorization: Bearer sk_test_..."

# Баланс одной монеты (мультичейн → уточните network)
curl -X GET "https://fin-os.io/api/v1/wallets/USDT/balance?network=TRON" \
  -H "Authorization: Bearer sk_test_..."

# Депозит-адрес
curl -X POST https://fin-os.io/api/v1/wallets/USDT/address \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"network": "TRON"}'

# Вывод на внешний адрес (может вернуть 503, пока вывод не включён)
curl -X POST https://fin-os.io/api/v1/wallets/USDT/withdraw \
  -H "Authorization: Bearer sk_test_..." \
  -d '{"to": "TExternalRecipientAddr...", "amount": "25.5", "network": "TRON"}'
const FINOS = 'https://fin-os.io/api/v1';
const SECRET = process.env.FINOS_SECRET_KEY; // sk_test_ или sk_live_
const H = { 'Authorization': `Bearer ${SECRET}`, 'Content-Type': 'application/json' };

// Депозит-адрес казначейства для монеты в конкретной сети
async function depositAddress(coin, network) {
  const res = await fetch(`${FINOS}/wallets/${coin}/address`, {
    method: 'POST',
    headers: H,
    body: JSON.stringify({ network }),
  });
  const { data } = await res.json();
  return data; // { asset, network, address, sandbox? }
}

// Вывод; корректно обрабатываем дормантный 503
async function withdraw(coin, to, amount, network) {
  const res = await fetch(`${FINOS}/wallets/${coin}/withdraw`, {
    method: 'POST',
    headers: H,
    body: JSON.stringify({ to, amount, network }),
  });
  if (res.status === 503) {
    // Вывод временно недоступен - повторите позже
    throw new Error('withdraw temporarily unavailable');
  }
  const { data } = await res.json();
  return data; // 202: { status: 'accepted', ..., tx_hash }
}
use Illuminate\Support\Facades\Http;

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

    public function __construct(private string $secretKey) {}

    public function coins(): array
    {
        return Http::withToken($this->secretKey)
            ->get("{$this->base}/wallets/coins")
            ->json('data');
    }

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

    public function withdraw(string $coin, string $to, string $amount, ?string $network = null): array
    {
        $res = Http::withToken($this->secretKey)
            ->post("{$this->base}/wallets/{$coin}/withdraw", array_filter([
                'to'      => $to,
                'amount'  => $amount,
                'network' => $network,
            ]));

        if ($res->status() === 503) {
            throw new \RuntimeException('Withdraw temporarily unavailable, retry later.');
        }

        return $res->json('data');
    }
}
import requests, os

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

def coins():
    r = requests.get(f'{FINOS}/wallets/coins', headers=H)
    r.raise_for_status()
    return r.json()['data']

def deposit_address(coin, network=None):
    body = {'network': network} if network else {}
    r = requests.post(f'{FINOS}/wallets/{coin}/address', headers=H, json=body)
    r.raise_for_status()
    return r.json()['data']

def withdraw(coin, to, amount, network=None):
    body = {'to': to, 'amount': amount}
    if network:
        body['network'] = network
    r = requests.post(f'{FINOS}/wallets/{coin}/withdraw', headers=H, json=body)
    if r.status_code == 503:
        raise RuntimeError('Withdraw temporarily unavailable, retry later.')
    r.raise_for_status()
    return r.json()['data']

# usage
addr = deposit_address('USDT', 'TRON')
print('Deposit to:', addr['address'])