> 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.