Песочница

Документация SportWire API

REST-интерфейс к спортивным данным: расписание, live, события, статистика, xG, составы, коэффициенты, таблицы, история и справочники. Базовый URL — https://api.sportwire.ru. Все ответы — JSON в UTF-8; даты — ISO-8601 (UTC).

Неизвестный параметр не пропадает молча с 22.09.2026. Если в запросе к /v1/… есть параметр, которого ручка не знает, ответ несёт заголовок X-Ignored-Params с его именем (несколько — через запятую). Сам запрос выполняется как раньше и ошибкой не становится: заголовок говорит, какой фильтр не применён, чтобы опечатку или несуществующий параметр не принять за «таких данных нет».

Обзор

REST API поверх единой модели данных: эндпоинты /v1/…, ключ в заголовке X-API-Key.

Каждая сущность (матч, турнир, команда, игрок) — единая и недублированная; названия доступны на английском, русском и китайском (см. Локализация).

Аутентификация

API-ключ выдаётся мгновенно в личном кабинете (тариф Trial — бесплатно).

REST — заголовок:

curl · api.sportwire.ru
curl https://api.sportwire.ru/v1/sports \
  -H "X-API-Key: ВАШ_КЛЮЧ"

Для встраивания картинок в <img> ключ можно передать как ?key=.

Лимиты и тарифы

ТарифЗапросов / секЦена
Trial1бесплатно
Starter5₽15 990 / мес
Pro20₽39 900 / мес
Business100₽89 900 / мес

Текущий расход виден в личном кабинете. При превышении частоты запросов (запросов/сек) — ответ 429. Суточных и месячных лимитов нет.

Ошибки

КодЗначение
400Ошибка ввода, названная по имени: bad_date (дата не YYYY-MM-DD — поле в field), unknown_sport (нет такого slug — см. /v1/sports), q_too_short (поиск короче 3 символов). Отрицательные offset/limit не ошибка — приводятся к 0 / 1
401Ключ не передан или недействителен
402Вид спорта вне тарифа — в лентах, карточке и матчах участника, рейтингах (error + ссылка upgrade)
404Сущность не найдена
422Неверные параметры запроса; unsupported_lang — язык не из en / ru / zh
429Превышена частота запросов (лимит запросов/сек тарифа)
503Сервис временно недоступен. Если ответ содержит Retry-After, учитывайте указанную задержку перед следующим запросом чтения. Это не ответ «данных нет».

Язык текста ошибки с 13.10.2026. Поля detail / error по умолчанию — по-русски. С заголовком Accept-Language: zh текст ошибки приходит по-китайски, с en (и любым другим языком, кроме ru) — по-английски; коды и имена полей от языка не зависят. Данные ответа (name, name_ru, …) этот заголовок не меняет — язык данных задаёт ?lang=.

Локализация: английский, русский, китайский обновлено 23.09.2026

Имя любой сущности приходит плоскими полями рядом: name — английское (оригинал), name_ru — русское. Так устроены все ответы: у турнира в ленте — tournament и tournament_ru, у игрока в составе — player_name и player_name_ru, у страны — country_ru. Где русского ещё нет, *_ru равно null — показывайте name_ru ?? name. Карточки /v1/participant/{id} и /v1/sports дополнительно несут весь объект name_i18n.

Китайский — параметром lang=zh на любой ручке /v1 новое. Рядом с каждым полем X_ru появляется X_zh: name_zh, tournament_zh, player_name_zh, country_zh, у стадии матча — stage.name_zh («第6轮», «1/8决赛», «B组»). null — китайского имени у нас пока нет; показывайте name_zh ?? name — откат на английский, та же идиома, что с русским. Имена — в упрощённом письме (简体).

GET · /v1/events?tournament_id=31382&lang=zh (сокращено)
{ "id": 11189007, "tournament": "Premier League", "tournament_ru": "Английская Премьер-лига",
  "tournament_zh": "英超",
  "stage": { "type": "regular", "code": "regular", "name": "Round 5", "name_ru": "5-й тур",
             "round": 5, "name_zh": "第5轮" },
  "participants": [
    { "participant_id": 7598, "side": "home", "name": "Manchester City",
      "name_ru": "Манчестер Сити", "name_zh": "曼城", "score": 5 },
    { "participant_id": 7597, "side": "away", "name": "Sunderland",
      "name_ru": "Сандерленд", "name_zh": "桑德兰", "score": 3 } ] }
ПравилоПочему
name остаётся английским при любом langна нём держатся сверка и поиск у интеграторов; параметр добавляет поля, а не меняет смысл существующих
lang=en и lang=ru принимаются и ничего не меняютанглийский и русский и так есть в каждом ответе
zh в любой записи тега: zh-CN, zh_Hans, zh-TWкитайские имена у нас одного варианта — упрощённого
другой язык — 422 с error: unsupported_lang и спискоммолча отдать английский на lang=ja значило бы выдать отказ за ответ
заголовок Accept-Language не читаетсяязык задаётся только параметром — он виден в адресе и не путается в кэшах
заголовок ответа X-Lang: zh; filled=21/21сколько имён в ответе получили китайский из скольких — видно без разбора тела

Китайское имя ставится только той сущности, которой принадлежит имя. Мы не подставляем перевод «по соседству»: у события в инцидентах participant_id — это команда, а имя — игрока, и китайское имя игрока берётся по его собственному id. Где id у объекта нет (тренеры в карточке матча, подпись окна истории), X_zh равно null.

Покрытие китайскими именами (замер 23.09.2026, заполнение идёт постоянно): страны — 327 из 327, виды спорта — все, турниры — 38 834 из 55 050 (значимые — 1 145 из 1 450), команды — 26 662, игроки — 31 657. Команды АПЛ, NBA, NFL и MLB последних сезонов — все. В MCP язык задаётся тем же lang=zh — см. MCP-сервер.

В лентах матчей русское название лиги приходит отдельным плоским полем tournament_ru рядом с tournament — и в /v1/events, и в карточке /v1/event/{id}. Второй запрос за русским названием турнира делать не нужно; с lang=zh рядом встаёт tournament_zh.

Виды спорта

GET/v1/sports

Список видов спорта с идентификаторами и названиями.

JSON
{ "sports": [
  { "id": 1, "slug": "football",   "name_i18n": {"en":"Football","ru":"Футбол"} },
  { "id": 2, "slug": "basketball", "name_i18n": {"en":"Basketball","ru":"Баскетбол"} }
]}

Матчи и расписание

GET/v1/events

Матчи с фильтрами. Параметры:

ПараметрОписание
sportslug вида спорта (например football)
dateдата YYYY-MM-DD (по времени начала)
statusscheduled · live · finished
toptrue — только сильнейшие турниры: importance ≥ 90. Это топ-дивизионы ведущих стран, континентальные клубные турниры и соревнования сборных. Вторые дивизионы, региональные и любительские лиги сюда не входят — для них задайте свой порог через min_importance (например min_importance=82 вернёт и вторые дивизионы). На фиде по дате топ-матчи идут первыми; в ответе у каждого матча есть importance (0–100).
Порог поднят с 50 до 90 17.08.2026: прежний пропускал в «топ» вторые дивизионы наравне с сильнейшими лигами. Если вам нужна прежняя широта — min_importance=50.
min_importanceсвой порог важности лиги (0–100)
tournament_id новоематчи одного турнира (id из /v1/tournaments). Склеенный дубль турнира приводится к основному id — в ответе он возвращается объектом tournament
participant_id новоематчи команды или игрока. Игрок командного вида (футбол, баскетбол, хоккей…) ищется по составам — сторона матча у него команда; подробности в «Матчах участника»
date_from / date_to новоеокно дат YYYY-MM-DD по времени начала (UTC). date_from включается, date_to — нет: май 2026 — это date_from=2026-05-01&date_to=2026-06-01. Без tournament_id/participant_id окно не шире 31 дня, с ними — до 366; шире — ответ 400 window_too_wide. date_to не позже date_from — 400 empty_window (для одного дня есть date). Задана одна граница — вторая достраивается до предела, окно, по которому реально искали, приходит в ответе полем window. Вместе с date не используется
order новоеasc | desc — порядок по дате при окне или привязке к турниру/участнику. Без окна asc даёт текущие и будущие матчи, desc — уже начавшиеся, свежие первыми. По умолчанию asc, а при status=finished — desc
gender 27.09.2026 · 02.10.2026male · female · mixed — матчи, у которых поле gender матча равно этому значению, по тому же правилу, что и в карточке: явное решение для матча, иначе пол турнира, иначе общий пол всех сторон (см. «Пол и возраст»). Другое значение — 400 unknown_gender
limit / offsetпагинация (limit до 1000). В ответе has_more — есть ли следующая страница; при tournament_id/participant_id ещё и count_total — сколько всего матчей под фильтром

Без новых параметров лента работает как раньше — тот же порядок и те же поля. Примеры: матчи АПЛ за май 2026 — /v1/events?tournament_id=31382&date_from=2026-05-01&date_to=2026-06-01; домашний календарь команды на неделю — /v1/participant/{id}/events?side=home&date_from=…&date_to=…; результаты вида за вчера — /v1/events?sport=football&date=…&status=finished.

Расписание вперёд. Матчи действующих турниров появляются в ленте за неделю до начала (сбор обновляется несколько раз в сутки), календари крупных турниров — на весь сезон. Лента дня (date=) открывается важными лигами; турниры, которым важность ещё не присвоена, идут после них. Порядок строк однозначный (с id в конце): пока данные не менялись, страницы offset одного запроса не повторяют и не теряют матчи; проходите страницы до has_more: false. Расписание и /v1/sync. Если в ответе /v1/sync meta.cursor = "commit_order", дельта отдаёт любое публичное изменение матча: новый матч расписания (в том числе без детали), перенос времени, статус, счёт, стадию, стороны, деталь и перевод названий (для матчей не старше 7 суток), а карточки, которые больше не отдаются, — списком removed (см. сверку). Пока в ответе meta.cursor = "legacy", голое расписание в /v1/sync не приходит — берите его из /v1/events?date_from=…&date_to=… или /v1/tournament/{id}/events.

GET · /v1/events?sport=football&date=2026-06-02
GET /v1/events?sport=football&date=2026-06-02

{ "count": 312, "events": [{
    "id": 1662352, "scheduled_start": "2026-06-02T18:00:00Z",
    "status": "finished", "round_name": null,
    "tournament": "Premier League", "tournament_ru": "Премьер-лига",
    "sport": "football",
    "has_detail": true, "has_match_detail": true,
    "has_lineups": true, "lineup_confirmed": true,
    "last_detail_at": "2026-06-02T20:00:00Z",
    "last_checked_at": "2026-06-02T20:05:00Z",
    "participants": [
      {"participant_id": 4504, "name": "Liverpool", "side": "home",
       "score": 2, "image_url": "/v1/participant/4504/image"},
      {"participant_id": 4509, "name": "Arsenal", "side": "away",
       "score": 1, "image_url": "/v1/participant/4509/image"}
    ]
}]}

Какие данные доступны в карточке

В списках /v1/events, /v1/events/live и /v1/tournament/{id}/events каждый матч несёт признаки наличия данных. Они учитывают модули вашего тарифа: false может означать отсутствие содержимого или отсутствие доступа к нему.

ПолеЗначение
has_detailЕсть хотя бы одна доступная группа содержимого карточки: лента, статистика, составы, тренеры, расширенные данные игроков, моментум, погода, отсутствующие игроки, коэффициенты либо дополнительные блоки. Учитываются также протокол гонки, карты киберспорта, раунды боя, иннинги, H2H и форма. Названный судья или стадион тоже считаются содержимым и могут сделать этот признак true без матчевой статистики.
has_match_detailБолее узкий признак: есть лента инцидентов, командная статистика или строки состава. Для выбора матчей с этими тремя группами используйте его.
has_lineupsЕсть реальные строки состава, доступные вашему тарифу: модуль lineups при status=scheduled, detail при остальных статусах.
lineup_confirmedОтдельная отметка подтверждения состава. Сама по себе она не означает, что строки состава уже доступны. Если модуль состава недоступен, отметка скрывается значением false.
last_detail_atВремя изменения детали, ISO 8601 или null. Наличие этой даты не доказывает наличие заполненных блоков.
last_checked_atВремя последней успешной проверки, ISO 8601 или null. Может обновляться, когда содержимое не изменилось.

Признаки описывают наличие, а не полноту, точность или свежесть всех данных. Например, у заезда с протоколом может быть has_detail=true и has_match_detail=false. Конкретные блоки смотрите в карточке /v1/event/{id} и её meta.completeness. Время проверки и время изменения детали сохраняют прежний смысл.

Не у всех событий две стороны. Мотоспорт и биатлон — это заезды: в participants[] приходят все участники (до 90+), у каждого заполнено position (место), а side и score — null. Массив отсортирован по месту. Подробности и сам протокол заезда — раздел «Гонки».

Пустой ответ объясняет себя. Сезонные виды (биатлон — с апреля по ноябрь) законно молчат месяцами. Если фильтрам не соответствует ни одно событие и задан sport=, в ответ добавляются next_event_at / last_event_at (ISO-время ближайшего будущего и последнего прошедшего события вида, либо null) и человекочитаемый hint. В непустых ответах этих полей нет.

Важность лиги (importance)

У каждого турнира есть число importance от 0 до 100: чем выше, тем крупнее соревнование. Оно приходит в каждом матче и турнире, по нему работают top=true и min_importance, а в «Обзоре данных» лиги сгруппированы по тем же ступеням. Выбирайте порог, а не точное число: значения внутри ступени различаются слабо. Турнирам без оценки отдаётся 0, они идут после остальных.

СтупеньimportanceЧто входитПримеры (футбол)
Топ90 и вышеВысшие лиги ведущих стран, клубные турниры континентов, соревнования сборных, ОлимпиадаPremier League, Bundesliga, EURO, Olympic Games
Высшие и вторые дивизионы80–89Высшие лиги небольших и средних стран, вторые дивизионы ведущих странAustrian Bundesliga, Czech First League, Brasileirão Série B, Durand Cup
Профессиональные лиги ниже60–79Нижние профессиональные дивизионы и чемпионаты небольших странLiga 3, Philippines Football League, OFC Pro League, Motsepe Foundation Championship
Региональные и любительские40–59Региональные лиги, молодёжные и ветеранские турнирыNelonen Eteläinen, Goiano Feminino, Zagrebačka liga veterana
Местные лиги20–39Местные и любительские лиги низших уровнейIsparta Süper Amatör Ligi, Liga 4 Tulcea, Interinsular Preferente de Tenerife
Низший уровень0–19Местные кубки, детские турниры, небольшие и вспомогательные соревнованияDistrictsbeker Oost, Newell’s Cup U11, Fa Cup Pucón U17

Live-матчи

GET/v1/events/live

Только идущие сейчас матчи (опционально ?sport=). Счёт и статус обновляются каждые ~20 секунд.

Параметры: sport, limit (до 1000), offset (смещение страницы; в ответе has_more — есть ли следующая), top=true (только турниры с важностью ≥ 90), min_importance (свой порог важности 0–100) и tournament_id. Если идущих матчей больше одной страницы, листайте: при limit=500 — offset=500, затем offset=1000 и так далее, пока has_more не станет false.

Теннис: текущий гейм и подающий

В /v1/events/live?sport=tennis, /v1/events и /v1/event/{id} поле live_game содержит, например, {"home":"30","away":"40","serving":2}. serving=1 — подаёт сторона home, 2 — away, null — источник не указал подающего. Очки — строки: 0/15/30/40/A (преимущество также может прийти как AD); в тай-брейке — числовые строки.

ПолеТип и значение
live_game.homeСтрока: очки стороны home в текущем гейме.
live_game.awayСтрока: очки стороны away в текущем гейме.
live_game.serving1, 2 или null. Неизвестный подающий не удаляет известные очки гейма.

Текущий гейм берётся из живого табло независимо от point_by_point. Вне live, при отсутствии очков или снимке старше 90 секунд поле равно null. Кэш списка может добавить до 15 секунд. Подробная лента point_by_point обновляется отдельно; исходные очки завершённых розыгрышей защищены от потери и неподтверждённых изменений.

Поле относится к базовому счёту и не требует модуля advanced или отдельного запроса карточки. Источник передаёт очки и подающего не для каждого матча: при отсутствии свежего табло, прерывании матча или резервной подмене live_game=null. Из старой ленты очки и текущий подающий не вычисляются. История розыгрышей остаётся в модуле advanced.

Минута, фаза и счёт по периодам в списке новое

Каждая строка /v1/events, /v1/events/live и карточка /v1/event/{id} несут одни и те же поля живого состояния — запрашивать карточку ради минуты не нужно. У матча не в статусе live все они null.

ПолеЗначение
live_minuteМинута матча (как прежде): считается от начала текущего периода и базы периода. Есть там, где источник даёт часы матча (футбол основных лиг); у хоккея и баскетбола — null.
live_phaseФаза матча: {"code": "P2", "period": 2} или null. period — номер текущего периода, в перерыве — номер только что сыгранного, у пенальти и буллитов — null.
live_period_startНачало текущей фазы, ISO 8601 (у перерыва — начало перерыва) или null.
live_period_minuteМинута внутри текущего периода по игровым часам (как на табло: 6:09 второго периода → 7) или null. Есть у хоккея и баскетбола, где её даёт источник; отдаётся, только пока снимок часов не старше двух минут на момент сборки ответа (список кэшируется до 15 секунд, страницы участника и личных встреч — до минуты).
extra.period_scoresСчёт по периодам — в списке в той же форме, что в карточке (как читать). До 04.10.2026 в списках extra не было вовсе.
live_phase.codeФазаperiod
1H · 2Hпервый / второй тайм (футбол, гандбол, регби, футзал)1 · 2
HTперерыв между таймами (у баскетбола и ам. футбола — после второй четверти)1 (у видов с четвертями — 2)
ET1 · ET2 · ETпервый / второй дополнительный тайм; ET — доп. время, тайм не указан3 · 4 · null
PENсерия пенальтиnull
P1 · P2 · P3период (хоккей, флорбол)1 · 2 · 3
OTовертайм (хоккей, баскетбол, ам. футбол)хоккей 4, баскетбол 5 (второй овертайм — 6…)
SOбуллитыnull
Q1 … Q4четверть (баскетбол, ам. футбол)1 … 4
S1 … S7партия (теннис, настольный теннис, волейбол)1 … 7
I1 … I20иннинг (бейсбол)1 … 20
BREAKпрочие перерывы: между периодами и четвертями, перед доп. временем, между доп. таймами, перед пенальти или буллитаминомер сыгранного периода (если известен)

Когда live_phase равно null у идущего матча. Источник ведёт по матчу только счёт («матч идёт» без фазы и часов) — так у многих матчей низших и молодёжных лиг; фазу мы не выдумываем по времени от начала. Последнее обновление фазы старше 15 минут. Матч идёт под резервным источником (meta.live_source = reserve), а у резервного источника фазы сейчас нет. Значения словаря — только эти; новые не появятся без объявления здесь.

Особое завершение матча — status_detail

Основных значений у status пять: scheduled · live · finished · postponed · cancelled (плюс interrupted — матч, прерванный источником и не возобновлённый, см. ниже). Снятие по травме, неявка и присуждённый результат попадают в тот же finished, что и честно доигранный матч, — по статусу их не отличить. Чем именно кончился матч, лежит в отдельном поле status_detail.

status_detailЧто произошлоМатчей в базе
retiredснятие по ходу матча (травма, отказ продолжать) — сыгранная часть осталась в счёте по партиям8 113
walkoverнеявка: матч не игрался, результат присуждён1 316
removedучастник снят (метка источника Removed)68
defaultedдисквалификация участника по ходу матча15
awardedрезультат присуждён — техническое поражение6
abandonedматч прерван и не доигран7

Замер 02.08.2026 по всей базе: 9 525 матчей с признаком — 9 519 со status: "finished" и 6 со status: "cancelled". Значения — это нормализованные метки источника (sofascore отдаёт числовой код завершения, flashscore — метку стадии); своих смыслов мы к ним не добавляем и наугад не переименовываем. Чаще всего признак встречается там, где снятие в порядке вещей: теннис — 7 659 retired и 555 walkover, бадминтон — 363 walkover, настольный теннис — 249 retired.

⚠️ Нет данных — нет ключа. При обычном завершении матча ключа status_detail в ответе нет вовсе — ни null, ни пустой строки. Проверяйте наличие ключа, а не его значение; ветку «особое завершение» стройте на присутствии поля.

Поле приходит на тех же ручках, что и сам матч:

РучкаЧто вернёт
GET /v1/eventsлента матчей (и её частный случай /v1/events/live)
GET /v1/event/{id}карточка матча
GET /v1/tournament/{id}/eventsкалендарь турнира по сезонам
GET /v1/syncинкрементальная синхронизация

В карточке турнира calendar_years перечисляет годы с матчами по UTC. В seasons каждый период источника сохранён отдельно; одинаковый год в названии не означает дубликат. Для календарного 2026 года используйте /v1/tournament/{id}/events?date_from=2026-01-01&date_to=2027-01-01 без season. Параметр season выбирает период источника, который может охватывать несколько лет.

JSON
// снятие по ходу матча (теннис): счёт по партиям остался в extra.period_scores
{ "id": 12088167, "status": "finished", "status_detail": "retired",
  "participants": [ {"side":"home","score":1}, {"side":"away","score":0} ] }

// обычное завершение — КЛЮЧА status_detail НЕТ
{ "id": 1662352, "status": "finished",
  "participants": [ {"side":"home","score":2}, {"side":"away","score":1} ] }
⚠️ Чего это поле не делает.
  • Матч, прерванный ПРЯМО СЕЙЧАС, приходит как live, и отдельного признака «идёт, но остановлен» у нас нет. Признак появляется только когда источник закрыл матч: тогда это abandoned — обычно со status: "cancelled" (5 матчей из 7), реже с finished. Пока матч идёт, отличить остановку от обычного хода игры по API нельзя.
  • Матч, который источник прервал и так и не возобновил (у источника он навсегда остался «Interrupted», а не закрыт отменой или результатом), с 27.09.2026 приходит со status: "interrupted" и без status_detail — так, как его держит источник. Это не «доигран» и не «отменён»: счёт — тот, на котором матч остановили, победитель — только если его назвал источник. Прежде такие матчи ошибочно закрывались у нас как finished по давности.
  • Фильтра по status_detail в /v1/events нет — параметр status отбирает только по пяти статусам. Отбирайте признак на своей стороне.
  • Итоговый счёт у снятия бывает нулевым. У removed, defaulted и части retired в participants[].score стоит 0:0, хотя партии игрались — сыгранное лежит в extra.period_scores. Показывать такой матч как «0:0» нельзя: это не результат, а отсутствие присуждённого счёта.

Матч не начался в срок — start_overdue новое

Время начала прошло, а матч всё ещё "status": "scheduled". Так бывает чаще, чем кажется: игрок снялся до матча, низовая лига вносит результаты через несколько часов, источник перенёс матч, не сказав об этом, — или мы сами ещё не подтянули его завершение. Раньше такой матч отдавался молча, и отличить «у источника тоже не начат» от нашего отставания было нельзя. С 24.09.2026 у такого матча есть объект start_overdue с причиной.

Статус мы не выдумываем. status остаётся тем, что записано: поле только объясняет, почему матч, чьё время прошло, до сих пор «запланирован». Объект появляется у матча со status: "scheduled", если с scheduled_start прошло не меньше 30 минут. У всех остальных матчей ключа нет вовсе — та же идиома, что у status_detail.

ПолеЗначение
minutesсколько минут прошло с scheduled_start; считается на каждый запрос
reasonпочему матч всё ещё «запланирован» — см. таблицу ниже
source_statusстатус у источника в нашем словаре (scheduled · live · finished · postponed · cancelled) либо null, если источник матч не отдаёт или проверки не было; у result_pending и result_unconfirmed — статус второй записи матча (всегда finished), а не основного источника
source_startвремя начала у источника (ISO-8601, UTC), только если оно отличается от нашего scheduled_start; иначе null
checked_atкогда источник это сказал (ISO-8601, UTC) — не время нашей записи; null, если ответа источника после начала нет. У result_pending и result_unconfirmed — когда мы последний раз получили карточку второй записи
reasonЧто это значитЧто делать клиенту
result_pendingматч сыгран: в него сведена вторая запись того же матча (повторная запись у источника под новым идентификатором или второй источник), она завершена, и перенос её исхода в эту карточку идёт автоматически — проход раз в 2 часа сверяет обе записи с живым источником и переносит статус и счёт. Эту причину мы отдаём, только пока проход работает: последний полный проход — не старше 4,5 часа. Временный сбой связи с источником (нет ответа, 403, 429, 5xx) причину не меняет: это не ответ о матче, запись переспрашивается в следующий проход. Пока не перенесено — это наше отставание. Причина главнее ответа источника по старой записине закрывать матч как несостоявшийся; перезапросить через 2–3 часа — счёт и статус придут в этот же id
result_unconfirmed 26.09вторая запись матча завершена, но то, что это тот же матч, не доказано, и сам исход в эту карточку не придёт: основной источник по этому матчу говорит «не начат» или «перенесён»; у записей расходятся стороны, время или тур; у этой пары соперников есть другой видимый матч в пределах трёх суток (источник пересоздал матч под новым идентификатором); у второй записи нет счёта; или источник удалил обе записи. Та же причина — если проход, переносящий исходы, не отрабатывал дольше 4,5 часа: перенос сейчас не идёт, и обещать его мы не можем. source_status — статус второй записи, а не основного источникане считать счёт второй записи итогом этого id; поискать завершённую запись тех же соперников в /v1/events под другим id; если её нет — решать по minutes
source_not_startedисточник после начала подтвердил: матч не начат (или задержан). У него то же, что у нас. Чаще всего матч не состоялся — снятие, неявка, — а источник его не закрылпоказывать «начало задерживается / нет данных о начале»; через несколько часов проверить снова
source_missingисточник на запрос этого матча отвечает «такого нет»: матч убран у источника (часто — пересоздан под новым идентификатором после переноса)не ждать результата по этому id. Если источник пересоздал матч, а мы его собрали отдельной записью, он есть в ленте /v1/events под другим id с теми же соперниками (замер 24.09.2026: у 210 из 1 337 таких матчей завершённая запись тех же соперников нашлась в пределах 6 часов от начала); если нет — матч, скорее всего, не состоялся
source_changedисточник уже сменил статус (например, finished) или время начала, а мы ещё нет — это наше отставание, данные подтягиваются. Что у источника — в source_status / source_startперезапросить карточку позже; результата у нас пока нет, выдумывать его не нужно
not_checkedпосле начала источник ещё не переспрашивали, но проверка придёт: матч в её границах (см. ниже). Первая проверка — в ближайший проход после начала + 30 минут, проходы раз в 30 минутперезапросить через 30–60 минут
not_monitoredавтоматической перепроверки у этого матча нет и не будет: он начался больше 21 суток назад, заведён без идентификатора основного источника, у источника нет карточки этого вида спорта или пять перезапросов прошли без ответа. status — последнее, что мы записалине перезапрашивать ради смены причины — она не сменится сама; решать по minutes: давно прошедший «запланированный» матч, скорее всего, не состоялся
nullпричину сейчас узнать не удалось (сбой чтения на нашей стороне)перезапросить позже

Как проверяем. Источник переспрашивается по карточке матча с теми же паузами, что у нашего сбора застрявших матчей: через 30 минут после начала, затем через 6, 24, 48 и 48 часов — не больше пяти раз за жизнь матча, в окне 21 суток. Если источник однажды показал, что матч сыгран или перенесён, это source_changed, и поздний ответ «такого матча нет» его не перекрывает. Порядок причин: сперва завершённая вторая запись — result_pending, если перенос её исхода идёт, result_unconfirmed, если тождество не доказано, — затем ответ источника, затем границы проверки — not_checked только там, где проверка действительно придёт, вне границ — not_monitored. Замер 23.09.2026 по карточке источника — матчи, начавшиеся от суток до недели назад и оставшиеся «запланированными», 1 756 штук: у 1 070 источник сам держит «не начат», 422 он больше не отдаёт, в 264 отстаём мы (источник закрыл или перенёс матч, либо исход пришёл от второго источника и ещё не перенесён) — эти 264 и есть наша очередь починки.

Поле приходит на тех же ручках, что и сам матч: /v1/events, /v1/event/{id} (считается на каждый запрос, мимо кэша карточки), /v1/tournament/{id}/events, /v1/participant/{id}/events, /v1/h2h и /v1/sync.

JSON
// все примеры — запрос 23.09.2026 в 21:10 UTC, значения из живых записей
// начало 11:20 UTC; источник в 20:56 подтвердил «не начат»
{ "id": 12701204, "status": "scheduled", "scheduled_start": "2026-09-23T11:20:00+00:00",
  "start_overdue": { "minutes": 590, "reason": "source_not_started",
                     "source_status": "scheduled", "source_start": null,
                     "checked_at": "2026-09-23T20:56:39.173578+00:00" } }

// (26.09.2026 19:55 UTC) матч сыгран: вторая запись того же матча завершена, тождество
// доказано, перенос её исхода идёт автоматически — наше отставание, исход придёт в эту карточку
{ "id": 12658830, "status": "scheduled", "scheduled_start": "2026-09-22T09:00:00+00:00",
  "start_overdue": { "minutes": 6415, "reason": "result_pending",
                     "source_status": "finished", "source_start": null,
                     "checked_at": "2026-09-22T11:09:09.687730+00:00" } }

// (26.09.2026 05:05 UTC) вторая запись (второй источник) завершена 1:2, но у той же пары есть
// другой видимый завершённый матч в то же время: источник пересоздал матч в другом турнире —
// тождество не доказано, исход сюда не переносится
{ "id": 12670795, "status": "scheduled", "scheduled_start": "2026-09-22T14:40:00+00:00",
  "start_overdue": { "minutes": 5185, "reason": "result_unconfirmed",
                     "source_status": "finished", "source_start": null,
                     "checked_at": "2026-09-22T16:45:34.670625+00:00" } }

// источник перенёс матч, а у нас старое время
{ "id": 12669010, "status": "scheduled", "scheduled_start": "2026-09-23T17:15:00+00:00",
  "start_overdue": { "minutes": 235, "reason": "source_changed",
                     "source_status": "scheduled", "source_start": "2026-11-05T18:15:00+00:00",
                     "checked_at": "2026-09-23T20:56:38.130497+00:00" } }

