OFFCODEDOCSОткрыть платформу

Публичный API

Четыре маршрута только для чтения, без ключа и без cookie, с именами полей, которые ожидают агрегаторы (CoinGecko, CoinMarketCap) и рыночные библиотеки (CCXT). Они существуют, чтобы их читали роботы: тот, кто строит дашборд, питает индекс или сверяет цену, читает отсюда. Маршрута для ордеров в этом API нет — ничто здесь не двигает средства и не требует счёта.

База и соглашения

Всё висит на https://offcode.pro/api/public/, принимает только GET и отвечает application/json. ticker_id имеет вид BASE_TARGET (BTC_USDT): до подчёркивания указывается базовая валюта, после — валюта котировки; допускаются заглавные буквы и цифры, от 2 до 15 символов в каждой части. Пара, символ которой не укладывается в эту форму, не публикуется.

Каждое число передаётся десятичной строкой, никогда числом с плавающей точкой — монета с восемью знаками теряет знаки, проходя через float. Моменты — миллисекунды с начала эпохи Unix.

МаршрутЧто возвращаетПараметры
GET /pairsВсе пары, которые обслуживает этот фид, каждая с источником цены
GET /tickersЦена, объём за 24 ч, максимум, минимум и обе стороны для спотовых пар
GET /orderbookКнига ордеров одной парыticker_id · depth
GET /tradesПоследние сделки одной пары, новые первымиticker_id · type · limit
GET /historical_tradesТо же, что /trades — два имени, одна реализацияте же

Пары — /pairs

Список всего, что обслуживает этот фид, и ворота для остальных маршрутов: ticker_id, которого здесь нет, получает 404 на книге и на сделках. kind отделяет спотовый рынок платформы (spot) от бессрочного (perp); когда одно имя обслуживает оба, оно остаётся за спотом. price_source указывает, откуда берётся число, прямо в элементе.

GET /api/public/pairs
[
  { "ticker_id": "BTC_USDT", "base": "BTC", "target": "USDT", "kind": "spot", "price_source": "offcode-perp" },
  { "ticker_id": "1000PEPE_USDT", "base": "1000PEPE", "target": "USDT", "kind": "perp", "price_source": "offcode-perp" }
]

Тикеры — /tickers

Цена, объём за 24 часа, максимум, минимум и обе стороны для каждой пары spot. Пары perp не включены: это срез, который описывает стандарт агрегаторов.

GET /api/public/tickers
[
  {
    "ticker_id": "BTC_USDT", "base_currency": "BTC", "target_currency": "USDT",
    "last_price": "…", "base_volume": "…", "target_volume": "…",
    "bid": "…", "ask": "…", "high": "…", "low": "…", "timestamp": 1756800000000
  }
]
ticker_idПара в форме BASE_TARGET.
base_currency · target_currencyБазовая валюта и валюта котировки торговой пары, каждая в отдельном поле.
last_priceПоследняя цена сделки на бессрочном контракте.
base_volumeОбъём за 24 ч в базовой валюте по данным площадки, если она его публикует. Только при отсутствии этих данных API делит target_volume на last_price и округляет результат вниз до 8 знаков после запятой. Это явно обозначенное расчётное значение, а не показатель, полученный от биржи.
target_volumeОбъём за 24 ч в котируемой валюте, как есть из источника.
bid · askРавны друг другу и средней цене между лучшими ценами покупки и продажи бессрочного контракта. Эти значения не являются лучшими ценами заявок в стакане. См. Откуда берутся данные.
high · lowМаксимум и минимум за последние 24 часа.
timestampМомент чтения, в миллисекундах с начала эпохи Unix.

Книга ордеров — /orderbook

Книга ордеров одной пары. bids и asks — пары [price, size], обе десятичными строками, лучшая цена первой. timestamp — часы источника, не наши: локальные позволили бы устаревшей книге сойти за свежую.

depth необязателен и доходит до 100 уровней на сторону. Без него возвращается потолок: 100. Больший запрос не отклоняется — он обрезается до 100.

GET /api/public/orderbook?ticker_id=BTC_USDT&depth=100
{
  "ticker_id": "BTC_USDT",
  "timestamp": 1756800000000,
  "bids": [["…", "…"], ["…", "…"]],
  "asks": [["…", "…"], ["…", "…"]]
}

