Finance OS / API

Virtual Cards

Виртуальные карты as-a-Service на ваших managed customers: выпуск (issue), список, детали и пополнение (top-up). Каждая карта привязана к конкретному customer'у и номинирована в RUB. Наружу отдаётся только обезличенное представление карты — внутренний платёжный контур не раскрывается.

Денежные операции дормант
Выпуск и пополнение карт в live по умолчанию выключены и возвращают 503, пока функция не активирована для платформы. В sandbox всё работает на синтетике: POST /cards отдаёт фиктивную карту с искусственным masked_pan, а POST /cards/{card}/topup симулирует зачисление, не обращаясь к апстриму.
Окружение по префиксу ключа
Как и во всём B2B API, окружение выводится из префикса ключа: sk_test_ = sandbox, sk_live_ = live. Отдельного заголовка нет. Карта видна только тому ключу, чьё окружение совпадает с env карты; обращение к карте из чужого окружения возвращает 404 (намеренно, чтобы не раскрывать существование).

Объект карты

Все endpoints возвращают карту в одном и том же обезличенном виде:

Card object

id string optional
UUID карты. Используется в путях /cards/{card}.
status string optional
Статус карты в нижнем регистре, напр. open.
masked_pan string|null optional
Маскированный номer, напр. 537643••••9f3a. Для только что выпущенной live-карты может быть null, пока не подтянется снимок при GET /cards/{card}.
balance string optional
Баланс в RUB строкой с 2 знаками, напр. 0.00.
currency string optional
Всегда RUB.
created_at string optional
ISO 8601 timestamp создания.
markup object optional
Ваша наценка (%) по операциям карт: issue_pct, topup_pct, payout_pct. 0, если наценка не настроена.

Выпуск карты

POST /api/v1/customers/{uuid}/cards
Bearer Token

Выпустить виртуальную карту для customer'а

Все поля профиля держателя необязательны — карту можно выпустить и без них. В sandbox возвращается детерминированная синтетическая карта; в live операция дормант (см. предупреждение выше).

Body

first_name string optional
Имя держателя. До 100 символов.
last_name string optional
Фамилия держателя. До 100 символов.
patronymic string optional
Отчество. До 100 символов.
birth_date string optional
Дата рождения. До 20 символов.
email string optional
Email держателя. Валидный email, до 150 символов.

Responses

{
  "data": {
    "id": "1a7ee75b-7f0e-48af-a075-a56721f1140e",
    "status": "open",
    "masked_pan": "537643••••9f3a",
    "balance": "0.00",
    "currency": "RUB",
    "created_at": "2026-07-13T12:41:08+00:00",
    "markup": {
      "issue_pct": 2.5,
      "topup_pct": 1.5,
      "payout_pct": 1.0
    }
  }
}

Список карт

GET /api/v1/customers/{uuid}/cards
Bearer Token

Все карты customer'а

Возвращает карты данного customer'а в текущем окружении, самые свежие первыми.

Responses

{
  "data": [
    {
      "id": "1a7ee75b-7f0e-48af-a075-a56721f1140e",
      "status": "open",
      "masked_pan": "537643••••9f3a",
      "balance": "1500.00",
      "currency": "RUB",
      "created_at": "2026-07-13T12:41:08+00:00",
      "markup": {
        "issue_pct": 2.5,
        "topup_pct": 1.5,
        "payout_pct": 1.0
      }
    }
  ]
}

Детали карты

GET /api/v1/customers/{uuid}/cards/{card}
Bearer Token

Одна карта по UUID

В live (если функция включена) подтягивает свежий снимок баланса/статуса; иначе отдаёт последний локальный снимок. При любой ошибке апстрима наружу возвращается локальный снимок — сырой ответ провайдера не раскрывается.

Responses

{
  "data": {
    "id": "1a7ee75b-7f0e-48af-a075-a56721f1140e",
    "status": "open",
    "masked_pan": "537643••••9f3a",
    "balance": "1500.00",
    "currency": "RUB",
    "created_at": "2026-07-13T12:41:08+00:00",
    "markup": {
      "issue_pct": 2.5,
      "topup_pct": 1.5,
      "payout_pct": 1.0
    }
  }
}

Пополнение карты

POST /api/v1/customers/{uuid}/cards/{card}/topup
Bearer Token

Пополнить карту со счёта площадки (RUB)

Зачисляет RUB на карту. В sandbox зачисление симулируется и баланс сразу увеличивается; в live операция дормант (503, пока функция не активирована). Ответ приходит с кодом 202 Accepted — карта пополняется асинхронно.

Body

