Skip to content

Обзор API

MARV предоставляет V2 как основную клиентскую API; V1 поддерживается для обратной совместимости. ACP - административный профиль; EXT - внешние вебхуки мерчантов.

Авторизация (V2)

V2 использует session-based авторизацию через JWT.

1. Login — клиент отправляет POST /v2/login с платформенными данными в JSON body:

json
{
  "api_type": 30,
  "api_uid": "12345",
  "api_token": "platform-signed-token",
  "platform_data": "vk_user_id=12345&sign=abc...",
  "client_version": "1.5.0",
  "language": "ru"
}

Поле platform_data — raw JSON с платформ-специфичными данными для верификации. Содержимое зависит от api_type:

Платформаplatform_dataОписание
VK"vk_user_id=123&sign=abc..."URL-encoded launch params (HMAC-SHA256)
OK"session_key"Ключ сессии для MD5-верификации
DR"session_key"Ключ сессии для MD5-верификации
MS Start"base64-payload"Base64-encoded JSON с RSA подписью
GamePush"base64-query"Base64-encoded query params
ДругиеНе требуется (можно не передавать)

Сервер проверяет платформенный токен, создаёт/получает пользователя и возвращает JWT вместе с профилем:

json
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "expires_at": 1709136000,
  "user": { "id": 1, "api_uid": "12345", "data": { ... } }
}

Объект user содержит полный профиль (с data), что избавляет от отдельного запроса к /v2/users/me при старте сессии.

2. Запросы — все остальные V2 эндпоинты требуют JWT в заголовке:

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Платформенные заголовки (X-Auth-Token, X-Auth-Type, X-Auth-Uid) для V2 не нужны. Без валидного Bearer-токена запрос будет отклонён с 401.

3. Single session — каждый вызов /v2/login выдаёт новый токен с уникальным jti (JWT ID) и сохраняет его на сервере. Предыдущий токен того же пользователя автоматически инвалидируется: запросы со старым jti получат 401. Один пользователь = одна активная сессия.

4. Истечение — токен действует 24ч (настраивается через app.security.jwt.ttl). При истечении сервер вернёт 401; клиент должен повторить login.

JWT подписан HMAC-SHA256 ключом app.security.jwt.secret. Этот ключ можно использовать в других сервисах для верификации токена. Payload содержит: uid (user_id), atp (api_type), aui (api_uid), jti (session id), exp, iat.

Авторизация (V1)

V1 использует per-request платформенную авторизацию через заголовки:

ЗаголовокОбязателенОписание
X-Auth-TokenдаТокен авторизации: по умолчанию MD5(secret_apiType_apiUID)
X-Auth-TypeдаТип платформы (целое), выбирает конфигурацию платформы
X-Auth-UidдаUID пользователя на платформе
X-Session-TokenнетСессионный токен (только при single_mode.enabled)
X-Client-VersionнетВерсия клиента; валидируется согласно конфигу
X-Client-Language-CodeнетКод языка клиента; сохраняется в профиле пользователя
X-SessionнетСессия платформы (используется некоторыми платформами для верификации)
X-Access-TokenнетAccess-токен платформы
X-Inviter-UidнетUID пригласившего пользователя (для реферальной системы)

Single session mode (deprecated): при app.session.single_mode.enabled: true и немобильной платформе, при первом вызове /v1/users/fetch без X-Session-Token сервер генерирует токен, сохраняет его в БД и возвращает в заголовке X-Set-Session-Token. Клиент должен передавать этот токен в последующих запросах через X-Session-Token. При blocking: true запросы с устаревшим или отсутствующим токеном отклоняются (код 8 или 11).

Request ID и страна клиента

  • Клиент может (но не обязан) передавать X-Request-Id. Если заголовок не задан, MARV сгенерирует UUID самостоятельно.
  • Каждый ответ содержит X-Request-Id — его можно использовать для сопоставления с логами (trace_id добавляется в zerolog контекст).
  • При работе через Cloudflare заголовок CF-IPCountry проксируется в ответ как X-Client-Country-Code, поэтому страна клиента доступна даже без отдельного API.
  • В ответ также добавляется X-Server-Id: это идентификатор узла (app.server.id из конфига либо автогенерированное значение), что упрощает отладку и мониторинг лидерства.

Верификация IAP

  • ApiType/ApiUid всегда берутся из аутентифицированного пользователя; значения в теле запроса игнорируются.
  • Поле user_id опционально (передаётся внутри store_payload). Если его нет, MARV подставляет ApiUid текущего пользователя. Указывайте user_id явно только на платформах, где IAP использует другой идентификатор (например, Discord).
  • V2 передаёт store-specific данные через store_payload (JSON object). V1 использует flat-поля для обратной совместимости.
  • Необязательное поле merchant_profile позволяет выбрать профиль мерчанта (по умолчанию default).

