Finance OS / API

Errors

Finance OS API возвращает стандартные HTTP-коды и единообразное JSON-тело для всех ошибок. Это позволяет писать унифицированный обработчик в клиенте.

Формат тела ошибки

{
  "message": "The given data was invalid.",
  "errors": {
    "email": ["The email field is required."],
    "password": ["The password must be at least 8 characters."]
  },
  "error_code": "VALIDATION_FAILED"
}

Поля

message string required
Человекочитаемое описание ошибки. На английском (локализация — v1.1).
errors object optional
Только для 422: словарь поле → массив строк. Используйте для подсветки конкретных input в UI.
error_code string optional
Машиночитаемый код. Используйте для switch-логики и логирования.
request_id string (uuid) optional
UUID запроса. Приложите его в support-тикет — мы найдём по логам.

HTTP-коды

Семейство 2xx — успех

200 OK optional
Чтение успешно. Тело — JSON с данными.
201 Created optional
Ресурс создан. Заголовок Location указывает на новый ресурс.
202 Accepted optional
Запрос принят в обработку (асинхронно — например, вывод средств).
204 No Content optional
Успех без тела (после DELETE).

Семейство 4xx — клиентская ошибка

400 Bad Request optional
Запрос не парсится (битый JSON, неправильный Content-Type).
401 Unauthorized optional
API-ключ отсутствует, невалиден или перегенерирован. Возьмите актуальный ключ в кабинете.
403 Forbidden optional
Ключ валидный, но контур не подключён или ресурс принадлежит другой учётной записи.
404 Not Found optional
Ресурс не существует или удалён.
409 Conflict optional
Конфликт состояния (двойной submit, уже подтверждённая операция).
422 Unprocessable Entity optional
Валидация не прошла. errors содержит детали по каждому полю.
423 Locked optional
Аккаунт заморожен (KYC-fail, compliance-hold).
429 Too Many Requests optional
Превышен лимит. См. Rate Limiting.

Семейство 5xx — серверная ошибка

500 Internal Server Error optional
Неожиданная ошибка на сервере. Логи у нас, в теле — request_id. Повторите через минуту.
502 Bad Gateway optional
Сбой апстрима (платёжный шлюз или verification-провайдер недоступен). Повторите через минуту.
503 Service Unavailable optional
Плановое обслуживание. Заголовок Retry-After укажет ожидаемое окно.
504 Gateway Timeout optional
Долгий ответ от апстрима. Часто — операции с блокчейном. Повторите с idempotency-key.

Машиночитаемые коды

Часто встречающиеся error_code значения:

VALIDATION_FAILED 422 optional
Ошибки в полях — детали в errors.
TOKEN_INVALID 401 optional
API-ключ невалиден или перегенерирован.
INSUFFICIENT_BALANCE 422 optional
Недостаточно средств для операции (вывод, перевод).
KYC_REQUIRED 403 optional
Операция требует завершённой верификации клиента. Проведите KYC и повторите.
SANCTION_HIT 423 optional
Адрес/контрагент попал под санкции — операция заблокирована.
ACCOUNT_FROZEN 423 optional
Аккаунт заморожен (compliance).
NETWORK_CONGESTED 503 optional
Сеть блокчейна перегружена — попробуйте позже или измените сеть.
IDEMPOTENCY_CONFLICT 409 optional
Idempotency-key уже использован с другим телом запроса.

Идемпотентность

Для всех POST на создание ресурса (особенно денежных операций) клиент может передать заголовок Idempotency-Key — UUID, который сервер запомнит на 24 часа и при повторном запросе с тем же ключом вернёт оригинальный ответ, не выполняя операцию повторно.

curl -X POST https://fin-os.io/api/v1/customers/{uuid}/wallet/withdraw \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Idempotency-Key: 5f8d-7a3c-…" \
  -d '{"amount": "10.00", "currency": "USDT", "network": "TRON", "address": "T..."}'
Используйте Idempotency-Key для money-операций
Сеть моргнула, ваш запрос не дошёл — вы повторяете. Без idempotency-key возможен двойной вывод. С ним — гарантия «exactly-once».

Примеры кода

# Пример валидационной ошибки (422)
curl -X POST https://fin-os.io/api/v1/customers \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"phone": "not-a-phone"}'

# HTTP/2 422
# {
#   "message": "The given data was invalid.",
#   "errors": {
#     "phone": ["The phone must be a valid E.164 number."]
#   },
#   "error_code": "VALIDATION_FAILED",
#   "request_id": "5f8d7a3c-1234-4567-89ab-cdef01234567"
# }
// Унифицированный обработчик
async function apiCall(url, init = {}) {
  const res = await fetch(url, {
    ...init,
    headers: { 'Accept': 'application/json', ...init.headers },
  });

  if (res.ok) return res.json();

  const err = await res.json().catch(() => ({ message: 'Network error' }));

  switch (err.error_code) {
    case 'TOKEN_INVALID':
      throw new Error('API-ключ невалиден - обновите секрет в конфигурации сервиса');
    case 'INSUFFICIENT_BALANCE':
      throw new Error('Недостаточно средств');
    default:
      throw new Error(err.message || `HTTP ${res.status}`);
  }
}
try {
    $response = Http::withToken($token)
        ->throw()
        ->post('https://fin-os.io/api/v1/customers/' . $uuid . '/wallet/withdraw', $data);
} catch (\Illuminate\Http\Client\RequestException $e) {
    $body = $e->response->json();
    $code = $body['error_code'] ?? null;

    match ($code) {
        'INSUFFICIENT_BALANCE' => throw new InsufficientBalanceException($body['message']),
        'KYC_REQUIRED'         => throw new KycRequiredException($body['message']),
        'SANCTION_HIT'         => logger()->alert('Sanction hit', ['request_id' => $body['request_id'] ?? null]),
        default                => throw $e,
    };
}
import requests

class FinanceOsError(Exception):
    def __init__(self, code, message, request_id=None):
        self.code = code
        self.message = message
        self.request_id = request_id
        super().__init__(f'{code}: {message}')

def api_call(method, url, **kwargs):
    r = requests.request(method, url, **kwargs)
    if r.ok:
        return r.json()
    body = r.json() if 'application/json' in r.headers.get('Content-Type', '') else {}
    raise FinanceOsError(
        code=body.get('error_code', f'HTTP_{r.status_code}'),
        message=body.get('message', 'Unknown error'),
        request_id=body.get('request_id'),
    )

try:
    balance = api_call('GET', 'https://fin-os.io/api/v1/merchant/balance',
                       headers={'Authorization': f'Bearer {TOKEN}'})
except FinanceOsError as e:
    if e.code == 'TOKEN_INVALID':
        print('Ключ невалиден - обновите секрет')
    else:
        print(f'API error: {e.code} ({e.request_id})')