> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.voodoo.center/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.voodoo.center/_mcp/server.

# Каталог

Полный каталог продуктов — каждый товар, доступный для заказа, с его ценой,
запасом, типом продукта и формой ввода — публикуется как единый снимок для
скачивания. Вы скачиваете его, читаете локально и используете `id` каждого
товара и его `fields`, чтобы размещать заказы. Нет пагинированного эндпоинта
«list products»; снимок **и есть** каталог.

URL для скачивания:

```
https://downloads.voodoo.center/catalog.lmdb.zst
```

Для скачивания аутентификация не требуется.

## Персональный каталог

URL выше предоставляет **глобальный** каталог, который использует стандартные
цены. Если ваш аккаунт имеет **персональные (согласованные) цены**, существует
отдельный **персональный каталог**, опубликованный по URL, содержащему ваш
**client id**. Он содержит те же записи, но `price` и `subscriber_price`
отражают *ваши* цены. Используйте персональный файл, если он существует; иначе
используйте глобальный.

URL для скачивания персонального каталога:

```
https://downloads.voodoo.center/{client_id}.lmdb.zst
```

Найдите свой **Client ID** — и готовый к использованию URL персонального
каталога — на странице **API** в дашборде (та же страница, где вы управляете
API-ключами и webhook). Не каждый аккаунт имеет персональные цены: если ваш не
имеет, этот URL возвращает **404** — вернитесь к `catalog.lmdb.zst`.