Сделки — /trades и /historical_trades

Недавняя лента одной пары, новая сделка первой. Два маршрута — одна реализация и один кэш: стандарт агрегаторов просит historical_trades, CCXT обычно просит trades, и так никогда не будет дня, когда один ответит иначе, чем другой.

type необязателен (buy или sell, по агрессивной стороне), а limit доходит до 100 и тоже обрезается, а не отклоняется. Без type возвращаются обе стороны, без limit — потолок: 100. trade_id производный — момент, сторона, цена и размер — стабилен для одной и той же сделки между двумя чтениями, и это не собственный идентификатор сделки площадки.

GET /api/public/trades?ticker_id=BTC_USDT&type=buy&limit=100
[
  { "trade_id": "…", "price": "…", "base_volume": "…", "target_volume": "…", "trade_timestamp": 1756800000000, "type": "buy" }
]

Откуда берутся данные

Цена, книга, сделки и объём за 24 часа в этом фиде приходят с бессрочного рынка — в том числе для пар с меткой spot. У спотового рынка OFFCODE нет собственного стакана: для котировки используется средняя цена между лучшими ценами покупки и продажи бессрочного контракта с тем же символом. Поэтому каждый элемент /pairs несёт price_source: тот, кто читает фид, узнаёт источник в самом фиде, а не выводит его из метки — и не выводит неверно.

bid и ask содержат одну и ту же среднюю цену между лучшими ценами покупки и продажи. Собственной спотовой книги нет, поэтому ask − bid здесь равен нулю. Нулевой спред невозможен в реальной книге: не считайте эти два поля сторонами одной.

base_volume — объём за 24 ч в базовой валюте бессрочного контракта, как его измеряет площадка при публикации. Только в его отсутствие маршрут делит target_volume на last_price и обрезает до 8 знаков вниз — объявленный расчёт, а не число с биржи.

Пара публикуется только там, где существует торгуемый рынок с тем же символом: публикация — это обещание обслуживать, и ticker_id, который выходит из /pairs, должен сразу же отвечать на книге и на сделках. Хранимая монета без торгуемой пары остаётся вне фида, как и пара, чьё имя на площадке несёт множитель — бессрочный 1000PEPE_USDT публикуется как есть, никогда как PEPE_USDT.

Лимиты, кэш и CORS

600 запросов в минуту на IP, на каждом маршруте. Выше — 429.

Ответ 2xx несёт Cache-Control: public, max-age=5; всё, что не 2xx, несёт no-store, потому что 404, удержанный пять секунд, переживает только что залистингованную пару, а удержанный 502 возвращает площадку, которая лежит, после того как она уже поднялась.

Внизу книга перечитывается не чаще, чем раз в 350 мс на пару и глубину, а сделки — раз в 1 секунду на пару: потолок обращений к площадке — это кэш, а не число спрашивающих. Неудачное чтение никогда не кэшируется.

Access-Control-Allow-Origin: *, без учётных данных — нет cookie, ключа и заголовка аутентификации, и ничто в ответе не персонализировано.

Ошибки

КодКогда возникаетТело
400ticker_id не по форме, type, который ни buy, ни sell, нецелый depth или limit{"code": "VALIDATION_ERROR", "message": "…"}
404Корректный ticker_id, которого нет в /pairs{"error": "UNKNOWN_TICKER"}
429Превышен лимит запросов для этого IP{"statusCode": 429, "code": "RATE_LIMITED", "message": "…"}
502Площадка не ответила или ответила ошибкой{"code": "UPSTREAM_UNAVAILABLE", "message": "…"}

502, а не 500, намеренно: сбой на площадке — не наш сбой, и тому, кто читает снаружи, нужна эта разница, чтобы понять, стоит ли пробовать снова.

Агрегаторы и автоматический доступ

Домен стоит за Cloudflare. Браузер проходит; вызов из командной строки без cookie может получить проверку вместо JSON — это не отказ маршрута, это край сети спрашивает, кто пришёл. Если вашему роботу нужно непрерывно обходить фид, попросите разрешение до включения краулера: укажите user-agent, исходящие диапазоны IP и предполагаемую частоту через Контакты.

Комиссий и торговых правил в этом API нет: они живут по адресу /api/public/fees и в разделе Комиссии. Состояние сервисов — на странице Статус платформы.