Пример V2 запроса POST /v2/transactions/iap/verify:

json
{
  "product_id": 10,
  "price": 9.99,
  "currency": "USD",
  "store_payload": {
    "transaction_id": "receipt-123",
    "purchase_token": "purchase-token-abc",
    "package_name": "com.example.app"
  }
}

Ответ включает success, order_id, product_sku, transaction_id (внутренний ID) и duplicate (true если транзакция уже существовала).

Поля store_payload по платформам

Все поля ниже передаются внутри store_payload. Основные поля запроса (product_id, price, currency) обязательны для всех платформ.

Платформаpackage_nameproduct_skupurchase_tokenreceipttransaction_iduser_id
Google Play
App Store (iOS)
RuStore
Amazon
Galaxy Store
Bazaar
Myket
Discordопц.
Facebook
Jestопц.
Yandex

✅ — обязательное; опц. — опциональное; — — не используется.

Yandex

IAP-верификация на платформе Yandex не поддерживается. Запрос вернёт ошибку.

Примеры store_payload по платформам

Google Play / RuStore / Bazaar / Myket:

json
{
  "package_name": "com.example.game",
  "product_sku": "gem_pack_100",
  "purchase_token": "opaque-purchase-token"
}

App Store (iOS):

json
{
  "receipt": "MIIT...base64-encoded-receipt-data",
  "transaction_id": "1000000123456789"
}

Amazon:

json
{
  "receipt": "receipt-id-from-amazon",
  "user_id": "amazon-user-id"
}

Galaxy Store:

json
{
  "purchase_token": "payment-id-token"
}

Discord:

json
{
  "transaction_id": "entitlement-id",
  "user_id": "discord-user-id"
}

Facebook:

json
{
  "receipt": "signature.base64payload"
}

Jest:

json
{
  "purchase_token": "jwt-signed-token",
  "transaction_id": "optional-purchase-id"
}

Форматы ответов

  • Успех (V2): { "time": <unix>, "data": <payload> }
  • Ошибка (V2/ACP): { "time": <unix>, "error": { "code": "...", "http_status": N, "message": "...", "details": { ... }}}

Подробно про ошибки и соответствия доменных ошибок см. в V2 Errors.

Конфигурация и таймауты

  • HTTP‑таймауты и бакеты метрик настраиваются в config/main.md, секциях app.http.timeouts.* и observability.metrics.http_buckets_ms.

EXT (мерчанты)

  • Общие принципы и требования описаны в EXT Guide. Для каждого мерчанта используется собственная авторизация/подпись.

ACP: инициализация и мониторинг

  • POST /acp/me — основная точка входа ACP. Возвращает текущего ACP-пользователя, привязанные аккаунты, информацию о сервере (версия, аптайм, ОС), список включённых модулей, сконфигурированных платформ и активных маршрутов. Заменяет устаревший /acp/acp-users/me.
  • POST /acp/system/overview — сводная аналитика для дашборда: статистика пользователей (total, today, week, month, DAU, MAU), количество онлайн, сессий за сегодня, состояние кластера (leader), память, Redis и статусы задач.
  • POST /acp/system/metrics — Prometheus-совместимые метрики (HTTP, DB, cache, controllers, tasks). Описание полей и настройки — в Observability.

