Здесь описано публичное чтение. API возвращает конфигурацию, каталог, публичную ленту и статус процесса. Корзины, заказы и выдачу на игровые серверы ведёт отдельный API плагинов по ключу магазина — ключи создаются в кабинете, в разделе «Плагин и API». Платежи через HTTP пока не принимаются.
01 / QUICK START
Первый запрос за пару строк
Получите каталог магазина. Тело запроса и заголовок Authorization не требуются.
Все примеры используют таймаут клиента 10 секунд. Это рекомендация для примера, а не гарантированное время ответа API. Python-пример использует стандартную библиотеку. C#-пример предназначен для консольного приложения .NET 6+ и не требует сторонних пакетов.
01
Уточните игру и валютуВызовите GET /v1/store перед отображением цен.
02
Загрузите товарыИспользуйте GET /v1/products и обработайте пустой массив.
03
Покажите результатВыберите нужный вариант товара и кэшируйте ответ на своей стороне.
02 / ACCESS
Доступ и окружение
Публичное чтение
API-ключ, Bearer-токен и пользовательская сессия не нужны. Методы не возвращают приватные профили, email или реквизиты платежей. Ключ магазина нужен только API плагинов игровых серверов; методы этого справочника его не принимают, поэтому ограничение запросов здесь считается по IP клиента.
Отдельный backend
Страница /api/ — документация. Локальный API запускается отдельно командой npm run api:dev на порту 3001. Порт сайта 3000 не обслуживает /v1/*.
Запросы из браузера и CORS
Сайт — adonate.app, панель — my.adonate.app, магазины — <slug>.adonate.app. CORS разрешает HTTPS-источники платформы и зарегистрированных магазинов из STORE_REGISTRY, а также точные источники из WEB_ORIGIN. Произвольный поддомен не получает доступ автоматически.
Для браузера разрешены GET, HEAD и OPTIONS; credentialed CORS не включён. Запрос без заголовка Origin — cURL, серверный клиент, мониторинг — механизмом CORS не ограничивается, но лимит запросов действует на него так же.
Для публичной интеграции используйте HTTPS-адрес развёрнутого backend. NEXT_PUBLIC_API_URL задаёт адрес для витрины и примеров на этой странице. API_INTERNAL_URL используется только серверным рендерингом витрины и не должен становиться публичным адресом.
ДемоDATABASE_URL не задан
Каталог из встроенных примеров. Лента возвращает 4 демонстрационные покупки.
База данныхDATABASE_URL задан
Каталог из PostgreSQL. Лента возвращает пустой массив до подключения реальных покупок.
Машиночитаемый контракт
Swagger UI: /docs · OpenAPI JSON: /openapi.json на запущенном сервере API. Схемы Product, Store и PurchaseFeed строятся из общих Zod-контрактов. Межполевые правила для вариантов и шансов описаны ниже: JSON Schema не отражает все проверки.
Оба маршрута монтируются только вне production-режима: при ADONATE_ENV=production процесс их не публикует, поскольку дамп схемы перечисляет все поля и проверки. В Caddy наружу проксируется только /v1/*, так что и в демо-режиме документация доступна лишь на самом процессе API.
03 / ENDPOINTS
Методы API
На домене магазина он определяется по Host; на основном домене передайте ?store=slug. Каталог поддерживает фильтры и пагинацию. В примерах замените demo на slug подключённого магазина. Успешный ответ — 200 application/json.
Получите идентификатор, игру, валюту, навигацию и виджеты магазина. Начните с этого метода, чтобы правильно интерпретировать каталог.
Каждый магазин имеет свою игру, ID, slug и адрес https://<slug>.adonate.app. STORE_REGISTRY связывает домен с магазином; два магазина одной игры имеют отдельные каталоги.
На домене магазина он определяется автоматически. На adonate.app и my.adonate.app передайте ?store=<slug>. Неизвестный магазин — 404, несовпадение домена и store — 400. Флаг development относится к интерфейсу; источник каталога проверяйте через /v1/health.
Изменения из демо-админки хранятся в браузере: этот метод не читает их. Массив серверов отдельным полем Store пока не возвращается.
Получите товары выбранной игры: привилегии, наборы, валюту и рулетки. Ответ — JSON-массив без обёртки data, счётчика total или курсора.
Без DATABASE_URL возвращаются 10 демо-товаров выбранной игры. С базой данных — только строки с ID и игрой выбранного магазина. Пустой каталог возвращается как [] со статусом 200.
Параметры: category=ranks|kits|currency|roulette; q — поиск по названию и подзаголовку (до 100 символов); sort=popular|price-asc|price-desc|name; limit=1…100; offset=0…100000. Без limit возвращается весь результат. По умолчанию sort=popular и offset=0. Неизвестные, повторные и неверные параметры — 400.
Цены — положительные целые числа в единицах валюты магазина: 290 означает 290 ₸ при KZT. Это не тиыны. Конвертации валют и локализации через этот API нет.
При выборе варианта используйте его price и oldPrice. defaultVariantId указывает на вариант по умолчанию; если его нет, витрина выбирает первый вариант. При недоступности БД или нарушении схемы ответа возвращается 503.
Получите публичную ленту для виджета последних покупок. Это отдельный формат для витрины, а не список заказов или подтверждение оплаты.
Лента отдаёт только подтверждённые публичные покупки. Пока такие записи не подключены, items пустой в обоих режимах.
При заданном DATABASE_URL ответ — { mode: "live", items: [] }. Чтение реальных заказов и публикация покупок пока не подключены; даже mode: live не подтверждает наличие оплаченных заказов.
Записи содержат публичный alias, а не SteamID, email или платёжные данные. minutesAgo — относительное число минут, не временная метка. Статусов платежей, сумм и истории заказов в ответе нет.
Проверьте, что процесс API отвечает, и узнайте выбранный источник каталога. Метод подходит для простой проверки доступности процесса.
status: ok означает, что обработчик доступен. Подключения к PostgreSQL и игровым серверам здесь не проверяются. Это liveness-проверка, а не гарантия готовности всех зависимостей.
mode: bundled — переменная DATABASE_URL отсутствует; mode: database — задана. При недоступной БД health может вернуть 200, а products — 503.
paymentsEnabled всегда false в текущей версии. Доступность метода не означает, что сервис принимает реальные платежи.
Обязательность относится к полям ответа. Публичных методов создания или изменения этих объектов в API нет.
Необязательные поля могут отсутствовать. При чтении БД известные опциональные поля со значением null убираются перед отправкой. Учитывайте дополнительные поля в будущих версиях; не привязывайте клиент к порядку ключей.
ProductТовар каталога
Поля Product
Поле / тип
Описание
idstringОбязательное
Идентификатор товара. В демо — rust-vip и аналогичные значения; не разбирайте ID как обязательный формат.
gameGameIdОбязательное
Игра магазина. Шесть допустимых значений приведены ниже.
categoryenumОбязательное
ranks · kits · currency · roulette. Значение all — фильтр интерфейса и не категория товара.
namestringОбязательное
Название товара.
subtitlestringОбязательное
Краткий подзаголовок.
customCopybooleanОпциональное
Признак собственного текста товара для витрины.
descriptionstringОпциональное
Развёрнутое описание, до 10 000 символов.
priceinteger > 0Обязательное
Базовая цена в валюте магазина. Для варианта используется variants[].price.
oldPriceinteger > 0Опциональное
Предыдущая цена. Проверка oldPrice > price схемой не задаётся.
crown · chest · roulette — ключ иллюстрации, не URL и не загруженный файл.
badgestringОпциональное
Текст метки на карточке.
featuresstring[]Обязательное
Особенности товара. Массив может быть пустым.
periodstringОбязательное
Текст срока или количества: например, «30 дней». Для варианта отображается label.
popularnumberОбязательное
Числовой показатель для сортировки. Это не количество оплаченных заказов.
layoutenumОпциональное
vertical · horizontal · square. При отсутствии вид выбирается витриной по категории.
variantsProductVariant[]Опциональное
Если передан — минимум один вариант; id внутри массива уникальны.
defaultVariantIdstringОпциональное
Непустой ID существующего элемента variants.
rewardsRouletteReward[]Опциональное
Обязателен для category: roulette. Минимум одна награда, сумма шансов — 100%.
ProductVariantВариант товара
Поля ProductVariant
Поле / тип
Описание
idstringОбязательное
Непустой ID, уникальный внутри товара.
labelstringОбязательное
Непустая подпись, например «30 дней» или «5 открытий».
priceinteger > 0Обязательное
Цена выбранного варианта в валюте магазина.
oldPriceinteger > 0Опциональное
Предыдущая цена варианта; отсутствие не заменяется базовой oldPrice.
durationDaysinteger > 0Опциональное
Продолжительность в днях. API не отсчитывает и не активирует срок.
openingsinteger > 0Опциональное
Количество открытий. API не запускает розыгрыш награды.
RouletteRewardНаграда рулетки
Поля RouletteReward
Поле / тип
Описание
idstringОбязательное
Непустой идентификатор награды. Схема не требует совпадения с Product.id.
labelstringОбязательное
Непустое название награды.
chancenumberОбязательное
Больше 0, не больше 100. Сумма chance всех наград — 100 с допуском 0.000001.
StoreКонфигурация магазина
Поля Store
Поле / тип
Описание
slugstringОпциональное
Уникальный поддомен магазина. Служебные имена, включая my, api и www, зарезервированы.
urlstring (HTTPS URL)Опциональное
Канонический адрес магазина на adonate.app.
idstringОбязательное
Непустой ID магазина; текущий формат конфигурации — adonate-{game}.
namestringОбязательное
Непустое название. По умолчанию aDonate.
gameGameIdОбязательное
Одна игра на магазин. Выбирается на сервере, не параметром запроса.
currencyCurrencyCodeОбязательное
Валюта отображения. Конфигурация этого API возвращает KZT.
developmentbooleanОбязательное
Витрина рисует черновик владельца из браузера, а не конфигурацию сервера. В продакшене всегда false.
monitoringEnabledbooleanОбязательное
Флаг виджета мониторинга. HTTP-метода мониторинга серверов пока нет.
serversobject[]Опциональное
Созданные владельцем серверы: id — неизменяемый ключ; name — название (1–60 символов); host — IP или домен без протокола и порта (пустая строка скрывает подключение); port — целое 1–65535; visible — видимость на витрине. Пустой список не подменяется готовыми серверами. Черновики кабинета сохраняются в браузере и автоматически в API не публикуются.
widgetsWidget[]Обязательное
Виджеты сообщества типов discord, telegram, vk или custom.
navigationNavigationItem[]Обязательное
Упорядоченные ссылки навигации магазина.
PurchaseFeedПубличная лента покупок
Поля PurchaseFeed
Поле / тип
Описание
modebundled | liveОбязательное
Режим всей ленты; items пока всегда пустой.
itemsRecentPurchase[]Обязательное
Массив публичных записей, может быть пустым.
items[].idstringОбязательное
Идентификатор записи ленты, не платёжный ID.
items[].productIdstringОбязательное
Ссылка на товар каталога.
items[].serverstringОбязательное
Название игрового сервера.
items[].aliasstringОбязательное
Публичный псевдоним, до 24 символов.
items[].minutesAgointeger ≥ 0Обязательное
Относительное время в минутах; в демо — фиксированные значения.
HealthСтатус процесса
Поля Health
Поле / тип
Описание
status"ok"Обязательное
Ответ работающего обработчика. Не проверяет зависимости.
modebundled | databaseОбязательное
Выбранный источник каталога по наличию DATABASE_URL.
paymentsEnabledfalseОбязательное
Приём платежей в этой версии отключён.
WidgetВиджет сообщества
Поля Widget
Поле / тип
Описание
idstringОбязательное
Идентификатор виджета.
typeenumОбязательное
discord · telegram · vk · custom. Определяет набор остальных полей; вложенного поля discord или telegram нет.
titlestringОбязательное
Заголовок. По умолчанию Discord, Telegram или VKontakte для соответствующего типа; для custom задаётся явно.
serverIdstringОпциональное
Только discord, присутствует в его ответе. По умолчанию adonate-guild.
inviteUrlstringОпциональное
Только discord, присутствует в его ответе. Ссылка-приглашение.
memberCountintegerОпциональное
Присутствует у discord, telegram и vk. Отображаемое количество участников, не результат отдельного метода мониторинга.
onlineCountintegerОпциональное
Присутствует только у discord. Отображаемое количество онлайн.
urlstringОпциональное
Присутствует у telegram и vk; необязателен у custom. Ссылка виджета.
handlestringОпциональное
Присутствует у telegram и vk. Имя сообщества.
descriptionstringОпциональное
Необязательное описание только для custom.
iconstringОпциональное
Необязательное обозначение иконки только для custom.
badgestringОпциональное
Необязательная метка только для custom.
NavigationItemСсылка навигации
Поля NavigationItem
Поле / тип
Описание
idstringОбязательное
Непустой ID ссылки.
labelstring | objectОбязательное
Непустая строка или объект с обязательным ru и необязательными kk, en, pl, uk.
hrefstringОбязательное
Локальный путь /…, якорь #… или HTTP(S) URL; без пробелов, обратных слешей и protocol-relative // адресов.
visiblebooleanОбязательное
Видимость ссылки, по умолчанию true.
Игры · GameId
Minecraftminecraft
Rustrust
Unturnedunturned
alt:Valtv
Project Zomboidzomboid
DayZdayz
Валюты · CurrencyCode
Общая схема принимает KZT · RUB · USD · PLN · UAH · USDT · USDC · TON · GRAM · TRX · LTC · BTC · ETH · SOL. Текущая конфигурация API всегда возвращает KZT. Этот список описывает допустимые коды, а не подключённые платёжные способы или сервис обмена валют.
Форматы аккаунта игрока
Эти проверки применяются общей библиотекой витрины. Перечисленные GET-методы не принимают аккаунт игрока, не проверяют его существование и не выполняют вход.
Игра
Формат
Пример
Minecraft
3–16 латинских букв, цифр или _
Steve_123
alt:V
1–10 цифр; первая от 1 до 9
12345
Rust, Unturned, Zomboid, DayZ
SteamID64: 17 цифр, начало 7656119
76561198000000000
05 / LIMITS
Лимиты без мелкого шрифта
Ограничение запросов включено: 120 запросов в минуту по умолчанию, на IP клиента. Тарифных планов и гарантированного SLA по-прежнему нет.
Запросы в минуту
120 по умолчанию
Считается по IP клиента, в скользящем окне 60 секунд, отдельно для каждого процесса API. Для /v1/products лимит строже, для /v1/health — выше; таблица ниже. При превышении возвращается 429 с заголовком Retry-After.
Заголовки лимита
x-ratelimit-*
Каждый ответ несёт остаток и время до сброса окна, поэтому опрашивать лимит отдельным запросом не нужно. Ориентируйтесь на них, а не на подсчёт запросов у себя.
Ключ лимита
IP клиента
Публичные методы работают без ключа, поэтому окно общее для всех клиентов за одним адресом: NAT, корпоративный прокси или CI делят одну квоту. За обратным прокси реальный IP учитывается только при настроенном TRUST_PROXY. Ключ магазина есть только у API плагинов /v1/plugin/*, и там действует ещё и лимит на ключ.
Размер каталога
limit и offset
limit — 1…100, offset — 0…100000. Без limit сохраняется совместимость: возвращается весь каталог магазина, подходящий под фильтры.
Запись данных
Только API плагинов
Методы этого справочника — только GET: они не создают платёж, заказ или выдачу. Корзины, заказы и выдачу игровые серверы ведут через API плагинов по ключу магазина; он описан отдельно и в справочник не входит.
Таймаут API
SLA не установлен
Подключение и выполнение SQL ограничены 5 секундами каждое. Клиенту всё равно следует задавать собственный HTTP-таймаут.
Соединения с БД
До 5 на процесс
Пул PostgreSQL ограничен 5 соединениями на процесс API. Именно его защищает пониженный лимит на /v1/products.
Кэш и повторы
На стороне клиента
API возвращает Cache-Control: no-store и X-Request-Id для поиска запроса в логах; Retry-After приходит с 429. Встроенная витрина считает данные свежими 60 секунд и выполняет одну повторную попытку.
Подписки и события
Недоступны
Нет webhooks, WebSocket и SSE. Лента покупок — обычный GET без потока обновлений; плагины забирают выдачу опросом.
Квоты по методам
Путь
Запросов / 60 с
Почему так
/v1/products
60
Каждый запрос обращается к PostgreSQL: самый дорогой метод, лимит строже общего.
/v1/health
600
Liveness-проверка не читает базу; лимит рассчитан на опрос системами мониторинга.
/v1/store, /v1/purchases
120
Общий лимит. Обе конфигурации кэшируются на стороне клиента и меняются редко.
Заголовки ответа
Заголовок
Когда
Значение
x-ratelimit-limit
Каждый ответ
Максимум запросов в текущем окне для этого пути.
x-ratelimit-remaining
Каждый ответ
Сколько запросов осталось до конца окна. 0 — следующий будет 429.
x-ratelimit-reset
Каждый ответ
Секунд до обнуления счётчика.
retry-after
Только 429
Секунд до следующей разрешённой попытки. Используйте это значение, а не фиксированную паузу.
Окно считается независимо каждым процессом API: за балансировщиком фактический предел умножается на число процессов, поэтому не стройте на нём точный учёт. Ключ — IP клиента, так что за NAT или корпоративным прокси квота общая. Реальный адрес учитывается только при заданной переменной TRUST_PROXY: без неё запросы из-за обратного прокси считаются по адресу прокси.
Ограничения значений
price, oldPrice, durationDays, openings: положительные целые числа; отдельного бизнес-лимита сверху нет. description — до 10 000 символов; alias — до 24.
Если variants или rewards переданы, массив не может быть пустым. Верхний предел длины не задан. ID вариантов уникальны в пределах товара.
defaultVariantId должен существовать в variants. У рулеток обязателен массив rewards, а сумма положительных шансов должна быть 100% с допуском 0.000001.
minutesAgo — целое число от 0. Значения категорий, игр, оформления и изображений ограничены перечислениями в моделях.
РЕКОМЕНДАЦИЯ ДЛЯ ИНТЕГРАЦИИ
Кэшируйте. Обновляйте осознанно.
Кэш каталога на 60 секунд, одна одновременная загрузка на витрину и таймаут клиента 10 секунд держат обычную витрину на порядок ниже квоты. При 503 или сетевой ошибке — ограниченное число повторов с растущей задержкой; при 429 — пауза ровно на Retry-After. Следите за x-ratelimit-remaining: если он регулярно подходит к нулю, добавьте кэш, а не параллельные запросы.
06 / ERRORS
Ошибки и восстановление
Сначала проверяйте HTTP-статус, затем разбирайте JSON. Прокси может вернуть HTML или оборвать соединение — JSON-тело есть не у каждой ошибки.
200
OKУспешный ответ. Пустые массивы каталога и ленты допустимы.
400
Bad RequestНеверный параметр, не указан store на основном домене или выбранный магазин не соответствует домену запроса.
404
Not FoundНеизвестный магазин, товар или путь. Проверьте домен, slug, ID товара и метод GET.
429
Too Many RequestsПревышен лимит запросов. Дождитесь числа секунд из заголовка Retry-After и повторите — не отправляйте запрос сразу и не наращивайте параллелизм. Тело: statusCode, error, message.
503
Service UnavailableКаталог недоступен: ошибка чтения БД или проверки схемы результата. Повторите позже с задержкой; сохранённый каталог можно показать как устаревший.
NETWORK
Нет доступного HTTP-ответаПроверьте адрес backend, TLS, таймаут и CORS. Не выдавайте такую ошибку за пустой каталог.
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded, retry in 42 seconds"
}
Ответы 401, 403 и 422 методами этого справочника не формируются: аутентификации и тел запросов у них нет. Если они приходят, их отдаёт внешний прокси — обрабатывайте отдельно, его правила задаёт оператор. Не выводите игрокам необработанный текст внутренних ошибок.
07 / WHAT’S NEXT
Что пока не входит в API
Эти возможности требуют отдельной реализации. Наличие похожего экрана в демо-панели не означает наличие серверного метода.
Создание магазинов и управление ключами через APIНЕДОСТУПНО
Изменение товаров, промокодов и страницНЕДОСТУПНО
Платежи и возвратыНЕДОСТУПНО
Лента реальных покупокНЕДОСТУПНО
Вебхуки и проверка их подписиНЕДОСТУПНО
Steam OAuth, мониторинг и загрузка файловНЕДОСТУПНО
Префикс /v1 — версия маршрутов, 0.2.0 — текущая версия контракта приложения. Политика сроков поддержки и вывода версий из эксплуатации пока не опубликована. Перед обновлением сверяйте контракт и межполевые правила.