// матч старше 21 суток — перепроверки нет
{ "id": 12451777, "status": "scheduled", "scheduled_start": "2026-08-29T22:00:15+00:00",
  "start_overdue": { "minutes": 35949, "reason": "not_monitored",
                     "source_status": null, "source_start": null, "checked_at": null } }
⚠️ Чего это поле не делает.
  • Не меняет status. Матч с result_pending, result_unconfirmed или source_changed и source_status: "finished" остаётся у нас scheduled, пока результат не перенесён: статус и счёт по слову «завершён» мы не угадываем. Счёт в participants у такого матча может уже стоять или быть неполным — итоговым его считайте только после status: "finished".
  • Не является фильтром. /v1/events?status=scheduled по-прежнему вернёт и такие матчи; отбирайте их по наличию ключа start_overdue.
  • Прогноз «до начала». Если ваш продукт закрывает приём прогнозов по scheduled_start, матч с start_overdue уже прошёл время начала — решение, держать ли его открытым, остаётся за вами; reason говорит, ждать ли, что он ещё начнётся.

Детализация матча

GET/v1/event/{id}

Полная карточка матча со всей собранной детализацией:

Соперники ещё не определены. У матчей плей-офф, где сетка не сыграна, массив participants пуст, а рядом стоит "participants_pending": true. Это НЕ потеря данных: источник сообщает «TBD», и мы не заводим команду с таким названием, чтобы на витрине не появлялись призраки. Матч действительно запланирован — запросите его ближе к дате, стороны появятся. У обычных матчей поля нет вовсе.

Управление весом ответа. Параметр exclude убирает перечисленные через запятую поля верхнего уровня — карточка матча бывает объёмной, и лишнее проще не получать, чем игнорировать. Например ?exclude=odds_bookmakers,odds_best убирает роспись котировок и сокращает ответ примерно на треть, а ?exclude=odds_bookmakers,odds_best,commentary,shotmap — вдвое. Поля id, status, sport и scheduled_start отдаются всегда. Без параметра ответ прежний, менять существующие запросы не нужно.

JSON
{ "id": 1662352, "status": "finished", "sport": "football",
  "tournament": "Premier League", "tournament_ru": "Премьер-лига",
  "season_label": "25/26",
  "venue": "Anfield", "venue_city": "Liverpool", "venue_capacity": 53394,
  "referee": "Michael Oliver", "attendance": 53221,
  "participants": [ … счёт по сторонам … ],
  "incidents":  [ {"minute":23,"kind":"goal","period":"1H","player_name":"…","assist_name":"…"} ],
  "statistics": [ {"period":"ALL","group_name":"Possession",
                   "stat_name":"Ball possession","home_value":58,"away_value":42} ],
  "lineups":    [ {"side":"home","player_participant_id":…,"position":"GK",
                   "shirt_number":"1","is_substitute":false,"formation":"4-3-3","stats":{…}} ],
  "player_stats":[ {"participant_id":…,"player_name":"…","side":"home","position":"M",
                    "stats":{"rating":8.1,"accuratePass":76,"keyPass":4,"totalTackle":1,
                             "expectedGoals":0.32,"expectedAssists":0.42,"touches":103}} ],
  "coaches":    [ {"side":"home","coach_name":"…","coach_name_ru":"…"} ],
  "missing_players":[ {"side":"home","player_name":"…","position":"D",
                       "type":"missing","status":"…","reason_text":"…"} ],
  "shotmap":    [ {"player":{…},"xg":0.074,"shotType":"goal","situation":"assisted"} ],
  "momentum":   { "graphPoints":[…] },
  "odds":       [ {"market_name":"Full time","choice_name":"1","fractional_value":"1.73",
                   "bookmaker":"…","opening_value":"1.80","movement":"down"} ],
  "odds_bookmakers":[ {"bookmaker":"…","market":"1x2","side":"home","odds":2.77,
                       "opening":2.80,"is_live":false,
                       "fetched_at":"2026-09-26T09:12:40Z","confirmed_at":"2026-09-26T13:47:05Z",
                       "missing_since":null} ],
  "odds_best":  { "1x2":{"home":{"odds":2.77,"bookmaker":"…"},"draw":{…},"away":{…}},
                  "total_2_5":{…}, "btts":{…}, "double_chance":{…} },

Коэффициенты букмекеров: как собираются и сколько живут

Что отдаём. Максимально широкую роспись по каждому букмекеру: исход (1x2), тотал, фора, двойной шанс, «обе забьют», точный счёт, чёт/нечет — всё, что букмекер публикует. Каждая линия отдаётся отдельной строкой с рынком, периодом, значением линии и стороной.

Как обновляются. Мы обходим фиды букмекеров по кругу: каждая контора опрашивается целиком (пре-матч и лайв) один раз за круг обхода — по замеру 26.09.2026 круг занимает 22–34 минуты (проход быстрых фидов 5–19 минут и пауза 15 минут); фид Марафона идёт своим отдельным непрерывным циклом. Коэффициент перезаписывается поверх прежнего, как только он изменился. В ответе — последнее значение, которое контора отдала, и два времени, по которым видно, насколько оно свежее.

Два времени у строки цены.

ПолеЧто означает
fetched_atКогда цена (или признак is_live) последний раз изменилась. Неизменная цена его не двигает: у линии, которая стоит сутки, fetched_at суточной давности, хотя контора отдаёт её в каждом проходе. Не используйте его как «время выгрузки» — отсечка «старше N часов» по нему выбрасывает действующие цены.
confirmed_atКогда контора последний раз отдала эту линию — момент последнего прохода её фида, в котором линия была. Это и есть свежесть цены: для отсечки устаревших строк используйте его. Для линии в обычном состоянии он не старше одного круга обхода: 22–34 мин, медиана 28 мин (замер 26.09.2026: проход быстрых фидов 5–19 мин и пауза 15 мин). У Марафона свой цикл — его проход длился около 74 мин. Строки, записанные до 26.09.2026, могут нести confirmed_at = fetched_at до первого нового прохода — это нижняя граница, а не утверждение, что линию не подтверждали.
missing_sincenull — линия была в последнем проходе конторы. Время — первый проход, в котором её не было (контора сняла или приостановила рынок). Такая строка остаётся с прежней ценой и прежним confirmed_at до следующего прохода: если линия вернулась — пометка снимается, opening сохраняется; если её нет и во втором проходе (не раньше чем через 10 минут после пропажи) — строка удаляется. Только у прематча: у матча, который уже идёт, линия без пометки удаляется в первом же проходе, где её нет.

В odds_best у лучшей цены есть те же fetched_at / age_sec (возраст смены цены) и confirmed_at / confirmed_age_sec (возраст подтверждения). Линии с missing_since в odds_best, в value_bets и в сравнении прогноза с рынком не участвуют: лучшая цена — та, по которой можно поставить сейчас.

Что такое opening. Первое значение, которое мы увидели по этой линии — как правило прематчевое. Оно фиксируется один раз и дальше не меняется, что и позволяет считать движение линии: поле movement показывает направление относительно открытия. Промежуточных значений между открытием и текущим в odds_bookmakers нет — это осознанное решение, а не ограничение. Для лайва см. market_live.

Сколько живут. Коэффициенты матча хранятся 7 суток после начала матча, затем удаляются полностью. Отсчёт ведётся от времени начала матча, а не от времени записи, поэтому линия, выставленная за месяц до игры, доживает до самой игры. Если вам нужны коэффициенты сыгранных матчей для бэктеста — забирайте их в течение недели после события.

Снятые линии. Если букмекер перестал предлагать линию (снял рынок или приостановил приём), в ближайшем проходе строка получает missing_since и выпадает из odds_best и value-расчётов; из ответа она убирается, если линии нет и в следующем проходе (не раньше чем через 10 минут после пропажи). Две ступени — намеренно: букмекеры часто снимают рынок на один проход и тут же возвращают, и мгновенное удаление превращалось в мигание строк у получателя и в потерю opening у вернувшейся линии. Это правило прематча: у матча, который уже начался (по времени начала, даже если контора ещё зовёт линию прематчевой), снятая линия удаляется в первом же проходе без неё — замер 26.09.2026: в следующем проходе вернулись 56 % прематчевых снятий и лишь 4 % лайвовых, а лайв-цена, которой уже нет у конторы, хуже её отсутствия.

Какие книги собираем. Каждая книга отдаётся отдельно, под своим bookmaker, и несёт поле jurisdiction:

ЮрисдикцияjurisdictionКниги
Россия (лицензия, приём через ЦУПИС)ru betcity, fonbet, leon, marathon, olimp, pari, tennisi, winline, zenit
БеларусьБеларусь fonbet_by, winline_by
КазахстанКазахстан fonbet_kz, ubet_kz

Почему Фонбет РФ, РБ и РК — это три РАЗНЫЕ книги. Это разные юрлица с самостоятельной линией: на один и тот же матч у них расходятся и коэффициенты, и состав рынков (у белорусской и казахстанской росписи есть точный счёт, двойной шанс, индивидуальные тоталы и лесенка альтернативных линий, которых в российской нет). Поэтому они никогда не смешиваются в одну книгу — сравнение линий разных стран это самостоятельный продукт, а не «дубль» одной конторы.

Где зарубежные книги НЕ участвуют. В odds_bookmakers вы получаете все книги. А вот odds_best («лучшая цена») и весь value-контур — прогноз, value_bets — считаются только по книгам РФ: лучшая цена должна быть той, по которой клиент реально может поставить, а не лучшей в мире. Если вам нужна лучшая цена по всем странам — стройте её сами из odds_bookmakers по полю jurisdiction.

Рыночные вероятности в лайве — market_live новое

У идущего матча карточка /v1/event/{id} отдаёт блок market_live: какую вероятность исхода закладывает в свою текущую лайв-цену каждый букмекер отдельно — коэффициент, очищенный от маржи. Это то же модуль «Коэффициенты БК» (odds): без него поле равно null. У матча, который не идёт, и там, где лайв-линий нет, — null.

  • Рынки раздельно, без смешения основ. 1x2 — исход основного времени (basis: "regulation"); final_win — итог с овертаймом/буллитами (basis: "final"); total и handicap — только половинные линии (у целых и четвертных есть возврат ставки, их вероятность несравнима). handicap: line — фора хозяев, у гостей та же линия с обратным знаком. Период — match (у баскетбольной росписи Казахстана бывает regular).
  • Без маржи — методом Шина (p): маржа книги ложится в основном на аутсайдеров, поэтому фаворит получает чуть больше, чем при простой нормировке. Простая нормировка тоже отдаётся (p_norm), маржа — margin.
  • Время цены. observed_at — когда мы запросили эту доску у букмекера (начало прохода), confirmed_at — последний проход, где доска была той же, age_sec — возраст подтверждения на момент as_of. Лайв книг olimp, winline, winline_by, betcity, fonbet опрашивается раз в ~90 с, pari — раз в ~2 мин, остальных — раз в круг (~25 мин), поэтому их цена почти всегда устаревшая.
  • Приостановка. Рынок, который был в прошлой доске книги и пропал в текущей (букмекер закрыл приём — обычно на гол, удаление, VAR, пенальти), отдаётся со status: "suspended" и suspended_since, без цены; через 10 минут без возврата он считается закрытым и пропадает. Отбракованное — status: "rejected" с причиной во flags: incomplete (нет одного из исходов), margin (сумма обратных цен вне 0,995–1,30 — заглушка или цены разных моментов), cap (цена выше потолка 66 — у части книг так выглядит приостановка, «9000»).
  • Пометки книги (flags): stale_age — подтверждение старше fresh_sec (180 с); stale_score — доска снята при другом счёте (цена до гола). У группы: outlier — исход отличается от медианы остальных книг больше чем на 0,15; orientation_suspect — отличие исчезает, если поменять хозяев и гостей местами (перевёрнутые стороны у книги). Помеченное в консенсус не входит.
  • Консенсус (consensus): медиана вероятностей по книгам с открытым, свежим и непомеченным рынком, нормированная к 1, и число таких книг (books). Входят книги всех юрисдикций — это оценка рынка, а не цена, по которой можно поставить; юрисдикция каждой книги — в jurisdiction.
JSON
"market_live": {
  "as_of": "2026-10-12T19:31:05+00:00", "method": "shin", "fresh_sec": 180,
  "bookmakers": [
    { "bookmaker": "winline", "jurisdiction": "ru",
      "observed_at": "2026-10-12T19:29:41+00:00", "confirmed_at": "2026-10-12T19:30:58+00:00",
      "age_sec": 7, "score": { "home": 1, "away": 0 }, "flags": [],
      "markets": [
        { "market": "1x2", "period": "match", "line": 0.0, "basis": "regulation",
          "status": "open", "flags": [], "margin": 0.0701,
          "outcomes": [ { "selection": "home", "odds": 1.42, "p": 0.675,  "p_norm": 0.6581 },
                        { "selection": "draw", "odds": 4.1,  "p": 0.2222, "p_norm": 0.2279 },
                        { "selection": "away", "odds": 8.2,  "p": 0.1028, "p_norm": 0.114  } ] },
        { "market": "total", "period": "match", "line": 2.5, "basis": "regulation",
          "status": "suspended", "flags": [], "suspended_since": "2026-10-12T19:29:41+00:00" } ] },
    { "bookmaker": "fonbet", "jurisdiction": "ru", "age_sec": 412, "flags": ["stale_age"], … } ],
  "consensus": [
    { "market": "1x2", "period": "match", "line": 0.0, "basis": "regulation", "books": 3,
      "outcomes": [ { "selection": "home", "p": 0.6641, "fair_odds": 1.51 },
                    { "selection": "draw", "p": 0.2305, "fair_odds": 4.34 },
                    { "selection": "away", "p": 0.1054, "fair_odds": 9.49 } ] } ],
  "model_vs_market": null }

Тот же блок — в live-трендах рядом с модельной вероятностью, с разницей «модель − рынок». Историю — ниже, /odds-history.

История лайв-коэффициентов для графиков новое

GET/v1/event/{id}/odds-history?market=1x2&bookmaker=consensus&interval=60

Ряд точек для графика движения лайв-линии одного рынка: по одной книге или по консенсусу книг. Модуль «Коэффициенты БК» (odds). Параметры:

  • market — 1x2 (основное время), final_win (с овертаймом), total (основная линия тотала), handicap (основная линия форы хозяев). «Основная линия» — половинная линия с самыми близкими ценами сторон; по ходу матча она меняется, поэтому линия есть в каждой точке.
  • bookmaker — код книги (winline, fonbet, …) или consensus (по умолчанию): медиана вероятностей без маржи по книгам с открытым рынком при текущем счёте, без выбросов.
  • interval — шаг прореживания в секундах, 30–3600, по умолчанию 60: в каждом интервале берётся последнее состояние.

Глубина. Матч последних 14 суток — полная детализация (каждое изменение цены, опрос лайва раз в ~90 с), прореженная до interval: "source": "detail". Старше — бессрочный прореженный уровень: консенсус и ключевые книги (winline, fonbet, olimp) на сетке 5 минут плюс точки «старт / гол / удаление / финал»: "source": "thinned", interval не меньше 300. Другие книги в старых матчах — пустой ряд. Не больше 1 500 точек (иначе шаг увеличивается, а при обрезке truncated: true). Ответ кэшируется: идущий матч — 30 с, завершённый — 1 час.

Формат — массивы, не объект на точку. columns называет поля точки: t (unix-секунды), line, status (0 открыт, 1 приостановлен, 2 закрыт), odds_* (коэффициенты исходов; у консенсуса — null), p_* (вероятности без маржи, Шин), score_home/score_away (счёт при цене), kind (тип точки прореженного уровня: kinds), books (число книг в консенсусе). У приостановленной точки цен нет — на графике это разрыв, а не «9000».

JSON
{ "event_id": 12801234, "market": "1x2", "basis": "regulation", "bookmaker": "winline",
  "jurisdiction": "ru", "method": "shin", "source": "detail", "interval_s": 60,
  "event_status": "live", "truncated": false,
  "columns": ["t","line","status","odds_home","odds_draw","odds_away","p_home","p_draw","p_away",
              "score_home","score_away","kind","books"],
  "points": [
    [1792440120, 0.0, 0, 2.1, 3.3, 3.6, 0.4555, 0.2847, 0.2598, 0, 0, null, null],
    [1792440960, 0.0, 1, null, null, null, null, null, null, 0, 0, null, null],
    [1792441080, 0.0, 0, 1.42, 4.1, 8.2, 0.675, 0.2222, 0.1028, 1, 0, null, null] ],
  "kinds": {"0":"grid","1":"start","2":"goal","3":"red_card","4":"final"},
  "statuses": {"0":"open","1":"suspended","2":"closed"} }
JSON

  "best_players":[ {"side":"home","rating":"7.6","player_name":"…","position":"M","jersey":"8"} ],
  "best_players_by_team": { "home":[{"side":"home","rating":"7.6","player_name":"…","position":"M","jersey":"8"}], "away":[…] },
  "player_of_match": { "side":"home","rating":"7.6","player_name":"…","position":"M","jersey":"8" },
  "win_probability": { "homeWin":52, "draw":27, "awayWin":21 },
  "votes":      { "vote":{"vote1":…,"voteX":…,"vote2":…} },
  "highlights": [ {"title":"…","url":"…","thumbnail":"…"} ],
  "average_positions": { "home":[{"player_name":"…","x":51.2,"y":33.0}], "away":[…] },
  "player_heatmaps": { "812041":[[56,7],[56,17],[88,10],…] },
  "player_actions":  { "812041":{ "passes":[{"playerCoordinates":{"x":56.6,"y":7.3},
                        "passEndCoordinates":{"x":63.7,"y":17.1},"outcome":true,"keypass":false}],
                        "dribbles":[…], "defensive":[…] } },
  "tv_channels": { "ES":[663], "BR":[7548] },
  "points_history": [ {"tab":"Set 1","points":[…]} ],
  "point_by_point": [ {"set":1,"games":[{"game":13,"score":{…},"points":[…]}]} ],
  "race":       { "series":"Formula 1", "session":"Race", "distance":"305.3 km",
                  "classification":[ {"position":1,"participant_id":1913314,"name":"…","time":"1:27:32.841"} ] },
  "fight":      { "method":"TKO", "finish_round":3 },
  "weather":    { "temperature_c":18, "conditions":"…" },
  "commentary": [ {"minute":45,"text":"Гол! …","text_en":"Goal! …","type":"goal","side":"home","player_name":"…"} ],
  "meta":       { "last_detail_at":"…", "last_checked_at":"…", "completeness":{…},
                  "prematch":{ "checked_at":"…", "blocks":{"venue":"ok","lineups":"source_not_provided",…} } }
}

В примере выше ключа stage НЕТ — и это штатный, самый частый для лиговых матчей случай: источник по такому матчу прислал только номер, а по голому числу мы не решаем, тур это или внутренний код кубкового раунда. Когда стадия названа, объект появляется рядом: "stage": {"type":"playoff","code":"round_of_16","name":"Round of 16","name_ru":"1/8 финала"}. Как читать и где она есть — раздел «Стадия турнира у матча».

Недоступная хронология. Если противоречивая лента матча отозвана, карточка может содержать meta.quality.incidents: {"status":"unavailable","reason":"source_retracted_conflicting_timeline", "result_verified":true,"chronology_verified":false}. Это означает, что итоговый результат подтверждён, а последовательность событий — нет. meta.completeness.incidents в таком случае равно false, даже если сохранились отдельные карточки или замены. По отсутствию этого признака нельзя делать вывод о независимой проверке хронологии. Поле доступно с модулем детали матча.

Источник статуса и счёта — meta.live_source новое. В обычном случае ключа нет: статус и счёт пришли от основного источника матча. Ключ появляется, когда они взяты у резервного источника того же матча:

ЗначениеКогдаГде видно
reserveОсновной источник «застрял» на этом матче (матч идёт, а он показывает «не начался»), резервный свежий и показывает игру. Статус и счёт записаны в матч из резерва. У завершённого матча — итог взят из резерва; признак снимается, когда основной источник сам пришлёт свой ход матча или итог (в карточке — не позже 10 минут после этого) значения статуса и счёта — везде: карточка, /v1/events, /v1/sync, вебхуки; сам признак — в карточке
failoverОсновной сбор недоступен целиком (несколько минут без данных). Карточка идущего матча в момент ответа показывает свежие статус и счёт резервного источника только карточка: в лентах и вебхуках — последнее значение основного источника

Под reserve поле live_minute равно null, а live_time не отдаётся: часы основного источника на таком матче стоят, а минуты от начала матча у резервного нет (у футбола — вовсе, у хоккея и баскетбола — только минута внутри текущего периода; пересчитывать её в минуту матча без длины периода лиги мы не будем). Фаза матча, начало фазы и минута периода (live_phase) под reserve берутся у резервного источника, если он их даёт. Значения словаря — только эти два; новые не появятся без объявления здесь.

best_players сохраняет прежнюю форму списка лидеров home/away. Если одиночное поле лидера отсутствует у источника, используется только первый элемент явно присланного списка этой команды; явное пустое значение лидера не подменяется. best_players_by_team содержит только присланные списки источника, в исходном порядке; наличие списка не означает подтверждённое число игроков или общий рейтинг всех участников. player_of_match появляется только при явном выборе источника — не вычисляется из максимального рейтинга. У него side равен null, если сторона не установлена однозначно. Записи несут rating, player_name, position, jersey и, при наличии перевода у источника, player_name_ru. Эти поля доступны одинаково в GET /v1/event/{id} и GET /v1/sync с модулем detail; без player_ratings значения rating равны null. Канонические ID игроков из имён или ID поставщика не создаются.

Состав полей зависит от вида спорта и наличия данных у матча. Ключевые блоки: player_stats[].stats — полная статистика по каждому игроку (рейтинг, передачи, отборы, xG/xA, касания, спорт-специфичные метрики); coaches — тренеры сторон; missing_players — травмы/дисквалификации (type + reason_text; description — формулировка источника, напр. «Leg Injury»; expected_end — ожидаемая дата возвращения, если известна); referee, venue_city, venue_capacity, attendance — справка о матче; best_players (по одному лидеру команды), best_players_by_team (явно присланные списки по сторонам), player_of_match (явно выбранный игрок матча), win_probability, votes, highlights (видео), average_positions, tv_channels, player_heatmaps (тепловая карта касаний игрока: ключ — id игрока, значение — точки [x, y] на поле 0–100) и player_actions (координатные действия игрока: пасы с точками начала/конца, исходом и признаком key pass, дриблинг, перехваты и подборы; топ-футбол, модуль advanced), points_history (история геймов и очков — теннис; фреймы/леги/очки партий — другие виды), point_by_point (сеты → геймы → очки с подающим; там же счёт тайбрейка), fight (метод/раунд — ММА), combat_stats новое (статистика боя по раундам: удары по зонам, тейкдауны, контроль — ММА), innings новое (иннинги крикета: раны, калитки, оверы, батсмены и боулеры), graph_sequence новое (последовательность моментума по периодам, модуль advanced), race (протокол заезда — мотоспорт и биатлон). Исход боя (method, final_round, fight_card, card_order, weight_class) с 05.09 приходит и в лентах /v1/events, /v1/sync, а не только в карточке. Коэффициенты: odds — усреднённые рыночные линии (включая зарубежные конторы) с движением и опенингом; odds_bookmakers — отдельные линии по каждому рынку: книги РФ (ЦУПИС), Беларуси и Казахстана, у каждой строки своя jurisdiction (подробнее); odds_best — лучшая цена по каждому исходу (line-shopping) среди книг РФ. Пре-матч и лайв; отдельный подключаемый модуль «Коэффициенты БК».

Жизненный цикл линий БК. Пре-матч линии появляются заранее — как только контора открывает роспись на событие (обычно за несколько дней до старта). Лайв-линии приходят с началом матча (доступны не у всех контор — часть отдаёт только пре-матч). Цена обновляется по мере движения рынка; стартовая цена (opening_value) и направление движения (movement) сохраняются на протяжении всего времени. Снятые/приостановленные конторой линии из ответа исчезают. После завершения матча линии контор по нему удаляются вскоре после финала — актуальны только события в игре и предстоящие. Блок meta.completeness показывает, какие секции заполнены. Глубина зависит от уровня лиги.

До матча — meta.prematch новое. Есть только у запланированного матча и отвечает на вопрос «почему блок пуст»: источник его до матча не даёт или мы ещё не собрали. checked_at — когда мы последний раз спрашивали источник об этом матче; blocks — по одному ключу на venue, referee, weather, lineups, missing_players:

ЗначениеЧто значит
okданные в карточке есть
source_not_providedисточник спрошен в checked_at и этого блока не дал. Не приговор: ближе к началу спрашиваем чаще (за 3 ч до матча — каждые 20 мин), и блок может появиться
not_collected_yetещё не спрашивали, ответа не было или источник дал, а запись не успела — придёт при следующем проходе
not_applicableу вида такого блока не бывает: состав у тенниса, погода в зале (хоккей, баскетбол, теннис indoor)
not_trackedдо матча этот матч у источника не спрашиваем: вид приходит только после начала (футзал, дартс, бадминтон, водное поло, флорбол) или у матча нет адреса у основного источника. Ждать до начала нечего

Что источник отдаёт до матча (замер 23.09.2026): стадион — у большинства матчей топ-лиг футбола, баскетбола и хоккея за 1–3 дня, у теннисного матча — корт; прогнозный состав и травмы — у части футбольных лиг за 1–3 дня (lineup_confirmed: false), официальный состав — за 20–120 минут до начала (lineup_confirmed: true); у бейсбола до матча — только стартовые питчеры; у хоккея и баскетбола составов до матча у источника почти нет (КХЛ, Liiga — примерно за 20 минут, НХЛ и баскетбол — как правило, только после начала); судья — редко и обычно в день матча; погода — прогноз на час начала по координатам стадиона, только открытые виды.

Счёт по партиям и периодам — extra.period_scores

GET/v1/event/{id}

Разбивка итогового счёта по партиям (теннис, волейбол), таймам (футбол), периодам (хоккей), иннингам (бейсбол). Две стороны — home и away, ключи одинаковые.

JSON
// финал Roland Garros 2025, Синнер — Алькарас (event 8416559): 4-6, 6-7(4), 6-4, 7-6(3), 7-6(10-2)
"participants": [ {"side":"home","score":2}, {"side":"away","score":3} ],
"extra": { "period_scores": {
  "home": { "period1": 6, "period2": 7, "period3": 4, "period4": 6, "period5": 6,
            "period2TieBreak": 7, "period4TieBreak": 3, "period5TieBreak": 2,
            "current": 2, "normaltime": 2 },
  "away": { "period1": 4, "period2": 6, "period3": 6, "period4": 7, "period5": 7,
            "period2TieBreak": 4, "period4TieBreak": 7, "period5TieBreak": 10,
            "current": 3, "normaltime": 3 } } }
КлючОписание
periodNсчёт стороны в N-й партии / тайме / периоде / иннинге. Нумерация с 1, число партий не ограничено списком — пять сетов тенниса и девять (плюс экстра) иннингов бейсбола приходят целиком
periodNTieBreakсчёт тайбрейка в N-й партии. Появляется только там, где тайбрейк игрался: в примере выше вторая партия 7:6 сложилась из тайбрейка 7:4, а решающая — из чемпионского тайбрейка 2:10
currentитог стороны — тот же, что в participants[].score
normaltime · overtime · penaltiesосновное время, овертайм, серия пенальти (командные виды)
ℹ️ Сумма по партиям обязана сходиться со счётом. Выигранные партии, посчитанные по periodN, дают ровно participants[].score. Исключение — особое завершение (снятие, неявка): там присуждённого счёта нет, см. status_detail.

Историю тайбрейков мы доливаем проходом по архиву — на матчах последних сезонов и на всех крупных турнирах ключи periodNTieBreak уже есть, в глубине архива появляются по мере прохода. Отсутствие ключа означает «ещё не добрали», а не «тайбрейка не было»; сам факт тайбрейка всегда виден по партии 7:6.

По-очковая раскладка — point_by_point

Для текущего счёта гейма и подающего используйте live_game в списке или карточке. score.serving в истории относится к гейму; из него нельзя определить подающего следующего розыгрыша тай-брейка.

GET/v1/event/{id} модуль «advanced»

Розыгрыш за розыгрышем: партии → геймы → очки, с указанием подающего. Приходит ключом point_by_point в карточке матча — теннис, настольный теннис, бадминтон, дартс. Итоговый счёт тайбрейка проще взять из extra.period_scores (periodNTieBreak); здесь же видно, как тайбрейк складывался очко за очком.

JSON
"point_by_point": [
  { "set": 1,
    "games": [
      { "game": 13,                                    // тайбрейк — гейм, сыгранный при 6:6
        "score": { "homeScore": 7, "awayScore": 6,     // счёт по геймам ПОСЛЕ этого гейма
                   "serving": 1, "scoring": 1 },       // подающий · кто выиграл гейм
        "points": [ …,
          { "homePoint": "7", "awayPoint": "3",        // ← последняя точка = счёт тайбрейка
            "homePointType": 0, "awayPointType": 0, "pointDescription": 0 } ] },

      { "game": 12,                                    // обычный гейм
        "score": { "homeScore": 6, "awayScore": 6, "serving": 1, "scoring": 1 },
        "points": [ { "homePoint": "15", "awayPoint": "0",  "homePointType": 1, "awayPointType": 5 },
                    { "homePoint": "40", "awayPoint": "15", "homePointType": 1, "awayPointType": 5 } ] } ] } ]
ПолеОписание
setномер партии
games[].gameномер гейма в партии
score.homeScore / awayScoreсчёт по геймам в партии после того, как этот гейм доигран (не счёт очков)
score.servingкто подавал в гейме: 1 = home, 2 = away. Значения -1, 0 и отсутствие ключа означают «источник не сказал» — таких мало: на 223 661 гейм тенниса приходится 191 гейм без ключа, 94 с -1 и 30 с 0
score.scoringкто выиграл гейм: 1 = home, 2 = away, -1 = не определён (гейм ещё идёт — типично для live)
points[]очки гейма по порядку. homePoint / awayPoint — счёт после розыгрыша: в обычном гейме "0" · "15" · "30" · "40" · "A", в тайбрейке — числами "1", "2", "3"…
homePointType / awayPointType / pointDescriptionсырые числовые коды источника. Расшифровки к ним источник не даёт, поэтому и мы их не расшифровываем — отдаём как есть

