# Как три API Polymarket делят работу

> У Polymarket три REST API на трёх хостах, и выбор не того из них — самая частая причина, по которой буксует первая интеграция. Gamma описывает, что существует, CLOB это оценивает и торгует, Data API отвечает за кошелёк.

- Источник: https://papaproxy.net/ru/blog/polymarket-three-apis.php
- Опубликовано: 2026-08-08
- Автор: Alex Young
- Рубрика: API и данные · Блог PapaProxy.net

---

## Ключевые выводы

- Три хоста — три задачи: `gamma-api` для метаданных рынков и событий, `clob` для стаканов, цен и торговли, `data-api` для всего, что привязано к адресу кошелька.
- В объектах рынка Gamma поля `outcomes`, `outcomePrices` и `clobTokenIds` имеют тип string и приходят в JSON-кодировке — декодируйте до обращения по индексу, иначе будете сравнивать символы.
- Конвейер держится на ключах связи: `conditionId` связывает Gamma с Data API, а декодированные `clobTokenIds` — это `token_id` в CLOB и `asset` в Data API.
- Запрашивайте события, а не рынки, где это возможно: событие несёт свои рынки внутри и вдвое сокращает число походов.
- Большие обходы листайте через `/markets/keyset` и `/events/keyset`: курсорная пагинация, `limit` максимум 100, `offset` отклоняется с кодом 422, а на последней странице `next_cursor` отсутствует; offset-эндпоинты пока работают, но постепенно выводятся из обращения.
- В стакане CLOB `bids` отсортированы по цене по убыванию, а `asks` — по возрастанию, поэтому лучшая котировка с каждой стороны лежит по индексу `0`.
- Бюджеты раздельные и считаются по IP: Gamma — 4 000 запросов/10 с, CLOB — 9 000, Data API — 1 000 (у `/positions` — 150) под общим потолком 15 000, поэтому обогащение метаданными не конкурирует с опросом позиций.
- Учётные данные нужны только для торговли и собственного журнала: подпись L1 для получения API-ключа, затем HMAC-заголовки L2, с типом подписи и адресом фандера под тип аккаунта.

## Что покрывает Gamma

Базовый адрес Gamma API — `https://gamma-api.polymarket.com`, и его задача — каталог: всё, что нужно фронтенду, чтобы показать рынок ещё до того, как кто-либо начнёт торговать. В спецификации OpenAPI маршруты помечены как публичные, поэтому ни ключа, ни кошелька, ни подписи здесь не требуется.

Основной трафик несут два эндпоинта. Эндпоинт рынков Gamma API, `GET /markets`, отдаёт список рынков с фильтрами почти под любую задачу поиска: `slug`, `condition_ids`, `clob_token_ids`, `id` и `question_ids` для прямых запросов; `tag_id` вместе с `related_tags` для просмотра по категориям; `liquidity_num_min`/`max` и `volume_num_min`/`max` для порогов по размеру; `start_date_min`/`max` и `end_date_min`/`max` для временных окон; плюс `limit`, `offset`, `order` (список полей через запятую) и `ascending` для пагинации и сортировки. Параметр `closed` по умолчанию равен `false`, так что разрешившиеся рынки уже исключены, пока вы не попросите обратного.

Эндпоинт событий Gamma API, `GET /events`, на практике стоит брать первым. Событие — это контейнер («решение ФРС в октябре»), и приходит оно с вложенным массивом `markets`, поэтому один вызов события заменяет вызов рынков плюс цикл. Собственное руководство Polymarket советует то же самое: идти от событий, чтобы сократить число вызовов. Для прямого поиска по ссылке есть `GET /events/slug/{slug}` — слаг берётся прямо из URL Polymarket.

Одну ловушку на уровне полей стоит назвать до того, как вы напишете парсер, потому что она сидит в схеме, а не в тексте документации. В объекте рынка `outcomes`, `outcomePrices` и `clobTokenIds` имеют тип **string**, а не массив: они приходят в JSON-кодировке, поэтому `market["outcomePrices"][0]` вернёт символ `[`, а не цену. Сначала декодируйте. Всё остальное в объекте — то, чего ждёшь от каталога: `conditionId`, `question`, `slug`, `bestBid`, `bestAsk`, `lastTradePrice`, `spread`, `volumeNum`, `liquidityNum`, `volume24hr`, поля изменения цены по горизонтам, плюс операционные флаги вроде `enableOrderBook`, `acceptingOrders`, `orderPriceMinTickSize` и `orderMinSize`.

## Что покрывает CLOB

Базовый адрес CLOB API — `https://clob.polymarket.com`, и это торговая поверхность: живой стакан плюс всё, что вы с ним делаете. Рыночные данные здесь читаются свободно и без учётных данных: `/book` и `/books` для стаканов, `/price` и `/prices` для цены одной стороны, `/midpoint` и `/midpoints`, `/prices-history` для ценового ряда и отдельный запрос шага цены по рынку.

