Game Supplier Developer API
API check v2.0 OpenAPI JSON
Разделы документации

Partner infrastructure · API v2.0

Простой API. Надёжная выдача.

Каталог, проверка, заказы и баланс. Коротко и предсказуемо.

Base URL https://api.tg-gemesup.shop
  1. 01 Каталогproduct_id и поля
  2. 02 Проверкаполучатель
  3. 03 Заказодин idempotency_key
  4. 04 Статусдо терминального

Авторизация

Ключ должен жить только на вашем сервере. Передавайте его в заголовке каждого партнёрского запроса.

Не отправляйте ключ в URL, JSON, браузерный JavaScript или логи.

API принимает Authorization: Bearer TOKEN или X-API-Key: TOKEN. Токены в query-параметрах отклоняются.

HTTP header
Authorization: Bearer YOUR_API_KEY

Готовый Python-клиент

Пример использует httpx, хранит ключ в переменной окружения и сохраняет один ключ идемпотентности на все сетевые повторы заказа.

pip install httpx
client.py
import os
import time
import uuid

import httpx


class Game SupplierClient:
    def __init__(self) -> None:
        self.http = httpx.Client(
            base_url="https://api.tg-gemesup.shop",
            headers={
                "Authorization": f"Bearer {os.environ['GAME_SUPPLIER_API_KEY']}",
                "Accept": "application/json",
            },
            timeout=30.0,
        )

    def request(self, method: str, path: str, **kwargs) -> dict:
        response = self.http.request(method, path, **kwargs)
        payload = response.json()
        if response.is_error:
            message = payload.get("message", "API request failed")
            raise RuntimeError(f"{response.status_code}: {message}")
        return payload

    def products(self, locale: str = "ru") -> list[dict]:
        return self.request(
            "GET",
            "/api/v1/products",
            params={"locale": locale},
        )["products"]

    @staticmethod
    def product_body(
        product_id: str,
        fields: dict[str, str],
        quantity: int | None = None,
    ) -> dict:
        body = {"product_id": product_id, "fields": fields}
        for key in ("player_id", "server_id"):
            if key in fields:
                body[key] = fields[key]
        if quantity is not None:
            body["quantity"] = quantity
        return body

    def check_player(
        self,
        product_id: str,
        fields: dict[str, str],
        quantity: int | None = None,
    ) -> dict:
        return self.request(
            "POST",
            "/api/v1/check-player",
            json=self.product_body(product_id, fields, quantity),
        )

    def create_order(
        self,
        product_id: str,
        fields: dict[str, str],
        partner_order_id: str,
        quantity: int | None = None,
    ) -> dict:
        key = uuid.uuid4().hex
        body = self.product_body(product_id, fields)
        body["partner_order_id"] = partner_order_id
        body["idempotency_key"] = key
        if quantity is not None:
            body["quantity"] = quantity

        for attempt in range(5):
            try:
                response = self.http.post("/api/v1/order", json=body)
            except httpx.TransportError:
                if attempt == 4:
                    raise
                time.sleep(2 ** attempt)
                continue

            if response.status_code >= 500 and attempt < 4:
                time.sleep(2 ** attempt)
                continue

            payload = response.json()
            if response.is_error:
                raise RuntimeError(
                    f"{response.status_code}: "
                    f"{payload.get('message', 'Order request failed')}"
                )
            return payload

        raise RuntimeError("Order request failed")

    def wait_order(self, order_id: int, timeout: int = 180) -> dict:
        terminal = {"completed", "refunded", "manual_review", "partial_completed"}
        deadline = time.monotonic() + timeout
        while time.monotonic() < deadline:
            order = self.request("GET", f"/api/v1/order/{order_id}")
            if order["status"] in terminal:
                return order
            time.sleep(3)
        raise TimeoutError(f"Order {order_id} is still processing")
GET

Баланс

/api/balance

Возвращает доступный баланс текущего партнёра.

Python
balance = client.request("GET", "/api/balance")
print(balance["balance"])
Пример ответа 200 OK
{
  "success": true,
  "partner_id": 24,
  "telegram_id": 123456789,
  "balance": 100.5
}
GET

Каталог

/api/v1/products

Загружайте каталог перед оформлением. Цена, доступность, количество и входные поля могут меняться.

locale query · string · необязательно

Язык каталога, например ru или en.

Python
products = client.products(locale="ru")
available = [product for product in products if product["available"]]

for product in available:
    print(product["section"], product["product_id"], product["name"], product["price"])
    for field in product["input_fields"]:
        print(field["key"], field["type"], field["required"])

Динамические поля товара

section содержит games или services и позволяет без эвристик разделить каталог в интерфейсе.

Стройте форму по input_fields и отправляйте значения по публичному key внутри объекта fields. Поддерживаются типы text, password, select и decimal.

