Сервер корта: компьютерное зрение
Розыгрыши с траекторией мяча, итог матча, здоровье камер и брони на корты. Запись — клубный токен; чтение — клубный токен или токен игрока.
Розыгрыш целиком #
/v1/cv/rallyДоступ: Клубный токен Заголовок X-Club-Token: <клубный токен> — токен сервера корта клуба.
Розыгрыш целиком: время, прореженная траектория мяча в пикселях кадра, удары со скоростью, победившая команда. Повтор того же rally_no перезаписывает запись — очередь корта после обрыва связи может слать дубли.
Заголовки
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
x-club-token | строка | да | Клубный токен сервера корта |
Тело запроса application/json
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
match_id | строка (uuid) | да | matchId |
court | строка | нет | Имя корта |
rally_no | целое | да | Номер розыгрыша — по нему повтор перезаписывает запись не меньше 0 |
t_start | число | да | Начало, unix-секунды |
t_end | число | да | Конец, unix-секунды |
duration_s | число | нет | Длительность, с; 0 — посчитать из t_start и t_endпо умолчанию 0.0 |
path | массив [массив [число]] | нет | Прореженная траектория мяча [x, y] в пикселях кадра, до 400 точекэлементов до 400 |
path_fw | целое | нет | Ширина кадра траектории по умолчанию 0 |
path_fh | целое | нет | Высота кадра траектории по умолчанию 0 |
shots | массив [объект] | нет | Удары, до 500 элементов до 500 |
shots[].t | число | да | Время удара, unix-секунды |
shots[].v_kmh | число | нет | Скорость, км/ч по умолчанию 0.0 |
shots[].x | целое | нет | x в пикселях кадрапо умолчанию 0 |
shots[].y | целое | нет | y в пикселях кадрапо умолчанию 0 |
max_kmh | число | нет | Максимальная скорость, км/ч по умолчанию 0.0 |
avg_kmh | число | нет | Средняя скорость, км/ч по умолчанию 0.0 |
shots_count | целое | нет | Число ударов; 0 — по длине shotsпо умолчанию 0 |
winner_team | строка, или null | нет | Выигравшая команда: A или B (1 и 2 тоже принимаются) |
Пример запроса
curl -X POST "https://адрес-вашего-сервера/api/v1/cv/rally" \
-H "X-Club-Token: <клубный токен>" \
-H "Content-Type: application/json" \
-d '{
"match_id": "8c0f4a52-2d1e-4b7a-9f63-1a5e7d9b3c20",
"court": "court4",
"rally_no": 1,
"t_start": 1759418000.0,
"t_end": 1759418012.4,
"duration_s": 12.4,
"path": [[312, 401], [355, 380]],
"path_fw": 1024,
"path_fh": 768,
"shots": [{"t": 1759418001.2, "v_kmh": 74.0, "x": 312, "y": 401}],
"max_kmh": 96.0,
"avg_kmh": 58.2,
"shots_count": 7,
"winner_team": "A"
}'Ответы
| Код | detail | Значение |
|---|---|---|
| 200 | — | Успешный ответ; пример ниже |
| 401 | bad_club_token | Неверный клубный токен |
| 404 | match_not_found | Матч не найден, чужого клуба или вы не участник |
| 422 | […] | Ошибка проверки параметров или тела: detail — список ошибок |
Пример ответа
{"ok": true, "rallyId": "1f7c2a90-3b4d-4e5f-8a6b-7c8d9e0f1a2b", "shots": 9}Итог матча #
/v1/cv/match-summaryДоступ: Клубный токен Заголовок X-Club-Token: <клубный токен> — токен сервера корта клуба.
Итог матча: число розыгрышей, скорости, тепловая карта и маршруты мяча. Повтор обновляет итог, вторая карта не создаётся. Если есть розыгрыши и удары, облако заполняет у матча среднее число ударов за розыгрыш.
Заголовки
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
x-club-token | строка | да | Клубный токен сервера корта |
Тело запроса application/json
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
match_id | строка (uuid) | да | matchId |
court | строка | нет | Имя корта |
rallies | целое | нет | Число розыгрышей по умолчанию 0 |
max_kmh | число | нет | Максимальная скорость, км/ч по умолчанию 0.0 |
avg_kmh | число | нет | Средняя скорость, км/ч по умолчанию 0.0 |
total_shots | целое | нет | Всего ударов по умолчанию 0 |
heat | массив [массив [число]] | нет | Тепловая карта: [x, y, n] — клетка в пикселях кадра и счётчик, до 8000элементов до 8000 |
trails | массив [массив [число]] | нет | Маршруты мяча: [x1, y1, x2, y2, n] — переход между клетками и счётчик, до 4000элементов до 4000 |
fw | целое | нет | Ширина кадра по умолчанию 0 |
fh | целое | нет | Высота кадра по умолчанию 0 |
Пример запроса
curl -X POST "https://адрес-вашего-сервера/api/v1/cv/match-summary" \
-H "X-Club-Token: <клубный токен>" \
-H "Content-Type: application/json" \
-d '{
"match_id": "8c0f4a52-2d1e-4b7a-9f63-1a5e7d9b3c20",
"court": "court4",
"rallies": 42,
"max_kmh": 118.0,
"avg_kmh": 61.5,
"total_shots": 310,
"heat": [[320, 240, 7]],
"trails": [[320, 240, 360, 260, 3]],
"fw": 1024,
"fh": 768
}'Ответы
| Код | detail | Значение |
|---|---|---|
| 200 | — | Успешный ответ; пример ниже |
| 401 | bad_club_token | Неверный клубный токен |
| 404 | match_not_found | Матч не найден, чужого клуба или вы не участник |
| 422 | […] | Ошибка проверки параметров или тела: detail — список ошибок |
Пример ответа
{"ok": true, "heat": 120, "trails": 64}Здоровье корта раз в 10 с #
/v1/cv/healthДоступ: Клубный токен Заголовок X-Club-Token: <клубный токен> — токен сервера корта клуба.
Здоровье корта: камеры, версия программы, диск и видеокарта. Сервер корта шлёт его раз в 10 секунд; хранится только последнее состояние. Незнакомый тег камеры не отбрасывается, а возвращается в unknown_tags. В ответе — отметки розыгрышей, сделанные игроками в приложении за последние 30 минут (каждая выдаётся один раз).
Заголовки
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
x-club-token | строка | да | Клубный токен сервера корта |
Тело запроса application/json
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
court | строка | да | Имя корта |
ts | число | нет | Время замера, unix-секунды; 0 — время облака по умолчанию 0.0 |
cameras | массив [объект] | нет | Камеры, до 32 элементов до 32 |
cameras[].tag | строка | да | Роль камеры — тег из согласованного списка (выдаётся при подключении) |
cameras[].model | строка | нет | Модель камеры |
cameras[].online | логическое | нет | Камера на связи по умолчанию false |
cameras[].fps | число | нет | Кадров в секунду по умолчанию 0.0 |
cameras[].fails | целое | нет | Сбоев чтения (накопительно) по умолчанию 0 |
cameras[].reopens | целое | нет | Переподключений (накопительно) по умолчанию 0 |
cameras[].last_frame_age_s | число | нет | Возраст последнего кадра, с по умолчанию 0.0 |
cameras[].bright | число | нет | Яркость кадра по умолчанию 0.0 |
cameras[].temp_c | число, или null | нет | Температура камеры, °C, если известна |
cameras[].dropped | целое | нет | Потерянных кадров (накопительно) по умолчанию 0 |
cameras[].drop_ratio | число | нет | Доля потерянных кадров по умолчанию 0.0 |
app | объект | нет | Программа сервера корта |
app.version | строка | нет | Версия программы |
app.uptime_s | целое | нет | Время работы, с по умолчанию 0 |
host | объект | нет | Железо сервера корта |
host.disk_free_gb | число, или null | нет | Свободно на диске, ГБ |
host.disk_used_pct | число, или null | нет | Занято диска, % |
host.gpu_temp_c | число, или null | нет | Температура видеокарты, °C |
host.gpu_name | строка | нет | Видеокарта |
Пример запроса
curl -X POST "https://адрес-вашего-сервера/api/v1/cv/health" \
-H "X-Club-Token: <клубный токен>" \
-H "Content-Type: application/json" \
-d '{
"court": "court4",
"ts": 1759418400.0,
"cameras": [{"tag": "обзор-хайлайты", "online": true, "fps": 25.0}],
"app": {"version": "1.2137", "uptime_s": 86400},
"host": {"disk_free_gb": 412.5, "gpu_temp_c": 61.0}
}'Ответы
| Код | detail | Значение |
|---|---|---|
| 200 | — | Успешный ответ; пример ниже |
| 401 | bad_club_token | Неверный клубный токен |
| 422 | […] | Ошибка проверки параметров или тела: detail — список ошибок |
Пример ответа
{
"ok": true,
"cameras": 4,
"unknown_tags": [],
"marks": [{"id": "0d4e8f21-6a3b-4c7d-9e1f-2a5b8c0d3e47", "ts": 1759418400.5, "source": "app"}]
}Данные компьютерного зрения по матчу #
/v1/cv/match/{match_id}Доступ: Клубный токен или токен игрока Либо X-Club-Token: <клубный токен>, либо Authorization: Bearer <токен игрока>. Если передан клубный токен, проверяется он.
Розыгрыши матча с траекториями и ударами и итог (тепловая карта, маршруты), если матч закрыт. С клубным токеном доступны только матчи своего клуба.
Параметры пути
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
match_id | строка (uuid) | да | Идентификатор матча |
Заголовки
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
x-club-token | строка | нет | Клубный токен сервера корта |
Пример запроса
curl -X GET "https://адрес-вашего-сервера/api/v1/cv/match/8c0f4a52-2d1e-4b7a-9f63-1a5e7d9b3c20" \
-H "X-Club-Token: <клубный токен>"Ответы
| Код | detail | Значение |
|---|---|---|
| 200 | — | Успешный ответ; пример ниже |
| 401 | no_token | Нет токена в заголовке Authorization |
| 401 | invalid_token | Токен просрочен или подделан — обновите его через /v1/auth/refresh |
| 401 | bad_club_token | Неверный клубный токен |
| 404 | match_not_found | Матч не найден, чужого клуба или вы не участник |
| 422 | […] | Ошибка проверки параметров или тела: detail — список ошибок |
Пример ответа
{
"matchId": "8c0f4a52-2d1e-4b7a-9f63-1a5e7d9b3c20",
"clubId": "c1",
"court": "Корт 4",
"summary": {
"rallies": 42,
"maxKmh": 118.0,
"avgKmh": 61.5,
"totalShots": 310,
"heat": [[320, 240, 7]],
"trails": [[320, 240, 360, 260, 3]],
"fw": 1024,
"fh": 768
},
"rallies": [
{
"no": 1,
"tStart": 1759418000.0,
"tEnd": 1759418012.4,
"durationS": 12.4,
"maxKmh": 96.0,
"avgKmh": 58.2,
"shotsCount": 7,
"winnerTeam": "A",
"path": [[312, 401], [355, 380]],
"pathFw": 1024,
"pathFh": 768,
"shots": [{"t": 1759418001.2, "vKmh": 74.0, "x": 312, "y": 401}]
}
]
}Последнее состояние корта #
/v1/cv/court/{court}/healthДоступ: Клубный токен или токен игрока Либо X-Club-Token: <клубный токен>, либо Authorization: Bearer <токен игрока>. Если передан клубный токен, проверяется он.
Последнее состояние корта и возраст замера в секундах (ageS). С клубным токеном — корт своего клуба; с токеном игрока нужно указать club_id.
Параметры пути
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
court | строка | да | Имя корта, как на сервере корта |
Параметры запроса
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
club_id | строка | нет | Идентификатор клуба |
Заголовки
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
x-club-token | строка | нет | Клубный токен сервера корта |
Пример запроса
curl -X GET "https://адрес-вашего-сервера/api/v1/cv/court/court4/health?club_id=c1" \
-H "X-Club-Token: <клубный токен>"Ответы
| Код | detail | Значение |
|---|---|---|
| 200 | — | Успешный ответ; пример ниже |
| 401 | no_token | Нет токена в заголовке Authorization |
| 401 | invalid_token | Токен просрочен или подделан — обновите его через /v1/auth/refresh |
| 401 | bad_club_token | Неверный клубный токен |
| 400 | club_id_required | Для токена игрока нужен club_id |
| 404 | court_health_not_found | От корта ещё не было данных о здоровье |
| 422 | […] | Ошибка проверки параметров или тела: detail — список ошибок |
Пример ответа
{
"clubId": "c1",
"court": "Корт 4",
"ts": 1759418400.0,
"ageS": 4.2,
"cameras": [
{
"tag": "обзор-хайлайты",
"model": "",
"online": true,
"fps": 25.0,
"fails": 0,
"reopens": 0,
"last_frame_age_s": 0.1,
"bright": 0.0,
"temp_c": null,
"dropped": 0,
"drop_ratio": 0.0
}
],
"app": {"version": "1.2137", "uptime_s": 86400}
}Брони на корты клуба #
/v1/cv/court-bookingsДоступ: Клубный токен Заголовок X-Club-Token: <клубный токен> — токен сервера корта клуба.
Брони на корты клуба: идущая сейчас и ближайшие на три часа вперёд, с составом (без телефонов). Корты передаются повторяющимся параметром court (до 16) с именами, как на сервере корта; без него — все корты клуба. Корт, которого облако не знает, возвращается с known=false. Отменённые брони не возвращаются.
Параметры запроса
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
court | массив [строка] | нет | Имя корта, как на сервере корта элементов до 16 |
Заголовки
| Имя | Тип | Обязательно | Описание |
|---|---|---|---|
x-club-token | строка | да | Клубный токен сервера корта |
Пример запроса
curl -X GET "https://адрес-вашего-сервера/api/v1/cv/court-bookings?court=court4" \
-H "X-Club-Token: <клубный токен>"Ответы
| Код | detail | Значение |
|---|---|---|
| 200 | — | Успешный ответ; пример ниже |
| 401 | bad_club_token | Неверный клубный токен |
| 422 | […] | Ошибка проверки параметров или тела: detail — список ошибок |
Пример ответа
{
"clubId": "c1",
"now": "2026-10-02 18:05",
"courts": {
"court4": {
"known": true,
"current": {
"id": "b71c2e94-0a3f-4d58-8e26-9f1d3c5a7b40",
"court": "Корт 4",
"day": "2026-10-02",
"start": "18:00",
"end": "19:30",
"durationMin": 90,
"current": true,
"players": [
{
"user_id": "3d6e9a10-5b2c-4f8e-a1d7-6c4b2e0f9a83",
"name": "Иван Петров",
"short": "Иван П.",
"booker": true
}
]
},
"upcoming": []
}
}
}