Реклама (ACP → Ads)

  • /acp/ads/list возвращает все объявления и runtime‑снимок контроллерных метрик (количество вызовов, ошибки, p95) по префиксу /acp/ads/*. Эндпоинт read-only, отдельная роль не требуется.
  • /acp/ads/create|update|delete|toggle требуют роль AccessAds (1024). После модификаций ответы всегда содержат commit: ["ads"] и актуальный список ads, чтобы UI мог обновить состояние без повторного запроса.
  • DTO и поля объявлений описаны в openapi.yaml (AcpAd, AcpCreateAdDTO, ...). Поля title/description/data — произвольные JSON‑структуры, weight задаёт приоритет показа.

Alert Center (ACP)

  • /acp/alerts/list — фильтрует критические уведомления (type, severity, status, tags, диапазон дат). Требует роль AccessAlerts (2048).
  • /acp/alerts/detail — возвращает карточку уведомления и историю доставок (Slack/Telegram webhooks).
  • /acp/alerts/ack — подтверждает обработку, сохраняет комментарий (виден всем операторам).
  • Конфигурация каналов и шаблонов описана в config/main.md; подробный контракт — в openapi.yaml.

Реклама (V1 → Ads)

  • POST /v1/ads/fetch возвращает доступные объявления (только включённые и отфильтрованные по весу). Дополнительно формируется video_url по domain.stream.subdomain из конфигурации.
  • POST /v1/ads/impression и POST /v1/ads/click инкрементируют счётчики просмотров/кликов по sid. Эти вызовы не возвращают payload.
  • Все V1 Ads маршруты используют стандартную платформенную авторизацию (X-Auth-*). См. структуры V1Ad, V1AdImpressionDTO, V1AdClickDTO в openapi.yaml.

Реклама (V2 → Ads)

  • POST /v2/ads возвращает массив объявлений (V2Ad) с теми же полями, что и V1 (включая вычисленный video_url).
  • POST /v2/ads/impression и POST /v2/ads/click принимают DTO (V2TrackImpression, V2TrackClick) и увеличивают счётчики по sid.
  • Все вызовы требуют V2 авторизацию (Authorization: Bearer JWT). Ответы следуют формату { "time": ..., "data": ... }. Схемы перечислены в openapi.yaml.

Заказы (Orders)

Модуль order реализует маппинг «SKU клиента → транзакция сервера» для IAP-покупок. Клиент создаёт заказ до инициации платежа на платформе, затем резолвит его при получении подтверждения.

Таблица orders

ПолеТипОписание
idserial PK
api_typeintegerТип платформы
api_uidtextUID пользователя на платформе
skutextИдентификатор продукта
transaction_idtext, nullableID транзакции (заполняется при resolve)
statustextpending / completed
created_attimestampВремя создания
updated_attimestampВремя последнего обновления

V1 API

  • POST /v1/orders/create — создаёт pending-заказ. Body: { "sku": "gem_pack_100" }. Возвращает { "order_id": 42, "sku": "gem_pack_100", "status": "pending" }.
  • POST /v1/orders/resolve — привязывает transaction_id к существующему заказу. Body: { "sku": "gem_pack_100", "transaction_id": "txn_abc123" }. Идемпотентен: если заказ с таким sku+transaction_id уже есть — вернёт его.

V2 API

  • POST /v2/orders/create — аналог V1, V2 формат ответа ({ "time": ..., "data": ... }).
  • POST /v2/orders/resolve — аналог V1, V2 формат ответа.

Cron-задача order_cleanup

Удаляет неразрешённые (pending) заказы старше domain.orders.ttl_days (по умолчанию 7 дней). Настройка:

yaml
infrastructure:
  scheduler:
    tasks:
      - name: Order Cleanup
        tag: order_cleanup
        type: order_cleanup
        expression: "0 3 * * *"   # каждый день в 3:00
        enabled: true

Конфигурация

КлючПо умолчаниюОписание
domain.orders.ttl_days7Сколько дней хранить pending-заказы

A/B тесты: Platform-Aware Distribution

При включённой опции platform_aware (per-test boolean в таблице experiments), распределение пользователей по экспериментальных группам учитывает api_type. Это гарантирует, что пропорции групп соблюдаются внутри каждой платформы, а не по всей таблице.

В MARV2 (legacy) используется конфиг-флаг experiments.platform_aware_distribution, в MARV (DDD) — колонка platform_aware на каждом тесте.

Rival Query: TABLESAMPLE

Запрос соперников (/v1/rivals/fetch) использует 3-фазный подход для оптимизации:

  1. TABLESAMPLE SYSTEM_ROWS — блочная выборка O(1), самый быстрый путь
  2. TABLESAMPLE BERNOULLI(1%) — fallback при разреженных данных
  3. ORDER BY RANDOM() — last resort для маленьких таблиц

Это заменяет прежний ORDER BY RANDOM(), который на таблицах с миллионами записей вызывал полный SeqScan + сортировку.

Требование: расширение tsm_system_rows должно быть установлено: CREATE EXTENSION IF NOT EXISTS tsm_system_rows; (миграция включена).

QueryBuilder: Raw SQL

QueryBuilder поддерживает raw SQL через методы Raw() и Scan():

go
recs := []orm.RivalRecord{}
err := db.Query(ctx).
    Raw("SELECT * FROM rivals TABLESAMPLE SYSTEM_ROWS(?) WHERE api_type IN (?) LIMIT ?", 200, apiTypes, limit).
    Scan(&recs).Error

Эти методы доступны через все обёртки: GORM-реализация, metrics-декоратор, transaction.