Порядок массивов — обратный. Партии и геймы приходят от последнего к первому: в массиве первой стоит текущая (последняя сыгранная) партия, внутри неё — последний гейм. Замер за 2 суток: у 742 партий из 764 геймы идут по убыванию, у остальных 22 гейм всего один; первой в массиве партия №1 оказалась лишь в 13 случаях из 340. Мы порядок источника не переставляем — сортируйте по set и game у себя.

Как достать счёт тайбрейка

Тайбрейк — это гейм, в котором очки считаются числами ("1", "2", "3"…), а не «0 / 15 / 30 / 40 / A». Счёт тайбрейка = последняя точка такого гейма: homePoint: "7", awayPoint: "3" → 7:3.

Не отбирайте тайбрейк по счёту гейма. Правило «score.homeScore и score.awayScore оба ≥ 6» на замере за 14 дней (теннис, 101 111 геймов с раскладкой) отбирает 2 674 гейма, из которых тайбрейков только 1 468: остальные 1 206 — обычные геймы, доигранные при счёте 6:6. И наоборот, 139 тайбрейков это правило теряет — решающая партия в формате супер-тайбрейка идёт отдельным геймом со счётом по геймам 1:0. Отбор по виду очков даёт 1 607 тайбрейков — всё, что есть в этой выборке.

Честное покрытие

Раскладку отдаёт источник, и собирается она недавно, поэтому у архива её практически нет. Замер 02.08.2026 по завершённым матчам за 30 дней:

Вид спортаЗавершённых за 30 днейС раскладкойДоля
Настольный теннис36 60331 56586%
Теннис13 9099 76970%
Дартс1 01443443%
Бадминтон53944983%

На годовом окне картина другая: теннис — 10 273 матча с раскладкой из 129 701 завершённого (7,9%), настольный теннис — 31 840 из 353 961 (9,0%). Почти всё это июль–август 2026: за июнь у тенниса раскладка есть у 16 матчей из 15 542. Строить работу на архивной раскладке нельзя — по старым матчам ключа просто не будет.

Живые матчи покрыты заметно хуже завершённых: раскладка наполняется по ходу игры и появляется не у всех. Срез 02.08.2026 — она была у 1 идущего теннисного матча из 23. Это снимок одного момента, а не средняя за период, но порядок величины он показывает честно: рассчитывать на раскладку у любого live-матча нельзя.

point_by_point и points_history — разные ключи. В теннисе points_history — геймы внутри сета от второго источника: [{"tab":"Set 1","points":[{"h":"2","a":"3","scored":2,"serving":2,"fifteens_content":"0:15, 15:15, 30:15, 30:30, 30:40"}, …]}]. h/a — суммарный счёт геймов после этого гейма, scored — его победитель, serving — подающий (1=home, 2=away), fifteens_content — исходная последовательность очков; отметки вида |B1| обозначают брейк-пойнт. Последние два поля необязательны: отсутствуют, если источник их не передал или сохранена прежняя версия истории. У других видов структура остаётся фреймами/легами/очками партии. point_by_point — партии → геймы → очки, с подающим и счётом тайбрейка. Ключи независимы: за 30 дней у тенниса раскладка есть у 9 769 матчей, points_history — у 5 389, оба сразу — у 4 582, хотя бы один — у 10 576 из 13 911. Берите тот, что пришёл, и не ждите обоих. Оба ключа — в модуле «advanced».

Гонки — протокол заезда race новое

GET/v1/event/{id} модуль «Детали матча»

Мотоспорт (Формула 1/2/3, Формула E, MotoGP · Moto2 · Moto3, WRC, IndyCar, NASCAR, DTM, Supercars, спидвей) и биатлон приходят не как матч. У заезда нет сторон и счёта: результат — это таблица участников с местом, временем и отставанием. Поэтому:

  • participants[] — все участники заезда (от 2 до 90+), у каждого position = место, а side и score — null. Массив отсортирован по месту. Логика «взять side == "home"» на гонке не найдёт ничего — это сделано намеренно: заезд не притворяется матчем со счётом 0:0;
  • race — сам протокол: серия, сессия, дистанция и классификация.
JSON
"race": {
  "series":   "Formula 1",        // серия/дисциплина: "MotoGP", "WRC", "Sprint - Men", "Relay - Women"
  "session":  "Race",             // сессия уик-энда: Race · Qualification 1-3 · Practice 1-3 · Warm Up
  "distance": "305.3 km",         // дистанция заезда
  "classification": [
    { "position": 1,              // место в заезде (по нему массив отсортирован)
      "participant_id": 1913314,  // наш id — джойнится с participants[] этого же матча
      "name": "Schumacher M.",
      "time": "1:27:32.841",      // время победителя / участника
      "gap":  "+0.000",           // отставание от лидера
      "grid": 1,                  // стартовая позиция (мотоспорт)
      "laps": 66,                 // пройдено кругов
      "laps_behind": null,        // кругов позади лидера
      "pit_stops": null,          // число пит-стопов
      "team": "Ferrari",          // команда (мотоспорт)
      "country": "Germany",
      "shooting_misses": null,    // промахи на стрельбе / штрафные круги (биатлон)
      "spare_rounds": null } ] }  // запасные патроны (биатлонная эстафета)
ПолеОписание
positionместо в заезде. null у всех строк = это ещё не результат, а стартовый список (предстоящая или отменённая сессия)
participant_idнаш постоянный id гонщика (или сборной — в биатлонной эстафете участник это команда страны, а не человек). Тот же id, что в participants[] и в /v1/participant/{id}
time / gapвремя и отставание строками, ровно в той форме, в какой их даёт источник ("1:27:32.841", "+13.290", у биатлона — "22:53.1", "13.7"). В секунды не пересчитываем и формат не выравниваем
gridстартовая позиция. Есть у сессий, где источник её печатает (гонки, не квалификации)
laps / laps_behindпройдено кругов и на сколько кругов отстал от лидера (у сошедших/отставших на круг)
pit_stopsчисло пит-стопов за гонку. Времени и раскладки по кругам источник не даёт
teamкоманда/конюшня (мотоспорт). У биатлона всегда null
shooting_missesбиатлон: промахи на стрельбе. В спринте, гонке преследования и масс-старте каждый промах = штрафной круг, то есть «промахи» и «штрафы» здесь одно и то же число — второго поля нет, потому что второго числа нет и у источника
spare_roundsбиатлон, только эстафеты: израсходованные запасные патроны

Ключи в строке всегда одни и те же — незаполненные приходят как null, а не пропадают. Что именно заполнено, зависит от вида: мотоспорту источник даёт круги, стартовую позицию и пит-стопы, биатлону — стрельбу.

Олимпийские игры обновлено · 29.09.2026

GET/v1/olympics

Единый каталог загруженных протоколов и подтверждённых матчей: games[] → disciplines[]. У дисциплины сохранён настоящий sport, указаны gender, gender_class, tournament_id, stage_id, число событий и event_ids. result_kind отличает protocol от обычного match. Каталог учитывает доступные вашему ключу виды и лиги. Для подтверждённых стадий olympic_sport и odf_discipline содержат коды и названия IOC ODF. Это уровни представления Игр: например, шоссе, трек, BMX и маунтинбайк объединяются под велоспортом, но исходный sport и ID стадии сохраняются. При отсутствии проверенного соответствия оба поля равны null. Для медальных протоколов gender_class различает open (открытая категория) и mixed (смешанный состав); у открытой категории gender=null, поскольку это не пол участников. Название дисциплины относится к выбранным Играм: например, RS:X в Токио и iQFOiL в Париже.

События дисциплины: /v1/tournament/{tournament_id}/events?season={stage_id}. season здесь — точный ID стадии, а не год. Карточка протокола: /v1/event/{id}. «Олимпийские игры» — раздел каталога, отдельного значения sport=olympics нет.

GET/v1/event/{id} блок race, модуль «Детали матча»

Что есть. 294 из 329 медальных дисциплин Парижа-2024 и 302 из 339 Токио-2020 в 30 видах — 596 событий (сверка 29.09.2026), по каждому — протокол: место каждого участника (у забегов — все участники, с раундом выбывания), результат финала, медали, составы эстафет и команд. Участников — 13 020 (из них 509 сборных), с датой рождения — 92%, с китайским именем — 8 854. Полнота всей программы Игр ещё не подтверждена: игровой спорт, ракеточные дисциплины и гольф требуют дополнительной сверки. Поле coverage.full_program_verified в каталоге явно сообщает это состояние.

В тот же каталог включены 415 матчей десяти проверенных стадий футбола, бадминтона, тенниса и водного поло. Число матчей не равно числу медальных дисциплин: coverage.protocols и coverage.matches считаются отдельно. В протоколе доступны сохранённые результаты раундов round_results[] (phase, result), детали финала final_details[] (name, value) и исходная подпись места rank_label. Результаты этапов состава находятся в members[].round_results.

История спортсмена /v1/participant/{id}/events включает его участие в олимпийских сборных и эстафетах: player.participation=olympic_team, matches_from.olympic_teams. Состав сборной берётся из протокола; футбольный стартовый состав и сыгранные минуты для него не выдумываются.

Вид (slug)СобытийВид (slug)Событий
Лёгкая атлетика athletics96Конный спорт equestrian12
Плавание swimming (с открытой водой)74Стрельба из лука archery10
Борьба wrestling36Карате karate (Токио)8
Гребля на байдарках и каноэ canoeing32Велоспорт: шоссе cycling8
Дзюдо judo30Велоспорт: BMX bmx8
Пулевая стрельба shooting30Скейтбординг skateboarding8
Академическая гребля rowing28Скалолазание sport_climbing6
Спортивная гимнастика artistic_gymnastics28Триатлон triathlon6
Бокс boxing (олимпийские турниры)26Артистическое плавание artistic_swimming4
Фехтование fencing24Велоспорт: маунтинбайк mountain_bike4
Тяжёлая атлетика weight_lifting24Современное пятиборье modern_pentathlon4
Велоспорт: трек track_cycling24Сёрфинг surfing4
Парусный спорт sailing20Прыжки на батуте trampoline_gymnastics4
Прыжки в воду diving16Художественная гимнастика rhythmic_gymnastics4
Тхэквондо taekwondo16Брейкинг breaking (Париж)2

Как устроено. Одно событие — одна медальная дисциплина Игр целиком, со всеми раундами: «100 м, мужчины, Париж-2024» — это одно событие, в котором все 102 участника забегов. Турнир — дисциплина (Olympic Games: 100 metres - Men), сезон — Игры (season_label 2024 — Париж, 2020 — Токио, прошедший в 2021-м). scheduled_start — старт финала в UTC (у дисциплин, где источник даёт только день, — полночь UTC и extra.time_known=false). Форма та же, что у гонок: сторон нет, место — participants[].position, протокол — блок race. К общим ключам строки у олимпийского события добавлены:

GET · /v1/event/12702237 → race (100 м, мужчины, Париж-2024)
"race": {
  "series": "100 metres - Men", "session": "Final Round", "distance": null,
  "olympic": { "games": "Paris 2024" },
  "wind": "+1.0 m/s",                  // ветер финала (лёгкая атлетика), где он есть
  "classification": [
    { "position": 1, "participant_id": 2185345, "name": "Noah Lyles", "country": "USA",
      "medal": "gold",                 // gold · silver · bronze · null
      "result": "9.79", "result_type": "time",
      "time": "9.79",                  // официальное время
      "time_precise": "9.784",         // фотофиниш: тысячные, как их пишет протокол
      "status": null, "noc": "USA", "team_number": null, "members": null, … },
    { "position": 2, "name": "Kishane Thompson", "medal": "silver",
      "time": "9.79", "time_precise": "9.789", … } ] }
ПолеОписание
medalмедаль строки. При дележе места (две бронзы в единоборствах, «=3» в прыжках) медаль у обоих. У эстафет и команд медаль у сборной, её спортсмены — в members
result / result_typeглавный результат финала строкой и его вид: time · distance (м) · height (м) · points (баллы, очки) · weight (сумма двоеборья, кг). time дублирует результат у временных дисциплин. У участника, выбывшего до финала, result — null: место есть, результата финала нет
time_preciseлёгкая атлетика: время с тысячными (фотофиниш). Официальное time — до сотых, округлено вверх по правилу World Athletics (9.784 → 9.79)
statusDNS · DNF · DQ · NM · NH — финалист без результата и почему. Без этого поля «8-е место, результата нет» читалось бы как пропуск данных
nocкод НОК (USA, CHN, ROC, EOR — сборная беженцев)
membersсостав сборной (эстафеты, командные, пары и синхронные прыжки): [{participant_id, name, legs}]; legs — этапы, где источник их даёт («1-3» / «2-4» у смешанной эстафеты по ходьбе). Спортсмены — полноценные участники со своей карточкой
team_numberвторая и третья сборная одной страны в одном событии (смешанная эстафета по ходьбе, смешанные пары стрельбы): 2, 3; имя — «Spain 2». У первой сборной — null

Как найти. Виды — в /v1/sports (athletics, swimming, artistic_gymnastics…); турниры вида — /v1/tournaments?sport=athletics; события дисциплины — /v1/events?tournament_id=… или по дате (/v1/events?sport=swimming&date=2024-07-29); спортсмен — поиск (/v1/participants/search?q=Pan Zhanle), его старты — /v1/participant/{id}/events. Эстафетная медаль спортсмена видна через members события, а не через его строку участия: в эстафете участник — сборная.

Имена по-китайски (lang=zh): у спортсменов — метка Wikidata, привязанная к самому спортсмену (не угаданная по имени): 6 029 из 13 020 участников, у сборных — по справочнику стран (中国, 中华台北, 俄罗斯奥委会 у ROC; вторая сборная страны — 中国二队). Китайское имя есть у 175 из 176 китайских призёров обеих Игр (без него — брейкерша, записанная в протоколе сценическим именем «671»). Где имени нет, приходит только английское — переводите сами.

Чего нет — говорим сразу.
  • Только Париж-2024 и Токио-2020. Прошлых Игр и зимних Игр в этом разделе нет (олимпийский биатлон — в гонках). Прямого эфира олимпийских стартов нет: протокол появляется у нас после Игр;
  • Игровые и ракеточные виды Игр (футбол, баскетбол, волейбол, гандбол, хоккей на траве, водное поло, регби, теннис, настольный теннис, бадминтон, гольф, пляжный волейбол) идут матчами своих видов — турнир «Olympic Games…» в обычной ленте. Медального протокола у них нет: медаль определяет финальный матч;
  • Единоборства (борьба, дзюдо, бокс, фехтование, тхэквондо, карате): итоговые места и медали (две бронзы — обе с position: 3), без сетки и счёта схваток. То же у финалов-поединков стрельбы (смешанные пары), скалолазания (скорость) и стрельбы из лука: result у них null;
  • у выбывших до финала — место (или null с раундом выбывания у источника), но не их время в забеге: отдаётся только результат финала;
  • у выездки результата финала в протоколе нет — только места и медали;
  • медального зачёта Игр одной ручкой нет — считайте по полю medal событий (эстафетная и командная медаль — одна на сборную, а не на каждого её члена).

Бой — метод победы method

GET/v1/event/{id} ММА, кикбоксинг, бокс

У боя нет счёта: результат — это победитель, раунд окончания и метод. Метод приходит полем method верхнего уровня (и дублируется внутри fight), раунд — полем final_round. Значения приведены к одному словарю: мы собираем бои из нескольких источников, и каждый писал их по-своему — «единогласное решение» встречалось четырьмя написаниями. Наружу отдаётся только список ниже, поэтому по method можно фильтровать.

ЗначениеЧто означает
UDединогласное решение судей
SDраздельное решение
MDрешение большинства
PTSпобеда по очкам — источник не назвал тип решения (кикбоксинг, бокс)
KOнокаут
TKOтехнический нокаут, включая остановку врачом
KO/TKOнокаут, но источник не различил, какой именно. Значение честное, а не мусорное: так эти бои пришли, и додумывать за источник мы не будем. Встречается у части архива UFC
SUBсдача — болевой или удушающий приём
DQдисквалификация
NCбой признан несостоявшимся (no contest)
CNCбоец не смог продолжить. Не то же, что NC — это разные исходы, и мы их не схлопываем
FFнеявка или отказ от боя
INJостановлен из-за травмы
OVERTURNEDрезультат впоследствии отменён
OTHERисточник назвал исход «иным»

Метода нет — приходит null: у части боёв (как правило, мелкие промоушены) источник его просто не печатает. Пустую строку или выдуманное значение вместо null мы не отдаём.

Честное покрытие

Замер 03.08.2026 по всей базе: 2 101 сессия, 44 805 строк классификации, 56 турниров.

ВидСессийСтрокПериодВремяОтставаниеКругиСтарт. поз.Пит-стопыСтрельба
Мотоспорт1 98538 27807.03.2004 — 09.08.202686%87%94%19%12%—
Биатлон1166 52709.02.2017 — 21.02.202696%96%———84%

Внутри мотоспорта основной массив — Формула 1 (1 915 сессий с 2004 года, по 17–23 сезона на Гран-при); MotoGP · Moto2 · Moto3, WRC, IndyCar, NASCAR, DTM, Формула E, Формула 2/3, Supercars и спидвей собираются с 26.07.2026, поэтому истории у них пока нет — только текущий сезон. Стартовая позиция и пит-стопы стоят низко не из-за пропусков сбора: источник печатает их только в гонках, а гонка — одна сессия уик-энда из семи-восьми (310 сессий типа Race против 1 675 квалификаций, практик и прогревов). Запасные патроны есть у 13 178 строк — это ровно эстафеты, в личных гонках их не бывает.

Чего в гонках нет — говорим сразу.
  • Биатлон — только Олимпиады и чемпионаты мира. Кубка мира и Кубка IBU у нас нет: источник их не отдаёт. И зимний вид законно молчит с апреля по ноябрь — пустой календарь летом это межсезонье, а не сбой;
  • биатлон: нет посегментной стрельбы (лёжа/стоя), рубежей, скорости на круге и промежуточных отсечек — только итог гонки;
  • мотоспорт: нет покруговки, телеметрии, стратегии шин, интервалов по кругам, штрафов и причин схода;
  • Строки без места — это стартовый список, а не результат. У ещё не состоявшейся или отменённой сессии race приходит, но position, time и gap у всех null — есть только заявка (имя, команда, страна). На 03.08.2026 таких сессий 33: 31 предстоящая и 2 отменённые. Отличайте результат от заявки по наличию position, а не по статусу матча;
  • incidents, statistics, lineups у заезда пусты по устройству — их место занимает race. В meta.completeness для этого есть отдельный флаг race.

Турнирные таблицы новое

GET/v1/tournament/{id}/standings

Таблица сезона: позиция, игры, В/Н/П, забито/пропущено, очки. type = total (по умолч.) · home · away · form.

season — id сезона или его ярлык 22.09.2026: season=25/26, season=2025/2026, season=2025-26, season=2025 или id из /v1/tournament/{id}. Сравниваются годы, а не строки: у NBA сезон называется «NBA 25/26», и 25/26 его находит. Один год (2025) — это сезон с ярлыком ровно «2025», а если такого с данными нет — сезон, начавшийся в 2025-м (у лиг «осень — весна» это 2025/2026). Какой сезон взят, ответ говорит полями season_id и season_label. Неизвестный ярлык — 404 с error: season_not_found и списком ярлыков турнира в available.

Без season — самый свежий начавшийся сезон. Сезон, в котором ещё нет ни одного сыгранного матча и таблица целиком из нулей (NBA в сентябре), пропускается: отдаётся прошлый, а пропущенный назван в поле season_not_started. Явный season= отдаёт и нулевую таблицу.

JSON
{ "tournament":"Premier League", "season_label":"25/26", "standing_type":"total",
  "table": [ {"position":1,"participant_id":42,"name":"Arsenal","played":38,
              "wins":26,"draws":7,"losses":5,"scores_for":71,"scores_against":27,"points":85} ] }

Формула-1: сезон, зачёт пилотов и команд, карточки 23.09.2026

У Ф-1 два уровня. Гран-при — отдельные турниры («Formula 1: Monaco Grand Prix»), их сессии (тренировки, квалификация, спринт, гонка) — события с протоколом race; история с 2004 года, все этапы каждого сезона (2025 — 24 Гран-при). Чемпионат «Formula 1» — турнир типа championship: у него нет своих заездов, зато есть сезон целиком — официальный зачёт пилотов и команд с 1950 года и календарь сезона со ссылками на гонки. Найти его: /v1/tournaments?sport=motorsports&q=Formula 1 (или q=F1); сезоны — /v1/tournament/{id}, поле seasons.

GET/v1/tournament/{id}/standings?season=2025&type=drivers
typeчто отдаётся
drivers (= total, по умолч.)личный зачёт: место, очки, played — стартов, wins, podiums, poles, fastest_laps, команда пилота в этом сезоне
constructorsкубок конструкторов: место, очки, победы, шасси и двигатель

Очки — официальные, у источника, а не наш пересчёт (method: "source"): в них уже учтены очко за быстрейший круг 2019–2024, штрафы и дисквалификации, половинные очки (2021, Спа: у Ферстаппена 395.5 — число дробное, не округляется). Имена команд — как у источника: он может показывать команду нынешним именем (Sauber — Audi). В ответе рядом с таблицей:

  • rounds — календарь сезона из наших данных: номер этапа, Гран-при (tournament_id), дата, race_event_id и sprint_event_id (протоколы — /v1/event/{id}), winner_participant_id, статус; rounds_count / rounds_finished;
  • races_in_source — сколько гонок сезона насчитал источник: сравните с rounds_finished, чтобы увидеть, полон ли календарь;
  • results_check — воспроизводят ли НАШИ протоколы гонок эти официальные очки (пересчёт по таблице очков сезона, с учётом спринтов): matched / mismatched пилотов. Для 2019–2024 — not_checked: очко за быстрейший круг в протоколе не приходит, пересчёт был бы неверным. Таблица при этом официальная в любом случае.
GET · /v1/tournament/{id}/standings?season=2025
{ "tournament":"Formula 1", "sport":"motorsports", "season_label":"2025", "standing_type":"total",
  "source":"sofascore", "method":"source", "races_in_source":24, "rounds_count":24,
  "table":[ {"position":1,"participant_id":…,"name":"Lando Norris","played":24,"wins":7,"points":423,
             "podiums":18,"poles":7,"team":{"participant_id":…,"name":"McLaren"}},
            {"position":2,"name":"Max Verstappen","points":421,…}, … ],
  "rounds":[ {"round":1,"name":"Australian Grand Prix","tournament_id":85733,"date":"2025-03-16T…",
              "race_event_id":…,"sprint_event_id":null,"winner_participant_id":…}, … ] }

Пилот. /v1/participant/{id} — полное имя («Max Verstappen»), дата рождения, страна, пол (со страницы пилота у источника, по его идентификатору, а не по имени). /v1/participant/{id}/career — сезон за сезоном: место в чемпионате (championship_position), очки, победы, подиумы, поулы, быстрейшие круги, команда; сумма по карьере — в career_totals (место в сумму не входит). /v1/participant/{id}/honours — титулы чемпиона мира (award_id: "f1-wdc", по завершённым сезонам). Команда: /v1/participant/{id}/achievements — кубки конструкторов с годами. История зачёта до 2004 года есть и у пилотов, чьих гонок у нас нет (Сенна, Прост): их карточки заведены из зачёта.

Бомбардиры новое

GET/v1/tournament/{id}/scorers

Таблица бомбардиров: ранг, игрок, команда, голы (футбол, хоккей, гандбол…) или очки с числом матчей и средним за матч (баскетбол) — поле metric. limit ≤ 500.

seasonчто отдаётся
не заданпоследний сыгранный сезон — где уже есть завершённые матчи. До 22.09.2026 бралась самая свежая стадия по календарю, и у NBA в сентябре это был несыгранный 26/27 → 404 при полном 25/26
id или ярлыкэтот сезон; ярлыки — как у таблиц (25/26, 2025/2026, 2025). Неизвестный — 404 season_not_found, а не молчаливая подмена свежим сезоном
all новоесумма по всем сезонам, что есть у нас, с окном истории в coverage и подписью note / note_ru
GET · /v1/tournament/31382/scorers?season=all&limit=3
{ "tournament_id": 31382, "season": "all", "metric": "goals", "source": "derived",
  "coverage": { "since": "1992-08-15T13:00:00+00:00", "until": "2026-09-20T15:30:00+00:00",
                "seasons_with_data": 35, "seasons_finished": 35, "matches_finished": 13201,
                "matches_with_data": 13189, "goals_by_final_score": 35551, "goals_with_known_scorer": 34380 },
  "note_ru": "Наша история матчей, а не официальная статистика за всё время: с 1992 года, 13189 матчей с данными об авторах голов из 13201 сыгранных в нашей базе; у 34380 из 35551 голов по итоговому счёту известен автор.",
  "scorers": [
    { "rank": 1, "participant_id": 1682804, "player_name": "Alan Shearer", "player_name_ru": "Алан Ширер",
      "team_name": "Ньюкасл Юнайтед", "goals": 260, "penalties": 57,
      "first_goal": "1992-08-15T14:00:00+00:00", "last_goal": "2006-04-17T14:00:00+00:00" },
    { "rank": 2, "participant_id": 1757734, "player_name": "Harry Kane", "player_name_ru": "Гарри Кейн", "goals": 213, … },
    { "rank": 3, "participant_id": 372809, "player_name": "Wayne Rooney", "player_name_ru": "Уэйн Руни", "goals": 208, … } ] }

Имя игрока — та же схема, что в карточке матча. player_name — оригинал (английское имя карточки игрока; у строки без participant_id — имя из ленты), player_name_ru — русское имя или null: ключ есть в каждой строке, и для русского интерфейса берите player_name_ru ?? player_name. С lang=zh рядом встаёт player_name_zh (или null). team_name — русское название команды, где оно есть, иначе оригинал. participant_id — канонический id игрока (у дубля карточки — id основной).

Таблица источника ("source": "collected"). В лигах без наших инцидентов таблица берётся у источника со ссылкой на карточку игрока. Имя карточки и player_name_ru ставятся, только если карточка доказанно та же: тот же вид спорта, что у турнира, и фамилия из таблицы источника есть в имени карточки. Иначе player_name — имя из таблицы источника, player_name_ru: null; ссылка источника на карточку другого вида спорта (такие были — теннисная пара в таблице футбольной Serie A) отдаётся как participant_id: null. Исправление имени игрока в нашей базе доходит до таблицы бомбардиров не позже чем через 6 часов.

GET · /v1/tournament/31706/scorers?limit=2&lang=zh (сокращено)
{ "tournament_id": 31706, "season_id": 460991, "metric": "goals", "source": "derived", "count": 2,
  "scorers": [
    { "rank": 1, "participant_id": 1212749, "player_name": "Justin Bailey",
      "player_name_ru": "Джастин Бейли", "player_name_zh": null,
      "team_name": "…", "goals": 6, "penalties": 0, "first_goal": "…", "last_goal": "…" },
    { "rank": 2, "participant_id": 643377, "player_name": "Alexander Belyayev",
      "player_name_ru": "Александр Беляев", "player_name_zh": null, … } ] }
Изменение 23.09.2026. Посезонная таблица (без season=all) до этого дня не отдавала player_name_ru вовсе, а у баскетбола русское имя лежало прямо в player_name — язык поля зависел от вида спорта. Теперь у всех видов player_name — оригинал, русское — в player_name_ru, как в season=all, бомбардирах клуба и карточке матча. Если вы показывали player_name баскетбола как русское имя — перейдите на player_name_ru ?? player_name. Голы теперь группируются по игроку, а не по строке имени из ленты: игрок под двумя написаниями больше не делится на две строки, а два разных игрока с одинаковым именем («Elias Pettersson» в NHL) — не складываются в одного.

Почему в ответе окно и подпись. season=all считает наши матчи и инциденты, а не официальный реестр лиги: где наша история короче или часть голов пришла без автора, сумма меньше официальной. В АПЛ после добора авторов голов 23.09 суммы лидеров совпали с официальными (Ширер 260, Кейн 213, Руни 208), но у клубов с короткой историей кубков и у лиг, где часть голов пришла без автора, разница остаётся. Отвечая пользователю, называйте окно: «с 1992 года, по 13 189 матчам». Баскетбол (metric: points) считается по поматчевой статистике: у NBA она есть с 1946 года, но не у каждого матча — сколько матчей учтено, сказано в coverage.matches_with_data. Первый вызов по турниру с длинной историей собирается посезонно и может ответить 503 с Retry-After — готовые сезоны уже в кэше, повтор через указанное время доходит.

Гольф: таблица турнира новое

GET/v1/tournament/{id}/leaderboard

Таблица турнира по игре на счёт ударов (stroke play): игрок, результат к пару, счёт по раундам, место. season — id сезона (иначе самый свежий из тех, где есть строки). Полное поле — 155–162 строки на турнир.

Где взять id: GET /v1/tournaments?sport=golf — там же у каждого турнира гольфа едет leaderboard_rows (строк таблицы за все сезоны), а в карточке /v1/tournament/{id} — флаг has_leaderboard.

JSON
{ "tournament":"PURE Insurance Championship", "sport":"golf", "season_id":…, "count":156,
  "leaderboard": [ {"participant":{"id":…,"name":"…","name_ru":"…","type":"player"},
                    "score_to_par":-6, "rounds":[68,70], "position":1, "event_status":"finished"} ] }