Обратите внимание на формы множественного числа. Батч-варианты существуют ровно затем, чтобы вы не ходили циклом: `/books` и `/prices` принимают набор токенов одним запросом. Их квота по числу запросов ниже — 500 за 10 секунд против 1 500 у одиночных версий, — но каждый запрос покрывает много токенов, поэтому для любой многотокенной задачи они сокращают именно количество запросов, а не наоборот. Насколько именно — зависит от того, сколько токенов вы упаковываете в вызов.

Эндпоинты CLOB API для состояния аккаунта и торговли закрыты аутентификацией CLOB API, устроенной в два уровня. L1 — подпись вашим приватным ключом (EIP-712), которая один раз создаёт или выводит API-ключ. L2 — сам ключ, отправляемый HMAC-заголовками в последующих запросах. Сработает это или нет, решают две детали аккаунта: тип подписи, соответствующий тому, как аккаунт создавался (обычный EOA либо типы `POLY_PROXY`, `GNOSIS_SAFE`, `POLY_1271`), и адрес фандера, который фактически держит деньги — для аккаунта на EOA это сам EOA. Аутентифицированные маршруты дальше покрывают выставление и отмену ордеров плюс ваш собственный журнал через `/data/orders` и `/data/trades` — именно ваши ордера и сделки, а не произвольного кошелька.

Две детали лимитов важны здесь сильнее, чем где-либо ещё. У эндпоинтов API-ключей свой жёсткий лимит — 100 запросов за 10 секунд, а ордера и отмены управляются и IP-лимитами Cloudflare, и отдельными токен-бакетами на подписанта: то есть один аккаунт не сможет пробить потолок, распределившись по адресам, и пытаться не стоит.

## Что покрывает Data API

Data API на `https://data-api.polymarket.com` отвечает на вопросы, ключом которых служит адрес. `/positions` и `/closed-positions` — для holdings, `/activity` — для хронологической ончейн-ленты, `/trades` — для исполнений, `/value` — для итога по портфелю, `/holders` — для крупнейших держателей конкретного рынка, плюс поверхности лидербордов.

Стоит сказать прямо, потому что это частый поисковый запрос: эндпоинта рынков у Data API нет — в смысле каталога рынков. Метаданные рынков живут в Gamma, а рыночная по форме поверхность Data API — это `/holders` плюс фильтры `market` и `eventId` на эндпоинтах, привязанных к адресу. Если вы ищете вопрос рынка, его слаг или ID токенов, вы не на том хосте; если ищете, кто им владеет, — на том.

## Как выбрать API под задачу

Решение сводится к одному вопросу: что у вас есть в качестве ключа? Каждый API индексирован своим идентификатором, и понимание того, как пользоваться Gamma API, по большей части сводится к умению переводить между ними.

| У вас есть | Нужно | Хост | Эндпоинт |
| --- | --- | --- | --- |
| Ссылка Polymarket | Метаданные рынка | Gamma | `/events/slug/{slug}` |
| `conditionId` | Вопрос рынка, даты, флаги | Gamma | `/markets?condition_ids=...` |
| ID токена | Живой стакан или цена | CLOB | `/book`, `/price` |
| Адрес кошелька | Позиции, активность, сделки | Data | `/positions`, `/activity`, `/trades` |
| Рынок | Его крупнейшие держатели | Data | `/holders` |

Практическое ядро — ключи связи. `conditionId` связывает Gamma с Data API: он присутствует и в объекте рынка, и в объекте позиции. `clobTokenIds` в рынке Gamma после декодирования даёт два ID токенов исходов — это и есть `token_id` в терминах CLOB и `asset` в позиции из Data API. Отсюда каноничный конвейер: находим в Gamma, оцениваем в CLOB, атрибутируем в Data — перенося `conditionId` и ID токенов по цепочке.