amount string required
Сумма пополнения в RUB. Строка-число до 2 знаков после точки, напр. 1500.00 или 1500.

Responses

{
  "data": {
    "status": "accepted",
    "amount": "1500.00",
    "currency": "RUB",
    "card_id": "1a7ee75b-7f0e-48af-a075-a56721f1140e",
    "pricing": {
      "markup_pct": 1.5
    },
    "sandbox": true
  }
}

pricing.markup_pct — ваша наценка на пополнение (та же, что markup.topup_pct в объекте карты). Флаг sandbox присутствует только в sandbox-ответах; в live его нет.

Коды ошибок

Ошибки приходят в стандартном конверте { "error": { "code", "message", "request_id" } }. Успех — { "data": ... }.

Introduction.'], ]" />

Примеры кода

# Выпустить виртуальную карту для customer'а
curl -X POST https://fin-os.io/api/v1/customers/$UUID/cards \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"first_name": "Ivan", "last_name": "Petrov", "email": "ivan@example.com"}'

# Список карт customer'а
curl https://fin-os.io/api/v1/customers/$UUID/cards \
  -H "Authorization: Bearer sk_test_..."

# Детали одной карты
curl https://fin-os.io/api/v1/customers/$UUID/cards/$CARD_ID \
  -H "Authorization: Bearer sk_test_..."

# Пополнить карту со счёта площадки (RUB)
curl -X POST https://fin-os.io/api/v1/customers/$UUID/cards/$CARD_ID/topup \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{"amount": "1500.00"}'
const FINOS_API = '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 issueCard(uuid, profile = {}) {
  const res = await fetch(`${FINOS_API}/customers/${uuid}/cards`, {
    method: 'POST',
    headers: H,
    body: JSON.stringify(profile), // { first_name, last_name, patronymic, birth_date, email }
  });
  if (res.status === 503) throw new Error('Card issuance not enabled yet');
  const { data } = await res.json();
  return data; // { id, status, masked_pan, balance, currency, markup, ... }
}

// Список карт
async function listCards(uuid) {
  const res = await fetch(`${FINOS_API}/customers/${uuid}/cards`, { headers: H });
  return (await res.json()).data;
}

// Пополнить карту (RUB, строкой до 2 знаков)
async function topupCard(uuid, cardId, amount) {
  const res = await fetch(
    `${FINOS_API}/customers/${uuid}/cards/${cardId}/topup`,
    { method: 'POST', headers: H, body: JSON.stringify({ amount }) }
  );
  if (res.status === 503) throw new Error('Card top-up not enabled yet');
  return (await res.json()).data; // { status: 'accepted', amount, currency, card_id, pricing }
}
use Illuminate\Support\Facades\Http;

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

    public function __construct(private string $secretKey) {} // sk_test_ или sk_live_

    /** Выпустить виртуальную карту. */
    public function issue(string $customerUuid, array $profile = []): array
    {
        return Http::withToken($this->secretKey)
            ->post("{$this->base}/customers/{$customerUuid}/cards", $profile)
            ->throw()
            ->json('data');
    }

    /** Все карты customer'а. */
    public function list(string $customerUuid): array
    {
        return Http::withToken($this->secretKey)
            ->get("{$this->base}/customers/{$customerUuid}/cards")
            ->json('data');
    }

    /** Пополнить карту (amount — строка RUB до 2 знаков). */
    public function topup(string $customerUuid, string $cardId, string $amount): array
    {
        return Http::withToken($this->secretKey)
            ->post("{$this->base}/customers/{$customerUuid}/cards/{$cardId}/topup", [
                'amount' => $amount,
            ])
            ->throw()
            ->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 issue_card(uuid, profile=None):
    r = requests.post(
        f'{FINOS}/customers/{uuid}/cards', headers=H, json=profile or {}
    )
    if r.status_code == 503:
        raise RuntimeError('Card issuance not enabled yet')
    r.raise_for_status()
    return r.json()['data']

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

def topup_card(uuid, card_id, amount):
    # amount — строка RUB до 2 знаков, напр. '1500.00'
    r = requests.post(
        f'{FINOS}/customers/{uuid}/cards/{card_id}/topup',
        headers=H, json={'amount': amount},
    )
    if r.status_code == 503:
        raise RuntimeError('Card top-up not enabled yet')
    r.raise_for_status()
    return r.json()['data']

# usage
card = issue_card(customer_uuid, {'first_name': 'Ivan', 'email': 'ivan@example.com'})
print('Card:', card['id'], card['masked_pan'])
topup_card(customer_uuid, card['id'], '1500.00')