Как читать эту таблицу.
  • score_to_par — результат К ПАРУ, знаковое число. Меньше — лучше: -6 выше, чем -1, а 0 значит «ровно пар» и это хороший результат, а не «нет данных». Данных нет ⇒ null, и такая строка стоит в конце таблицы без места;
  • score_to_par_missing — почему числа нет. Появляется только у строк с score_to_par: null и разводит два РАЗНЫХ незнания: "not_started" — игрок ещё не отыграл (раундов нет, событие не завершено); "unparsed" — игрок играл (есть rounds) или турнир завершён, а числа к пару у нас нет. Второе бывает, когда результат приходит не числом — в гольфе ровный пар принято писать буквой E. Такую строку нельзя читать как «не вышел на старт»: это игрок с результатом, который мы не смогли разобрать, и место ему не присвоено;
  • position считаем мы, по score_to_par по возрастанию. Равные результаты делят место и съедают следующие номера: 1, 2, 2, 4 — стандарт гольфа (два вторых, затем четвёртое), а не 1, 2, 2, 3;
  • rounds — счёт по раундам, целые числа: [68,70], [72,72,73], у плей-офф [5,4]. Раундов ещё нет ⇒ ключа нет вовсе (пустой список означал бы «сыграно ноль раундов» — другое утверждение). Если какой-то раунд пришёл не числом, рядом появится rounds_raw — ровно то, что пришло, чтобы потеря была видна;
  • total_strokes — сумма ударов за турнир, если источник её прислал. Она приходит ПЯТЫМ элементом того же массива, что и раунды, и мы её оттуда убираем: иначе в карточке игрока появлялся бы пятый раунд в 260 ударов. Различитель — итог равен сумме раундов И больше любого из них (у двух одинаковых раундов 66 и 66 первое условие тоже выполняется, второе — нет).

  • event_status — это статус СОБЫТИЯ (scheduled · live · finished), а не судьба игрока в турнире. Снятие с турнира (CUT / WD / DQ) мы пока не собираем: игрок, снявшийся после второго раунда, придёт с event_status: "finished" — ровно как доигравший. Поэтому поле и не называется status: под таким именем его читали бы как «игрок закончил турнир», и чужой результат встал бы на его место;
  • Только гольф. На турнире любого другого вида ручка отвечает 404: порядок «меньше — лучше» это правило гольфа, и применять его к виду, где score значит набранные очки, значит отдать перевёрнутую таблицу.
  • Матчевый гольф сюда не попадает. Кубки со встречами «игрок против игрока» (Solheim Cup и подобные) — обычные матчи: они отдаются через /v1/events и /v1/event/{id}, а не этой ручкой.

Турнирная сетка (кубки) новое

GET/v1/tournament/{id}/bracket

Сетка плей-офф для кубковых турниров: раунды → пары (команды, счёт, результат, сыграно). season — id сезона (иначе самый свежий). Доступно для футбольных/баскетбольных кубков. Стадию отдельного матча смотрите в поле stage.

JSON
{ "tournament":"FA Cup 25/26", "rounds": [
   { "name":"...", "matchups": [
      {"finished":true,"result":"1:0","home_score":"1","away_score":"0",
       "teams":[{"name":"Blackpool","code":"BLP"},{"name":"Scunthorpe United","code":"SCU"}]} ] } ] }

Стадия турнира у матча новое

Где матч стоит внутри турнира: квалификация, групповой этап, 1/8 финала, финал — либо тур регулярного сезона. Приходит объектом stage у матча — в /v1/events, /v1/event/{id} и /v1/sync. Поле необязательное: если источник стадию не назвал, ключа stage в ответе не будет — см. «Голое число — не стадия».

JSON
// плей-офф — именованная стадия (теннис ITF, кубки)
"stage": { "type":"playoff", "code":"round_of_16",
           "name":"Round of 16", "name_ru":"1/8 финала" }

// квалификация кубка
"stage": { "type":"qualification", "code":"preliminary",
           "name":"Preliminary", "name_ru":"Предварительный раунд" }

// регулярный сезон — источник НАЗВАЛ стадию туром («Round 19»)
"stage": { "type":"regular", "code":"regular",
           "name":"Round 19", "name_ru":"19-й тур", "round":19 }

// регулярный сезон, но источник стадию НЕ назвал — прислал одно число.
// Класс выведен нами по структуре сезона (см. ниже), поэтому name/code нет:
// выдумывать подпись за источник мы не будем
"stage": { "type":"regular", "name_ru":"2-й тур", "round":2 }

// группа матча — отдельным полем, рядом с классом
"stage": { "type":"group", "code":"group", "name":"Round 2",
           "name_ru":"Группа B", "round":2, "group":"Group B" }

// группа бывает и у регулярного сезона: у лиг с географическими дивизионами
// класс и группа истинны одновременно
"stage": { "type":"regular", "name_ru":"7-й тур", "round":7,
           "group":"Western Group 1" }

// у матча может быть ТОЛЬКО группа — если стадию источник не назвал
"stage": { "group":"Group B" }

// источник прислал только число, а вывести класс не из чего (кубковая сетка,
// теннис, бейсбол, товарищеский матч) -> ключа stage в ответе НЕТ ВОВСЕ
ПолеОписание
typeмашинный класс стадии, ровно одно из пяти: regular · qualification · group · playoff · placement. Список закрытый — новых значений без анонса не появится. Ключа нет, если стадия не распозналась однозначно. Ориентируйтесь на него, а не на разбор английской строки. Финал — это type: "playoff" с code: "final"; отдельного типа final не существует
codeмашинный код КОНКРЕТНОЙ стадии: final · semifinal · quarterfinal · round_of_16 · round_of_32 · round_of_64 · round_of_128 · round_of_256 · third_place · playoff · qualification · preliminary · group · regular · placement. Считается по машинной шкале сетки, а не по разбору строки. ⚠️ round_of_16 — это 16 команд = русское «1/8 финала»: английский счёт по командам, русский по парам
nameназвание стадии как его даёт источник, английское: Round of 16, 1/16-finals, Semi-finals, Qualifications, Preliminary, Round 19
name_ruрусское название по словарю. Незнакомое название не переводим — ключа просто нет, показывайте name
group новоегруппа матча так, как её назвал источник: Group B, Gruppe 1, Girone A, Western Group 1. Приходит, когда источник дал группу или когда её однозначно определяет турнирная таблица сезона (обе стороны в одной группе). Не зависит от type: у лиг с географическими дивизионами матч одновременно и тур регулярного сезона, и матч группы, поэтому группа отдаётся отдельным полем, а не через code. Подпись отдаётся как есть — мы её не переводим и не нормализуем
roundномер тура. Приходит при type: "regular" и type: "group" (и при квалификации с явным номером круга — Qualification round 2). У названной стадии кубка номер из фида служебный — у Preliminary в MOL Cup там 300, у Qualifications — 10, — и наружу он не отдаётся вовсе, чтобы его нельзя было показать как «тур 300». Сырое число фида, если оно вам нужно для сортировки, остаётся в extra.round

Где приходит стадия и как по ней фильтровать

Объект stage приходит на четырёх ручках — везде в одной и той же форме:

РучкаЧто вернёт
GET /v1/event/{id}стадия конкретного матча (+ объект season)
GET /v1/eventsлента; поддерживает фильтр по стадии, см. ниже
GET /v1/syncинкрементальная синхронизация — стадия едет вместе с матчем
GET /v1/tournament/{id}/eventsматчи турнира по сезонам

Фильтр: ?stage= принимает одно из пяти значений regular · qualification · group · playoff · placement. Незнакомое значение вернёт 400 unknown_stage со списком допустимых — а не пустую ленту, которую легко принять за «матчей нет».

curl · api.sportwire.ru
# все матчи стадии плей-офф по футболу
curl -H "X-API-Key: $KEY" \
  "https://api.sportwire.ru/v1/events?sport=football&stage=playoff&limit=50"

# ближайшие матчи квалификации
curl -H "X-API-Key: $KEY" \
  "https://api.sportwire.ru/v1/events?stage=qualification&status=scheduled"

# архив финалов и полуфиналов сезона: фильтруйте по stage=playoff,
# конкретную стадию берите из stage.code (final / semifinal / …)
curl -H "X-API-Key: $KEY" \
  "https://api.sportwire.ru/v1/events?sport=basketball&stage=playoff&status=finished"

⚠️ Фильтр отбирает по stage.type (класс), а не по stage.code. Отдельного фильтра «только финалы» нет намеренно: финал — это type=playoff с code=final, и разделять их в параметре значило бы плодить два способа спросить одно и то же. Отбирайте класс запросом, конкретную стадию — по code на своей стороне.

Матчи без стадии фильтр не вернёт вовсе — у них поля нет, и это штатно (см. следующий раздел). Если нужен полный список матчей, не задавайте stage=.

Голое число само по себе — не стадия

Из одного лишь номера раунда мы никогда не выводим класс на лету. Причина не теоретическая: в теннисе регулярного сезона не существует, а номер есть почти у каждого матча (за 45 дней — 22 492 матча с номером против 39 с именем), и round=5 там означает «Round of 16», а вовсе не «5-й тур». В ММА номер раунда доходит до 74 при пяти раундах в бою. Один и тот же «номер» у разных видов значит разное, поэтому догадка по нему была бы правдоподобной ложью.

Но там, где номер ДОКАЗУЕМО является туром, поле заполняется — отдельным проходом по истории, а не разбором на лету, и только когда выполнены сразу четыре условия: вид — командная лига; внутри сезона встречается не менее пяти разных номеров и самый частый из них держит меньше 30% матчей; номер лежит до первого разрыва в ряду (кубковые коды сидят за разрывом: у Лиги Европы туры 1–8, дальше сразу 27/28/29); имя турнира не кубковое. Такие матчи несут type и round, но не несут name и code — по ним и отличайте выведенный класс от названного источником. Проверка правила: номер сверен с подписью флеш-твина «Round N» на 77 238 парах — совпало 96.9%.

Что пришлоКак читать и что показывать
есть stage.typeстадия распознана. Показывайте name_ru → name; ветвление в коде стройте на type/code
есть только stage.nameисточник назвал стадию, но название не в нашем словаре. Показывайте name как есть — не угадывайте класс сами
stage нетстадии по этому матчу нет. Не рисуйте «Стадия: —» — поля не существует, интерфейс должен просто обойтись без него. Номер раунда, если он был в фиде, лежит в extra.round — но что он означает, гарантировать нельзя

Честное покрытие по видам спорта

Стадию отдаёт источник, а не мы: где он молчит — поля не будет, сколько ни ждать. Замер по всем матчам за 14 дней (не по выборке): сколько матчей вида имеют имя стадии.

Вид спортаМатчей за 14 днейС именем стадииЧто это значит на практике
Валорант273273стадия названа у каждого матча
Пляжный волейбол36593круги турнира названы у четверти матчей
Бадминтон5891071/8-finals, Quarterfinals — примерно каждый пятый
Регби-лиг48973плей-офф назван, регулярка идёт номером
Водное поло8041около половины
Теннис11 963162имя приходит редко, номер круга — почти всегда, но что он значит, фид не сообщает
Настольный теннис18 788188см. врезку ниже — имя приходит с флеш-твина
Футбол51 628284имя есть у кубков и еврокубков; у лиговых матчей стадии, как правило, не будет
Баскетбол · хоккей · гандбол · волейбол18 61987имя — у плей-офф; регулярка приходит номером, то есть без стадии
Снукер · ММА · бокс · гольф · автоспорт1 0010за две недели имя стадии не пришло ни разу

По ряду видов — настольный теннис, крикет, футзал, бадминтон, дартс, снукер, пляжный волейбол, ММА — основной фид стадию не передаёт вообще. Но это не значит «стадии не будет никогда»: часть таких матчей связана с записью второго источника, и оттуда название стадии приходит. Замер за 14 дней: настольный теннис — 188 матчей с именем (1/8-finals 40, 1/16-finals 33, 1/32-finals 32, Quarterfinals …), бадминтон — 107, пляжный волейбол — 93. Правило простое и одинаковое для всех видов: есть stage — пользуйтесь, нет — считайте это нормой, а не ошибкой.

⚠️ Настольный теннис — самый массовый вид у нас (≈37 000 матчей в месяц) — идёт почти целиком без стадии: имя есть примерно у 1 матча из 100. Если ваш интерфейс рассчитывает на стадию у каждого матча, он «поедет» именно здесь. Проектируйте показ так, чтобы отсутствие stage было штатным состоянием.

Числа выше — факт на дату замера, а не обязательство: покрытие определяет источник. Станет отдавать шире — вырастет само, без изменения контракта; обратной гарантии нет, планируйте на то, что стадии может не быть у любого матча любого вида.

Русские названия стадий

Названия стадий приходят от источника по-английски. Мы переводим их по словарю и кладём в name_ru. Примеры соответствий:

Источник (EN)name_ru
FinalФинал
Semifinals / Semi-finals1/2 финала
Quarterfinals1/4 финала
Round of 16 · Round of 32 · Round of 641/8 финала · 1/16 финала · 1/32 финала
Qualification / QualificationsКвалификация
PreliminaryПредварительный раунд
Group Stage / Group AГрупповой этап / Группа A
Play-off / Playoffs / Knockout stageПлей-офф
3rd place / Bronze medal matchМатч за 3-е место
Placement match / 5th place matchМатч за место / Матч за 5-е место
1/16-finals (форма второго источника)1/16 финала
Round 19 / Matchday 519-й тур / 5-й тур

Словарь закрытый: незнакомое название источника мы не переводим наугад — тогда name_ru в ответе отсутствует, а оригинал остаётся в name. Надёжный порядок показа: stage.name_ru → stage.name → ничего (объекта stage нет — не подставляйте заглушку).

⚠️ season_label и tournament_stage — это СЕЗОН, а не стадия. Слово «stage» в нашей модели исторически занято изданием турнира: season_label = «25/26», а параметр season у таблиц, бомбардиров и сеток — это сезон, а не стадия (у таблиц и бомбардиров — id сезона или его ярлык, у сеток — id). Стадия матча (1/8 финала, квалификация, тур) живёт только в поле stage у матча. Отдельно не путайте со словом «стадия» в гайде по миграции: там оно означает status матча (scheduled / live / finished) — состояние, а не место в сетке.
⚠️ Изменение контракта 28.07.2026. Раньше в этой документации значилась пара round_num / round_name. round_num API не отдавал никогда — это была ошибка документации, и из инвентаря полей он убран. Что появилось и что осталось:
  • stage — новый объект, описан выше. Аддитивно: ни одно существующее поле не изменило смысл и ни одно не убрано.
  • round_name остаётся в ответе как раньше и равен stage.name. Новый код пишите на stage.
  • extra.round остаётся как раньше — это сырое число из фида, и оно не равно stage.round: у названной стадии кубка там служебный код (300 у предварительного раунда), а stage.round в этом случае не отдаётся вовсе. Показывать extra.round пользователю как «тур» нельзя.
⚠️ Изменение контракта 03.09.2026. В /v1/rankings для футбольных видов (fifa, uefa_clubs, uefa_countries) поле participant.id теперь содержит наш канонический идентификатор и джойнится с /v1/participant/{id}. Раньше там по недосмотру отдавался внутренний идентификатор поставщика: он пересекается с нашим пространством id, поэтому переход по нему приводил в другую команду. Если вы сохраняли эти значения — их нужно перезапросить. Там, где сопоставление с нашей сущностью отсутствует, поле равно null (так же ведут себя теннисные рейтинги).

Рейтинги новое

GET/v1/rankings?kind={kind}

kind: fifa · uefa_clubs · uefa_countries · atp_singles · atp_doubles · atp_singles_race · atp_doubles_race · wta_singles · wta_doubles · wta_singles_race · wta_doubles_race. Каждая запись: ранг, участник, очки, movement (со знаком, где источник даёт прошлый срез).

JSON
{ "kind":"fifa", "count":211,
  "rankings": [ {"rank":1,"participant":{"id":…,"name":"Argentina","name_ru":"Аргентина",
                 "country":"ARG"},"points":1877.3,"movement":2} ] }

Трансферы beta

GET/v1/participant/{id}/transfers

История трансферов участника. Для игрока — его переходы (откуда/куда, дата, тип, сумма); для команды — входящие и исходящие. type = transfer · loan · loan_return · other. Beta — глубина и полнота зависят от лиги.

JSON
{ "participant_id":…, "type":"player", "count":3,
  "transfers": [ {"date":"2025-08-16","type":"transfer","player_name":"…",
                  "from_team":"…","to_team":"…","fee":12.5,"fee_currency":"EUR"} ] }

Глубина покрытия по лигам

Базовые данные — расписание, счёт, статус, участники — есть по всем матчам всех видов спорта. Глубина детализации (статистика, составы, шотмап/xG, предматчевая модель) зависит от уровня лиги: по топ-лигам доступно всё, по низшим дивизионам обычно только счёт и ключевые события. Это отражает наличие данных у источника, а не пробел в нашем сборе — глубокой статистики низших дивизионов и молодёжных турниров не существует ни у одного провайдера.

ДанныеТоп-лигиНац. дивизионы / кубкиНизшие / молодёжь
Live счёт + статус (~20 сек)✓✓✓
Live инциденты (голы, карточки, замены)✓✓✓
Live шотмап + xG (растут по ходу матча)✓ футболчастично—
Статистика матча✓ ~95%~12%редко
Составы (lineups) + предматч-составы (предполагаемый XI, флаг confirmed)✓ ~95%~11%редко
Травмы / дисквалификации (missing players)✓ где есть составычастично—
Карта ударов (shotmap)~30–40% (футбол)~6%—
xG по игрокам (из шотмапа: xG/xGOT/удары/голы)✓ футболчастично—
Текстовый онлайн (commentary) плей-бай-плей, live, премиум✓ топ liveчастично—
Коэффициенты✓частичноредко
Предматч: модель, форма, H2H, таблица✓✓ *редко *
Погода на стадионе✓✓✓ если есть координаты

Проценты — реальное измеренное покрытие среди уже детализированных матчей (футбол): топ-лиги — ~95% статистика / ~95% составы / ~30–40% шотмап; остальные — 12% / 11% / 6%. Для live и недавних матчей топ-лиг детализация полная; исторический архив покрыт на измеренную выше глубину и пополняется регламентными до-сборами. Предматч-составы, травмы/дисквалификации и xG по игрокам доступны там же, где собираются составы и шотмапы. * Предматчевая модель считается автоматически, когда у обеих команд достаточно истории (≥ 4 матча на сторону) — для редких или молодёжных команд её может не быть (поле model = null).

Лиги с расширенными данными (составы / предматч-составы / травмы / статистика; для футбола ещё шотмап, xG, xG по игрокам): футбол — АПЛ, Ла Лига, Серия A, Бундеслига, Лига 1, Эредивизи, Лига Чемпионов / Европы / Конференций, Saudi Pro League, Бразилейрао, Чемпионшип, MLS и др.; баскетбол — NBA, WNBA, Евролига, Еврокубок, ABA, NCAA, NBB и др.; хоккей — NHL, КХЛ; бейсбол — MLB; плюс топ-турниры по регби, гандболу и волейболу. Точный текущий список и покрытие по каждой лиге — через GET /v1/tournaments.

Детальное покрытие по ведущим лигам

✓ — полное покрытие (≈90%+ finished-матчей) · % — доля finished-матчей с этим типом данных · — — данные этого типа у источника не существуют (напр. шотмап/xG есть только для футбола; моментум — для футбола и баскетбола). Окно — последние 365 дней; для live и недавних матчей детализация полная.

ЛигаВидТаймлайнСоставыСтатистикаШотмапМоментумxG игроков
Premier Leagueфутбол✓✓✓30%30%30%
LaLigaфутбол✓✓✓36%36%36%
Serie Aфутбол✓✓✓39%38%39%
Bundesligaфутбол✓✓✓35%35%35%
Ligue 1футбол✓✓✓—38%—
Лига Чемпионовфутбол✓✓77%—16%—
Лига Европыфутбол✓✓✓16%16%16%
Лига Конференцийфутбол✓✓55%11%11%11%
Saudi Pro Leagueфутбол✓✓✓39%38%39%
Championshipфутбол✓✓✓34%34%34%
MLSфутбол✓✓✓35%34%35%
Brasileirão Série Aфутбол✓✓✓28%28%28%
J1 Leagueфутбол✓✓✓38%39%38%
NBAбаскетбол✓✓✓—41%—
WNBAбаскетбол✓✓✓—32%—
Евролигабаскетбол✓✓✓—30%—
China CBAбаскетбол✓✓✓—47%—
Brazil NBBбаскетбол✓✓✓—43%—
NHLхоккей✓✓✓———
КХЛхоккей✓✓✓———
AHLхоккей✓12%✓———
MLBбейсбол✓ раны, хоум-раны, питчеры✓✓———

Срез по матчам за последние 365 дней (на момент генерации). Шотмап, xG и xG по игрокам — футбольные метрики; моментум доступен для футбола и баскетбола. Для live и недавних матчей топ-лиг детализация полная. Полный машиночитаемый список лиг и покрытие — через GET /v1/tournaments.

Турниры и лиги

GET/v1/tournaments

Список турниров/лиг (опционально ?sport=), с количеством матчей, страной и полом (gender — male/female/mixed, фильтр ?gender=; плюс age_group и reserve — см. «Пол и возраст»). В карточке /v1/tournament/{id} — tournament_type новое: league / cup / friendly по классификации источника (null, пока источник тип не назвал), и tier (1 = топ).

JSON
{ "count": 18, "tournaments": [
  { "id": 17, "name": "Premier League",
    "name_i18n": {"en":"Premier League","ru":"Премьер-лига"},
    "sport": "football", "gender": "male", "age_group": null, "reserve": null,
    "country": "EN", "events": 14023 }
]}

Поиск по названию — значимые первыми 22.09.2026. С q порядок — важность турнира с надбавкой за качество совпадения: имя целиком выше, чем запрос отдельными словами в имени, а то выше простой подстроки; совпадение только по стране — ниже всех. Понимаются ходовые названия и сокращения, которых у источника нет: АПЛ, EPL, ЛЧ, UCL, Бразилейрао, КСЛ, F1, китайские 英超, 欧冠, 中超, 西甲 и другие по топ-лигам — такой турнир идёт первым. q=Champions League → 31509, q=Premier League → 31382, q=Championship → 34466 (английский Чемпионшип).

Турниры, где отдаётся таблица, а не матчи. В гольфе единица отдачи — строка лидерборда (игрок + результат к пару), а не встреча, поэтому у таких турниров events честно равен 0 (матчей нет), а рядом едет leaderboard_rows — сколько строк таблицы мы отдаём по этому турниру за все сезоны (та же мерка, что у events; одна таблица сезона — 155–162 строки). Ключ появляется только там, где строки есть. Сама таблица — /v1/tournament/{id}/leaderboard.

Категория тура в теннисе: ATP, WTA, Challenger, ITF, Большой шлем 23.09.2026. У теннисного турнира в списке и в карточке /v1/tournament/{id} есть объект category: slug и name — категория турнира по данным источника (atp, wta, challenger, itf-men, itf-women, wta-125, davis-cup, billie-jean-king-cup, united-cup, utr-men, utr-women, exhibition); level — ступень (grand_slam, atp_1000, atp_500, atp_250, wta_1000, wta_500, wta_250, wta_125, challenger_50…challenger_175, challenger, itf); ranking_points — очки победителю по данным источника; surface — покрытие так, как его называет источник (Hardcourt outdoor, Hardcourt indoor, Red clay, Clay, Grass, Carpet indoor…); покрытие приходит из карточки турнира и дозаполняется ежедневно, пока его нет — null. Фильтры списка: ?sport=tennis&category=atp, ?level=grand_slam; оба принимают несколько значений через запятую (category=atp,wta).

Ступень мы не угадываем: она берётся из очков, которые называет источник. Если очков нет (командные и выставочные турниры, UTR, часть челленджеров), level равен null, а у челленджера без очков — просто challenger. Объект есть только у турниров, где категория известна; у других видов спорта его нет, и фильтр по категории для них возвращает пустой список. Категорию получают турниры, которые есть у sofascore. Часть квалификаций ITF и выставочных турниров приходит только из второго источника, и у них категории пока нет.

GET · /v1/tournaments?sport=tennis&level=grand_slam
{ "id": 39352, "name": "Wimbledon, Men", "sport": "tennis", "events": 242,
  "category": { "slug": "atp", "name": "ATP", "level": "grand_slam",
                "ranking_points": 2000, "surface": "Grass" } }

Пол и возраст: турниры, матчи, команды новое · 27.09.2026

Женский матч отличается от мужского полем, а не названием: у женских команд источник часто пишет то же имя, что у мужских («Ajax — ADO Den Haag» в Vrouwen Eredivisie), а у части женских лиг в названии нет маркера (Kansallinen Liiga, Damallsvenskan, NWSL). Поэтому пол, возрастная категория и признак второй команды отдаются отдельными полями.

ГдеПоля
Каждая сторона матча participants[] — в /v1/events, /v1/event/{id}, /v1/sync, /v1/h2h, /v1/tournament/{id}/events, /v1/participant/{id}/events gender, age_group, reserve — свойства карточки команды/игрока (см. оговорку ниже: пол матча определяйте по gender самого матча)
Сам матч — там же gender — явное решение для матча; если его нет — пол турнира; если и он не установлен — общий пол всех сторон, когда он известен у каждой и совпадает; иначе null. Явное решение null запрещает наследовать пол турнира при противоречивых доказательствах. age_group — так же: возраст турнира, иначе общий возраст всех сторон
Турнир — /v1/tournaments, /v1/tournament/{id} gender, age_group, reserve
Участник — /v1/participant/{id}, /v1/participants, /v1/participants/search, /v1/tournament/{id}/participants gender, age_group, reserve

Значения. gender: male · female · mixed (смешанный: микст, Hopman Cup, детские команды «co-ed», олимпийские дисциплины «… - Open», открытые обоим полам). age_group: U10…U23 или youth (молодёжный турнир, возраст которого источник не назвал: Youth League, Primavera, Juniores). reserve: true — вторая/резервная команда клуба («Jong Ajax», «Real Sociedad B», «Bayern München II», «Atlético Madrileño») или турнир резервных составов.

null — «не установлено», а не «нет». Для gender это «пол неизвестен» (не «мужской»); для age_group — «взрослая команда или неизвестно»; для reserve — «основная команда или неизвестно». Значение появляется только при доказательстве: поле источника, состав турнира или явный маркер в названии по строгому шаблону (U19, «Women», «Femenino», «Jong …», « II»). Свободную догадку по названию мы не подставляем: название без маркера (Kansallinen Liiga, NWSL) пола не получает, а маркер, который спорит с источником или составом, даёт null.

Пол матча авторитетнее пола стороны. participants[].gender — пол карточки участника, gender матча — явное решение для матча, пол турнира либо общий пол всех сторон. Известный дефект: часть сторон стоит на карточке одноимённой команды другого пола (в футболе на 27.09.2026 — 0,24 % сторон в турнирах с установленным полом, 19 787 из 8,18 млн; за последний год — 3 148 из 1,51 млн; чиним переносом сторон на карточку своего пола). В таком матче пол стороны расходится с полом матча — для классификации матча и его участников ориентируйтесь на gender самого матча.

Откуда берётся пол турнира (по убыванию силы): 1) поле пола турнира у источника, у тенниса — категория тура (ATP, WTA, ITF Men/Women, Challenger, UTR); 2) если у источника поля нет — единогласный пол команд турнира: не меньше четырёх команд с известным полом и ни одной другого пола; 3) явный маркер в названии («Women», «Femenino», «Frauen», «Kadınlar», «Men», «Mixed»…) — только когда других свидетелей нет или они с ним согласны. Противоречие свидетелей даёт null. Правило проверено на турнирах, где известны оба источника: единогласие команд совпало с полем источника в 10 164 случаях из 10 164, маркер в названии — в 6 830 из 6 830.

