{
  "openapi": "3.0.3",
  "info": {
    "title": "TDUgame API",
    "version": "1.2.0",
    "description": "API TDUgame — сервиса аренды Steam-аккаунтов. Два уровня:\n\n1. **Публичный каталог** (`/products`) — без ключа, read-only, для витрин/сравнения цен.\n2. **Reseller API** (`/v1/*`) — аренда, продление, получение логина/пароля и кода Steam Guard программно, для автоматизаций (например, плагин FunPayCardinal). Требует заголовок `X-Api-Key`.\n\nКлюч получаете в профиле на сайте (кнопка «Сгенерировать ключ»). Лимит — **120 запросов в 60 секунд** на ключ, остаток виден в заголовках `X-RateLimit-Remaining`/`X-RateLimit-Reset` каждого ответа `/v1/*`.\n\n`POST /v1/rent` и `POST /v1/rentals/{uid}/extend` принимают заголовок `Idempotency-Key` — повторный запрос с тем же значением вернёт тот же результат без повторного списания (защита от ретраев при сетевых таймаутах).\n\nВебхуки о смене статуса аренды (`rental.created`/`expiring_soon`/`expired`/`extended`/`terminated`) настраиваются на странице документации — подробности и проверка подписи: https://tdugame.ru/api-docs.html#вебхуки.\n\n### Коды ошибок (`/v1/*`)\n| HTTP | error | Описание |\n|---|---|---|\n| 401 | invalid_api_key | Ключ отсутствует, неверен или отозван |\n| 403 | account_disabled | Аккаунт заблокирован |\n| 403 | not_active | Аренда не активна (истекла/завершена) |\n| 400 | insufficient_balance | Недостаточно средств на балансе |\n| 400 | no_stock | Нет свободных аккаунтов на нужное количество |\n| 404 | not_found | Товар или аренда не найдены / не принадлежат вам |\n| 429 | rate_limited | Превышен лимит запросов |\n| 502 | catalog_unavailable | Технический сбой каталога, попробуйте позже |",
    "contact": {
      "url": "https://tdugame.ru/support"
    }
  },
  "servers": [
    {
      "url": "https://tdugame.ru/api",
      "description": "Продакшн"
    }
  ],
  "tags": [
    {
      "name": "Каталог (без ключа)",
      "description": "Список игр, цены, расчёт стоимости аренды — публично, без авторизации"
    },
    {
      "name": "Reseller API",
      "description": "Требует X-Api-Key. Аренда, управление активными арендами, логин/пароль, код Steam Guard."
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key",
        "description": "Ключ из профиля на сайте (tdugame.ru/profile), формат tdu_<64 hex>"
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Свой уникальный ID запроса. Повторный вызов с тем же значением возвращает тот же ответ без повторного списания — используйте при ретраях после таймаута."
      }
    },
    "schemas": {
      "Product": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "headerImage": {
            "type": "string",
            "nullable": true
          },
          "pricePerHourRub": {
            "type": "number"
          },
          "pricePerHourUsd": {
            "type": "number"
          },
          "availableAccounts": {
            "type": "integer"
          },
          "minHours": {
            "type": "integer"
          },
          "maxHours": {
            "type": "integer"
          },
          "nextAvailable": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "availableInSeconds": {
                  "type": "integer"
                },
                "availableInHuman": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Quote": {
        "type": "object",
        "properties": {
          "priceRub": {
            "type": "number"
          },
          "priceUsd": {
            "type": "number"
          },
          "discountPercent": {
            "type": "number"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "Rental": {
        "type": "object",
        "properties": {
          "rentalUid": {
            "type": "string"
          },
          "productId": {
            "type": "string"
          },
          "productName": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "expired",
              "terminated"
            ]
          },
          "expiresAt": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string"
          }
        }
      }
    }
  },
  "paths": {
    "/products": {
      "get": {
        "tags": [
          "Каталог (без ключа)"
        ],
        "summary": "Список игр в каталоге",
        "description": "Возвращает все доступные для аренды игры с ценами и наличием. Сортировка — по убыванию количества свободных аккаунтов.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Поиск по названию игры"
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Product"
                  }
                },
                "example": [
                  {
                    "id": 279,
                    "name": "MECCHA CHAMELEON",
                    "headerImage": "https://shared.fastly.steamstatic.com/store_item_assets/steam/apps/.../header.jpg",
                    "pricePerHourRub": 1,
                    "pricePerHourUsd": 0.02,
                    "availableAccounts": 239,
                    "minHours": 1,
                    "maxHours": 720,
                    "nextAvailable": []
                  }
                ]
              }
            }
          },
          "502": {
            "description": "Каталог временно недоступен",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{id}": {
      "get": {
        "tags": [
          "Каталог (без ключа)"
        ],
        "summary": "Одна игра по ID",
        "description": "Точечный доступ вместо вытягивания всего каталога.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "404": {
            "description": "not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{id}/quote": {
      "post": {
        "tags": [
          "Каталог (без ключа)"
        ],
        "summary": "Расчёт цены аренды",
        "description": "Точный расчёт стоимости — та же формула, что при оформлении заказа, скидка за длительность нелинейная.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hours"
                ],
                "properties": {
                  "hours": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  }
                }
              },
              "example": {
                "hours": 24,
                "quantity": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "priceRub": 42,
                  "priceUsd": 0.51,
                  "discountPercent": 12
                }
              }
            }
          },
          "400": {
            "description": "Некорректные параметры",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Технический сбой, попробуйте позже",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Баланс кошелька",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "example": {
                  "balanceRub": 1200
                }
              }
            }
          },
          "401": {
            "description": "Неверный ключ",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/products": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Список игр (с ключом)",
        "description": "То же самое, что публичный /products, но требует ключ — для единообразия с остальным reseller-API.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Product"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Неверный ключ"
          }
        }
      }
    },
    "/v1/products/{id}": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Одна игра по ID",
        "description": "Идентично публичному /products/{id}, но с ключом.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "404": {
            "description": "not_found"
          }
        }
      }
    },
    "/v1/products/{id}/quote": {
      "post": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Расчёт цены (с ключом)",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hours"
                ],
                "properties": {
                  "hours": {
                    "type": "integer"
                  },
                  "quantity": {
                    "type": "integer",
                    "default": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "400": {
            "description": "Некорректные параметры"
          },
          "502": {
            "description": "Технический сбой, попробуйте позже"
          }
        }
      }
    },
    "/v1/rent": {
      "post": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Арендовать аккаунт",
        "description": "Списывает стоимость с баланса ключа и сразу арендует. Логин/пароль отдельным запросом — GET /v1/rentals/{uid}/credentials.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "productId",
                  "hours"
                ],
                "properties": {
                  "productId": {
                    "type": "integer"
                  },
                  "hours": {
                    "type": "integer"
                  },
                  "quantity": {
                    "type": "integer",
                    "default": 1
                  }
                }
              },
              "example": {
                "productId": 279,
                "hours": 24,
                "quantity": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "example": {
                  "rentalUids": [
                    "7a6543f9-f8b2-4c00-993c-48e4dd810e38"
                  ],
                  "failedCount": 0,
                  "priceRub": 42,
                  "balanceRub": 158
                }
              }
            }
          },
          "400": {
            "description": "insufficient_balance / no_stock / invalid_duration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "not_found — товар не найден"
          },
          "502": {
            "description": "catalog_unavailable"
          }
        }
      }
    },
    "/v1/rentals": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Активные аренды",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Rental"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/rentals/{uid}": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Статус одной аренды",
        "description": "Точечная проверка вместо сканирования всего списка активных аренд.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rental"
                }
              }
            }
          },
          "404": {
            "description": "not_found"
          }
        }
      }
    },
    "/v1/rentals/{uid}/credentials": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Получить логин/пароль",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "example": {
                  "login": "jk97211859",
                  "password": "fOsfEMUAzo1Y28qW"
                }
              }
            }
          },
          "404": {
            "description": "not_found"
          },
          "403": {
            "description": "not_active"
          }
        }
      }
    },
    "/v1/rentals/{uid}/code": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Код Steam Guard",
        "description": "Одноразовый код, меняется при каждом запросе.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "example": {
                  "code": "ABCDE",
                  "expiresIn": 30
                }
              }
            }
          },
          "404": {
            "description": "not_found"
          },
          "409": {
            "description": "operation_inflight — код уже запрашивается, подождите"
          }
        }
      }
    },
    "/v1/rentals/{uid}/extend": {
      "post": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Продлить аренду",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hours"
                ],
                "properties": {
                  "hours": {
                    "type": "integer"
                  }
                }
              },
              "example": {
                "hours": 24
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "example": {
                  "priceRub": 42,
                  "balanceRub": 116,
                  "expiresAt": "2026-09-22T10:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "insufficient_balance"
          },
          "404": {
            "description": "not_found"
          }
        }
      }
    },
    "/v1/rentals/{uid}/terminate": {
      "post": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Завершить аренду досрочно",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "example": {
                  "status": "terminated"
                }
              }
            }
          },
          "404": {
            "description": "not_found"
          }
        }
      }
    },
    "/v1/price-table": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Цены всех игр на нужные сроки",
        "description": "Цены «игра × срок» одним запросом (ориентировочные; точная сумма — в /v1/rent и /v1/products/{id}/quote).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "hours",
            "in": "query",
            "required": false,
            "description": "Сроки в часах через запятую (до 40 значений); по умолчанию стандартный набор.",
            "schema": {
              "type": "string",
              "example": "1,3,24,168"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "example": {
                  "hours": [
                    1,
                    24
                  ],
                  "prices": {
                    "218": {
                      "1": 4,
                      "24": 48
                    }
                  },
                  "ready": true
                }
              }
            }
          },
          "401": {
            "description": "Неверный ключ",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/funpay-map": {
      "get": {
        "tags": [
          "Reseller API"
        ],
        "summary": "Категории FunPay для игр",
        "description": "В какой раздел FunPay обычно выставляют игру и какие поля формы нужны.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Успех",
            "content": {
              "application/json": {
                "example": {
                  "games": [
                    {
                      "productId": 2,
                      "name": "Rust",
                      "aliases": [],
                      "funpay": {
                        "categoryId": 92,
                        "subcategoryId": 250,
                        "subcategoryType": "common",
                        "fields": {
                          "fields[type]": "Аренда"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Неверный ключ",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}