HTTP API
Весь HTTP платформы — это один шлюз и одна форма пути. Здесь: как устроены версии, как выглядят успех и ошибка, откуда берётся воркспейс запроса, как листать списки и какие ограничения действуют на границе.
Форма пути и версии
Каждый маршрут выглядит как /api/v{n}/<домен>/...: версия — всегда второй сегмент, и рядом с ней ничего не стоит. Шлюз приводит входящий путь к этой форме до маршрутизации, поэтому таблицы маршрутов, спецификации и кэш видят только канонические пути.
| Путь | Что это |
|---|---|
/api/v1/... | контракт: обычный, «развёрнутый» JSON |
/api/v2/... | те же пути, обработчики и HTTP-статусы; тело завёрнуто в конверт |
/api/docs, /api/openapi.json, /api/openapi.v2.json | справочник и спецификации — вне версии, потому что описывают версии |
/api/health | проба, проксируется на фронтенд-контейнер |
/bff/... | внутренние эндпоинты сайта, авторизуются кукой; это не публичный API |
v2 — это не другой набор эндпоинтов. Путь /api/v2/X переписывается на /api/v1/X и попадает в тот же обработчик с тем же статусом; отличается только тело. Переезд с v1 на v2 не меняет ни пути, ни параметры, ни коды ответов.
Полный пример, v1
GET /api/v1/workspaces/by-host?host=owt.craazzzyyfoxx.me HTTP/1.1
Host: owt.craazzzyyfoxx.me
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-store
{"workspace_id": 1, "slug": "anak"}
Тот же запрос в v2
GET /api/v2/workspaces/by-host?host=owt.craazzzyyfoxx.me HTTP/1.1
Host: owt.craazzzyyfoxx.me
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-store
{"ok": true, "data": {"workspace_id": 1, "slug": "anak"}}
В v2 успешный ответ — это всегда {"ok": true, "data": ...}, иногда с дополнительным массивом warnings. Пустое тело (204 No Content) остаётся пустым в обеих версиях, а data может быть литеральным null.
Ошибки
Ошибка в v1 — плоский объект: человекочитаемый detail, машинный code и любые структурированные детали, которые прислал сервис, на верхнем уровне:
{
"detail": "workspace_id query parameter is required",
"code": "bad_request"
}
Та же ошибка в v2:
{
"ok": false,
"error": {
"code": "bad_request",
"message": "workspace_id query parameter is required"
}
}
Ветвиться нужно по code (v1) или error.code (v2); detail и message — текст для человека, парсить его не надо. Структурированные подробности приходят как fields (список: field, msg, code и всё, что добавил сервис) и retry_after; в v2 они лежат внутри error.details.
code | HTTP | Когда |
|---|---|---|
bad_request | 400 | параметр не назван или не разобран |
unauthorized | 401 | учётка не предъявлена или не действует |
forbidden | 403 | учётка распознана, но действие не разрешено |
not_found | 404 | нет такого объекта, либо он вне вашего воркспейса |
conflict | 409 | состояние не позволяет: дубликат, гонка, занятый слот |
gone | 410 | объект существовал и больше недоступен |
payload_too_large | 413 | тело больше потолка |
unprocessable | 422 | JSON разобран, но схема или правило не выполнены — смотрите fields |
rate_limited | 429 | лимит или квота; читайте Retry-After |
unavailable | 503 | зависимость сервиса недоступна, попробуйте позже |
internal | 500 | всё остальное |
Собственные отказы шлюза (до сервиса дело не дошло) в v1 могут прийти без code — например 404 {"detail": "Not Found"} на несуществующем маршруте /api/v1/... или 503 {"detail": "service unavailable"}. В v2 code там выводится из статуса, то есть присутствует всегда.
Старые префиксы
Раньше auth, analytics, balancer, streams, notifications и announcements стояли рядом с версией: /api/auth/me. Эти написания ещё отвечают — шлюз переписывает их на канонические (/api/v1/auth/me), посегментно, так что /api/authx не считается совпадением. Каждый такой ответ несёт три заголовка:
Deprecation: true
Sunset: Thu, 01 Apr 2027 00:00:00 GMT
Link: </api/v1/auth/me>; rel="successor-version"
Переезд — это только префикс: тело, статусы и параметры не меняются.
Воркспейс запроса
Воркспейс — корень аренды, и в API он называется явно, параметром workspace_id. Хост запроса выбирает сообщество для сайта (поддомен платформы или подтверждённый собственный домен), но не подставляет аренду в API: собственный домен сообщества отвечает по тем же путям /api/..., что и платформенный хост, и доменные чтения там точно так же ждут workspace_id. Сопоставление хоста и воркспейса отдаёт публичный GET /api/v1/workspaces/by-host?host=... — он же в примере выше; неизвестный или неподтверждённый хост даёт null.
Правила разрешения такие:
- Явный
workspace_idв запросе побеждает всегда. - Если параметра нет, а предъявленная учётка привязана ровно к одному воркспейсу (API-ключ — всегда, сессия — если участник состоит в одном сообществе), шлюз подставит его сам. Так ключу не приходится повторять в строке запроса то, что уже сказано в самой учётке.
- Если воркспейсов у учётки несколько или её нет вовсе, шлюз не угадывает: доменное чтение без
workspace_idотвечает400 bad_request.
Подстановка не расширяет права: шлюз называет только тот воркспейс, который уже есть в учётке, а проверку прав всё равно выполняет сервис.
Пагинация
Списки страничные, по номеру страницы: page (с 1) и per_page. Ответ — конверт из четырёх ключей:
{"page": 1, "per_page": 10, "total": 137, "results": []}
Значения по умолчанию и потолок per_page зависят от эндпоинта (встречаются 10, 20, 25, 30, 50 при максимуме 100–500), поэтому сверяйтесь со справочником эндпоинтов. Особый случай — per_page=-1: «всё», с жёсткой защитой в 10 000 строк. Многие списки принимают ещё sort и order (asc либо desc), а поиск — query.
Два места устроены иначе, и это видно по полям ответа:
- Уведомления листаются курсором: в ответе есть
next_cursor(на последней странице —null), его же и передают обратно параметромcursor. - История чата комнат (пик/бан, драфт) догружается параметром
after_id— по идентификатору последнего известного сообщения.
Кэш публичных ответов
Часть публичных чтений шлюз держит в собственном кэше в памяти. Правила жёсткие и их стоит знать:
- Кэшируются только
GETи только v1 — запрос в v2 всегда идёт мимо. - По умолчанию кэш видит только анонимные запросы: наличие заголовка
Authorizationего отключает. Исключение — несколько заведомо не зависящих от зрителя чтений (страница турнира, стадии, таблица, списки встреч и команд), где вошедший читатель делит ту же запись. - Хранятся только ответы
200. Ошибки и404скрытых турниров не кэшируются никогда. - Время жизни записи — 30 секунд по умолчанию, но это лишь страховка: записи турнира сбрасываются событием, как только воркер что-то в нём изменил.
Результат виден в заголовке X-Cache: HIT — ответ из кэша, MISS — этот запрос сходил наверх, COALESCED — запрос дождался чужого похода наверх по тому же ключу (параллельные промахи по одному ключу схлопываются в один вызов).
Ключ кэша — путь плюс отсортированная строка запроса, поэтому ?a=1&b=2 и ?b=2&a=1 — одна запись, а разные workspace_id разводятся автоматически.
Снаружи ответы API кэшировать нельзя: если сервис не поставил свой заголовок, шлюз проставляет Cache-Control: private, no-store на всё под /api/ и /bff/. Содержимое зависит от зрителя, и промежуточный кэш отдал бы чужое.
Ограничения на границе
- Эндпоинты аутентификации ограничены по IP: регистрация, вход, OAuth-callback и обмен SSO-тикета — по умолчанию 10 запросов за 60 секунд. Обновление сессии считает только неудачные попытки, чтобы общий выход в интернет не разлогинивал всех, кто за ним сидит.
- API-ключи меряются по самому ключу, а не по IP, против его собственного
requests_per_minute(при отсутствии значения — 60 в минуту). На каждом таком ответе, успешном или нет, есть тройка заголовковRateLimit-Limit,RateLimit-RemainingиRateLimit-Reset(секунды до сброса) — по ним удобно держать темп, не упираясь в стену. - Анонимный трафик может быть ограничен по IP общим бюджетом; включается настройкой деплоя.
Отказ выглядит одинаково у всех слоёв: 429, заголовок Retry-After и тело с code равным rate_limited.
{"retry_after": 60, "detail": "Too many requests", "code": "rate_limited"}
Лимиты на границе — не то же самое, что квоты воркспейса, ключа и сессии: те считают ещё и тяжёлые операции, и приходят с quota_exceeded в fields. Подробнее — в статье Аутентификация и ключи API.
Таймауты и отказы шлюза
Каждый вызов идёт к доменному сервису с бюджетом времени (по умолчанию 120 секунд, у дешёвых чтений — меньше). Снаружи это видно так:
| Ответ | Что случилось |
|---|---|
503, Retry-After: 1 | сервис или аутентификация недоступны, запрос даже не начался — повторяйте |
504 | сервис не ответил в отведённый срок |
502 | ответ сервиса не удалось разобрать |
404 | путь под /api/v1/ не совпал ни с одним маршрутом |
Тело JSON-запроса читается не более 12 МиБ; всё, что больше, обрывается и приводит к 400. Отдельный потолок на размер тела может задавать квота — тогда ответ будет 413.
Где полный список эндпоинтов
Статьи описывают модель и правила, а перечень маршрутов генерируется из таблиц самого шлюза:
/api/docs— интерактивный справочник с переключателем v1/v2./api/openapi.json— спецификация v1,/api/openapi.v2.json— спецификация v2.
См. также
- Аутентификация и ключи API — учётки, скоупы, квоты, 401 против 403.
- Воркспейсы и права — откуда берётся
workspace_idи что он ограничивает. - Realtime (WebSocket) — как получать изменения, не опрашивая API.
- Обзор — устройство платформы целиком.