Возраст и резерв. age_group команды — U-число в её названии у источника (U19, U-21, Under 20, Sub-17); слова Youth/Juniors у команд возрастом не считаются («Boca Juniors», «Young Boys» — взрослые клубы). У турнира — U-число в названии, иначе общий возраст всех его команд, иначе слово Youth/Junior/Primavera → youth, если оно называет сам турнир: не в составном имени лиги («Women's & Youth Football League»), не «Coppa Primavera» (весенний кубок взрослых любителей) и не при составе из взрослых женских команд. reserve команды — родительский клуб у источника, либо суффикс второй команды (II, B, 2, Reserves, префикс Jong) при существовании основного клуба с тем же именем в той же стране: «B36 Tórshavn» и «Universidad Carlos III» резервом не становятся. Для сборной вторая команда — только буквенно-цифровой суффикс («Egypt B», «England C»); «Jong Aruba» — самостоятельный клуб, а не резерв сборной Арубы.

Фильтры. /v1/events?gender=female и /v1/tournaments?gender=female (также male, mixed). Лента выбирает матч, если его поле gender равно значению фильтра — тем же правилом, что в карточке: явное решение для матча, иначе пол турнира, иначе общий пол всех сторон (в турнире с неустановленным полом, у матча не меньше двух сторон, и пол известен у каждой). Явное решение исключает матч из фильтра другого пола, даже если турнир имеет такой пол. 02.10.2026 Матчи, чей пол выведен по сторонам, в ленте без date, date_from/date_to, tournament_id и participant_id отбираются за последние 31 сутки и будущие; для более старых задайте окно дат или турнир. Фильтр турниров по-прежнему смотрит на пол самого турнира. Смена пола/возраста/резерва у стороны или турнира доходит лентой /v1/sync как изменение матча (матчи от 7 суток назад и будущие).

Покрытие на 27.09.2026, футбол. Пол есть у 7 867 из 9 336 турниров /v1/tournaments?sport=football (84,3 %; в них 98,4 % отдаваемых матчей): женских 942, мужских 6 872, смешанных 53. Возраст — у 1 846 турниров и 26 893 команд, резерв — у 60 турниров и 5 282 команд. Теннис — пол у 19 460 из 20 087 турниров; все виды — у 38 391 из 51 247 (94,7 % матчей). Новые турниры и команды получают значения ежедневным правилом.

GET · /v1/event/11247580
{ "id": 11247580, "sport": "football", "status": "scheduled",
  "tournament_id": 36412, "tournament": "Eurojackpot Vrouwen Eredivisie",
  "gender": "female", "age_group": null,
  "participants": [
    { "participant_id": 1875729, "name": "ADO Den Haag", "side": "home",
      "gender": "female", "age_group": null, "reserve": null, … },
    { "participant_id": 1875601, "name": "Ajax", "side": "away",
      "gender": "female", "age_group": null, "reserve": null, … } ] }
GET/v1/participants/search?q=…

Нечёткий поиск команд и игроков по названию (с транслитерацией). Параметры: q, sport, type (team|player), limit, offset (до 10 000). В ответе has_more.

GET · /v1/participants/search?q=зенит&type=team
GET /v1/participants/search?q=зенит&type=team

Поиск нужен, когда название вы уже знаете. Если требуется пройти каталог целиком или получить состав лиги, не угадывая имён, — пользуйтесь тремя ручками ниже: они для этого и открыты.

GET/v1/tournament/{id}/participants

Состав лиги одним запросом. Отдаёт команды (в индивидуальных видах — участников) турнира с числом матчей и датой последней игры. Параметры: season (id стадии, см. /v1/tournament/{id}; без него берётся свежайшая стадия с матчами), all_seasons=1 (за всю историю турнира — заметно дороже), type, limit (до 500), offset.

GET · /v1/tournament/31381/participants
GET /v1/tournament/31381/participants
{ "tournament_id": 31381, "season": 465711, "count": 32, "has_more": false,
  "participants": [
    { "id": 28103, "name": "Philadelphia Flyers", "name_ru": "Филадельфия Флайерз",
      "type": "team", "sport": "ice_hockey", "country": "US",
      "events": 26, "last_event_at": "2026-11-21T00:00:00+00:00",
      "gender": "male", "image_url": "/v1/participant/28103/image" } ]}
GET/v1/participant/{id}/squad

Состав команды. Игроки, выходившие за неё в матчах за окно days (по умолчанию 365, максимум 1825), отсортированные по числу матчей — основа сверху. Параметры: days, limit (до 500), offset.

Состав у нас выводной, а не заявочный: источник не отдаёт ростер отдельной сущностью, есть только заявки на матчи. Поэтому в ответе у каждого игрока стоит matches — на скольких матчах основан вывод. Узкое окно (days=90) даёт актуальную основу, широкое захватит резерв и юношей.

GET · /v1/participant/28099/squad?days=90
GET /v1/participant/28099/squad?days=90
GET/v1/participants

Перечисление каталога курсором — без поиска по названию. Параметры: sport, type, gender, limit (до 500), cursor. В ответе next_cursor: передайте его в следующий запрос, пока он не станет null.

В отличие от поиска, глубина здесь не ограничена: порядок идёт по первичному ключу, поэтому стоимость страницы одинакова в начале и в конце каталога. Это способ выгрузить справочник целиком.

GET · /v1/participants?type=team&sport=football&limit=200
GET /v1/participants?type=team&sport=football&limit=200
{ "count": 200, "has_more": true, "next_cursor": 4471, "participants": [ … ] }

Карточка /v1/participant/{id}. Кроме имени, вида, пола и даты рождения — то, что известно об участнике: у команды national (сборная ли), name_code (трёхбуквенный код, как на табло), team_colors (primary / secondary / text, hex), disbanded, а также основание, стадион и тренер (таблица ниже); у игрока — height, weight, position, shirt_number, preferred_foot, retired, birth_city (город рождения строкой) и nickname (прозвище). Ключа нет, когда значения у нас нет. Карточка, матчи и трансферы участника гейтятся по виду спорта тарифа (402).

Поле команды обновлено 23.09.2026Что внутри
foundedдата основания строкой "1902-03-06", если известен только год — "1946". Никогда не секунды
founded_yearгод основания числом (1902) — есть всегда, когда есть founded. Для показа берите его
venue, venue_ruназвание домашнего стадиона строкой и его русское название (если есть)
venue_city, venue_capacityгород стадиона и вместимость числом
manager, manager_ruглавный тренер строкой и его русское имя (если есть); manager_id — id его карточки, когда она у нас есть
venue_zh, manager_zhкитайские названия — только с lang=zh; null, если их у нас нет
GET · /v1/participant/5351?lang=zh (сокращено)
{ "id": 5351, "type": "team", "sport": "football", "name": "Real Madrid",
  "name_ru": "Реал Мадрид", "name_zh": "皇家马德里",
  "country": "ES", "country_ru": "Испания", "country_zh": "西班牙",
  "founded": "1902-03-06", "founded_year": 1902,
  "venue": "Bernabéu", "venue_ru": "Стадион Бернабеу", "venue_zh": null,
  "venue_city": "Madrid", "venue_capacity": 83186,
  "manager": "José Mourinho", "manager_ru": "Жозе Моуринью", "manager_zh": null }

Что изменилось 23.09.2026. Раньше founded отдавался как пришёл от источника — unix-секундами (у «Estudiantes de La Plata» -2032646400), а founded_year выводился рядом. Теперь данные основания приведены к дате, и founded — строка "ГГГГ-ММ-ДД" или "ГГГГ"; founded_year остался прежним. venue и manager — по-прежнему строки: 22.09 примерно с 21:00 до 23:10 UTC они приходили объектами — если вы успели под это подстроиться, верните чтение строки.

Прозвище — nickname строкой, nickname_ru — русское (если есть), nickname_zh — с lang=zh. Сегодня прозвища есть у бойцов ММА (3 057, например «Dora The Destroyer» у Лесли Эрнандес, 289973) и боксёров (281): их даёт спортивный источник. Прозвища игроков других видов — из Wikidata, у 947 карточек на 23.09.2026: «La Pulga» у Месси (543227), «CR7» и с lang=zh «C罗» у Роналду (1753573), «King James» у Леброна (499348), «KD» у Дюранта, «Chef Curry» у Карри. Берутся прямые утверждения Wikidata «прозвище» и альтернативные имена, подтверждённые в двух языках (или аббревиатура вида «KD»), которые не являются формой самого имени (другая транслитерация, имя при рождении, «Leo Messi» — не прозвище). Мы сознательно предпочли точность охвату: альтернативное имя одного языка в Wikidata часто оказывается описанием, чужим именем или шуткой правщика, и такое не отдаётся. Поэтому прозвищ мало: у игроков топ-лиг футбола, NBA, NFL и MLB за два сезона поле есть у 125 из 18 879 — это знаменитости. Боевое прозвище спортивного источника мы не заменяем. nickname без lang — английское, если английского нет — ключа нет; nickname_ru есть у 11 карточек, nickname_zh — у 4.

Город рождения. birth_city — строка вида «Jensen Beach, Florida, USA». Поля birthPlace у источника мы не отдаём под этим именем: там лежит код региона («US-FL»), а не город, и подпись обязана быть правдой.

Пол участника. Поле gender во всех ответах приведено к единому словарю: male, female, mixed. Если пол неизвестен, стоит null — мы не подставляем догадку вместо факта. Фильтр gender в перечислении принимает те же три значения. Рядом едут age_group и reserve — подробно в «Пол и возраст».

Команда / игрок

GET/v1/participant/{id}

Профиль: тип, вид спорта, страна, пол, дата рождения (для игроков), name_i18n, image_url и дополнительные поля (стадион, амплуа, рост — где доступно).

Связанные команды (related_teams, только для type=team). У киберспортивных организаций рядом с основной командой живут отдельные коллективы: академия, женский и молодёжный составы, а также состав, ушедший из организации. Это РАЗНЫЕ команды в разных турнирах, поэтому мы их не объединяем — но показываем родство, чтобы вы могли решить сами. Каждая связь несёт своё доказательство:

relationчто означает
divisionподразделение той же организации: Academy, Talent, Female, NXT, Challengers, Youth
former_rosterбывший состав: «ex-X» рядом с «X»
shared_rosterчетыре и больше общих игрока за 180 суток; число в поле shared_players. Это НЕ та же команда — в киберспорте состав переходит целиком
JSON
{ id: 451856, name: Vivo Keyd Stars Academy, type: team, sport: league_of_legends,
  related_teams: [
    { participant_id: 458100, name: Vivo Keyd Stars, relation: division,
      evidence: «Vivo Keyd Stars Academy» — подразделение «Vivo Keyd Stars», matches: 133 } ] }
GET/v1/participant/{id}/stats обновлено 22.09.2026

Карьерные суммы ИГРОКА по нашим матчам — любого вида спорта, где у составов есть поматчевая статистика: футбол, баскетбол, бейсбол, американский футбол, хоккей, гандбол. Модуль «xG · shotmap» (advanced); средняя оценка — модуль player_ratings. Параметр limit (1–50, по умолчанию 15) — сколько последних матчей приложить.

ПолеОписание
sourcebox_score — поматчевая статистика составов; shotmap — только карты ударов (футбол, когда статистики составов нет); appearances — только число матчей в составах
totalsсуммы всех целочисленных счётчиков вида в том написании, в каком их отдаёт карточка матча: goals, goalAssist, totalShots у футбола; points, rebounds, assists у баскетбола; battingHits, battingHomeRuns, battingRbi, pitchingStrikeOuts у бейсбола; passingYards, passingTouchdowns, rushingYards у NFL. Плюс matches (матчей со статистикой) и avg_rating. Подачи питчера pitchingInningsPitched — в бейсбольной записи (2073.1 = 2 073 и одна треть), восстановлены из аутов
per_gameсреднее за матч: по основным счётчикам вида и по полям, записанным в каждом матче
bestsполя «самый длинный» (passingLongest, rushingLongest) — максимум за матч, а не сумма
coverageокно нашей истории: matches_with_stats, appearances (матчей в составах всего), first_match / last_match, counters_recorded_in — в скольких матчах источник записал каждое поле
not_summedполя, которые в сумму не попали: проценты, средние, отношения, «нарастающим итогом за сезон»
shotmap_totalsтолько футбол: голы, удары, xG и xGOT по матчам с картой ударов — со своим окном (у Роналду 29 матчей с 2012 года)
matchesпоследние матчи с поматчевыми числами

Что суммируется — правило то же, что у career_totals: только целые счётчики. Проценты, средние и отношения не суммируются даже когда они целые — в поматчевых данных fieldGoalPct у Леброна целый во всех 1 887 матчах, pitchingSaveConversion равен 100; такие поля узнаются по словам в имени (Pct, Percentage, Avg, Per…, Ratio, ToDate) и перечислены в not_summed. Среднее за матч отдаётся не у всех полей, и это намеренно: отсутствие поля в строке значит то «ноль» (у футболиста goals нет в матче без гола), то «не собиралось» (totalPass у Роналду есть в 603 матчах из 846) — по одной строке их не различить, поэтому среднее посчитано там, где смысл однозначен, а суммы и counters_recorded_in есть у всех полей.

Чем это отличается от /career. /stats складывает наши матчи; сезоны до начала нашей истории в сумму не входят, и окно названо в coverage. /career отдаёт карьеру у источника по сезонам.

GET · /v1/participant/1327494/stats?limit=1 (сокращено)
{ "participant_id": 1327494, "name": "Tom Brady", "sport": "am_football", "available": true,
  "source": "box_score",
  "totals": { "passingAttempts": 13488, "passingCompletions": 8639, "passingYards": 99070,
              "passingTouchdowns": 713, "passingInterceptions": 243, "rushingYards": 1219,
              "rushingTouchdowns": 34, …, "matches": 373 },
  "per_game": { "passingYards": 265.6, "passingTouchdowns": 1.91, "rushingYards": 3.27, … },
  "bests": { "passingLongest": 99, "rushingLongest": 22, … },
  "coverage": { "matches_with_stats": 373, "appearances": 373,
                "first_match": "2000-11-23T17:36:00+00:00", "last_match": "2023-01-17T01:15:00+00:00",
                "counters_recorded_in": { "passingYards": 373, "passingNetYards": 125, … } },
  "not_summed": [ "passingYardsPerAttempt", "puntingYardsPerPuntAvg", "receivingYardsPerReception",
                  "rushingYardsPerAttempt" ],
  "matches": [ { "event_id": 4030792, "tournament": "NFL", "home": "Tampa Bay Buccaneers",
                 "away": "Dallas Cowboys", "home_score": 14, "away_score": 31,
                 "stats": { "passingYards": 351, "passingTouchdowns": 2, … } } ] }

Числа на 22.09.2026: Фриман (MLB) — 2 366 хитов, 360 хоум-ранов, 1 272 RBI за 2 110 матчей; Леброн — 50 493 очка за 1 922 матча (26,27 за матч); Роналду — 707 голов и 167 голевых передач за 846 матчей со статистикой (954 в составах); Месси — 766 голов за 973. До 22.09.2026 у бейсбола и NFL ручка отдавала только число матчей, у футболиста — 29 матчей карты ударов.

Лучшие бомбардиры клуба новое

GET/v1/participant/{id}/top-scorers

Кто больше всех забил за КЛУБ за всю нашу историю матчей: голы за этот клуб из инцидентов (футбол, хоккей, гандбол), у баскетбола — очки из поматчевой статистики. Параметры: tournament_id (только этот турнир), limit (≤ 100, по умолчанию 20), include_friendlies (по умолчанию false: товарищеские, предсезонные — включая International Champions Cup, — летние лиги и матчи звёзд не считаются). id — команды; для игрока ручка отвечает 400 not_a_team.

Это не официальный рекорд клуба, и ответ это говорит. Окно нашей истории — в coverage (с какого года, сколько матчей с данными об авторах голов из сыгранных, у какой доли голов по итоговому счёту известен автор, сколько автоголов соперников) и по каждому турниру в coverage.by_tournament; готовая подпись — note / note_ru. Пример: у «Манчестер Юнайтед» Кубок Англии у нас есть с 2013 года, Кубок лиги с 2009-го — поэтому у Руни 225 голов за клуб против официальных 253; в АПЛ (tournament_id=31382) — 182 против официальных 183.

GET · /v1/participant/7344/top-scorers?limit=3 (сокращено)
{ "participant_id": 7344, "name": "Manchester United", "name_ru": "Манчестер Юнайтед",
  "sport": "football", "metric": "goals", "available": true,
  "scope": { "tournament_id": null, "friendlies": "excluded (friendlies, preseason, summer leagues, all-star, International Champions Cup)" },
  "coverage": { "since": "1970-08-15T15:00:00+00:00", "until": "2026-09-20T15:30:00+00:00",
                "matches_finished": 2532, "matches_with_data": 2448,
                "goals_by_final_score": 4226, "goals_with_known_scorer": 4111, "own_goals_by_opponents": 115,
                "by_tournament": [
                  { "tournament_id": 31382, "name": "Premier League", "matches_finished": 1307,
                    "matches_with_data": 1296, "since": "1992-08-15T14:00:00+00:00", … },
                  { "tournament_id": 34469, "name": "FA Cup", "matches_finished": 74,
                    "matches_with_data": 60, "since": "2013-01-05T17:15:00+00:00", … }, … ] },
  "note_ru": "Наша история матчей, а не официальная статистика клуба за всё время: с 1970 года, 2448 матчей с данными об авторах голов из 2532 сыгранных в нашей базе; у 4111 из 4226 голов по итоговому счёту известен автор.",
  "scorers": [
    { "rank": 1, "participant_id": 372809, "player_name": "Wayne Rooney", "player_name_ru": "Уэйн Руни",
      "goals": 225, "penalties": 26, "first_goal": "2004-09-28T18:45:00+00:00", "last_goal": "2017-05-14T15:30:00+00:00" },
    { "rank": 2, "participant_id": 1758738, "player_name": "Marcus Rashford", "goals": 137, … },
    { "rank": 3, "participant_id": 1753573, "player_name": "Cristiano Ronaldo", "goals": 126, … } ] }

Автор гола берётся из инцидента; если в инциденте только имя — из состава того же матча, а если состав неполон (у старых матчей по 6–10 игроков на сторону) — по имени среди игроков клуба, когда имя однозначно. Имя, которое носят два разных игрока, остаётся отдельной строкой с participant_id: null — чужие голы не приписываются. Баскетбол: у каждой строки points, matches, ppg.

Карьера и титулы новое

Модуль career. Отдельный модуль тарифа — не входит ни в один готовый пакет.

Чем это отличается от /v1/participant/{id}/stats. Та ручка складывает наши матчи. Эта отдаёт карьеру у источника — сезоны, которых в нашей ленте матчей может не быть вовсе: у Леброна Джеймса 23 сезона NBA, у Роналду 25 турниров.

GET/v1/participant/{id}/career модуль «career»

Статистика игрока по сезонам: турнир → сезон → срез → полный набор цифр этого вида спорта. Параметры: season (например 2024/2025), tournament_id, season_type.

Срез сезона (season_type) зависит от вида спорта. У футбола он один — overall. У баскетбола, американского футбола и бейсбола регулярный сезон (regularSeason) и плей-офф (playoffs) — разные строки: складывать их в одну значило бы сложить несравнимое. Сумма по обеим — в career_totals.

ПолеОписание
seasonсезон как его называет соревнование: 2024/2025, 25/26, 2025
season_typeoverall · regularSeason · playoffs
season_nameназвание сезона у соревнования, например Torneo Apertura и Torneo Clausura: у двух сезонов одного года одинаковое season, и различить строки можно только по нему. Если названия нет, поля нет
tournament_id / tournament / tournament_ruтурнир этого сезона; tournament_id — наш постоянный id, если турнир есть у нас в каталоге, иначе null при живом названии
teamза кого играл в этом сезоне: participant_id, name, name_ru
appearances / minutes / ratingматчи, минуты (в баскетболе пересчитаны из секунд), средняя оценка за сезон
statisticsвесь набор этого вида спорта. Футбол — 40+ полей (голы, xG-вовлечённость, точность передач, дуэли, штрафные); баскетбол — очки, подборы, передачи, проценты, дабл-даблы и трипл-даблы; американский футбол — пасы, ярды, тачдауны, перехваты, фамблы; бейсбол — battingHomeRuns, battingAvg, battingOps и питчерские поля
career_totalsсумма за карьеру по всем сезонам и турнирам

Что попадает в career_totals, а что нет — и почему. Суммируются только целочисленные счётчики: очки, голы, подборы, хоум-раны, матчи, минуты. Доли, проценты, рейтинги и средние за игру (battingAvg, accuratePassesPercentage, fieldGoalsPercentage) не суммируются и в итог не попадают — сложить девять раз .282 и назвать это средним нельзя. Признак берётся из типа значения, а не из написания имени. Исключение одно: avg_rating — средняя оценка, взвешенная по числу матчей. Правило продублировано в самом ответе полем career_totals_note.

GET · /v1/participant/499348/career
{ "participant_id": 499348, "name": "LeBron James", "type": "player", "available": true,
  "seasons": [
    { "season": "2024/2025", "season_type": "regularSeason",
      "tournament_id": 2841, "tournament": "NBA", "tournament_ru": "НБА",
      "team": { "participant_id": 344696, "name": "Los Angeles Lakers", "name_ru": "Лейкерс" },
      "appearances": 70, "minutes": 2444, "rating": 7.99,
      "statistics": { "points": 1710, "rebounds": 546, "assists": 575, "steals": 70, "blocks": 39,
                      "doubleDoubles": 32, "tripleDoubles": 10, "fieldGoalsPercentage": 51.26 } },
    { "season": "2024/2025", "season_type": "playoffs", "appearances": 5,
      "statistics": { "points": 127, "rebounds": 45, "assists": 28 } } ],
  "career_totals": { "points": 42184, "rebounds": 11326, "assists": 11570, "appearances": 1654,
                     "avg_rating": 8.12 },
  "career_totals_note": "summed integer counters only; percentages, ratios and per-game averages are not summable and are omitted" }
GET/v1/participant/{id}/achievements модуль «career»

Титулы команды: сколько раз выигран каждый турнир и в какие годы, плюс total_trophies — общее число трофеев.

Реестр титулов есть не у всех видов, и это свойство источника, а не нашей полноты. У футбола и баскетбола он есть; у NFL, MLB и тенниса его нет вовсе. Пустой ответ говорит об этом полем available: false, а не молчанием.

GET · /v1/participant/5351/achievements
{ "participant_id": 5351, "name": "Real Madrid", "type": "team", "available": true,
  "total_trophies": 101,
  "achievements": [
    { "tournament_id": 1234, "tournament": "LaLiga", "tournament_ru": "Ла Лига",
      "competition_type": "domestic-league", "major": true, "trophies_won": 36,
      "seasons": ["23/24","21/22","19/20","16/17","11/12","07/08", "…"] },
    { "tournament": "UEFA Champions League", "major": true, "trophies_won": 15,
      "seasons": ["23/24","21/22","17/18","16/17","15/16", "…"] } ] }

Матчи за сборную приезжают в карточке /v1/participant/{id} полем national_team (тот же модуль career): { "team": "Portugal", "team_id": 4704, "appearances": 233, "goals": 146, "debut": 1061337600 }. Собрано по футболу; у баскетбола, американского футбола и бейсбола источник отдаёт пустой список. Там же у команды — total_trophies.

GET/v1/participant/{id}/honours новое модуль «career»

Личные награды ИГРОКА: Золотой мяч, MVP, игрок года, приз лучшему бомбардиру — с годами и числом повторов.

🔴 Это единственные наши данные не от спортивных фидов, и источник назван прямо в ответе. Ни один из девяти спортивных источников личных наград не отдаёт: ручки achievements / honours / awards / medals отвечают 404 у оригинала и отказом у зеркала — проверено на четырёх игроках трёх видов. Награды взяты из Wikidata тремя путями (у игрока, у самой награды, у её выпусков года), а для ключевых наград — Золотой мяч, FIFA The Best, Золотая бутса, игрок года УЕФА, MVP и MVP финала НБА, MVP НФЛ и Супербоула, MVP МЛБ, приз Сая Янга, Laureus, матч всех звёзд НБА — сверены со вторым свидетелем, списком победителей в Википедии: строка, которой список противоречит, не отдаётся. С нашей карточкой награда сведена по id игрока у спортивного источника, записанному в самой Wikidata (с проверкой даты рождения и фамилии), либо по имени и дате рождения одновременно, если пара единственная с обеих сторон. Приписать Золотой мяч однофамильцу хуже, чем не приписать никому.

ПолеОписание
honours[]по строке на каждое получение: award, award_ru, award_id новое, year, source; с lang=zh — award_zh
awardназвание награды — английское, а у национальных призов без английского названия — собственное на родном языке. null, если названия нет ни на одном языке: сырой идентификатор вида Q10549123 названием не является и в это поле не попадает
award_idустойчивый идентификатор награды (Wikidata). Есть у каждой строки, в том числе у безымянной; по нему сведён by_award
by_award[]та же награда, полученная несколько раз, сведена в одну строку по award_id: times и years[]. Несколько Золотых мячей — несколько строк в honours и одна здесь
availablefalse — у этого игрока наград в источнике не нашлось. Это не «мы не собирали»
GET · /v1/participant/543227/honours?lang=zh (сокращено)
{ "participant_id": 543227, "name": "Lionel Messi", "name_ru": "Лионель Месси",
  "name_zh": "利昂内尔·梅西", "available": true, "count": 188,
  "honours": [ { "award": "Ballon d'Or", "award_ru": "Золотой мяч", "award_id": "Q166177",
                 "award_zh": "金球奖", "year": 2023, "source": "wikidata" } ],
  "by_award": [ { "award": "Ballon d'Or", "award_ru": "Золотой мяч", "award_zh": "金球奖",
                  "award_id": "Q166177", "times": 8,
                  "years": [2023, 2021, 2019, 2015, 2012, 2011, 2010, 2009] } ] }

Сверено со звёздами на живом API 23.09.2026: Золотых мячей у Роналду 5 (2008, 2013, 2014, 2016, 2017), у Месси 8; у Леброна 4 MVP и 4 MVP финала НБА; у Махоумса MVP НФЛ 2018 и 2022 и три MVP Супербоула; у Отани MVP МЛБ 2021, 2023, 2024 и 2025. «FIFA Ballon d'Or» 2010–2015 отдаётся как Золотой мяч — отдельной строкой он удвоил бы счёт. award_ru заполнено у 77 % строк, award_zh — у 69 % (у национальных призов малых стран переводов в Wikidata нет, и мы их не выдумываем). Список освежается по расписанию: ключевые награды — каждую неделю, все — раз в месяц, так что новый лауреат появляется в ответе в течение недели после того, как попал в Wikidata или Википедию.

Альма-матер и драфт приезжают в карточке /v1/participant/{id} (тот же модуль career): college, draft (year / round / pick / team — аббревиатура клуба, как у источника: "CLE", "GS", "NE") и bio_source (source, collected_at) — откуда и когда взято. Источник — ESPN, американские лиги (NFL, NBA, MLB, NHL); у европейского футбола ни колледжа, ни драфта не существует как явления.

GET · /v1/participant/460495 (сокращено)
{ "id": 460495, "name": "Stephen Curry", "college": "Davidson",
  "draft": { "year": 2009, "round": 1, "pick": 7, "team": "GS" },
  "bio_source": { "source": "espn", "collected_at": "2026-09-22T20:31:53.447884+00:00" } }

Покрытие на 22.09.2026 (сбор идёт): карточек с ответом ESPN — 18 829: NFL 5 113 (колледж у 5 083), MLB 7 237 (колледж у 2 795 — большая часть лиги приходит не из колледжей), NBA 3 620 (3 152), NHL 2 859 (814). draft нет, когда у ESPN нет записи драфта — например у Сёхэя Отани, пришедшего в MLB из Японии: bio_source при этом на месте.

Пустой колледж — это факт, а не пробел. У Леброна Джеймса college: null, потому что он пришёл в НБА из школы. Ключ bio_source при этом присутствует — то есть мы спрашивали и получили ответ «нет». Если bio_source в ответе нет вовсе, значит этого игрока мы ещё не обходили. Разные утверждения, и мы их не путаем.
Откуда что взято — чтобы не было сюрприза. Сезонная статистика и титулы команд приходят из спортивных источников. Личные награды, альма-матер и драфт — из ВНЕШНИХ (Wikidata и ESPN): у спортивных фидов их нет ни по одному каналу, и мы завели отдельные полосы сбора, а не сделали вид, что поля просто пустые. Поэтому у этих полей всегда назван источник и дата сбора.

Изображения

GET/v1/participant/{id}/image

Готовое изображение (логотип команды / фото игрока), 150×150. Можно встраивать напрямую:

JSON
<img src="https://api.sportwire.ru/v1/participant/4504/image?key=ВАШ_КЛЮЧ">

Логотип турнира отдаёт /v1/tournament/{id}/image тем же способом. Изображения (логотипы, эмблемы, фото) не входят в «Данные»: они поступают из общедоступных внешних источников, принадлежат своим правообладателям, и лицензия на них не предоставляется — права на использование вы получаете самостоятельно. Подробнее — раздел 7 Публичной оферты.

Матчи участника обновлено 22.09.2026

GET/v1/participant/{id}/events

Все матчи команды или игрока — прошедшие и будущие, в обе стороны от сегодня. Первая строка по умолчанию — ближайший матч: сначала идут матчи, которые ещё впереди или идут сейчас (по возрастанию даты), за ними прошедшие — от свежих к старым. Листая offset, вы проходите всю карьеру; поле count_upcoming говорит, с какого offset начинается прошлое.

ПараметрОписание
date_from / date_toокно дат (UTC); date_to не включается. Ограничения ширины нет: сезон, год, вся карьера
statusscheduled · live · finished · postponed · cancelled · suspended · interrupted; другое значение — 400. finished без order — последние результаты первыми
tournament_idтолько матчи этого турнира
seasonid сезона из /v1/tournament/{id} (seasons[].id)
sidehome — домашние, away — гостевые
ordernear (по умолчанию без фильтров) · asc · desc — вся история по возрастанию или от свежих. С окном дат или статусом по умолчанию asc
limit / offsetдо 500 за запрос; в ответе count_total и has_more

Игрок командного вида (футбол, NBA, хоккей…) — не сторона матча: сторона там команда. Его матчи берутся из составов, и у каждой такой строки есть объект player: за какую команду он был заявлен (team_id, team, side), в стартовом ли составе (starter) и сколько минут сыграл (minutes_played, где источник это даёт). matches_from показывает, сколько матчей найдено по сторонам, а сколько — по составам. Будущих матчей игрока в составах нет, пока состав не объявлен, поэтому в ответе есть player_teams — команды, за которые он играл последние 365 дней (клуб и сборная отдельно), с датой последнего матча и ближайшим матчем каждой команды (next_event) — в тех турнирах, где этот игрок уже играл: у сборной это отсекает, например, женские турниры под той же карточкой страны. Команда без такого матча — next_event: null. Теннисист, боец, пилот — сам сторона матча, у него всё как у команды. Склеенный дубль участника приводится к основному id (requested_id — то, что спросили).

GET · /v1/participant/499348/events?date_from=2024-10-01&date_to=2025-07-01
{ "participant_id": 499348,
  "participant": {"id": 499348, "name": "LeBron James", "type": "player", "sport": "basketball"},
  "order": "asc", "count_total": 64, "count": 50, "has_more": true,
  "matches_from": {"sides": 0, "lineups": 64},
  "window": {"date_from": "2024-10-01", "date_to": "2025-07-01", "date_to_exclusive": true},
  "player_teams": [{"team_id": 344696, "name": "Los Angeles Lakers", "last_appearance": "…",
                    "next_event": {"id": …, "scheduled_start": "…", "tournament": "NBA"}}],
  "events": [{ "id": …, "scheduled_start": "2024-10-23T02:00:00+00:00", "status": "finished",
               "tournament_id": 31372, "tournament": "NBA", "participants": [ … ],
               "player": {"team_id": 344696, "team": "Los Angeles Lakers", "side": "home",
                          "starter": true, "minutes_played": 35} }]}

До 22.09.2026 параметров было два (limit, offset), порядок шёл от самого далёкого будущего матча, а у игроков командных видов список был пуст. Прежний порядок — order=desc.

Личные встречи (H2H) новое

GET/v1/h2h?a={id}&b={id}

Вся история встреч двух соперников из нашей базы: команды друг против друга, теннисисты и бойцы, пилоты в одном заезде (выше тот, кто финишировал выше). Игроки командных видов — по составам, только матчи, где они были заявлены за разные стороны. Параметры: tournament_id, status, date_from/date_to (date_to не включается), order (desc по умолчанию | asc), limit (до 200), offset. Склеенные дубли приводятся к основному id; если a и b оказались одним участником — 400.

summary считается по всем встречам под фильтром, а не по странице: meetings, finished, a_wins, b_wins, draws, no_result, first_meeting, last_meeting. Исход решает счёт; в видах без ничьих (баскетбол, теннис…) ключа draws нет вовсе, а равный счёт разрешает победитель матча — если его нет, встреча идёт в no_result, ничья не выдумывается. next_meeting — ближайшая назначенная встреча. Список по умолчанию — история: уже сыгранные встречи от свежих; назначенные в него не входят (их число — count_upcoming, сами они — по status=scheduled или order=asc). У каждой строки a_side/b_side, a_score/b_score и result: a | b | draw | null.

GET · /v1/h2h?a=344696&b=18773&limit=1
{ "a": {"id": 344696, "name": "Los Angeles Lakers", "type": "team", "sport": "basketball"},
  "b": {"id": 18773, "name": "Houston Rockets", "type": "team", "sport": "basketball"},
  "summary": {"meetings": 262, "finished": 260, "a_wins": 157, "b_wins": 103, "no_result": 0,
              "first_meeting": "1971-10-24T04:00:00+00:00",
              "last_meeting": "2026-05-02T01:30:00+00:00"},
  "next_meeting": { "id": 12369709, "scheduled_start": "2026-12-01T00:30:00+00:00", … },
  "count_total": 260, "count_upcoming": 2, "count": 1, "has_more": true,
  "events": [{ "id": 1652347, "status": "finished", "tournament": "NBA",
               "a_side": "away", "b_side": "home", "a_score": 98, "b_score": 78,
               "result": "a", "participants": [ … ] }]}

Числа примера — на 22.09.2026: «Лейкерс» (a) и «Рокетс» (b) — 157 побед против 103 в 260 сыгранных встречах с 1971 года; ничьих в баскетболе нет, поэтому ключа draws нет.

H2H внутри карточки матча (h2h, h2h_meetings модуля detail) по-прежнему приходит от источника и описывает только эту пару в контексте назначенного матча; /v1/h2h работает для любой пары и в любой момент.

Страны

GET/v1/countries

Список стран (ISO) с числом лиг и набором видов спорта. Категории вида «Amateur / Women / Youth» сведены к одной стране. Опционально ?sport=.

JSON
{ "count": 221, "countries": [
  { "iso": "BR", "name": "Brazil", "name_ru": "Бразилия",
    "leagues": 1266, "sports": ["basketball","football","volleyball"] },
  { "iso": "EN", "name": "England", "name_ru": "Англия", "leagues": 699, "sports": ["football"] }
]}

Лиги по странам

Полный справочник всех лиг и турниров в разрезе по странам и видам спорта — отдельная страница с поиском и сворачиваемыми списками, у каждой лиги указан id:

→ /leagues.html — более 43 000 лиг и турниров, 221 страна.

Программный доступ — через /v1/tournaments с фильтрами:

GET/v1/tournaments?country=ES&sport=football&q=liga
ПараметрОписание
countryISO-код страны (например ES, RU)
sportslug вида спорта
qпоиск по названию (EN или RU)
limit / offsetпагинация

Предматчевая аналитика новое

GET/v1/event/{id}/insights

Готовый аналитический пакет по матчу: форма и тренды обеих команд, очные встречи, турнирный контекст, математическая модель (Пуассон) с вероятностями исходов, «справедливыми коэффициентами» и value-сигналами против рынка, а также готовые трендовые ярлыки. Всё считается из исторических данных (≈2,9 млн матчей со счётом) — без чьих-либо «прогнозов».

Блок довстречный — и говорит об этом сам. Форма, H2H и модель считаются строго as-of времени начала матча (as_of), то есть о ходе и счёте уже начавшегося матча они не знают ничего. Поэтому в ответе есть scope:"prematch", actionable и готовый заголовок title_ru: пока матч не начался — «Что модель считает важным перед матчем», после стартового свистка — «Довстречный расклад» плюс note с прямым предупреждением. Не подписывайте блок «перед матчем» на идущем матче — берите title_ru. Для не-actionable матча value_bets = null: довстречная модель против уже изменившейся картины ценности не показывает.

JSON
{ "event_id": 1651128, "sport": "football",
  "scope": "prematch", "as_of": "2026-08-03T18:30:00+00:00",
  "status": "scheduled", "actionable": true,
  "title_ru": "Что модель считает важным перед матчем", "note": null,
  "tournament": { "id": 31384, "name": "Serie A", "name_ru": "Серия А" },
  "home": {
    "name": "Torino", "name_ru": "Торино",
    "form": {
      "last10": { "matches":10, "performance_matches":10, "ppg":1.0, "gf_avg":1.0, "over_2_5":60.0,
                  "btts":60.0, "clean_sheet":20.0, "form":"DLWLL" },
      "home":   { "matches":10, "performance_matches":10, "ppg":1.4, "gf_avg":1.3, "over_2_5":50.0 } },
    "discipline": { "matches":10, "cards_avg":2.3 },
    "standing": { "position":12, "played":38, "points":45, "goals_for":44, "goals_against":63 } },
  "away": { "name":"Juventus", "name_ru":"Ювентус", "form": { … }, "standing": { … } },
  "h2h": { "matches":3, "performance_matches":3, "team_a_wins":0, "draws":2, "team_b_wins":1, "goals_avg":1.33, "btts":33.3,
           "recent":[{"date":"2025-11-09","score":"1:1"}] },
  "model": {
    "expected_goals": { "home":1.78, "away":1.51, "total":3.29 },
    "probabilities":  { "home_win":44.4, "draw":22.8, "away_win":32.8,
                        "over_2_5":63.9, "under_2_5":36.1, "btts":64.8 },
    "fair_odds":      { "home_win":2.25, "draw":4.38, "away_win":3.05, "over_2_5":1.56, "btts":1.54 },
    "likely_scorelines":[ {"score":"1:1","prob":10.0}, {"score":"2:1","prob":8.9} ] },
  "value_bets": [ {"market":"total","side":"over","line":2.5,"market_odds":2.03,"fair_odds":1.87,
                   "edge_pct":8.6,"bookmaker":"fonbet"} ],   // bookmaker = ЧЬЯ это цена
  "value_bets_market": { "source":"линия легальных БК РФ (приём через ЦУПИС)", "line":"prematch",
                         "bookmakers":["fonbet","olimp"], "note":null },
  "trends": [
    {"label":"Тотал больше 2.5","detail":"Торино: 60% из 10","side":"home"},
    {"label":"Обе забивают","detail":"Торино: 60% из 10","side":"home"} ],
  "disclaimer":"Статистические оценки на основе исторических данных. Не является ставкой, прогнозом или инвестиционной рекомендацией." }
БлокЧто внутри
formВ/Н/П, очки за матч, забито/пропущено, % тоталов (1.5/2.5/3.5), ОЗ, «на ноль», не забивает — общие и дома/в гостях, за последние 5/10 и в целом; серии
disciplineсредние карточки команды (по матчам с детализацией)
standingпозиция, очки, игры, забито/пропущено (где есть таблица)
h2hличные встречи: счёт, ОЗ, тоталы
modelмодель Пуассона: ожидаемые голы, вероятности П1/Х/П2, тоталов, ОЗ, «справедливые» кэфы, вероятные счета
value_betsисходы, где рынок платит больше модельной «справедливой» цены (edge_pct). Сравнение — только с доматчевой линией легальных БК РФ, и у каждой строки есть bookmaker — чья именно это цена. На идущем/сыгранном матче блок = null
value_bets_marketс чьей линией сравнивали: источник, тип линии (prematch), список задействованных букмекеров
scope / as_of / actionable / title_ru / noteобласть действия блока: что и на какой момент посчитано, можно ли трактовать это как оценку «сейчас», и готовый честный заголовок
trendsготовые человекочитаемые ярлыки трендов

Блоки формы — form.overall / last5 / last10 / home / away

Каждый блок формы — один и тот же набор метрик на разной выборке матчей (все / последние 5 / последние 10 / только дома / только в гостях). Для предстоящего матча выборка берётся строго ДО его даты — результат самого матча в расчёт не попадает (нет «подглядывания» в будущее).

В футболе технические результаты с status_detail=awarded или walkover сохраняются в matches, В/Н/П, очках за матч, официальной форме и сериях исходов. Для показателей голов они исключаются: знаменатель — performance_matches, число остальных матчей в том же окне. При ненулевой официальной истории и performance_matches=0 средние голы, проценты тоталов/ОЗ/сухих матчей, разница голов и голевые серии равны null, поскольку выборки сыгранных матчей нет. Голевые процентные теги требуют хотя бы трёх матчей в этой выборке.

ПолеОписание
matchesчисло матчей в официальной выборке, включая технические результаты
performance_matchesзнаменатель показателей голов; в футболе исключает awarded и walkover
w / d / lпобеды / ничьи / поражения
ppgочков за матч
gf_avg / ga_avg / goals_avgзабито / пропущено / суммарно голов за матч
win_pct / draw_pct / loss_pct% исходов
over_1_5 / over_2_5 / over_3_5% матчей с тоталом больше N
btts% матчей, где забили обе команды
clean_sheet / failed_to_score% «на ноль» / % без своих голов
streakтекущая серия: {type: win|draw|loss, len}
unbeaten_run / scoring_run / cleansheet_runдлина текущих серий: без поражений / с голами / сухих
formстрока последних исходов, напр. "WWDLW" (новые слева)

Модель — model (методология)

Модель Пуассона. Для каждой команды считается сила атаки и обороны относительно среднего по лиге (отдельно для домашних и гостевых матчей); из них — ожидаемые голы (λ хозяев и гостей), а из распределения Пуассона — матрица вероятностей счёта, и уже из неё все вероятности, «справедливые» коэффициенты и наиболее вероятные счета. Доступна для футбола и хоккея.

Футбольные awarded и walkover не участвуют в истории голов команды, оценке соперников и среднем по лиге. Модели нужно не меньше трёх сыгранных матчей каждой команды в выбранной истории; иначе model=null. Технический счёт сохраняет официальный исход, но не является наблюдением забитых голов.

ПолеОписание
expected_goals.home / away / totalожидаемые голы (λ)
probabilities.home_win / draw / away_winвероятности исхода, %
probabilities.over_1_5 / over_2_5 / over_3_5 / under_2_5 / bttsвероятности тоталов и ОЗ, %
fair_odds.*«справедливый» коэффициент = 1 / вероятность
likely_scorelinesтоп-5 наиболее вероятных счетов с их вероятностью

value_bets сравнивает fair_odds с реальными коэффициентами рынка и показывает исходы с положительным перевесом: edge_pct = на сколько % рынок «щедрее» модели.

Очные встречи — h2h

ПолеОписание
matchesчисло официальных личных встреч, включая технические результаты
performance_matchesвыборка для средних голов, тоталов и ОЗ; в футболе без awarded и walkover; при нуле эти метрики равны null
team_a_wins / draws / team_b_winsсчёт по встречам (team_a = команда home в карточке)
goals_avg / over_2_5 / bttsсредние голы, % тоталов >2.5, % ОЗ
recentпоследние встречи: дата и счёт
⚠️ Дисклеймер. Все значения — статистические оценки на основе исторических данных. Это не ставка, не прогноз и не инвестиционная рекомендация. Поле disclaimer присутствует в каждом ответе.
GET/v1/team/{id}/trends

Форма и тренды команды отдельно (общие / дома / в гостях / последние N), дисциплина и последние матчи.

GET/v1/tournament/{id}/trends

Профиль лиги: средние голы, доля П1/Х/П2, % тоталов и ОЗ — для выбранного сезона или окна последней активности (~400 дней).

Доли официальных исходов считаются по matches. Средние голы, тоталы и ОЗ — по performance_matches: в футболе технические awarded и walkover исключены. Если все результаты технические, performance_matches=0 и голевые метрики равны null; официальные исходы остаются. Та же политика действует в форме команды и очных встречах.

JSON
{ "tournament_id":31384, "name":"Serie A", "name_ru":"Серия А", "sport":"football",
  "matches":772, "performance_matches":772, "avg_goals":2.52, "avg_home_goals":1.32, "avg_away_goals":1.19,
  "home_win_pct":39.4, "draw_pct":28.1, "away_win_pct":32.5,
  "over_2_5_pct":47.3, "over_3_5_pct":24.5, "btts_pct":49.5 }
GET/v1/event/{id}/live-trends
GET/v1/live-trends

Живая вероятностная картина матча — отдельный виджет-фид: после каждого значимого эпизода (гол, удаление, пенальти) модель пересчитывает вероятности всех исходов — 1x2, двойной шанс, тоталы 0.5–6.5, форы, «обе забьют», следующий гол — и отдаёт вероятность + fair-коэффициент (1/p), рассчитанный независимо от букмекерских линий, движение вероятностей с прошлого пересчёта и краткий ИИ-комментарий (text_ru + highlights). Live-футбол топ-лиг (tier 1–2); пересчёт по инцидентам и каждые ~2,5 минуты. Списочный /v1/live-trends отдаёт все живые матчи с трендами одним ответом (limit ≤ 200) — удобно для ленты. Отдельный подключаемый модуль «Live-тренды».

JSON
{ "event_id": 10977319, "minute": 78, "phase": "2H", "score": { "home": 1, "away": 0 },
  "trigger": { "kind": "goal", "minute": 76, "side": "home" },
  "expected_remaining_goals": { "home": 0.21, "away": 0.35 },
  "outcomes": [
    { "market": "1x2", "selection": "home", "p": 0.82, "fair_odds": 1.22 },
    { "market": "double_chance", "selection": "1x", "p": 0.93, "fair_odds": 1.08 },
    { "market": "total", "line": 2.5, "selection": "over", "p": 0.18, "fair_odds": 5.56 },
    { "market": "handicap", "line": -1.5, "selection": "home", "p": 0.14, "fair_odds": 7.14 },
    { "market": "next_goal", "selection": "away", "p": 0.27, "fair_odds": 3.70 } ],
  "movement": [ { "market": "1x2", "selection": "home", "p_prev": 0.55, "p": 0.82, "delta": 0.27 } ],
  "prematch_1x2": { "home": 0.44, "draw": 0.27, "away": 0.29 },
  "ai": { "text_ru": "Гол на 76-й перевернул матч: хозяева ведут и контролируют темп…",
          "highlights": [ { "market": "total", "line": 2.5, "selection": "under",
                            "note": "низовой сценарий заметно укрепился" } ] },
  "model": "lt-poisson-1.0", "updated_at": "2026-07-03T14:20:07+00:00" }

Рынок рядом с моделью — market_live новое. При подключённом модуле «Коэффициенты БК» (odds) ответ /v1/event/{id}/live-trends несёт блок market_live: вероятности без маржи по каждому букмекеру (время цены, приостановка, пометки) и консенсус книг. Без модуля odds поля нет. В списочном /v1/live-trends блок компактный: только consensus и model_vs_market (без bookmakers).

model_vs_market — разность вероятностей «модель − рынок» по каждому исходу, где есть и модель, и консенсус книг: edge = model_p − market_p (как у InPlayFootballTips: разность вероятностей, а не EV — у длинных коэффициентов меньше вес). Считается только для футбола и только для рынков, где основа модели и рынка совпадает: 1x2 основного времени и тотал голов матча. У других видов и у устаревшего (stale) кадра модели — null. Большой edge (больше 0,04–0,05) чаще означает информацию, которой у модели нет (состав, травма, погода), чем ошибку рынка.

JSON
"model_vs_market": [
  { "market": "1x2", "line": 0.0, "selection": "home", "model_p": 0.69, "market_p": 0.6641,
    "edge": 0.0259, "books": 3 },
  { "market": "total", "line": 2.5, "selection": "over", "model_p": 0.41, "market_p": 0.4466,
    "edge": -0.0366, "books": 2 } ]

xT — зоны угрозы (expected threat) новое

GET/v1/event/{id}/xt
GET/v1/team/{id}/xt

Собственная модель угрозы на нашей истории ударов (455 721 удар / 53 122 гола с координатами): эмпирическая поверхность вероятности гола по зонам поля, грид 16×8 (x — глубина от линии ворот, y — поперёк), сглаженная к базовой конверсии. По матчу отдаёт xt_created по сторонам, разбивку по зонам, вклад игроков и finishing-дельту (голы − xT) — кто реализует выше/ниже качества своих моментов. По команде — профиль за N последних матчей (сколько угрозы создаёт/допускает, из каких зон). Модуль analytics.

Покрытие: 18 188 матчей в 291 турнире (везде, где собрана карта ударов — футбол, топ- и средние лиги). Методология честная: это shot-based зоны угрозы, не possession-xT по владениям (позиционных пасов с координатами источник не отдаёт).

JSON
{ "event_id": 11225833, "available": true, "model": "xt_v1",
  "home": { "xt": 2.29, "shots": 20, "goals": 1, "finishing": -1.29,
            "zones": { "box_center": 2.09, "mid_center": 0.20 } },
  "away": { "xt": 0.65, "shots": 7, "goals": 1, "finishing": 0.35, "zones": { … } },
  "players": [ { "player": "Ондржей Лингр", "is_home": true,
                 "xt": 0.40, "shots": 2, "goals": 0, "finishing": -0.40 }, … ] }
# finishing < 0 → команда/игрок недореализует качество моментов; > 0 → бьёт эффективнее ожидания.

Трек мяча beta

GET/v1/event/{id}/ball-track

Координаты мяча по ходу живого футбольного матча: x, y — положение в процентах длины и ширины поля. Два вида кадров: livedata (позиция/ситуация, примерно каждые 5 секунд) и action (отдельные действия с координатами, минутой и периодом; у матчей с полным покрытием). Только футбол; ряд хранится около 14 суток.

beta Контракт может измениться без мажорной версии. Опция скрытая и включается по аккаунту (ключ опции ball_track); без неё ручка отвечает 404, как несуществующий адрес, и не показывается в OpenAPI. Доступно по запросу — напишите info@sportwire.ru.

Покрытие: трек есть у матчей, по которым источник транслирует координаты. Его отсутствие — штатное состояние, причина всегда приходит в reason_code.

ПараметрОписание
limitсколько точек вернуть, по умолчанию 500, максимум 2000
sinceкурсор: ISO-8601 или epoch-секунды. С ним точки идут вперёд от курсора без пропусков (для поллинга берите next_since); без него — последние limit точек
kindlivedata · action · event · unknown

Точки всегда идут по возрастанию времени. Поля точки:

ПолеОписание
tsметка приёма кадра нашей стороной — единые часы ряда
ts_sourceметка источника, если он её дал, иначе null
ts_srcfalse — метка наша приёмная, true — источника
kindвид кадра
x, yкоординаты ≈ 0..100; возможен небольшой выход за границы (мяч за линией) — не обрезайте
minute, periodминута и период (у кадров action); иначе null
home_score, away_scoreнаш канонический счёт на минуту действия (у action); null, если определить нельзя

Стороны приведены к нашему канону: если у источника хозяева и гости перевёрнуты, поле развёрнуто на 180° и счёт обменян (track.mirrored: true). Блок track также содержит tracked, frames_collected, first_frame_at, last_frame_at, stale (кадры не идут более 6 минут), score_basis и coord_space. Верхний уровень ответа: available, beta, count, limit, window (latest | since), has_more, next_since, points.

Если трека нет, ответ — available: false с полями reason_code и reason:

reason_codeЗначение
wrong_sportне футбол — трека не существует
not_coveredматч не в трекинге: источник не транслирует координаты по нему либо трансляция не связана с нашим матчем
not_trackedисточник ответил, что координат по матчу у него нет
not_probedподписка на матч ещё не бралась
no_framesматч в трекинге, кадров с координатами пока не поступало
expiredтрек собирался, но ряд (около 14 суток) уже вычищен

Прогнозы матча (ML + рейтинговая модель) + value vs РФ-ЦУПИС новое

GET/v1/event/{id}/prediction

Прогноз отдаётся по видам с принятой и проверенной основой результата; его считают две разные модели. Ответ всегда говорит, какая именно, в поле model. Если основа вида не подтверждена, ответ содержит available:false с причиной вместо вероятностей.

  • Футбол — catboost-1x2-v1: собственная ML-модель (описана ниже), плюс тотал 2.5 и «обе забьют».
  • Другие принятые виды — rating-elo-v1: рейтинговая модель силы участников. Хоккей, баскетбол, теннис, настольный теннис, волейбол, пляжный волейбол, гандбол, футзал, регби (union и league), водное поло, флорбол, бадминтон, снукер, дартс, ММА, CS, Dota 2, League of Legends, Overwatch, Call of Duty. Отдаёт только исход матча: тоталов и «обе забьют» по ней нет, потому что у вида, где счёт измеряется сетами или картами, этих рынков не существует.

Крикет — по официальному исходу 26.09.2026. Исход крикетного матча — код результата источника: ничья многодневного матча — ничья, а не «победа по сумме ранов», при DLS побеждает названная источником сторона. Рейтинги пересчитаны на этой основе, модель заново проверена бэктестом по времени: log-loss 0,729 против 0,823 у базовой линии на 4 160 матчах проверки; на 582 матчах с двухисходной линией — 0,664 против 0,682 у базы (рынок на открытии — 0,598). Прогноз отдаётся, когда загружены параметры, замеренные на официальной основе; до этого /prediction отвечает прежним отказом. Value по крикету не заявляется (400 сигналов, доходность −21,1 %).

Если в используемом окне футбольных признаков есть технический awarded или walkover, CatBoost не запускается: проверяются и окна формы, и исходная выборка до 20 матчей для пуассоновских признаков; обученное окно не сокращается и технические голы не подаются модели. Ручка /prediction использует существующий переход к принятой рейтинговой модели Эло; если она недоступна, возвращает available:false с причиной. Пуассоновская модель в /insights остаётся отдельной и требует не меньше трёх матчей без технических результатов у каждой стороны. Если в истории формы для /insights есть технический результат, value_bets=null: прежняя проверка доходности такую изменённую выборку не подтверждает. Исключение технических голов из среднего по лиге исправляет качество входных данных, но не означает переобучение или новую калибровку CatBoost.

Основа исхода: итог матча и основное время 23.09.2026

У вероятности исхода всегда есть основа — что именно считается исходом, — и она названа в каждом блоке полем basis. Сравнивать прогноз с линией можно только той же основы: 1X2 букмекеров в хоккее — это основное время (ничьих ~20 %), а победитель матча с овертаймом и буллитами — совсем другое событие.

Для допущенной футбольной или хоккейной модели value_bets пересчитывается по текущим доматчевым котировкам при каждом запросе /insights, даже когда вероятности взяты из кэша. Снятые цены и live-линии не участвуют. computed_at относится к расчёту модели: изменение цены не запускает модель заново. Если текущую линию проверить не удалось, прогноз сохраняется, а value_bets становится null.

Рейтинговые модели других видов спорта используют общий путь value в /prediction и /insights. Поле value_bets содержит только допущенные сигналы; value_comparisons в /insights показывает соответствующие market и market_regulation из /prediction. Совместимый математический EV остаётся в selections.ev_pct даже при отказе проверки доходности, причина указана в value_note. Положительный EV не означает доказанную прибыльность. При неизвестной основе рынка сохраняются котировки без EV. Теннис, настольный теннис, волейбол, бадминтон и пляжный волейбол используют итог матча; игровые виды с дополнительным временем разделяют основное время и финального победителя. Для форматов с неподтверждённой семантикой требуется явно обозначенный рынок final_win. Если принятой модели или истории нет, value_model сообщает об этом; вероятности не выдумываются. Котировки перечитываются при запросе, а расчёт модели повторно не запускается.

Для подтверждённых рынков с возвратом при ничьей — BetBoom в киберспорте и «Лига Ставок» в дартсе и снукере — market_draw_refund показывает отдельное сравнение: settlement=draw_refund, refund_prob — вероятность возврата в процентах, EV = P(победа) × коэффициент + P(ничья) − 1. market_prob и conditional_model_prob относятся к исходам без ничьей. Этот блок также входит в value_comparisons. Допуск требует отдельной проверки доходности и расчёта возвратов, привязанной к текущей модели и этому рынку. Без действующего подтверждения value_allowed=false; допуск обычной победы не наследуется. При подтверждении и актуальной доматчевой цене исход с EV выше 2% может войти в value_bets как market=draw_no_bet.

Футбольный Poisson в /insights. 16.10.2026 При наличии не менее трёх пригодных матчей у каждой стороны модель использует подтверждённое основное время: basis:"regulation". Явный normaltime вместе с добавочными голами овертайма должен согласовываться с итогом; серии пенальти исключены. Та же основа используется для среднего по лиге и силы соперников. Если такой истории недостаточно, прежний прогноз сохраняется с basis:"final_score", а сравнение с котировками остаётся закрытым. Для value_bets нужны минимум четыре пригодных матча у каждой стороны без смешения дома/в гостях и без технических результатов в истории, будущий матч и доступная доматчевая линия БК РФ. Совместимость основ не гарантирует положительного перевеса. Обученные признаки CatBoost в /prediction не изменены. В хоккее value_bets доступны для полной трёхисходной линии 1X2 на основное время при тех же требованиях к истории. Все три исхода должны образовывать полную линию одного букмекера с согласованным временем подтверждения. Вероятности берутся из исходной матрицы, цены обновляются при каждом запросе. Тоталы и форы также допускаются для «Лиги Ставок» (ligastavok), только если источник явно подтверждает основное время (FULLTIME) для каждой текущей котировки. Нужна полная пара исходов из одного снимка источника не старше часа: больше/меньше на одной линии тотала или хозяева/гости с противоположными форами. Без такого подтверждения тоталы и форы не участвуют; наличие рынка у другой БК само по себе недостаточно. Победа с овертаймом/буллитами в этот допуск не входит. Расчётный EV не является доказательством прибыльности.

Футбол: совместимость основы. 08.10.2026 У текущих файлов CatBoost основа меток обучения не подтверждена: basis:"legacy_unverified". Это относится к 1X2, тоталу 2.5 и обе забьют. Пуассоновские признаки CatBoost используют сохранённый счёт с основой basis:"final_score"; такой счёт может включать дополнительное время. Эти прогнозы остаются доступны, но не рассчитывают ценность ставки против стандартной футбольной линии основного времени. В market поля market_basis и basis_compatible обозначают основу котировок и её совместимость с моделью. При отказе сохраняются котировки, названия книг и market_prob, отсутствуют сравнительные model_prob, fair_odds, edge_pct, ev_pct; value_allowed:false, все value:false, best_value:null, причина — value_note. Справедливые кэфы внутри самостоятельного блока модели остаются её оценкой и не означают совместимость с линией. Канонические рынки 1X2, двойной шанс, обе забьют, тотал и фора целого матча используют стандартный контракт основного времени; только явный final_win обозначает итогового победителя. Два доступных исхода сами по себе не доказывают включение дополнительного времени. Это правило не изменяет контракты других видов.

  • markets.match_result — итог (basis:"final"): кто победил в матче с учётом овертайма, буллитов, дополнительного времени и тай-брейков. Ничья здесь бывает только там, где её допускает формат, и это решают данные, а не список видов: вид, у которого ничьих в истории меньше 1 % (теннис, баскетбол, волейбол, дартс, киберспорт bo3, хоккей), и турнир, где ничьих меньше 1 % при истории от 30 матчей (КХЛ, НХЛ, кубковые стадии), получают prob_draw: 0, draw_possible: false и fair_odds без draw. Там, где ничьи — часть формата (лиги гандбола и футзала, bo2 в Dota 2, группы Championship League в снукере), вероятность ничьей учитывает долю ничьих этого турнира.
  • markets.regulation_time — основное время (basis:"regulation"): 1X2 без овертайма и буллитов, основа линии 1X2 букмекеров. Отдаётся хоккею, баскетболу, гандболу, футзалу, водному поло и регби-юнион. Та же сила участников, но перевод в вероятности откалиброван на счёте основного времени и проверен тем же бэктестом по времени; у блока своя quality — log-loss, база, ошибка калибровки и сравнение с рынком. Блок отдаётся, только если он побил базу и его проценты совпадают с частотами (ошибка калибровки ≤ 0,05); иначе — {"available": false, "reason": …} с числом (флорбол: калибровка 0,069 на 937 матчах — не отдаём).
  • У видов с овертаймом итог выводится из основного времени (победа в основное + ничья × доля побед в овертайме), поэтому два блока не противоречат друг другу: П1 итога никогда не меньше П1 основного времени. А в турнире, где ничья — часть формата (лига гандбола, футзала), овертайма нет, основное время и есть итог, и блоки совпадают.
  • Ничья основного времени 26.09.2026. Калибровка шла на неполной мишени: ничья основного времени, после которой матч решил гол в овертайме или буллит, а источник не записал разбивку (2:2 в основное, итог 2:3), выпадала из оценки — в ВХЛ с 01.12.2025 так пропадали 62 ничьи из 160. Теперь такие матчи считаются ничьей основного времени, а прогноз и замер считают долю ничьих турнира одним и тем же запросом по последним 2 000 матчам турнира. Вероятность ничьей основного времени берётся либо по виду, либо по доле ничьих этого турнира. Какой из двух способов, решает проверка на части истории до отрезка отчёта. У баскетбола прошёл способ по турниру: средняя ошибка доли ничьих по лигам 0,020 → 0,011. У хоккея способ по турниру проверку не прошёл (выигрыш log-loss 0,0009 при пороге 0,001), поэтому ничья считается по виду. На отчёте (21 882 матча) log-loss 0,9892 против 1,0475 у базы. Ошибка калибровки: П1 0,019, Х 0,0126; средняя ошибка доли ничьих по лигам 0,022 (НХЛ: факт 24,4 %, прогноз 22,3 %). Лиги, где ничьих заметно меньше среднего, так пока завышены: HockeyEttan — факт 14,4 %, прогноз 19,8 %.
  • Ничья после овертайма — по турниру. В NCAA Men и NCAA Women 7,4 % и 6,0 % матчей остаются ничьей и после ОТ, в первых матчах плей-офф Champions Hockey League ОТ нет вовсе. Для такого турнира (от 30 матчей, ≥ 1 % ничьих после ОТ по разбивке счёта источника) ничья итога = ничья основного времени × доля ничьих, которые ОТ в этом турнире не решает; draw_possible:true. Проверка на 1 509 матчах этих турниров: факт ничьих 6,2 %, прогноз 7,0 %, log-loss 0,793 против 0,875 у базы. До загрузки этой калибровки NCAA отвечает прежним отказом.
  • Калибровка по уровню турнира. Баскетбол на верхних лигах (tier 1) был переуверен: ошибка калибровки П1 на проверке 0,067. Для уровня, где проверка на части истории до отчёта показала выигрыш, разрыв сил сжимается (d′ = s·d + t); итог и основное время считаются из одного разрыва и остаются согласованы. На отчёте: tier 1 — 0,066 → 0,021, tier 2 — 0,049 → 0,040, на матчах с линией букмекеров 0,055 → 0,037 (у рынка 0,013; модель по-прежнему слабее рынка — value не заявляется).

Сравнение с рынком — по основе. market сравнивает match_result с линией той же основы: line_type:"12" (победитель матча, «Итоговая победа») там, где ничьей нет, и "1x2" там, где она часть формата. market_regulation сравнивает regulation_time с 1X2 основного времени. Строки «1X2 первого тайма / четверти» в сравнение не попадают. До 23.09.2026 итог хоккейной модели (ничья 2 %) сравнивался с 1X2 основного времени (ничья 27 %), и матч КХЛ показывал «value +78 %» на гостей, которого нет.

Value — только проверенный. Флаг value — это обещание прибыли, поэтому он ставится только там, где на проверке по времени сигналы value модели против линии той же основы дали прибыль, превышающую случайный разброс (доходность выше нуля больше чем на две стандартные ошибки, от 200 сигналов). Поле value_allowed говорит, так ли это, а value_note — почему нет. Сравнение идёт с ценой открытия линии источника: «последняя» цена сыгранного матча у источника часто уже знает исход (футбол 10–20.09, 3 691 матч: сыгравший исход дешевле 1,05 у 424 матчей, средняя вероятность сыгравшего исхода 0,514 по «последней» цене против 0,453 по цене открытия; замер 26.09.2026 15:41 UTC), и по ней рынок «знал» результат. Замер 26.09.2026: хоккей — 3 517 сигналов, доходность −5,5 %; теннис — 20 267, −14,8 %; баскетбол — 11 393, −1,5 %. Поэтому у rating-elo-v1 сейчас value_allowed:false: EV в selections остаётся арифметикой модели, но рекомендацией не является.

JSON
{ "event_id": 11191055, "available": true, "model": "rating-elo-v1", "sport": "ice_hockey",
  "markets": {
    "match_result": { "basis": "final", "prob_home": 35.0, "prob_draw": 0.0, "prob_away": 65.0,
                      "draw_possible": false, "predicted": "A",
                      "fair_odds": { "home": 2.86, "away": 1.54 } },          // без «draw»
    "regulation_time": { "basis": "regulation", "prob_home": 26.4, "prob_draw": 19.0,
                         "prob_away": 54.7, "draw_possible": true, "predicted": "A",
                         "fair_odds": { "home": 3.79, "draw": 5.27, "away": 1.83 },
                         "quality": { "test_log_loss": 0.9892, "baseline_log_loss": 1.0475,
                                      "calibration_error_home": 0.019, "calibration_error_draw": 0.0126,
                                      "test_matches": 21882, … } } },
  "market":            { "basis": "final", "line_type": "12", "value_allowed": false,   // П2 65 % против рынка 57 %
                         "value_note": "value не заявляется: … доходность −5,5 % на 3517 ставках …",
                         "selections": [ … ] },
  "market_regulation": { "basis": "regulation", "line_type": "1x2", "value_allowed": false,
                         "selections": [ … ] } }

Почему не по всем видам. У каждого вида модель проверена бэктестом по времени: параметры подобраны на более ранней части истории, качество измерено на более поздней, которой подбор не видел. Вид попадает в отдачу, только если он побил базовую линию — постоянные доли исходов этого же вида, посчитанные по самой проверке, — и на матчах с линией букмекеров (если их в проверке не меньше 300): это те матчи, на которые ставят. Сейчас не проходят: ам. футбол и бейсбол — в целом базу бьют, но на матчах с линией нет (ам. футбол: 462 матча, log-loss 0,6745 против 0,6613 у базы; бейсбол: 5 651 матч, 0,6924 против 0,6918); EA FC — 13 % его матчей проверки лежат в турнирах, где половина завершённых матчей записана 0:0 без победителя: исход там не собирается, и мерить модель на заглушках нельзя. Ответ честно говорит причину в reason и перечисляет виды, где прогноз есть. Модель, не превосходящая «всегда одно и то же», — это не прогноз.

Отменённые матчи в проверке. «Ничья» там, где формат её не допускает (теннисный матч 0:0 без победителя — отмена, 1:1 по сетам без победителя — снятие), — не исход, а возврат ставки; такие матчи из оценки итога исключаются и считаются отдельно. До 23.09.2026 они оценивались как исход, которому модель дала ноль, и именно это роняло ниже базы бадминтон, крикет, настольный теннис, пляжный волейбол, CS, Call of Duty и ММА. Это сняло первоначальный шум оценки, но не доказало корректность крикетной основы: её прогноз сейчас приостановлен до отдельной приёмки.

Качество лежит рядом с прогнозом. В ответе рейтинговой модели есть блок quality: test_log_loss, baseline_log_loss, advantage (насколько лучше базовой линии), test_matches и market_comparison — log-loss модели и рынка на тех же матчах проверки, ошибка калибровки и доходность сигналов value — числа по этому конкретному виду, а не общие слова. Например, гандбол: 0,691 против базы 0,840. Плюс блок ratings — рейтинги обеих сторон и сколько матчей за каждым стоит.

Когда прогноза нет у поддержанного вида. Если у одной из сторон меньше трёх сыгранных матчей, мы отвечаем отказом, а не «50 на 50»: стартовый рейтинг дебютанта означает незнание, а не «силы равны». Отказ получает и матч турнира без собранных исходов (половина его завершённых матчей — 0:0 без победителя).

Футбольная модель

Собственная ML-модель прогноза (CatBoost, градиентный бустинг) поверх пуассоновской: вероятности 1X2, тотал 2.5 и обе забьют с «справедливыми» коэффициентами (1/p), процентом уверенности и — где есть рыночная линия — value-анализом против БК РФ-ЦУПИС. Модель обучена на нашей истории (десятки тысяч матчей, футбол) и учитывает остаток поверх Пуассона: форму команд (PPG/забито/пропущено last-10 с home/away-сплитом), серию, отдых между матчами, H2H, рейтинги атаки/обороны и текущую букмекерскую линию. Модуль analytics.

Честность (бэктест). Все фичи считаются строго по данным ДО матча (as-of, без утечки будущего), оценка — на свежих матчах, которых модель не видела при обучении. На отложенной выборке обучения (n≈11 000) CatBoost по 1X2 давал log-loss 0.960 против 0.972 у букмекерского опенинга и 1.006 у чистого Пуассона. Проверка вне выборки 26.09.2026 — 9 627 матчей, сыгранных уже после обучения модели: на 6 574 матчах с ценой открытия log-loss модели 0,9775 против 0,9762 у рынка — наравне с рынком, но не точнее. Сигналы value на тех же матчах (1 379 ставок, средний EV +13,8 %) дали доходность −11,8 %, поэтому у catboost-1x2-v1 value_allowed:false, value_note с этими числами и блок quality.market_comparison. Тотал 2.5 — точность ≈65%, «обе забьют» — ≈59%. Используйте прогноз как оценку справедливой цены, а не как сигнал ставки.

Прогноз довстречный. Все фичи модели — строго до стартового свистка, счёта идущего матча она не видит. Поэтому в ответе есть scope:"prematch", as_of и actionable: до начала матча actionable:true, после — false, и тогда мы не выставляем ни одного флага value и не отдаём best_value. Сравнение всегда идёт с доматчевой линией (line:"prematch") — ставить довстречную модель против live-котировки, которая уже знает счёт, значит получать фантомную «ценность».

JSON
{ "event_id": 12345678, "available": true, "model": "catboost-1x2-v1", "status": "notstarted",
  "scope": "prematch", "as_of": "2026-08-03T18:30:00+00:00", "actionable": true, "note": null,
  "confidence": 54.3,                       // % уверенности = max вероятность фаворит-исхода
  "markets": {
    "match_result": { "basis": "legacy_unverified",   // текущие файлы модели: основа меток не подтверждена
                      "prob_home": 54.3, "prob_draw": 24.1, "prob_away": 21.6, "predicted": "H",
                      "fair_odds": { "home": 1.84, "draw": 4.15, "away": 4.63 } },
    "over_under_2_5": { "basis": "legacy_unverified", "prob_over": 58.2, "prob_under": 41.8, "fair_over": 1.72 },
    "btts":           { "basis": "legacy_unverified", "prob_yes": 55.0,  "prob_no": 45.0,   "fair_yes": 1.82 } },
  "poisson_1x2": { "basis": "final_score", "prob_home": 51.0, "prob_draw": 25.3, "prob_away": 23.7 },   // база для сравнения
  "market": {                               // сравнение с доматчевой линией легальных БК РФ
    "source": "линия легальных БК РФ (приём через ЦУПИС)", "line": "prematch",
    "bookmakers": ["betcity","fonbet","leon","marathon","olimp","pari","winline","zenit"],
    "n_books": 8, "actionable": true,
    "basis": "legacy_unverified", "market_basis": "regulation",
    "basis_compatible": false, "basis_policy": "football-market-basis-v1",
    "value_allowed": false,
    "value_note": "Основа результата у текущей модели не подтверждена. …",
    "selections": [
      { "selection": "home", "market_prob": 51.2, "best_odds": 1.95,
        "bookmaker": "pari", "value": false },       // котировка без несовместимого EV
      { "selection": "draw", "market_prob": 25.0, "best_odds": 3.90,
        "bookmaker": "olimp", "value": false }, … ],
    "best_value": null } }

Как читать value при совместимой основе. Только при подтверждённой совместимости доступны сравнительные поля: model_prob — вероятность исхода по нашей модели; market_prob — маржа-очищенная вероятность рынка (медиана по книгам); best_odds — лучшая цена исхода среди книг (что реально доступно игроку), а bookmaker — чья именно это цена (fonbet / winline / olimp / pari / betcity / zenit / leon / marathon — легальные БК РФ, приём через ЦУПИС; полный список задействованных книг — в market.bookmakers); edge_pct = наша вероятность − рыночная; ev_pct = мат.ожидание ставки по лучшей цене (p·odds − 1). value:true — когда EV выше порога шума (+2%). best_value — исход с максимальным EV (если есть). Value считается только до начала матча и только против доматчевой линии: при actionable:false все value = false, best_value = null, а цифры остаются справочными.

Live: пересчёт уверенности. Для идущего матча прогноз доступен и внутри live-виджета (/v1/event/{id}/live-widget) полем model_prediction, а live_confidence — это уверенность, пересчитанная по in-play-вероятностям 1X2, которые наш live-движок обновляет после каждого значимого эпизода (гол, удаление, пенальти) с учётом счёта и оставшегося времени. Так «процент уверенности движется вместе с матчем». Сам model_prediction при этом остаётся довстречным и несёт scope / as_of / actionable:false / note: счёт учитывает только live_confidence, а model_prediction.market — справочное сравнение с доматчевой линией, без вывода о ценности ставки.

Если модели недостаточно данных по матчу — available:false с причиной; клиент должен это обрабатывать (модель никогда не «выдумывает» прогноз на пустой истории).

Погода на стадионе новое

GET/v1/event/{id}/weather

Погода в момент матча по координатам стадиона: температура и «ощущается», осадки и их вероятность, ветер и порывы, влажность, облачность, текстовое описание. Также доступна в блоке weather карточки матча /v1/event/{id}. Собирается для предстоящих матчей (прогноз) и доступна по сыгранным (архив), где известна площадка.

JSON
{ "event_id":1651128, "available":true,
  "weather": {
    "stadium":"Stadio Olimpico Grande Torino", "city":"Turin",
    "temperature_c":28.8, "feels_like_c":29.0, "conditions":"Ясно",
    "wind_speed_ms":1.5, "wind_gusts_ms":5.8, "precipitation_mm":0.0,
    "precipitation_prob_pct":null, "humidity_pct":39, "cloud_cover_pct":6,
    "weather_code":0, "is_forecast":false, "observed_hour_utc":"2026-05-24T19:00" } }

Виджеты новое

Готовые встраиваемые виджеты — серверный рендер в <iframe>, ключ остаётся на нашей стороне (ничего не светится в браузере, CORS не нужен). Имена команд и турниров — на русском.

Турнирная таблица

JSON
<iframe src="https://sportwire.ru/widget/standings?tournament=17"
        width="100%" height="520" frameborder="0"></iframe>

Профиль / тренды лиги

JSON
<iframe src="https://sportwire.ru/widget/trends?tournament=17"
        width="100%" height="190" frameborder="0"></iframe>
ПараметрОписание
tournamentid турнира (обязательный)
themelight (по умолчанию) · dark
title0 — скрыть заголовок (для своей вёрстки)
type (standings)total · home · away · form
limit (standings)сколько строк таблицы показать (0 = все)

Демо и конструктор кода — на странице Продукты → Виджеты.

Способы доставки данных

beta Доступно по запросу — напишите info@sportwire.ru. Тарифы: Pro и выше.

SportWire отдаёт данные двумя способами — выбирайте под свою задачу:

СпособКак работаетКогда выбирать
REST (pull) /v1Вы сами опрашиваете эндпоинты (/v1/events/live, /v1/event/{id} …)Отчёты, витрины, периодическая синхронизация, ручные запросы
Вебхуки (push)Мы сами шлём вам HTTP POST в момент изменения — без опросаОперативные сценарии: live-табло, оповещения, ставки, боты («оперативно, без сложностей»)
SSE-стрим /v1/streamДержите одно HTTP-соединение — мы шлём события по мере поступления (Server-Sent Events), с докачкой по Last-Event-IdLive-табло и дашборды, длинные соединения без своего вебхук-эндпоинта
WebSocket /v1/stream/wsТот же поток по WebSocket с докачкой по курсоруИнтерактивные и мобильные клиенты

Все каналы едины: один формат конверта и один event-bus. Доставка построена на транзакционном outbox: каждое каноническое изменение матча записывается в шину событий в той же транзакции, что и сами данные — поэтому событие не теряется даже при сбое. Отдельный воркер разбирает шину и доставляет её подписчикам с подписью и повторами; те же события можно читать стримом (SSE/WS) или добрать через API повторной выдачи (replay).

Сверка полноты (reconciliation). Для REST-режима и для «двойного прогона» при миграции есть две сверочные точки: GET /v1/tournament/{id}/events — полный календарь турнира одним запросом (cron-diff вашей БД против нашей, без обхода ленты по дням), и GET /v1/sync — дельта-синхронизация изменённых матчей по курсору (since, since_id): передавайте обратно next_since и next_since_id последней страницы, пока done не станет true. В режиме meta.cursor = "commit_order" метка курсора выдаётся после фиксации изменения, поэтому поздно зафиксированное изменение не оказывается позади уже выданного курсора; кроме events страница несёт removed — [{"id", "reason": "merged" | "hidden" | "deleted" | "out_of_scope", "replaced_by"}]: карточку id больше не отдаём (при merged её заменила replaced_by, её карточка придёт в ленте; out_of_scope — вид матча вышел из вашего тарифа). count — число строк курсора на странице (карточки + removed), раздельно — events_count и removed_count; страница может состоять из одних removed — курсор всё равно продвигайте. Событие в пачке несёт деталь (события, статистику, составы, статистику игроков) и дополнительные блоки вашего тарифа — h2h, form, streaks, votes, best_players, best_players_by_team, player_of_match, win_probability, highlights, tv_channels, fight, points_history, point_by_point, games/rounds/map_bans (киберспорт), race (протокол заезда), hockey_units (хоккейные звенья, явно названные в составе источника). Тяжёлая координатная графика (shotmap, momentum, average_positions, player_heatmaps, player_actions) и построчный commentary — только в карточке /v1/event/{id}: на пачку в 500 матчей это десятки мегабайт. Подробнее — в разделе «Миграция».

Вебхуки (push-доставка) beta

Зарегистрируйте URL — и мы будем присылать на него подписанный JSON при каждом изменении подписанных вами матчей: смена статуса, изменение счёта, гол/карточка, подтверждение состава. Управление — в личном кабинете или через API кабинета (нужна сессия кабинета).

1. Регистрация вебхука

POST/account/api/webhooks
JSON
{ "url": "https://your-app.example.com/hooks/sportwire",
  "event_types": ["match.goal","match.score_changed"],   // [] = все типы
  "sports":  ["football","basketball"],                    // [] = все виды
  "leagues": [34, 35072] }                                 // id турниров, [] = все

// Ответ (secret показывается ОДИН раз — сохраните его):
{ "ok": true, "id": 12, "secret": "whsec_…", "event_types": ["match.goal","match.score_changed"],
  "sports": ["football","basketball"], "leagues": [34,35072] }

Ещё эндпоинты кабинета: GET /account/api/webhooks — список (со статусом и здоровьем доставки) · DELETE /account/api/webhooks/{id} — удалить · POST /account/api/webhooks/{id}/test — тестовый webhook.ping · POST …/{id}/rotate-secret — сменить secret · POST …/{id}/pause и …/{id}/resume — пауза/возобновление · GET …/{id}/deliveries — лог доставки · PUT …/{id}/event-types с телом {"event_types": [...]} — сменить типы событий существующей подписки без смены secret, адреса и накопленной очереди (новый набор действует на следующие события; каждое изменение видно в GET …/{id}/changes) · PUT …/{id}/blocks — сменить содержимое события · GET /account/api/webhooks/catalog — каталог событий и блоков содержимого (с признаком «есть в тарифе»).

Безопасность URL. Адрес вебхука обязан быть публичным https://…. Частные, loopback, link-local и метадата-адреса (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16, ::1, fc00::/7, 169.254.169.254 и т.п.) отклоняются при регистрации и повторно проверяются перед каждой отправкой; редиректы не выполняются. Если ваш endpoint N раз подряд уходит в dead-letter, подписка автоматически ставится на паузу («предохранитель»). Дальше мы сами пробуем восстановиться: подписанный пробный webhook.ping через 15 мин, затем реже (до 1 раза в 6 ч). Как только endpoint ответил 2xx — подписка снова активна, а пропущенные за время разрыва события доотправляются автоматически (по порядку sequence). Ручной resume делает то же самое сразу. Обычный деплой consumer-сервиса на 10–20 минут, таким образом, не требует никаких действий с вашей стороны. Изредка (только в «тихие» периоды, когда реальных доставок нет) может прийти проверочный webhook.ping — просто ответьте 2xx.

2. Каталог событий

ТипКогда шлётсяdelta содержит
match.status_changedСмена статуса (запланирован → идёт → перерыв → завершён)from, to
match.score_changedИзменение счёта (какая сторона забила). Текущий счёт обеих сторон — в snapshot (как читать)scored: [{side,from,to}]
match.goalГол (в т.ч. пенальти/автогол), in-playkind, side, minute, player, assist
match.cardКарточка (жёлтая/красная), in-playkind, side, minute, player
match.lineup_confirmedСтартовый состав подтверждёнconfirmed: true
match.detail_changed opt-inДеталь матча реально изменилась (статистика/составы/лента) — сигнал «пора перечитать /v1/event/{id}». Дебаунс ~90 с на матчlast_detail_at
odds.changed opt-inОбновились БК-котировки матча (модуль odds): цена изменилась, линия появилась или удалена. Первая смена даёт сигнал сразу, смены в следующие ~60 с сводятся в один «хвостовой» сигнал по закрытии окна (trailing: true, bookmakers — все конторы окна); отметка о несигнализированной смене хранится в базе и переживает перезапуск сборщика. Пропажа линии на один проход (missing_since) и её возврат сигнала не дают — цена не меняласьbookmaker, changed; у хвостового ещё bookmakers, trailing

Opt-in типы. Высокочастотные match.detail_changed и odds.changed доставляются только если явно перечислены в event_types подписки — пустой список («все события») их не включает. Это защита от неожиданного потока на существующие подписки. Добавить их к уже работающей подписке можно через PUT /account/api/webhooks/{id}/event-types — secret при этом не меняется. odds.changed — самый частый тип (порядка 175 тыс. сигналов в сутки по всем матчам): перед включением убедитесь, что приёмник сводит сигналы по матчу, а не перечитывает карточку на каждый. Паттерн использования: по match.detail_changed перечитайте /v1/event/{id} один раз — вместо пулла детали по каждому голу (деталь может не успеть измениться) и вместо слепых периодических пуллов (статистика меняется и без голов).

3. Формат доставки (payload) — пакетная

Все события вашей подписки, готовые к отправке за один цикл, приходят одним POST — в массиве events (по возрастанию, в порядке возникновения). Это держит доставку масштабируемой: всплеск в 1000+ изменений котировок = один запрос, а не 1000. Каждый элемент events[] — самостоятельный конверт (тот же формат, что и раньше); идемпотентность и порядок — по-прежнему на уровне отдельного события (id, sequence). Ответьте 2xx на пачку целиком.

POST · https://your-app.example.com/hooks/sportwire
POST https://your-app.example.com/hooks/sportwire
Content-Type: application/json
User-Agent: SportWire-Webhooks/1.0
X-SportWire-Timestamp: 1793456789
X-SportWire-Signature: sha256=<hex>
X-SportWire-Batch-Size: 3
X-SportWire-Event-Id: evt_1662352_12        // id первого события пачки (у каждого события свой id внутри)
Idempotency-Key: evt_1662352_12_3           // ключ пачки; дедуп делайте по id КАЖДОГО события

{ "count": 3,                                // сколько событий в пачке
  "events": [                                // ← события подписки за цикл, по возрастанию
    { "id": "evt_1662352_12",                // стабильный ключ идемпотентности (у каждого события свой)
      "type": "match.goal",
      "created_at": "2026-07-10T18:03:18Z",
      "sequence": 12,                        // порядковый номер В РАМКАХ матча
      "data": {
        "match": { "id": 1662352, "sport": "football", "status": "live",
                   "tournament": { "id": 34, "name": "Premier League" },
                   "home": { "name": "Arsenal" }, "away": { "name": "Chelsea" },
                   "last_detail_at": "2026-07-10T18:03:11Z",  // watermark реального изменения детали
                   "has_detail": true },                       // есть ли лента событий вообще
        "delta":    { "kind": "goal", "side": "home", "minute": 23, "player": "…", "assist": "…" },
        "snapshot": { "home_score": 1, "away_score": 0 }   // состояние на момент события
      } },
    { "id": "evt_1662352_13", "type": "match.score_changed", "sequence": 13, "data": { "…": "…" } },
    { "id": "evt_990877_4",   "type": "odds.changed",        "sequence": 4,  "data": { "…": "…" } }
  ] }

Обработка. Проверьте подпись (над сырым телом — раздел 4), затем пройдите events по порядку и примените каждый элемент как отдельное событие. Проверочный webhook.ping приходит одиночным объектом (без обёртки events) — просто ответьте 2xx. Устойчивый приёмник принимает оба вида: есть events[] — это пачка, иначе — одиночный конверт.

Нужно ли пуллить деталь? Сравните data.match.last_detail_at с тем, что вы уже применяли: не изменился — деталь перечитывать не нужно. has_detail=false — ленты у матча (пока) нет вовсе. Здесь data.match.has_detail относится именно к ленте инцидентов в конверте вебхука; расширенный признак наличия групп в REST-списках описан отдельно.

delta — что изменилось, snapshot — состояние сразу после изменения (зафиксировано в момент события, а не при доставке). Маркеры источника данных в payload не передаются.

Счёт берите из snapshot. У match.score_changed snapshot.home_score и snapshot.away_score — текущий счёт обеих сторон, это и есть авторитетное значение. delta.scored перечисляет только стороны, чей счёт изменился в этом событии: {"side","from","to"}, где from: null — до этого счёта у стороны не было. Сторона без изменения в scored не входит. Появление 0:0 в начале матча обычно событием score_changed не публикуется (у матча под резервом оно может прийти с обеими сторонами {"from": null, "to": 0}); если же в том же событии меняется и другая сторона, ноль войдёт в scored как {"from": null, "to": 0} (с 04.10.2026). Поэтому счёт, собранный только из дельт, может не знать начального нуля второй команды — snapshot знает всегда. Перемены, не являющиеся голом (исправление счёта источником), тоже приходят как score_changed: to может быть меньше from.

Содержимое события

Кроме типов событий вы выбираете, что лежит в теле события. Всегда приходят id, type, sequence, data.match.id и data.delta — что изменилось. Остальное — блоки:

БлокКуда кладётсяНужно в тарифе
match по умолчаниюdata.match целиком: вид спорта, статус, турнир, команды, last_detail_at—
snapshot по умолчаниюdata.snapshot — статус и счёт на момент события—
carddata.card — карточка как GET /v1/event/{id}, с отсечкой по вашему тарифу—
lineupsdata.lineups — составы, как в карточке«Детали матча» или «Предматчевые составы»
statisticsdata.statistics — статистика матча«Детали матча»
oddsdata.odds — {odds, odds_best, odds_bookmakers}«Коэффициенты БК»
live_probabilitiesdata.live_probabilities — как GET /v1/event/{id}/live-trends«Live-тренды»

По умолчанию — match и snapshot, ровно то тело, что приходило всегда: существующим подпискам ничего менять не нужно. Минимальное содержимое — пустой список блоков: только id матча и delta. Выбор задаётся при создании ("blocks": [...]) и меняется на месте: PUT /account/api/webhooks/{id}/blocks с телом {"blocks": ["match","odds"]} (null — вернуть по умолчанию); secret, адрес и очередь сохраняются. В кабинете: «Ещё → Содержимое события».

Блок, которого нет в тарифе, выбрать нельзя (ответ 422 с названием модуля). Если тариф сменился после выбора, блок перестаёт приходить, а в событии появляется data.omitted_blocks: [{"block": "odds", "reason": "not_in_plan"}]; "reason": "unavailable" — собрать блок не удалось (матч скрыт или данных нет). При выбранных блоках в событии есть data.blocks — что фактически положено. Всё, что вы не взяли в событие, можно запросить REST в пределах вашего лимита. Тяжёлые блоки (card, lineups, statistics, odds, live_probabilities) увеличивают тело, поэтому такие подписки получают пачки до 10 событий. Те же блоки задаёт параметр blocks у SSE, WebSocket и replay; с тяжёлыми блоками страница replay и порция стрима — до 50 событий (продолжайте по next_cursor).

4. Проверка подписи (обязательно)

Подпись — HMAC-SHA256 над строкой «{timestamp}.{тело}» вашим secret. Проверяйте её на каждом запросе и сверяйте, что X-SportWire-Timestamp не старше ~5 минут (защита от повторной отправки).

Ротация без простоя. Заголовок может содержать несколько подписей через запятую: X-SportWire-Signature: sha256=<новая>,sha256=<старая> — так происходит в течение ~24 ч после rotate-secret. Проверяйте любой из токенов (как в примерах ниже) — тогда смена секрета не рвёт доставку: обновите secret у себя в удобный момент внутри окна.

JSON
# Python
import hmac, hashlib
def verify(secret: str, headers, raw_body: bytes) -> bool:
    ts  = headers["X-SportWire-Timestamp"]
    expect = hmac.new(secret.encode(), ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    for tok in headers["X-SportWire-Signature"].split(","):     # 1..2 подписи (окно ротации)
        if hmac.compare_digest(tok.strip().removeprefix("sha256="), expect):
            return True
    return False
JSON
// Node.js
const crypto = require("crypto");
function verify(secret, headers, rawBody /* Buffer */) {
  const ts  = headers["x-sportwire-timestamp"];
  const expect = crypto.createHmac("sha256", secret)
                       .update(ts + "." + rawBody.toString()).digest("hex");
  return (headers["x-sportwire-signature"] || "").split(",")   // 1..2 подписи (окно ротации)
    .some(tok => {
      const sig = tok.trim().replace("sha256=", "");
      return sig.length === expect.length &&
             crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expect));
    });
}
JSON
# Shell / curl-приёмник — та же проверка через openssl (RAW_BODY — сырое тело запроса как есть)
TS="$HTTP_X_SPORTWIRE_TIMESTAMP"          # заголовок X-SportWire-Timestamp
SIG="$HTTP_X_SPORTWIRE_SIGNATURE"         # заголовок X-SportWire-Signature (1..2 подписи через запятую)
EXPECT=$(printf '%s.%s' "$TS" "$RAW_BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
case ",$SIG," in *",sha256=$EXPECT,"*) echo ok ;; *) echo REJECT ;; esac

