Partner infrastructure · API v2.0
Simple API. Reliable delivery.
Products, validation, orders and balance. Clear and predictable.
https://api.tg-gemesup.shop
https://api.tg-gemesup.shop
- 01 Catalogproduct_id and fields
- 02 Validationrecipient
- 03 Orderone idempotency_key
- 04 Statusuntil terminal
Authentication
Keep the key on your server only. Send it in the header of every partner request.
The API accepts Authorization: Bearer TOKEN or X-API-Key: TOKEN. Tokens in query parameters are rejected.
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
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")
Balance
/api/balanceReturns the current partner's available balance.
balance = client.request("GET", "/api/balance")
print(balance["balance"])
Response example 200 OK
{
"success": true,
"partner_id": 24,
"telegram_id": 123456789,
"balance": 100.5
}
Catalog
/api/v1/productsFetch the catalog before checkout. Price, availability, quantity, and input fields may change.
locale
query · string · optional
Catalog language, for example ru or en.
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.
| Property | Purpose |
|---|---|
required | The field is required in the order |
options | Allowed values for select |
min / max | Range for decimal |
min_length / max_length | Text length limit |
hint | User 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"}]
}
]
}
Recipient validation
/api/v1/check-playerValidates input before ordering. Send only fields declared by the product.
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"
}
Create order
/api/v1/orderidempotency_key.
On timeout or 5xx, retry the same body with the same key. A new key creates a new order and charge.
| Field | When to send | Constraint |
|---|---|---|
product_id | Always | From the latest catalog |
fields | According to input_fields | Up to 64 values |
quantity | For quantity-based products | Between min_quantity and max_quantity |
idempotency_key | Always | 1–128 characters |
partner_order_id | Optional | Up to 128 characters |
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
}
Order status and list
/api/v1/order/{order_id}
/api/v1/orders
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 processingcompletedOrder completedrefundedFunds returned to balancemanual_reviewManual review requiredpartial_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.
idempotency_key.retry_after_seconds.| Code | Meaning |
|---|---|
invalid_api_token | Key is missing, revoked, or invalid |
invalid_product_id | Product not found in the current catalog |
player_not_found | Recipient validation failed |
not_enough_balance | Insufficient balance; order not created |
product_unavailable | Product temporarily unavailable |
idempotency_key_conflict | Key already used with a different request body |
rate_limited | Request 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
