Game Supplier Developer API
API check v2.0 OpenAPI JSON
Documentation sections

Partner infrastructure · API v2.0

Simple API. Reliable delivery.

Products, validation, orders and balance. Clear and predictable.

Base URL https://api.tg-gemesup.shop
  1. 01 Catalogproduct_id and fields
  2. 02 Validationrecipient
  3. 03 Orderone idempotency_key
  4. 04 Statusuntil terminal

Authentication

Keep the key on your server only. Send it in the header of every partner request.

Never put the key in URLs, JSON, browser JavaScript, or logs.

The API accepts Authorization: Bearer TOKEN or X-API-Key: TOKEN. Tokens in query parameters are rejected.

HTTP header
Authorization: Bearer YOUR_API_KEY

Ready-to-use Python client

The example uses httpx, keeps the key in an environment variable, and reuses one idempotency key for every network retry.

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

Balance

/api/balance

Returns the current partner's available balance.

Python
balance = client.request("GET", "/api/balance")
print(balance["balance"])
Response example 200 OK
{
  "success": true,
  "partner_id": 24,
  "telegram_id": 123456789,
  "balance": 100.5
}
GET

Catalog

/api/v1/products

Fetch the catalog before checkout. Price, availability, quantity, and input fields may change.

locale query · string · optional

Catalog language, for example ru or 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"])

Dynamic product fields

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

Build the form from input_fields and send values using the public key inside the fields. Supported types: text, password, select and decimal.

PropertyPurpose
requiredThe field is required in the order
optionsAllowed values for select
min / maxRange for decimal
min_length / max_lengthText length limit
hintUser hint
Response excerpt 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

Recipient validation

/api/v1/check-player

Validates input before ordering. Send only fields declared by the product.

Python
recipient = client.check_player(
    product_id="public-product-id",
    fields={
        "player_id": "123456789",
        "server_id": "eu",
    },
)
print(recipient["nickname"])
Response example 200 OK
{
  "success": true,
  "valid": true,
  "product_id": "public-product-id",
  "player_id": "123456789",
  "server_id": "eu",
  "nickname": "PlayerName"
}
POST

Create order

/api/v1/order
One logical order — one idempotency_key.

On timeout or 5xx, retry the same body with the same key. A new key creates a new order and charge.

FieldWhen to sendConstraint
product_idAlwaysFrom the latest catalog
fieldsAccording to input_fieldsUp to 64 values
quantityFor quantity-based productsBetween min_quantity and max_quantity
idempotency_keyAlways1–128 characters
partner_order_idOptionalUp to 128 characters
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"])
Response example 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

Order status and list

/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},
)
processingOrder accepted and processing
completedOrder completed
refundedFunds returned to balance
manual_reviewManual review required
partial_completedПодтверждённая часть выдана, стоимость невыданной части возвращена
Проверяйте итоговые суммы у каждого терминального заказа.

fulfilled_amount — стоимость подтверждённо выданной части, refunded_amount — фактически возвращённая сумма. Для составного PUBG-заказа ответ также содержит redeemed_uc, failed_uc, redeemed_codes and 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
  }
}

Safe retries and errors

Read requests may be retried freely. Order creation retries depend on the response.

Timeout or network interruptionRetry the same body with the same idempotency_key.
502, 503, 504, or another 5xxWait 3–10 seconds and retry with the same key.
429Wait for the duration in retry_after_seconds.
Validation 4xxFix the request. Do not retry it blindly.
CodeMeaning
invalid_api_tokenKey is missing, revoked, or invalid
invalid_product_idProduct not found in the current catalog
player_not_foundRecipient validation failed
not_enough_balanceInsufficient balance; order not created
product_unavailableProduct temporarily unavailable
idempotency_key_conflictKey already used with a different request body
rate_limitedRequest rate limit exceeded

Limits

Limits apply per partner and protect orders from accidental retry cascades.

Create order
1 request/s · burst 5
Recipient validation
5 requests/s · burst 20
Read methods
10 requests/s · burst 60