5. Порядок, идемпотентность, повторы

ГарантияКак обрабатывать
ПорядокВнутри пачки events уже отсортированы по возрастанию. sequence монотонно растёт в рамках матча (data.match.id) — применяйте по возрастанию; меньший номер, пришедший позже (повтор/докачка между пачками), игнорируйте.
ИдемпотентностьОдно и то же событие (id) может прийти повторно (at-least-once) — в т.ч. если пачка была повторена целиком. Дедуплицируйте по id каждого события (а не по Idempotency-Key пачки).
ПовторыОтвечайте 2xx (на всю пачку) в течение ~8 сек. Иначе — повтор всей пачки с нарастающей паузой (≈1с → 5с → 30с → 2м → 5м); уже применённые события отсеются по id. После исчерпания попыток — dead-letter.
ОтветЛюбой 2xx = пачка принята. Тело ответа не важно. Отвечайте быстро, обработку делайте асинхронно.

Все доставки (и повторы, и dead-letter) видны в логе доставки — в кабинете и через API кабинета.

Стриминг — SSE beta

Одно длинное HTTP-соединение вместо своего вебхук-эндпоинта: те же события того же event-bus приходят потоком (Server-Sent Events) по мере поступления. Тариф Pro и выше. Формат конверта — тот же, что у вебхуков (см. «Формат доставки»).

