На сайт Песочница

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

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

Обзор

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

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

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

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

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

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

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

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

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

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

Ошибки

КодЗначение
401Ключ не передан или недействителен
404Сущность не найдена
422Неверные параметры запроса
429Превышена частота запросов (лимит запросов/сек тарифа)

Локализация (RU)

Названия лиг, турниров, команд и стран отдаются объектом name_i18n с ключами по локали — русский лежит рядом с оригиналом:

"name_i18n": { "en": "Premier League", "ru": "Премьер-лига" }

Берите name_i18n.ru для русского интерфейса или name_i18n.en для оригинала. Где русского ещё нет — поле может отсутствовать (заполнение идёт постоянно).

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

Виды спорта

GET/v1/sports

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

{ "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)
limit / offsetпагинация (limit до 1000)
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",
    "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"}
    ]
}]}

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

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

Live-матчи

GET/v1/events/live

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

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

Значений у status пять: scheduled · live · finished · postponed · cancelled. Снятие по травме, неявка и присуждённый результат попадают в тот же 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инкрементальная синхронизация
// снятие по ходу матча (теннис): счёт по партиям остался в 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 нельзя.
  • Фильтра по status_detail в /v1/events нет — параметр status отбирает только по пяти статусам. Отбирайте признак на своей стороне.
  • Итоговый счёт у снятия бывает нулевым. У removed, defaulted и части retired в participants[].score стоит 0:0, хотя партии игрались — сыгранное лежит в extra.period_scores. Показывать такой матч как «0:0» нельзя: это не результат, а отсутствие присуждённого счёта.

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

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 отдаются всегда. Без параметра ответ прежний, менять существующие запросы не нужно.

{ "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} ],
  "odds_best":  { "1x2":{"home":{"odds":2.77,"bookmaker":"…"},"draw":{…},"away":{…}},
                  "total_2_5":{…}, "btts":{…}, "double_chance":{…} },

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

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

Как обновляются. Мы опрашиваем букмекеров непрерывно, и коэффициент каждого рынка перезаписывается поверх прежнего, как только он изменился — и до матча, и в лайве. В ответе вы всегда видите актуальное значение (odds), а не срез какой-то давности.

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

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

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

Какие книги собираем. Каждая книга отдаётся отдельно, под своим 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.


  "best_players":[ {"side":"home","rating":"7.6","player_name":"…","position":"M"} ],
  "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":{…} }
}

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

Состав полей зависит от вида спорта и наличия данных у матча. Ключевые блоки: player_stats[].stats — полная статистика по каждому игроку (рейтинг, передачи, отборы, xG/xA, касания, спорт-специфичные метрики); coaches — тренеры сторон; missing_players — травмы/дисквалификации (type + reason_text); referee, venue_city, venue_capacity, attendance — справка о матче; best_players (MVP), 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 (метод/раунд — ММА), race (протокол заезда — мотоспорт и биатлон). Коэффициенты: odds — усреднённые рыночные линии (включая зарубежные конторы) с движением и опенингом; odds_bookmakers — отдельные линии по каждому рынку: книги РФ (ЦУПИС), Беларуси и Казахстана, у каждой строки своя jurisdiction (подробнее); odds_best — лучшая цена по каждому исходу (line-shopping) среди книг РФ. Пре-матч и лайв; отдельный подключаемый модуль «Коэффициенты БК».

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

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

GET/v1/event/{id}

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

// финал 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

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

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

"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":"1","a":"0","scored":1}, …]}], без разбивки на геймы и без подающего. 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 и scorenull. Массив отсортирован по месту. Логика «взять side == "home"» на гонке не найдёт ничего — это сделано намеренно: заезд не притворяется матчем со счётом 0:0;
  • race — сам протокол: серия, сессия, дистанция и классификация.
"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, а не пропадают. Что именно заполнено, зависит от вида: мотоспорту источник даёт круги, стартовую позицию и пит-стопы, биатлону — стрельбу.

Бой — метод победы 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 сезона (иначе самый свежий).