Это **тот же формат и схема**, что и у глобального каталога (сжатая zstd
однофайловая база данных [LMDB](http://www.lmdb.tech/doc/)); различаются только
значения цен. Итак: попробуйте свой персональный URL, вернитесь к глобальному
при 404, а затем распакуйте, откройте и прочитайте файл **точно так, как
показано ниже**.

```python
import urllib.error
import urllib.request

GLOBAL_URL = "https://downloads.voodoo.center/catalog.lmdb.zst"
PERSONAL_URL = "https://downloads.voodoo.center/{client_id}.lmdb.zst"


def download_catalog(client_id: str, dest: str = "catalog.lmdb.zst") -> str:
    # Предпочитаем персональный каталог (ваши согласованные цены); возвращаемся
    # к глобальному, если у этого аккаунта нет персонального файла (404 на персональном URL).
    try:
        urllib.request.urlretrieve(PERSONAL_URL.format(client_id=client_id), dest)
    except urllib.error.HTTPError as exc:
        if exc.code != 404:
            raise
        urllib.request.urlretrieve(GLOBAL_URL, dest)
    return dest


# Возьмите свой client id со страницы API в дашборде.
download_catalog("your-client-id")   # -> catalog.lmdb.zst
# Теперь распакуйте, откройте и прочитайте его точно так, как показано ниже.
```

## Что вы получаете

`catalog.lmdb.zst` — это однофайловая база данных [LMDB](http://www.lmdb.tech/doc/),
сжатая с помощью [zstandard](https://facebook.github.io/zstd/):

* **Один файл, одно пространство ключей.** Товары хранятся в базе данных LMDB по
  умолчанию. Каждый товар — это одна запись: **ключ** = 8-байтный item id в
  формате big-endian, **значение** = компактный JSON-объект.
* **Самодостаточные записи.** Каждый товар встраивает полную форму ввода
  (`fields`, где каждое поле выбора имеет свои `options`), поэтому единственный
  поиск по ключу даёт вам всё необходимое для заказа этого товара.
* **Маленький и быстрый.** Полный каталог сжимается примерно в 45× (база данных
  \~1,5 МБ → \~34 КБ). Распакуйте один раз, а затем отображайте базу данных в
  память и читайте её без обращений к серверу.

Цены в каталоге указаны в **центах** (целые числа) и отражают стандартные цены.
Ваше финальное списание всегда подтверждается ответом заказа и вашим балансом —
см. [Размещение заказов](/orders).

## Скачивание и открытие

Установите два ридера, а затем скачайте → распакуйте → откройте только для чтения.

```bash title="Установка зависимостей"
pip install zstandard lmdb
```

```bash
# Скачайте сжатый снимок
curl -o catalog.lmdb.zst https://downloads.voodoo.center/catalog.lmdb.zst

# Распакуйте zstd-фрейм в однофайловую базу данных LMDB
zstd -d catalog.lmdb.zst -o catalog.lmdb    # или: unzstd catalog.lmdb.zst
```

```python
import json
import struct
import urllib.request

import lmdb
import zstandard

CATALOG_URL = "https://downloads.voodoo.center/catalog.lmdb.zst"


def item_key(item_id: int) -> bytes:
    # Записи индексируются 8-байтным item id в формате big-endian (поэтому сортируются по id).
    return struct.pack(">Q", item_id)


# 1. Скачайте сжатый снимок.
urllib.request.urlretrieve(CATALOG_URL, "catalog.lmdb.zst")

# 2. Потоково распакуйте zstd-фрейм в однофайловую базу данных LMDB.
dctx = zstandard.ZstdDecompressor()
with open("catalog.lmdb.zst", "rb") as fin, open("catalog.lmdb", "wb") as fout:
    dctx.copy_stream(fin, fout)

# 3. Откройте окружение только для чтения. Это один ФАЙЛ, а не каталог (subdir=False),
#    и читатели в режиме только для чтения должны передавать lock=False.
env = lmdb.open("catalog.lmdb", subdir=False, readonly=True, lock=False)

# Найдите один товар по id.
with env.begin() as txn:
    raw = txn.get(item_key(689556))
    item = json.loads(raw) if raw else None
    print(item)

# Итерируйте весь каталог (записи выходят упорядоченными по id).
with env.begin() as txn:
    for key, value in txn.cursor():
        item = json.loads(value)
        if item["in_stock"]:
            price = item["price"] / 100  # центы -> основные единицы
            print(item["id"], item["product_type"], f"{price:.2f}", item["name"])
```

`★ Пояснение ─────────────────────────────────────`
LMDB отображается в память, поэтому открытие файла практически бесплатно, а
поиски читаются прямо из кеша страниц ОС — держите окружение открытым и
обращайтесь к нему миллионы раз, ничего не перечитывая заново. Именно поэтому
каталог поставляется как LMDB, а не как один огромный JSON-массив.
`─────────────────────────────────────────────────`

## Схема записи

Каждое значение — это JSON-объект:

| Поле               | Тип                 | Описание                                                                              |
| ------------------ | ------------------- | ------------------------------------------------------------------------------------- |
| `id`               | целое число         | Id товара. Передайте его как `item_id` при [размещении заказа](/orders).              |
| `name`             | строка              | Читабельное название товара.                                                          |
| `product_type`     | строка              | `key`, `topup` или `service` — определяет правила количества/полей при заказе.        |
| `min_quantity`     | число               | Минимальное количество для заказа.                                                    |
| `max_quantity`     | число               | Максимальное количество для заказа (фактический предел).                              |
| `price`            | целое число (центы) | Стандартная цена. Разделите на 100, чтобы получить основные единицы.                  |
| `base_price`       | целое число (центы) | Первоначальная/каталожная цена. Когда она больше `price`, показывайте её зачёркнутой. |
| `subscriber_price` | целое число (центы) | Цена с активной подпиской.                                                            |
| `amount_per_price` | число               | Единиц, доставляемых на единицу цены (актуально для пополнений).                      |
| `in_stock`         | логическое значение | Доступен ли товар для заказа сейчас.                                                  |
| `updated_at`       | строка (ISO 8601)   | Когда этот товар менялся в последний раз.                                             |
| `fields`           | массив              | Форма ввода товара — см. ниже.                                                        |

Каждая запись в **`fields`**:

| Поле       | Тип                 | Описание                                                               |
| ---------- | ------------------- | ---------------------------------------------------------------------- |
| `id`       | целое число         | Id поля.                                                               |
| `name`     | строка              | Ключ поля — используйте его как ключ в объекте `fields` заказа.        |
| `type`     | строка              | `string`, `integer`, `email`, `url` или `choice`.                      |
| `required` | логическое значение | Нужно ли предоставлять значение.                                       |
| `options`  | массив              | Только для полей `choice`: `[{ "id": <int>, "value": "<label>" }, …]`. |

```json title="Пример записи (товар типа top-up)"
{
  "id": 689556,
  "name": "Steam Top-up (USD)",
  "product_type": "topup",
  "min_quantity": 1,
  "max_quantity": 1000,
  "price": 500,
  "base_price": 500,
  "subscriber_price": 450,
  "amount_per_price": 1,
  "in_stock": true,
  "updated_at": "2026-07-05T12:00:00+00:00",
  "fields": [
    { "id": 88, "name": "steam_login", "type": "string", "required": true, "options": [] }
  ]
}
```

## Использование каталога с Orders API

Всё, что нужно для построения запроса `POST /api/v1/orders`, есть в записи:

* **`item_id`** = `id` записи.
* **`quantity`** — ограничена `min_quantity`/`max_quantity`. Обязательна для
  `topup` (десятичная) и `key` (целая, по умолчанию `1`); пропустите для `service`.
* **`fields`** — обязательны только для `topup` и `service`. Индексируйте каждую
  запись по **`name`** поля. Для поля **`choice`** отправляйте **`id`**
  выбранного варианта (целое число), а не его `value`. Для текстовых полей
  отправляйте строку.

```python
# `item` — это запись, прочитанная из каталога выше; `access_token` — ваш
# Bearer-токен (см. Аутентификация).
import urllib.request, json

body = {
    "item_id": item["id"],
    "quantity": 1,                  # в пределах min_quantity..max_quantity
    "merchant_order_id": "order-2024",
    "fields": {
        "steam_login": "test",      # текстовое поле -> его строковое значение
    },
}
req = urllib.request.Request(
    "https://api.voodoo.center/api/v1/orders",
    data=json.dumps(body).encode(),
    headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"},
    method="POST",
)
order = json.loads(urllib.request.urlopen(req).read())
print(order["id"], order["status"])
```

## Поддержание актуальности

Снимок регулярно перегенерируется. Его свежесть — это HTTP-заголовки `ETag` /
`Last-Modified` объекта; используйте условный запрос, чтобы повторно скачивать
только тогда, когда он действительно изменился:

```bash title="Повторное скачивание только при изменении"
# -z заставляет curl отправлять If-Modified-Since на основе mtime локального файла;
# 304 оставляет ваш файл без изменений.
curl -o catalog.lmdb.zst -z catalog.lmdb.zst https://downloads.voodoo.center/catalog.lmdb.zst
```

Надёжный подход для работающей интеграции: обновляйте по расписанию (например,
каждые несколько минут) условным запросом выше, и когда приходит новый файл,
распаковывайте его и заменяйте открытое окружение LMDB.

Используйте id и поля товара, чтобы разместить заказ.

Получите Bearer-токен, необходимый для вызова Orders API.