GET/v1/stream

Авторизация — вашим API-ключом: заголовок x-api-key или, для браузерного EventSource, параметр ?key=. Фильтры (необязательно, через запятую): sport=football,basketball · league=34,35072 (id турниров) · event_type=match.goal,match.score_changed · blocks=match,snapshot,odds (содержимое события). Отдаются только виды спорта из вашего тарифа.

curl · api.sportwire.ru
# поток (флаг -N = без буферизации curl)
curl -N -H "x-api-key: YOUR_KEY" \
  "https://api.sportwire.ru/v1/stream?sport=football&event_type=match.goal,match.score_changed"

# то, что приходит:
: connected cursor=845213
retry: 1000

id: 845214
event: match.goal
data: {"id":"evt_1662352_12","type":"match.goal","created_at":"2026-07-10T18:03:18Z","sequence":12,
       "data":{"match":{"id":1662352,"sport":"football","status":"live",
               "tournament":{"id":34,"name":"Premier League"},
               "home":{"name":"Arsenal"},"away":{"name":"Chelsea"}},
               "delta":{"kind":"goal","side":"home","minute":23},"snapshot":{"home_score":1,"away_score":0}}}

: keep-alive 1793456800 cursor=845214

Докачка (resume). В строке id: каждого события — курсор (глобальный, монотонный). При обрыве переподключитесь с заголовком Last-Event-Id: <курсор> (или ?last_event_id=) — мы доотдадим всё, что новее (в пределах окна свежести; для большого догона — Replay API ниже). Без курсора поток начинается «с текущего момента». Строки-комментарии : keep-alive раз в ~15 с держат соединение живым.