В пользу такого разделения есть и бюджетный аргумент. У трёх хостов раздельные квоты: Gamma — 4 000 запросов за 10 секунд, CLOB — 9 000, Data API — 1 000, под общим потолком в 15 000. Документация Polymarket говорит, что Cloudflare задерживает лишние запросы и ставит их в очередь, но [в нашем тесте августа 2026 года ответы 429 приходили сразу, без плавного замедления](/ru/blog/polymarket-api-rate-limits.php#_3). Поскольку бюджеты раздельные, обогащение позиций метаданными рынка не расходует квоту, которая нужна опросу позиций. Самый узкий из трёх — Data API, и проектировать стоит вокруг него: `/positions` разрешает 150 запросов за 10 секунд, `/trades` — 200.

Внутри этих бюджетов вас удерживают параметры эндпоинта рынков Gamma API. Фильтрация на стороне сервера через `tag_id`, границы дат и пороги объёма лучше, чем выкачать широко и отфильтровать у себя, а запрос событий вместо рынков схлопывает два похода в один. И кешируйте по ходу дела: вопрос рынка, его слаг и ID токенов не меняются, поэтому им место в локальном хранилище, а не в цикле опроса.

Для обходов больших выборок новый код должен листать курсорами, а не смещениями. Polymarket добавила `/markets/keyset` и `/events/keyset` как курсорную замену offset-эндпоинтам: вы читаете `next_cursor` из каждого ответа и передаёте его обратно в `after_cursor`, при `limit` не выше 100 и значении по умолчанию 20. Контракт здесь строг, и это удобно: `offset` на этих маршрутах явно отклоняется с кодом 422, а `next_cursor` присутствует, только пока остаются страницы, — то есть его отсутствие и есть условие остановки, а не догадка. Offset-эндпоинты пока работают, но идут к отказу от поддержки; всё, что строится сейчас, стоит строить на keyset.

## Как вызывать их из Python

Вот весь конвейер в одном примере на Python для CLOB API — от слага из ссылки до живого стакана; учётные данные нигде не нужны, так как все чтения ниже публичны.

PythonКопировать код

```python
import json, requests

GAMMA = "https://gamma-api.polymarket.com"
CLOB  = "https://clob.polymarket.com"
DATA  = "https://data-api.polymarket.com"
HEAD  = {"User-Agent": "market-tracker/1.0"}

def event_by_slug(slug):                      # 1. поиск: один вызов, рынки вложены внутрь
    r = requests.get(f"{GAMMA}/events/slug/{slug}", headers=HEAD, timeout=15)
    r.raise_for_status()
    return r.json()

def book(token_id):                           # 2. цена: ключ CLOB — токен, а не рынок
    r = requests.get(f"{CLOB}/book", params={"token_id": token_id}, headers=HEAD, timeout=15)
    r.raise_for_status()
    return r.json()

def holders(condition_id):                    # 3. атрибуция: ключ Data — адрес или рынок
    r = requests.get(f"{DATA}/holders", params={"market": condition_id}, headers=HEAD, timeout=15)
    r.raise_for_status()
    return r.json()

event = event_by_slug("fed-decision-in-october")     # подставьте любой живой слаг
for market in event.get("markets", []):
    outcomes  = json.loads(market["outcomes"])       # это JSON-строки, а не массивы
    token_ids = json.loads(market["clobTokenIds"])
    prices    = json.loads(market["outcomePrices"])
    print(market["question"], "|", market["conditionId"])

    for name, token, cached in zip(outcomes, token_ids, prices):
        b = book(token)
        # bids отсортированы по цене ПО УБЫВАНИЮ, asks — по возрастанию: лучшая котировка — индекс 0
        best_bid = b["bids"][0]["price"] if b.get("bids") else None
        best_ask = b["asks"][0]["price"] if b.get("asks") else None
        print(f"  {name:<6} gamma={cached:<8} bid={best_bid} ask={best_ask}")

    top = holders(market["conditionId"])              # третий хост: ключом служит тот же conditionId
    print(f"  держателей получено: {len(top)}")
```

На что смотреть — и чего пример намеренно не делает. Три вызова `json.loads` и есть смысл примера: пропустите их, и все дальнейшие сравнения будут молча работать с символами. Вторая ловушка — порядок в стакане: схема документирует `bids` как отсортированные по цене по убыванию, а `asks` — по возрастанию, поэтому лучшая котировка с каждой стороны лежит по индексу `0`. Потянувшись за `[-1]`, вы получите худшую стоящую заявку в стакане — в логе это выглядит достаточно правдоподобно, чтобы пережить ревью. Заголовок `User-Agent` выставлен потому, что запросы без узнаваемого агента, по сообщениям разработчиков, чаще упираются в Cloudflare. Цены из Gamma — каталожные значения и могут отставать от стакана, поэтому цикл печатает обе рядом: число Gamma годится для страницы со списком, число CLOB — для всего, на чём вы собираетесь торговать. В примере нет политики ретраев, нет батчинга (`/books` заменил бы цикл по токенам в реальном трекере), нет вебсокета для живых обновлений и нет пагинации.

И последнее операционное замечание, которое стоит людям вечера: Gamma и документация не полностью согласуются по фильтрам. Руководство Polymarket советует `active=true&closed=false` для живых рынков, тогда как текущая карточка OpenAPI для `/markets` перечисляет среди документированных параметров `closed` (со значением `false` по умолчанию), но не `active`. Отправляйте то, что документировано в карточке, остальное проверяйте по живым ответам и не считайте, что фильтр работает, раз его использовал туториал.
