Rychlý start
Každý požadavek musí obsahovat hlavičku x-api-key. Klíč získáte ze svého profilu v HockeyBook aplikaci (pole User.apiKey).
Base URL (prod)https://api.hockeybook.online
Base URL (dev)http://localhost:7500
Prefix/api
Formátapplication/json
Interaktivní docs/docs (Swagger UI)
OpenAPI spec/openapi.yaml
curl -H "x-api-key: YOUR_KEY" \
https://api.hockeybook.online/api/leagues
Autentizace
API používá API klíč v HTTP hlavičce x-api-key. Klíč je svázán s konkrétním uživatelem. Všechny endpointy automaticky filtrují data pouze pro ligy, ve kterých je tento uživatel uveden jako administrátor.
- Chybějící hlavička →
401 Unauthorized - Neplatný klíč nebo nedostatečná oprávnění →
403 Forbidden - Klíč nikdy neposílejte z frontendu prohlížeče; používejte ho výhradně server-to-server.
Limity, cache & CORS
Rate limit
Každý API klíč má limit 120 požadavků za minutu na všechny /api/* endpointy (klouzavé okno 60 s). Pokud limit překročíte, API vrátí 429 Too Many Requests s hlavičkou Retry-After. Pro vyšší limity nás kontaktujte.
Cache
Všechny GET endpointy vracejí Cache-Control: private, max-age=…, stale-while-revalidate=… a slabý ETag. Hlavička Vary: x-api-key, Accept-Encoding zajišťuje, že per-user data nemohou být sdílena přes proxy/CDN.
- Eventy, rozpis, kalendář, pinned:
max-age=30, SWR 60 s - Tabulka, statistiky, playoff, ligy:
max-age=120, SWR 5 min - Články, stránky, kempy:
max-age=600, SWR 30 min
Pošlete If-None-Match s předchozím ETag a server odpoví 304 Not Modified bez payloadu, pokud se data nezměnila.
Velikost požadavku
Maximální velikost JSON těla je 100 kB (vrací 413 Payload Too Large).
CORS
API je primárně určené pro server-to-server volání. Volání z prohlížeče je povoleno pouze z explicitně whitelistovaných origins. Klíč nikdy nevystavujte v klientském JavaScriptu — proxy ho přes vlastní backend.
Chybové odpovědi
Všechny chyby vrací JSON ve tvaru:
{ "error": "popis chyby", "code": "INVALID_MONTH" }
Pole code je stabilní strojově čitelný identifikátor (může chybět u 500).
| Status | Code | Význam |
|---|
400 | INVALID_ID | Neplatný ObjectId v URL |
400 | INVALID_MONTH | Formát month není YYYY-MM |
400 | TOO_MANY_ORGS | V orgs je víc než 30 položek |
400 | MISSING_PARAMS | Chybí povinný query parametr |
400 | INVALID_PHASE | Neznámá hodnota phase |
400 | INVALID_SLUG | Slug neodpovídá ^[a-z0-9][a-z0-9_-]{0,199}$ |
400 | VALIDATION | Validační chyba payloadu |
401 | MISSING_API_KEY | Chybí hlavička x-api-key |
403 | INVALID_API_KEY | Neplatný klíč |
403 | NO_ACCESS | K této lize/organizaci nemáte přístup |
404 | NO_SEASON | Liga nemá aktivní sezónu |
404 | NOT_FOUND | Záznam (článek, stránka, sezóna) neexistuje |
429 | — | Překročen rate limit |
500 | — | Interní chyba serveru |
Přehled endpointů
| Metoda | Cesta | Popis |
|---|
| GET | /health | Health check (bez auth) |
| GET | /api/leagues | Seznam lig uživatele |
| GET | /api/leagues/:leagueId/events | Eventy ligy (stránkované) |
| GET | /api/leagues/:leagueId/schedule | Poslední + nadcházející zápasy |
| GET | /api/orgs/:orgId/camps | Kempy organizace |
| GET | /api/orgs/:orgId/pinned-events | Připnuté nadcházející eventy |
| GET | /api/calendar | Eventy v měsíci napříč orgs |
| GET | /api/leagues/:leagueId/standings | Tabulka týmů |
| GET | /api/leagues/:leagueId/stats/skaters | Statistiky hráčů |
| GET | /api/leagues/:leagueId/stats/goalies | Statistiky brankářů |
| GET | /api/leagues/:leagueId/playoff | Playoff (skupiny, bracket) |
| GET | /api/articles | Publikované články |
| GET | /api/articles/:slug | Detail článku |
| GET | /api/pages | Publikované stránky |
| GET | /api/pages/:slug | Detail stránky |
GET /api/leagues
Vrací pole lig, kde je uživatel admin.
Odpověď
[
{
"_id": "65f0a1b2c3d4e5f60718293a",
"title": "Hobby Liga Norte",
"urlTitle": "hobby-liga-norte",
"type": "league",
"status": "active",
"imageUrl": "/uploads/leagues/abc.png",
"primaryColor": "#00C2FF",
"secondaryColor": "#0B1525",
"activeSeason": {
"_id": "65f0...",
"year": "2025/2026",
"active": true,
"dateFrom": "2025-09-01T00:00:00.000Z",
"dateTo": "2026-04-30T00:00:00.000Z"
},
"location": "Praha",
"isPublic": true
}
]
GET /api/leagues/:leagueId/events
Eventy dané ligy. Stránkováno.
Query parametry
| Parametr | Typ | Výchozí | Popis |
|---|
seasonId | string | activeSeason | ID sezony |
limit | int | 50 (max 200) | Počet záznamů |
page | int | 1 | Stránka |
Odpověď
{
"events": [
{
"_id": "...",
"title": "HC Praha vs HC Brno",
"type": "match",
"dateFromRaw": "2026-01-15T18:00:00.000Z",
"dateFrom": "15.1.2026",
"timeFrom": "18:00",
"place": "Zimní stadion Praha",
"homeTeamId": { "_id": "...", "name": "HC Praha", "imageUrl": "...", "primaryTeamColor": "#fff", "secondaryTeamColor": "#000" },
"awayTeamId": { "_id": "...", "name": "HC Brno", "imageUrl": "..." },
"gameStatus": { "matchCompleted": true, "home": { "goals": 3 }, "away": { "goals": 2 }, "overtime": true },
"group": "A"
}
],
"total": 42, "page": 1, "limit": 50
}
GET /api/leagues/:leagueId/schedule
Posledních past dohraných + nejbližších upcoming zápasů.
Query parametry
| Parametr | Typ | Výchozí |
|---|
seasonId | string | activeSeason |
past | int | 5 (max 50) |
upcoming | int | 5 (max 50) |
Odpověď
{
"schedule": [
{
"_id": "...",
"dateFromRaw": "2026-01-15T18:00:00.000Z",
"dateFrom": "15.1.2026",
"timeFrom": "18:00",
"place": "Zimní stadion Praha",
"homeTeam": { "_id": "...", "name": "HC Praha", "imageUrl": "..." },
"awayTeam": { "_id": "...", "name": "HC Brno", "imageUrl": "..." },
"score": "3:2pp",
"completed": true,
"group": "A"
}
]
}
Skóre má tvar H:A; přípona pp = prodloužení, sn = nájezdy.
GET /api/orgs/:orgId/camps
Kempy organizace (eventy s ikonou camp nebo typem camp). Vrací budoucí + posledních ~2 měsíce.
Odpověď
{
"camps": [
{
"_id": "...",
"title": "Letní kemp 2026",
"dateFromRaw": "2026-07-01T08:00:00.000Z",
"dateToRaw": "2026-07-07T16:00:00.000Z",
"dateFrom": "1.7.2026",
"dateTo": "7.7.2026",
"timeFrom": "08:00",
"timeTo": "16:00",
"place": "ZS Slavia",
"description": "Celodenní kemp pro děti 8–14 let.",
"price": 4500
}
],
"org": { "_id": "...", "title": "HC Praha" }
}
GET /api/orgs/:orgId/pinned-events
Připnuté budoucí eventy (max 10). Pokud má organizace nastavený displayTeam, zahrnuje i eventy, kde tento tým hraje.
Odpověď
{
"events": [
{
"_id": "...",
"title": "HC Praha vs HC Brno",
"type": "match",
"icon": "mdi-hockey-puck",
"dateFromRaw": "2026-02-10T19:00:00.000Z",
"dateFrom": "10.2.2026",
"timeFrom": "19:00",
"place": "ZS Slavia",
"homeTeam": { "_id": "...", "name": "HC Praha", "imageUrl": "..." },
"awayTeam": { "_id": "...", "name": "HC Brno", "imageUrl": "..." },
"players": { "capacity": 20, "signedUp": 14, "substitutes": 2 },
"goalies": { "capacity": 2, "signedUp": 2, "substitutes": 0 },
"isLeagueMatch": true
}
]
}
GET /api/calendar
Eventy daného měsíce napříč více organizacemi.
Query parametry (povinné)
| Parametr | Typ | Příklad |
|---|
month | string | 2026-07 (formát YYYY-MM) |
orgs | string | id1,id2,id3 (CSV ObjectId, max 30) |
Odpověď
{
"events": [
{
"_id": "...",
"title": "HC Praha vs HC Brno",
"type": "match",
"icon": "default",
"dateFromRaw": "2026-07-12T18:00:00.000Z",
"dateFrom": "12.7.2026",
"timeFrom": "18:00",
"place": "ZS Slavia",
"league": "65f0...",
"homeTeam": { "_id": "...", "name": "HC Praha", "shortName": "PRA", "imageUrl": "..." },
"awayTeam": { "_id": "...", "name": "HC Brno", "shortName": "BRN", "imageUrl": "..." },
"homeAttendants": { "players": 12, "goalies": 1 },
"awayAttendants": { "players": 11, "goalies": 1 },
"score": "3:2",
"completed": true,
"players": { "capacity": 40, "signedUp": 23, "substitutes": 4 },
"goalies": { "capacity": 4, "signedUp": 2, "substitutes": 0 },
"isLeagueMatch": true
}
],
"orgs": [
{ "_id": "...", "title": "HC Praha", "color": "#00C2FF", "displayTeam": "..." }
]
}
GET /api/leagues/:leagueId/standings
Tabulka týmů pro sezónu (s vyřešením shody bodů přes nastavené tiebreakery).
Query parametry
seasonId | volitelné, výchozí activeSeason |
Odpověď
{
"league": { "_id": "...", "title": "Hobby Liga", "urlTitle": "hobby-liga" },
"season": { "_id": "...", "year": "2025/2026", "groupsCount": 2 },
"standings": [
{
"team": { "_id": "...", "name": "HC Praha", "imageUrl": "...", "primaryTeamColor": "#fff", "secondaryTeamColor": "#000" },
"gamesPlayed": 24,
"wins": 18, "overtimeWins": 1, "overtimeLosses": 2,
"losses": 3, "draws": 0,
"goalsFor": 96, "goalsAgainst": 42, "goalDifference": 54,
"points": 58
}
]
}
GET /api/leagues/:leagueId/stats/skaters
Statistiky bruslařů. Agregováno přes všechny fáze (regular/playoff/groups/consolation) do jednoho řádku per hráč. Filtrováno na minimální počet odehraných zápasů.
Query parametry
| Parametr | Hodnoty | Výchozí |
|---|
seasonId | string | activeSeason |
phase | all, regular, playoff, groups, consolation | all |
sort | points, goals, assists, plusMinus, … | points |
order | asc / desc | desc |
limit | int (max 200) | 50 |
page | int | 1 |
Odpověď
{
"skaters": [
{
"player": { "_id": "...", "firstName": "Jan", "lastName": "Novák", "nickName": "Honza", "avatar": "..." },
"team": { "_id": "...", "name": "HC Praha", "imageUrl": "..." },
"gamesPlayed": 22,
"goals": 18, "assists": 24, "points": 42,
"penaltyMinutes": 14,
"plusMinus": 12,
"powerPlayGoals": 5,
"shotsOnGoal": 72,
"shootingPct": 25.0,
"pointsPerGame": 1.91
}
],
"total": 87, "page": 1, "limit": 50
}
GET /api/leagues/:leagueId/stats/goalies
Statistiky brankářů. Stejná logika filtrování jako u skaterů.
Query parametry
| Parametr | Hodnoty | Výchozí |
|---|
seasonId | string | activeSeason |
phase | all, regular, playoff, groups, consolation | all |
sort | savePct, goalsAgainstAverage, wins, … | savePct |
order | asc / desc | desc |
limit | int (max 200) | 50 |
page | int | 1 |
Odpověď
{
"goalies": [
{
"player": { "_id": "...", "firstName": "Petr", "lastName": "Svoboda", "avatar": "..." },
"team": { "_id": "...", "name": "HC Praha", "imageUrl": "..." },
"gamesPlayed": 18,
"wins": 14, "losses": 3, "ties": 1,
"shutouts": 2,
"goalsAgainst": 32,
"goalsAgainstAverage": 1.78,
"saves": 412,
"shotsAgainst": 444,
"savePct": 92.79,
"penaltyMinutes": 0
}
],
"total": 12, "page": 1, "limit": 50
}
GET /api/leagues/:leagueId/playoff
Kompletní playoff payload: skupiny s tabulkami, útěšenecká skupina, konfigurace bracketu (větve, postupová pravidla, zápasy o umístění), série vítězství a všechny playoff eventy pro vykreslení pavouka.
Odpověď (zkráceno)
{
"league": { "_id": "...", "title": "Hobby Liga", "urlTitle": "hobby-liga" },
"season": { "_id": "...", "year": "2025/2026" },
"playoffGroups": [
{
"label": "A",
"standings": [
{
"team": { "_id": "...", "name": "HC Praha", "imageUrl": "..." },
"placeholder": false,
"seed": 0,
"label": "ZČ-1",
"gamesPlayed": 6, "wins": 5, "overtimeWins": 0, "overtimeLosses": 1,
"losses": 0, "goalsFor": 24, "goalsAgainst": 9, "goalDifference": 15,
"points": 16, "bonusPoints": 5
}
]
}
],
"consolationGroup": [ /* stejné schéma jako standings entry */ ],
"playoffConfig": {
"scenarioType": "groups+bracket",
"groupCount": 2,
"groupFormat": "round-robin",
"consolationGroupEnabled": true,
"consolationGroupName": "Utěšenecká skupina",
"branches": [
{
"id": "main",
"name": "Hlavní pavouk",
"color": "#00C2FF",
"teams_from": "groups",
"team_count": 8,
"preliminary_rounds": 0,
"preliminary_series": 0,
"rounds": [ { "name": "Čtvrtfinále", "series": 4 }, { "name": "Semifinále", "series": 2 } ]
}
],
"advancementRules": [
{ "id": "r1", "description": "Top 4 z A+B postupují", "source": {...}, "target": {...}, "pairing": null }
],
"placementMatches": [
{ "place": "3", "name": "O 3. místo", "teams_from": "semifinále", "series": 1 }
]
},
"groupResults": [
{ "groupIndex": 0, "groupLetter": "A", "position": 1,
"teamId": "...", "teamName": "HC Praha", "teamLogo": "...",
"points": 21, "placeholder": false, "label": "ZČ-1" }
],
"seriesWins": {
"65f01_65f02": { "65f01": 2, "65f02": 1 }
},
"bracketEvents": [
{
"_id": "...",
"playoffMeta": { "branchId": "main", "roundIndex": 0, "seriesIndex": 0, "gameIndex": 0, "roundType": "quarterfinal" },
"meta": { "homeTeam": { "_id": "...", "name": "HC Praha", "imageUrl": "..." },
"awayTeam": { "_id": "...", "name": "HC Brno", "imageUrl": "..." } },
"gameStatus": { "matchCompleted": true, "homeGoals": 3, "awayGoals": 2 }
}
]
}
Klíč v seriesWins je teamIdA_teamIdB (seřazené ID), hodnota je mapa teamId → počet vyhraných zápasů v sérii.
GET /api/articles
Publikované články ze všech lig uživatele (kromě typu page).
Query parametry
limit | int (max 20), výchozí 9 |
page | int, výchozí 1 |
Odpověď
{
"articles": [
{
"_id": "...",
"urlTitle": "vitezstvi-hc-praha",
"title": "Vítězství HC Praha 5:1",
"type": "news",
"publishDate": "2026-01-20T10:00:00.000Z",
"intro": "HC Praha porazila…",
"imageUrl": "/uploads/articles/abc.jpg"
}
],
"total": 17, "page": 1, "pages": 2
}
GET /api/articles/:slug
Detail článku podle urlTitle.
{
"_id": "...",
"urlTitle": "vitezstvi-hc-praha",
"title": "Vítězství HC Praha 5:1",
"type": "news",
"publishDate": "2026-01-20T10:00:00.000Z",
"intro": "HC Praha porazila…",
"content": "<p>Plný HTML obsah článku…</p>",
"imageUrl": "/uploads/articles/abc.jpg"
}
GET /api/pages
Publikované stránky (články typu page).
{
"pages": [
{
"_id": "...",
"urlTitle": "o-nas",
"title": "O nás",
"publishDate": "2025-12-01T00:00:00.000Z",
"intro": "Krátký úvod…",
"imageUrl": "..."
}
]
}
GET /api/pages/:slug
Detail stránky podle urlTitle.
{
"_id": "...",
"urlTitle": "o-nas",
"title": "O nás",
"type": "page",
"publishDate": "2025-12-01T00:00:00.000Z",
"intro": "Krátký úvod…",
"content": "<p>Plný HTML obsah stránky…</p>",
"imageUrl": "..."
}