JSON
// Браузер — EventSource сам присылает Last-Event-Id при переподключении → докачка автоматическая
const es = new EventSource("https://api.sportwire.ru/v1/stream?key=YOUR_KEY&sport=football");
es.onmessage = (e) => { const evt = JSON.parse(e.data); /* e.lastEventId = курсор */ };
es.addEventListener("match.goal", (e) => { /* именованный тип */ });

Стриминг — WebSocket beta

Тот же поток по WebSocket. Тариф Pro и выше.

WS/v1/stream/ws

Авторизация — ?key=YOUR_KEY (или заголовок x-api-key для серверных клиентов). Те же фильтры sport/league/event_type. Докачка — ?cursor=<курсор>. Каждое событие — JSON-кадр {"cursor": <id>, …тот же конверт…}; служебные кадры {"type":"connected"|"heartbeat","cursor":…}.

JSON
const ws = new WebSocket("wss://api.sportwire.ru/v1/stream/ws?key=YOUR_KEY&sport=football&cursor=845214");
ws.onmessage = (e) => {
  const m = JSON.parse(e.data);
  if (m.type === "heartbeat" || m.type === "connected") return;
  // применяйте m (m.cursor — последний курсор для докачки)
};

API повторной выдачи (replay) beta

Догон/реконсиляция после простоя: страницами вернуть события из шины начиная с курсора. Ограничено по объёму и глубине (окно свежести). Тариф Pro и выше.

GET/v1/stream/replay

Авторизация API-ключом (x-api-key/?key=). Параметры: since — курсор (число) или ISO-время (2026-07-10T18:00:00Z); limit (с потолком); те же sport/league/event_type и blocks — содержимое события через запятую (не задан — как раньше, пусто — минимальное). Есть и вариант из кабинета по сессии: GET /account/api/webhooks/events.

GET · /v1/stream/replay?since=845000&limit=200&sport=football
GET /v1/stream/replay?since=845000&limit=200&sport=football
{ "events": [ { …тот же конверт… }, … ],
  "count": 200, "since": 845000, "next_cursor": 845200, "has_more": true }

# Догон без потерь: страницами по next_cursor, пока has_more=false,
# затем откройте SSE/WS с этого же курсора (Last-Event-Id / ?cursor=) — стык без дыр.

MCP-сервер — для Claude, ChatGPT, Cursor новое

Наш API втыкается в LLM напрямую по протоколу MCP (Model Context Protocol). Ваш ассистент (Claude Desktop, ChatGPT, Cursor, Gemini) получает 24 типизированных инструмента — искать матчи по любым сочетаниям вида, турнира, команды, игрока и дат, брать историю личных встреч, карточку матча с картой ударов, таблицы, бомбардиров, статистику и карьеру игрока, трансферы, прогнозы — и отвечает на обычном языке, сам ходя за данными. Один endpoint, ваш API-ключ.

POST/mcp Streamable HTTP · JSON-RPC 2.0

Авторизация — тем же API-ключом в заголовке Authorization: Bearer <ключ>. Данные и покрытие ограничены вашим тарифом ровно как в REST: недоступный модуль вернёт ошибку с пометкой о тарифе. Инструменты: list_sports, search, list_leagues, league_info, live_matches, list_matches, match_details, match_shotmap, match_live_widget, match_predictions, match_forecast, league_standings, league_scorers, golf_leaderboard, league_matches, entity, player_stats, entity_matches, head_to_head, transfers, player_career, player_honours, team_achievements, club_top_scorers.

Статистика через MCP 22.09.2026: player_stats — карьерные суммы игрока по нашим матчам любого вида (очки NBA, хиты и хоум-раны MLB, ярды и тачдауны NFL, голы футбола) с окном покрытия; league_scorers принимает ярлык сезона и season=all — бомбардиры всей истории турнира; club_top_scorers — лучшие бомбардиры клуба. Ответы компактные — строка на игрока, — и в каждом есть подпись окна истории: агенту сказано называть её пользователю, чтобы наша история не выдавалась за официальный рекорд.

Поиск матчей через MCP 22.09.2026: list_matches принимает вид, турнир, участника, день или окно дат, статус и порядок; league_matches — окно дат, сезон и статус (без них — матчи с сегодняшнего дня вперёд, а не календарь с первого сезона); entity_matches — окно, статус, турнир, сезон, домашние/гостевые, первым — ближайший матч; head_to_head — история встреч двух соперников со сводкой; list_leagues ищет турнир по названию без указания вида; league_info отдаёт сезоны с их id. Во всех инструментах date_to не включается. Списки приходят компактно — строка на матч с id турнира и сторон, — и каждый кончается строкой «показано N из M; дальше: offset=…» или «это все»: агент видит, сколько осталось, и не принимает страницу за весь ответ.

Имена по-китайски через MCP 23.09.2026: подключитесь адресом https://api.sportwire.ru/mcp?lang=zh или передайте "lang": "zh" в аргументах любого инструмента (он есть в схеме каждого). В тексте ответа второе имя — китайское: «Manchester City / 曼城 [7598] 5:3 Sunderland / 桑德兰 [7597]»; где китайского у нас нет — только английское, без русского. В structuredContent рядом с name_ru приходит name_zh. Аргумент вызова сильнее адреса; язык не из en/ru/zh — ошибка, а не молчаливый английский.

Claude Desktop / Cursor — добавьте в конфиг MCP (мост mcp-remote проксирует HTTP):

JSON
{
  "mcpServers": {
    "sportwire": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://api.sportwire.ru/mcp",
               "--header", "Authorization: Bearer ВАШ_API_КЛЮЧ"]
    }
  }
}

Клиенты с нативной поддержкой удалённого MCP (без моста) — просто URL и заголовок:

JSON
{ "mcpServers": { "sportwire": {
    "url": "https://api.sportwire.ru/mcp",
    "headers": { "Authorization": "Bearer ВАШ_API_КЛЮЧ" }
} } }

Проверка вручную (без клиента) — обычный JSON-RPC:

curl · api.sportwire.ru
curl -X POST https://api.sportwire.ru/mcp \
  -H "Authorization: Bearer ВАШ_API_КЛЮЧ" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"search","arguments":{"query":"Холанд","type":"player"}}}'

Миграция с enetpulse / другого провайдера гайд

Переходите с XML-фида enetpulse (или другого провайдера)? Наш путь — не «мост XML», а те же события, только чище: тот же поток изменений приходит через вебхуки или стриминг, но с канонизированными сущностями — без дубликатов, с единым i18n-именем (ru/en) и устойчивым id. Ниже — карта соответствия сущностей, соответствие событий и схема «двойного прогона» для проверки паритета без потерь.

1. Соответствие сущностей (field mapping)

У провайдера (напр. enetpulse)У SportWireГде взять
event (матч)каноническая единица — data.match.idвебхук/стрим · /v1/event/{id} · /v1/tournament/{id}/events
participant / team / competitorparticipants[] (сторона + счёт) → participantdata.match.home/away · /v1/participant/{id}
tournament / tournament_template / seasontournament + сезон (tournament_stage, он же season_label — это издание турнира, а не стадия матча)data.match.tournament · /v1/tournament/{id}
стадия матча в сетке (раунд / круг)stage у матча — класс, код, имя, номер тура. Ключ необязательный: появляется, только когда источник назвал стадиюstage в /v1/events · /v1/event/{id} · /v1/sync
incident (гол, карточка…)инцидент → вебхуки match.goal / match.cardвебхук/стрим · incidents[] в /v1/event/{id}
result / event_stage / статусstatus + snapshot счёта. Их «стадия» = состояние матча; стадия турнира — это отдельное поле stagematch.status_changed · match.score_changed
lineupсостав → match.lineup_confirmedвебхук · lineups в /v1/event/{id}
odds / outcomeусреднённые рыночные + отдельные линии букмекеров РФ (ЦУПИС), Беларуси и Казахстана + лучшая цена среди книг РФodds, odds_bookmakers, odds_best в /v1/event/{id}
их id сущностейнаш канонический id (стабильный, без дубль-твинов)кросс-уолк по натуральным ключам — п. 3

2. Соответствие событий (их push → наш каталог)

Событие у провайдераНаш тип
смена стадии / статуса матчаmatch.status_changed
изменение результата / счётаmatch.score_changed
гол (incident)match.goal
карточка (incident)match.card
подтверждение составаmatch.lineup_confirmed

3. Кросс-уолк идентификаторов

У нас единый канонический id матча / команды / турнира — не привязанный к провайдеру и без дублей-твинов. На время миграции сопоставьте свои прежние id с нашими по натуральным ключам (дата матча + команды + турнир) и сохраните нашу id рядом со своей; дальше работайте по нашей — она стабильна. Полный список матчей для сопоставления — /v1/tournament/{id}/events (весь календарь турнира одним запросом, с count_total для проверки полноты).

4. Двойной прогон и проверка паритета

Безопасный переход — параллельно принимать оба фида и сверять, пока расхождений не станет ноль:

  1. Подключите вебхуки (или SSE/WS) на наши события параллельно текущему провайдеру.
  2. Полнота: ночным cron сверяйте свою БД против GET /v1/tournament/{id}/events — так вы ловите пропущенные/лишние матчи без обхода ленты по дням.
  3. Дельта: GET /v1/sync?since=…&since_id=… отдаёт матчи, чья деталь изменилась после курсора — удобно догонять расхождения по счёту/статусу.
  4. Догон после простоя: GET /v1/stream/replay добирает пропущенные события из шины (страницами по next_cursor), затем открываете стрим с того же курсора — стык без дыр.
  5. Когда расхождения на нуле N дней подряд — отключаете старый фид.

Итог: вы получаете те же события оперативнее (push вместо опроса XML) и чище (канонизированные сущности, единый i18n, устойчивые id), а сверочные эндпоинты гарантируют, что при переключении ничего не потеряется. Нужна помощь с маппингом под ваш прежний фид — info@sportwire.ru.

Схема данных

Модель данных SportWire — основные сущности и связи. Матч (EVENT) связан с турниром, сезоном и сторонами (со счётом) и обогащается событиями, статистикой, составами, коэффициентами, картой ударов с xG, графиком моментума и погодой. Названия — мультиязычные (name_i18n: EN/RU); сущности единые и недублированные.

1 турнир → N сезонов → N матчей · каждый матч → стороны со счётом и слои детализации · сущности единые (без дублей), названия en/ru SPORT COUNTRY TOURNAMENT name_i18n · en/ru country · gender tier (1 = топ) TOURNAMENT_STAGE сезон 25/26 (не стадия) STANDINGS турнирная таблица EVENT — матч id scheduled_start · UTC status: scheduled/live/finished stage · стадия / тур venue · стадион lineup_confirmed EVENT_PARTICIPANT side: home/away · score PARTICIPANT type: team / player name_i18n · en/ru country · birth_date EVENT_INCIDENT события: минута · вид · автор EVENT_STATISTIC статистика home/away EVENT_LINEUP составы + статы игроков EVENT_ODDS коэффициенты EVENT_SHOTMAP удары + xG EVENT_GRAPH моментум EVENT_WEATHER погода на стадионе EVENT_AUX комментарии · live-тренды · доп. вид спорта матчи

Полный инвентарь данных

Что именно доступно — по каждому типу данных, до отдельного поля. Состав полей зависит от вида спорта и наличия детализации у конкретного матча; состав полей для конкретного матча отражают флаги полноты в ответе.

Матч — event

ПолеОписание
idидентификатор матча
scheduled_startвремя начала (ISO-8601, UTC)
rescheduled / original_starttrue + первоначальная дата, если матч был перенесён (иначе false / null). Уточнение чернового расписания — не перенос (с 13.10.2026): когда лига или УЕФА сначала ставит тур одной датой, а потом разбивает его по дням и ТВ-слотам, метки нет — меняется только scheduled_start. С 22.09.2026 приезжает и в ленте /v1/events — но там по идиоме status_detail: у обычного матча ключа нет вовсе, а не false. Раньше перенос знала только карточка, и сводку из ленты приходилось достраивать запросом на каждый матч
statusscheduled · live · finished · postponed · cancelled · interrupted — матч прерван, и источник так и не возобновил его и не закрыл результатом (с 27.09.2026; с 05.10.2026 — у всех таких матчей: раньше большая их часть отдавалась finished со счётом на момент остановки). Остановка по ходу матча (погода, свет) отдаётся как live; interrupted появляется, когда матч прерван дольше своей обычной длительности (футбол — 3 ч от начала, теннис — 8 ч). Победитель у такого матча есть, только если его назвал источник. Если источник позже перенёс или отменил матч, который уже отдавался finished, статус становится postponed / cancelled (победителя нет, если источник его не назвал), и приходит match.status_changed. Как читать
start_overdueвремя начала прошло (≥ 30 мин), а матч всё ещё scheduled: minutes · reason (result_pending · result_unconfirmed · source_not_started · source_missing · source_changed · not_checked · not_monitored) · source_status · source_start · checked_at. У остальных матчей ключа нет. Как читать
status_detailособое завершение: retired · walkover · removed · defaulted · awarded · abandoned. При обычном завершении ключа в ответе нет; матч, прерванный прямо сейчас, признака не несёт и идёт как live. Как читать + покрытие
tournament_id / tournament / tournament_ruтурнир матча: id, название от источника и русское название. tournament_ru едет и в ленте /v1/events, и в карточке /v1/event/{id} — отдельный запрос к /v1/tournament/{id} ради русской строки не нужен. Где перевода нет — null: английское название под видом русского не подставляется
stageстадия турнира: type (класс: regular · qualification · group · playoff · placement) · code (машинный код стадии) · name / name_ru (имя стадии) · round (номер тура — только у регулярного сезона). Ключ появляется, только если источник назвал стадию; по одному номеру раунда мы её не выдумываем, и у большинства лиговых матчей ключа не будет. Как читать + покрытие по видам
round_nameимя стадии строкой — дубль stage.name, оставлен для совместимости
venueплощадка (стадион)
lineup_confirmedfalse — предварительный состав, true — официальный, null — нет
extraсчёт по партиям/периодам с тайбрейками (period_scores, как читать), сезон, round — сырое число раунда из фида (у кубка это внутренний код стадии, а не тур; см. стадию), время начала текстом; surface новое — покрытие корта в теннисе (Hardcourt outdoor, Clay, Grass, Hardcourt indoor); aggregate новое — двухматчевое противостояние: winner (home / away / draw — по сумме двух матчей) и previous_leg_event_id — id первого матча, если он у нас есть; time_known: false новое — известна только дата, scheduled_start стоит на полночь и точным временем не является (ключа нет, когда время известно). period_scores отдаётся в одной форме независимо от того, каким диалектом счёт по партиям пришёл от источника; в сет-видах current — выигранные партии, в остальных — сумма
weatherпогода на стадионе (см. ниже)
hockey_unitsхоккейные звенья, явно названные в составе источника; появляются только при наличии такой разбивки. В карточке и /v1/sync, с тем же доступом, что составы. Как читать
raceпротокол заезда — мотоспорт и биатлон: серия, сессия, дистанция и классификация (место, время, отставание, круги, пит-стопы, стрельба). Модуль «Детали матча». Как читать + покрытие

Стороны и счёт — participants

ПолеОписание
participant_id / name / name_ruкоманда или игрок, локализованное имя
sidehome / away, либо null у многосторонних событий (гонки — см. раздел «Гонки»)
scoreитоговый счёт стороны. У гонки счёта нет — null
seed / entry новоесетка: seed — номер посева (число), entry — статус входа без посева: WC (уайлд-кард), Q (квалифаер), LL (лаки-лузер), PR (защищённый рейтинг) и т.п. Оба null, когда посева у матча нет (лиги, командные виды)
positionместо участника в заезде (мотоспорт, биатлон). У обычного матча — null; массив отсортирован по этому полю
image_urlлоготип / фото

События матча — incidents

Голы, карточки (жёлтые/красные), замены, VAR, пенальти, начало/конец таймов и др. — по всей истории.

ПолеОписание
minute / minute_plusминута (+добавленное). Бывает отрицательной — см. врезку ниже
periodтайм / период
kindтип события (goal, card, subst, var, penalty …)
sideсторона
player_name / assist_nameигрок и ассистент
payloadдетали (тип карточки, счёт после события, описание)

Отрицательная минута: -1 и -5

minute — сырое значение источника, и оно бывает отрицательным. Смысл ровно такой, без дополнительных трактовок:

ЗначениеЧто означаетСобытий в базе
-1минута неизвестна — источник её не прислал61 759 788
-5событие вне игрового времени: карточка после финального свистка24 064

Замер 02.08.2026 по всем 105 243 382 инцидентам. -1 — это 59% всех событий, и почти все они из видов, где источник вообще не привязывает розыгрыш ко времени: настольный теннис — 53 160 317, волейбол — 4 580 628, гандбол — 1 972 015, бадминтон — 1 264 872. В футболе -1 стоит у 231 760 событий, в баскетболе — у 222 584. У -5 из 24 064 событий 23 999 — карточки (20 431 жёлтая, 3 568 красных), и 23 972 из них футбольные.

Встречаются и другие отрицательные значения — -4 у 580 событий, -3 у 516, -2 у 74, плюс по несколько десятков на значениях от -6 до -30 (всего таких событий 2 319). Это то же сырьё источника; никакого дополнительного смысла мы им не приписываем, и читать их как минуту нельзя. Ещё у 144 283 событий minute = null.

⚠️ Порядок массива. Инциденты в /v1/event/{id} отсортированы по minute по возрастанию, поэтому отрицательные идут первыми, до 1-й минуты. Если вы рисуете таймлайн в порядке массива, события с неизвестной минутой окажутся в его начале. Отфильтруйте minute < 0 или покажите их отдельной группой — но не как «событие на −1-й минуте».

Статистика матча — statistics

Владение, удары (всего/в створ), угловые, фолы, офсайды, передачи, отборы, сейвы и десятки метрик; по таймам и за весь матч.

ПолеОписание
periodединый словарь периодов для всех видов: ALL — за весь матч; 1ST · 2ND · 3RD · 4TH … — таймы, четверти, партии, иннинги по номеру; ET1 / ET2 — дополнительное время, OT1…OT5 — овертайм / экстра-иннинг, PEN — серия пенальти. Диалекты источников (1H, 1Q, Extra time) приведены к этому словарю на отдаче
group_name / stat_nameгруппа и название метрики
home_value / away_valueчисловые значения сторон
home_text / away_textтекстовое представление (напр. «58%»)

Бейсбол: хиты и ошибки приходят разными наборами

В бейсболе статистика идёт двумя наборами от двух источников, и в одном матче они практически не встречаются вместе (пересечение — 0,5% матчей, цифры ниже): у матча есть либо один набор, либо другой. Hits есть в обоих, Errors — только во втором, и только там же они разложены по иннингам.

НаборЧто внутриperiodМатчей
Боксскор
group_name = Batting · Pitching · Fielding
Hits, At bats, Runs, RBI, Home runs, Doubles, Triples, Base on balls, Strike outs, Left on base, AVG/OBP/SLG/OPS; у питчеров — Innings pitched, Earned runs, ERA, Outs, Batters faced; в филдинге — Put outs и Assists. Errors в этом наборе нет вовсе ALL33 824
Иннинговый набор
group_name = MAIN
Errors и Hits — итог матча плюс разбивка по иннингам ALL — итог,
1ST…9TH — иннинги
4 168
JSON
// ошибки по иннингам: stat_name = Errors, group_name = MAIN
{"period":"ALL","group_name":"MAIN","stat_name":"Errors","home_value":0,"away_value":3}
{"period":"1ST","group_name":"MAIN","stat_name":"Errors","home_value":0,"away_value":2}
{"period":"4TH","group_name":"MAIN","stat_name":"Errors","home_value":0,"away_value":1}

// хиты из боксскора — другой набор, другой матч (Errors там не будет)
{"period":"ALL","group_name":"Batting","stat_name":"Hits","home_value":8,"away_value":5}

Замер 02.08.2026: 242 198 завершённых бейсбольных матчей, статистика есть у 38 556 из них. Боксскор — у 33 824, и это почти целиком MLB (31 975 матчей). Errors — у 4 921 матча: 12,8% матчей со статистикой и 2,0% всех завершённых, — и почти все они, наоборот, НЕ MLB, а европейские и азиатские лиги; у MLB ошибки есть лишь у 206 матчей. Пересечение наборов — 195 матчей (0,5%): рассчитывать, что у матча найдутся оба, нельзя.

У части матчей (≈790) тот же иннинговый набор приходит под другими именами групп — Match для итога и 1st Inning / 2 / 3… для иннингов. Это второй диалект того же источника, значения те же. Поэтому отбирайте по stat_name (Errors, Hits) и по period (ALL — итог, 1ST…9TH — иннинги), а не по имени группы.

Лента матча MLB: раны, хоум-раны, смены питчеров 23.09.2026. У бейсбола у источника нет ленты инцидентов в привычном виде, и до 23.09 массив incidents у матчей MLB был пуст. Теперь он собирается из порозыгрышной записи матча. period — номер иннинга, minute = null, порядок массива — порядок игры. Виды:

kindЧто этоПоля payload
runрозыгрыш, после которого отбивающая сторона набрала раны (кроме хоум-рана) runs — сколько ранов принёс розыгрыш; home_score/away_score — счёт после него; player — бьющий; inning_half — top/bottom; outcome — код исхода (aS сингл, aD дабл, bB уок, oSF жертвенный флай…); text
home_runхоум-ран (тоже ран: сумма runs по run и home_run стороны = её счёт) те же поля, outcome = aHR
pitcher_changeсмена питчера player — вошёл, player_out — ушёл (и player_out_name на верхнем уровне), inning_half, text

Перед записью сумма ранов каждой стороны сверяется с финальным счётом. Если не сошлось, ленту матча не отдаём: неполная запись хуже пустой. Замер 23.09.2026: лента есть у 98,2% матчей MLB за последние 365 дней (сезон 2025 — 2 458 из 2 476, 2026 — 2 310 из 2 356). Хоум-раны ленты сверены с боксскором (Home runs, группа Batting): совпало 4 746 матчей из 4 756 по обеим сторонам. История с 2013 года добирается по сезонам. Смен питчера в записи источника до 2017 года нет.

Составы — lineups

ПолеОписание
side / formationсторона и схема (напр. 4-3-3)
player_participant_idигрок
position / shirt_numberамплуа, номер
is_substituteв запасе / в старте. У хоккея с 03.10.2026 — и у вратарей: основной — false, остальные — true, где основной установлен (по статистике сыгравшего и первому звену источника). Читать вместе с lineups_meta ниже
captain новоеtrue у капитана, false у остальных игроков состава, где капитан известен; null — источник капитана не назвал (низшие лиги, старые матчи)
statsиндивидуальная статистика игрока в матче (рейтинг, голы, передачи …)

Известен ли старт — lineups_meta новое. Рядом с lineups (в карточке и в /v1/sync) — объект по сторонам, у которых есть строки состава: можно ли читать is_substitute этой стороны как «в старте / в запасе». Само is_substitute не меняется. Доступен с тем же модулем, что и состав; без него — {}.

JSON · хоккей
"lineups_meta": {
  "home": { "starter_known": true },
  "away": { "starter_known": false, "reason": "goalkeeper_not_established" }
}
starter_known / reasonЧто значит
trueстарт отмечен. У хоккея — ровно один вратарь стороны (position G) с is_substitute: false: это основной вратарь
false · no_starters_markedни одна строка стороны не отмечена «в старте»: источник не отметил, кто начал матч (так у большинства гандбольных составов). true здесь не значит «на скамейке». У хоккея — то же по полевым игрокам: в части матчей источник ставит «в запасе» всем заявленным
false · goalkeeper_not_establishedхоккей: основных вратарей не ровно один — сыграли оба и источник не назвал, кто начал, или его сведения противоречат друг другу. Флаги вратарей — как у источника; угадывать основного по сейвам мы не будем
false · goalkeepers_not_markedхоккей: ни одна строка стороны не отмечена вратарём (у части составов источник даёт только «в заявке / в запасе» без амплуа) — назвать основного вратаря нечем

Хоккейные звенья — hockey_units

Для sport=ice_hockey карточка GET /v1/event/{id} и события в GET /v1/sync дополнительно несут hockey_units, когда источник явно разбил состав матча на номерные звенья. Состав группы сохраняет эту разбивку: в ней могут быть вратарь, защитники и нападающие; это не обязательно только тройка нападения. Порядок строк обычного lineups не используется для определения звеньев.

ПолеОписание
basissource_explicit — группировка явно указана в составе источника
observed_atвремя нашего запроса или получения этой группировки (ISO-8601, UTC); не время объявления состава или выхода звена на лёд
groups[].side / numberhome / away и номер звена (от 1 до 4)
groups[].players[]player_participant_id (наш id игрока или null, если связь ещё не установлена), player_name, shirt_number, position (G — вратарь, D — защитник, F — нападающий; также C, L, R, если названо точное амплуа); номер и амплуа могут быть null
JSON · фрагмент
"hockey_units": {
  "basis": "source_explicit",
  "observed_at": "2026-10-01T12:00:00Z",
  "groups": [
    {"side":"home", "number":1, "players":[
      {"player_participant_id":null, "player_name":"…", "shirt_number":"30", "position":"G"},
      {"player_participant_id":null, "player_name":"…", "shirt_number":"4", "position":"D"}
    ]},
    {"side":"away", "number":1, "players":[ … ]}
  ]
}

Это группировка состава источника, а не текущие пятёрки на льду и не запись смен по ходу матча. Она сама по себе не подтверждает официальное объявление состава до начала матча. Наличие всех звеньев и покрытие всех лиг не гарантируются: отдаём только явно предоставленные группы обеих сторон, число групп у сторон может различаться. Если разбивки нет, ключ hockey_units отсутствует; пустые звенья не создаются. Доступ: модуль lineups при status=scheduled, модуль detail при остальных статусах.

Коэффициенты — odds

ПолеОписание
market_name / market_groupрынок (исход, тотал, фора …)
choice_nameисход (1 / X / 2 и т.д.)
fractional_valueтекущий коэффициент
opening_valueоткрывающий коэффициент (где известен — для анализа движения линии)
bookmakerбукмекер котировки, где источник его называет (bet365 / William Hill / Unibet); null для агрегированной рыночной цены
movementнаправление движения линии: up · down · null
winningсыграл ли исход (для сыгранных матчей)

Карта ударов + xG — shotmap

GET /v1/event/{id}/shotmap — по каждому удару, отсортировано по минуте:

ПолеОписание
player / is_homeбивший игрок и сторона
minute / added_timeвремя удара
shot_type / situation / body_partтип (гол/сейв/мимо/блок/штанга), ситуация (с игры/штрафной/пенальти/угловой/контратака), часть тела
xg / xgotожидаемые голы и xG по ударам в створ
x / yточка удара на поле (координаты 0–100)
trajectoryполилиния полёта мяча: start→end→goal (+ block у заблокированных), каждая точка {x,y}
goal_mouth / goal_mouth_xyзона попадания в ворота (low-left…) и точные координаты {x,y,z} (z — высота)
block_xyточка блока {x,y} (у заблокированных ударов)
⚠️ Изменение контракта 27.07.2026. Из ответа shotmap убраны поля player_id и id (идентификатор удара), а из /v1/event/{id}/xt — players[].player_id. Это были внутренние идентификаторы поставщика данных, попавшие в ответ по недосмотру. Не используйте их как идентификатор игрока: их числовые значения пересекаются с нашим пространством participant.id, и запрос /v1/participant/{player_id} с высокой вероятностью возвращал другого человека. Если вы связывали удары с игроками по этому полю — переходите на связку по player (имя) в паре с is_home, либо на составы /v1/event/{id} → lineups, где у игрока стоит наш канонический participant_id, стабильный и переживающий слияние дублей.
⚠️ Изменение контракта 03.09.2026. В shotmap у вратаря (goalkeeper) теперь остаются только name, short_name, position и jersey_number. Внутренние поля карточки поставщика (id, slug, userCount, fieldTranslations) из ответа убраны — это продолжение правки 27.07 по полевым игрокам. Не используйте прежний goalkeeper.id как идентификатор игрока: его числовые значения пересекаются с нашим пространством participant.id, и /v1/participant/{id} по нему возвращал другого человека. Связка вратаря с каноническим игроком — через составы /v1/event/{id} → lineups.

График хода матча — momentum

При наличии данных и модуля momentum поле содержит график в формате источника. В футболе и баскетболе это graphPoints с точками {minute, value}. В теннисе используется отдельный массив tennisPowerRankings: точки привязаны к сету и гейму.

Поле теннисной точкиОписание
setномер сета по данным источника
gameномер гейма по данным источника
valueчисловой показатель источника, без пересчёта
breakOccurredпереданный источником булев признак брейка

Пример структуры с условными значениями:

JSON
{
  "momentum": {
    "tennisPowerRankings": [
      {"set": 1, "game": 1, "value": 12.5, "breakOccurred": false},
      {"set": 1, "game": 2, "value": -8, "breakOccurred": true}
    ]
  }
}

В теннисной форме set и game не заменяются на minute. Для чтения ответа выбирайте обработчик по доступному массиву; не предполагается единая временная шкала для разных видов спорта.

Погода — weather

ПолеОписание
stadium / cityплощадка и город
temperature_c / feels_like_cтемпература и «ощущается», °C
precipitation_mm / precipitation_prob_pctосадки и вероятность
wind_speed_ms / wind_gusts_msветер и порывы, м/с
humidity_pct / cloud_cover_pctвлажность, облачность
conditions / is_forecastописание; прогноз или факт (архив)

Турниры и таблицы — tournament / standings

ПолеОписание
name_i18n / country / gender / tierназвание (EN/RU), страна, пол, уровень
сезоныseason_label (издание турнира, напр. «25/26»), даты начала/конца, текущий ли. Это не стадия матча — она в поле stage у матча
таблицапозиция, игры, В/Н/П, забито/пропущено, очки

Команды и игроки — participant

ПолеОписание
typeteam / player
name_i18n / country / genderимя (EN/RU), страна, пол
birth_dateдата рождения (игроки)
image_urlлоготип / фото
extraстадион, амплуа, рост и др. (где доступно)

Аналитика (вычисляемое)

Поверх данных доступны вычисляемые блоки — форма и тренды, очные встречи, профиль лиги, модель Пуассона с вероятностями и «справедливыми» кэфами, value-сигналы. См. Предматчевую аналитику и Тренды.