СвойствоНазначение
requiredНельзя пропускать поле в заказе
optionsРазрешённые значения для select
min / maxДиапазон для decimal
min_length / max_lengthОграничение длины текста
hintПодсказка пользователю
Фрагмент ответа 200 OK
{
  "product_id": "public-product-id",
  "section": "games",
  "game": "Mobile Legends",
  "category": "Global",
  "name": "86 Diamonds",
  "price": 1.25,
  "available": true,
  "required_fields": ["player_id", "server_id"],
  "input_fields": [
    {
      "key": "player_id",
      "label": "Player ID",
      "type": "text",
      "required": true,
      "max_length": 255
    },
    {
      "key": "server_id",
      "label": "Server ID",
      "type": "select",
      "required": true,
      "options": [{"value": "eu", "label": "Europe"}]
    }
  ]
}
POST

Проверка получателя

/api/v1/check-player

Проверяет входные данные перед заказом. Передавайте только поля, объявленные товаром.

Python
recipient = client.check_player(
    product_id="public-product-id",
    fields={
        "player_id": "123456789",
        "server_id": "eu",
    },
)
print(recipient["nickname"])
Пример ответа 200 OK
{
  "success": true,
  "valid": true,
  "product_id": "public-product-id",
  "player_id": "123456789",
  "server_id": "eu",
  "nickname": "PlayerName"
}
POST

Создание заказа

/api/v1/order
Один логический заказ — один idempotency_key.

При timeout или 5xx повторите то же тело с тем же ключом. Новый ключ создаст новый заказ и новое списание.

ПолеКогда передаватьОграничение
product_idВсегдаИз свежего каталога
fieldsПо input_fieldsДо 64 значений
quantityДля товаров с количествомМежду min_quantity и max_quantity
idempotency_keyВсегда1–128 символов
partner_order_idНеобязательноДо 128 символов
Python
order = client.create_order(
    product_id="public-product-id",
    fields={
        "player_id": "123456789",
        "server_id": "eu",
    },
    partner_order_id="SHOP-1042",
)

final_order = client.wait_order(order["order_id"])
print(final_order["status"])
print(final_order["fulfilled_amount"], final_order["refunded_amount"])
Пример ответа 200 OK
{
  "success": true,
  "order_id": 300001,
  "partner_order_id": "SHOP-1042",
  "idempotency_key": "5b1bde8bf55545d1975c86d76b0a94c1",
  "idempotent_replay": false,
  "status": "processing",
  "product_id": "public-product-id",
  "quantity": 1,
  "price": 1.25,
  "balance_after": 98.75
}
GET

Статус и список заказов

/api/v1/order/{order_id} /api/v1/orders
Python
order = client.request("GET", "/api/v1/order/300001")

page = client.request(
    "GET",
    "/api/v1/orders",
    params={"status": "processing", "limit": 20, "offset": 0},
)
processingЗаказ принят и выполняется
completedЗаказ выполнен
refundedСредства возвращены на баланс
manual_reviewТребуется ручная проверка
partial_completedПодтверждённая часть выдана, стоимость невыданной части возвращена
Проверяйте итоговые суммы у каждого терминального заказа.

fulfilled_amount — стоимость подтверждённо выданной части, refunded_amount — фактически возвращённая сумма. Для составного PUBG-заказа ответ также содержит redeemed_uc, failed_uc, redeemed_codes и failed_codes. Статус ожидания провайдера не считается ошибкой и не создаёт возврат.

Для подарочных кодов частичный возврат возможен только при явном терминальном частичном статусе конкретного внешнего заказа и точном уникальном наборе выданных кодов. Внешние пополнения, Telegram Stars и Telegram Premium считаются атомарными: неоднозначный результат после возможного внешнего действия переводится в ручную проверку без автоматического возврата.

Частичное исполнение 180 UC 200 OK
{
  "success": true,
  "order_id": 300001,
  "status": "partial_completed",
  "price": 3.0,
  "fulfilled_amount": 2.0,
  "refunded_amount": 1.0,
  "delivery": {
    "total_uc": 180,
    "redeemed_uc": 120,
    "failed_uc": 60,
    "redeemed_codes": 2,
    "failed_codes": 1
  }
}

Безопасные повторы и ошибки

Повторяйте чтение свободно. Для создания заказа решение зависит от ответа.

Timeout или обрыв сетиПовторить то же тело с тем же idempotency_key.
502, 503, 504 или другой 5xxПодождать 3–10 секунд и повторить с тем же ключом.
429Ждать время из retry_after_seconds.
4xx валидацииИсправить запрос. Не повторять его вслепую.
КодЧто означает
invalid_api_tokenКлюч отсутствует, отозван или неверен
invalid_product_idТовар не найден в текущем каталоге
player_not_foundПолучатель не прошёл проверку
not_enough_balanceБаланс недостаточен, заказ не создан
product_unavailableТовар временно недоступен
idempotency_key_conflictКлюч уже использован с другим телом запроса
rate_limitedПревышен лимит запросов

Лимиты

Лимиты применяются к партнёру и защищают заказ от случайных каскадных повторов.

Создание заказа
1 запрос/с · burst 5
Проверка получателя
5 запросов/с · burst 20
Методы чтения
10 запросов/с · burst 60