{ "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} ] }

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

GET/v1/tournament/{id}/scorers

Таблица бомбардиров сезона: ранг, игрок, команда, голы. season — id сезона (иначе самый свежий); limit ≤ 500.

{ "tournament_id":47325, "count":20,
  "scorers": [ {"rank":1,"participant_id":…,"player_name":"…","team_name":"…","goals":8} ] }

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

GET/v1/tournament/{id}/bracket

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

{ "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 в ответе не будет — см. «Голое число — не стадия».

// плей-офф — именованная стадия (теннис 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машинный класс стадии, ровно одно из пяти: 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
roundномер тура. Приходит только при type: "regular" (и при квалификации с явным номером круга — Qualification round 2). У названной стадии кубка номер из фида служебный — у Preliminary в MOL Cup там 300, у Qualifications10, — и наружу он не отдаётся вовсе, чтобы его нельзя было показать как «тур 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 -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_runame; ветвление в коде стройте на 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_rustage.name → ничего (объекта stage нет — не подставляйте заглушку).

⚠️ season_label и tournament_stage — это СЕЗОН, а не стадия. Слово «stage» в нашей модели исторически занято изданием турнира: season_label = «25/26», а параметр season у таблиц, бомбардиров и сеток — это 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 (со знаком, где источник даёт прошлый срез).

{ "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 — глубина и полнота зависят от лиги.

{ "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=), с количеством матчей, страной и полом.

{ "count": 18, "tournaments": [
  { "id": 17, "name": "Premier League",
    "name_i18n": {"en":"Premier League","ru":"Премьер-лига"},
    "sport": "football", "gender": "M", "country": "EN", "events": 14023 }
]}
GET/v1/participants/search?q=…

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

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

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

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. Это НЕ та же команда — в киберспорте состав переходит целиком
{ 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 новое

Персональная статистика ИГРОКА: агрегат по всем матчам с детализацией (голы, удары, удары в створ, xG, xGOT) и последние матчи с per-match числами. Модуль «xG · shotmap».

{ "participant_id": 543227, "type": "player", "available": true,
  "totals": { "matches": 19, "goals": 18, "shots": 92, "shots_on_target": 44, "xg": 10.90, "xgot": 11.58 },
  "matches": [
    { "event_id": 10982017, "scheduled_start": "2026-07-04T…", "goals": 1, "shots": 9, "xg": 1.33,
      "home": "Argentina", "away": "Cabo Verde", "home_score": 3, "away_score": 2 } ] }

Изображения

GET/v1/participant/{id}/image

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

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

Матчи участника

GET/v1/participant/{id}/events

Матчи конкретной команды/игрока (прошедшие и будущие), с пагинацией.

Страны

GET/v1/countries

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

{ "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: довстречная модель против уже изменившейся картины ценности не показывает.

{ "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, "ppg":1.0, "gf_avg":1.0, "over_2_5":60.0,
                  "btts":60.0, "clean_sheet":20.0, "form":"DLWLL" },
      "home":   { "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, "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 / только дома / только в гостях). Для предстоящего матча выборка берётся строго ДО его даты — результат самого матча в расчёт не попадает (нет «подглядывания» в будущее).

ПолеОписание
matchesчисло матчей в выборке
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 (методология)

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

ПолеОписание
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число личных встреч
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, % тоталов и ОЗ — за последние ~2,5 года.

{ "tournament_id":31384, "name":"Serie A", "name_ru":"Серия А", "sport":"football",
  "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-тренды».

{ "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" }

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 по владениям (позиционных пасов с координатами источник не отдаёт).

{ "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 → бьёт эффективнее ожидания.

Прогнозы матча (CatBoost ML) + value vs РФ-ЦУПИС новое

GET/v1/event/{id}/prediction

Собственная 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 у чистого Пуассона — то есть модель калибрована точнее рынка на момент открытия линии. Тотал 2.5 — точность ≈65%, «обе забьют» — ≈59%. Прогноз — это честная вероятность и value, а не «обыграй БК»: используйте его как оценку справедливой цены и поиск расхождений с рынком, а не как гарантию.

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

{ "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": { "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": { "prob_over": 58.2, "prob_under": 41.8, "fair_over": 1.72 },
    "btts":           { "prob_yes": 55.0,  "prob_no": 45.0,   "fair_yes": 1.82 } },
  "poisson_1x2": { "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,
    "selections": [
      { "selection": "home", "model_prob": 54.3, "market_prob": 51.2, "best_odds": 1.95,
        "bookmaker": "pari",                                          // ЧЬЯ это лучшая цена
        "fair_odds": 1.84, "edge_pct": 3.1, "ev_pct": 5.9, "value": true },   // +EV → есть value
      { "selection": "draw", "model_prob": 24.1, "market_prob": 25.0, "best_odds": 3.90,
        "bookmaker": "olimp",
        "fair_odds": 4.15, "edge_pct": -0.9, "ev_pct": -6.0, "value": false }, … ],
    "best_value": { "selection": "home", "ev_pct": 5.9, "best_odds": 1.95, "bookmaker": "pari", … } } }

Как читать 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}. Собирается для предстоящих матчей (прогноз) и доступна по сыгранным (архив), где известна площадка.

{ "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 не нужен). Имена команд и турниров — на русском.

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

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

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

<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). Событие в пачке несёт деталь (события, статистику, составы, статистику игроков) и дополнительные блоки вашего тарифа — h2h, form, streaks, votes, best_players, win_probability, highlights, tv_channels, fight, points_history, point_by_point, games/rounds/map_bans (киберспорт), race (протокол заезда). Тяжёлая координатная графика (shotmap, momentum, average_positions, player_heatmaps, player_actions) и построчный commentary — только в карточке /v1/event/{id}: на пачку в 500 матчей это десятки мегабайт. Подробнее — в разделе «Миграция».

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

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

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

POST/account/api/webhooks
{ "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 — лог доставки · 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Изменение счёта (какая сторона забила)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 с на матчbookmaker, changed

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

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

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

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 — ленты у матча (пока) нет вовсе.

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

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

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

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

# 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
// 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));
    });
}
# 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. Отдаются только виды спорта из вашего тарифа.

# поток (флаг -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 с держат соединение живым.

// Браузер — 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":…}.

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. Есть и вариант из кабинета по сессии: GET /account/api/webhooks/events.

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) получает 16 типизированных инструментов — искать матчи, брать карточку матча с картой ударов, таблицы, бомбардиров, статистику игрока, трансферы, модель Пуассона — и отвечает на обычном языке, сам ходя за данными. Один endpoint, ваш API-ключ.

POST/mcp Streamable HTTP · JSON-RPC 2.0

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

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

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

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

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

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

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)
statusscheduled · live · finished · postponed · cancelled
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сырое число раунда из фида (у кубка это внутренний код стадии, а не тур; см. стадию), время начала текстом
weatherпогода на стадионе (см. ниже)
raceпротокол заезда — мотоспорт и биатлон: серия, сессия, дистанция и классификация (место, время, отставание, круги, пит-стопы, стрельба). Модуль «Детали матча». Как читать + покрытие

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

ПолеОписание
participant_id / name / name_ruкоманда или игрок, локализованное имя
sidehome / away, либо null у многосторонних событий (гонки — см. раздел «Гонки»)
scoreитоговый счёт стороны. У гонки счёта нет — 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 — дополнительное время, OT1OT5 — овертайм / экстра-иннинг, 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 — итог,
1ST9TH — иннинги
4 168
// ошибки по иннингам: 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 — итог, 1ST9TH — иннинги), а не по имени группы.

Составы — lineups

ПолеОписание
side / formationсторона и схема (напр. 4-3-3)
player_participant_idигрок
position / shirt_numberамплуа, номер
is_substituteв запасе / в старте
statsиндивидуальная статистика игрока в матче (рейтинг, голы, передачи …)

Коэффициенты — 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полилиния полёта мяча: startendgoal (+ 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}/xtplayers[].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 — график моментума по ходу матча (футбол, баскетбол).

Погода — 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-сигналы. См. Предматчевую аналитику и Тренды.