Files
nike/docs/sportsdb_api_v1_reference.md
Robert Helewka ee8436d5b8 feat: implement MCP server and dashboard for football data platform
Add complete Nike football data platform with:
- FastMCP server exposing football data tools over HTTP
- RapidAPI client for free-api-live-football-data integration
- Bootstrap web dashboard with live match/standings views
- REST API endpoints for dashboard consumption
- Docker support with multi-stage build
- Comprehensive README with architecture docs
- Minimal .gitignore replacing verbose Python template
2026-03-21 18:19:42 +00:00

386 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TheSportsDB API V1 — Validated Reference
**Validated on:** 2026-03-09 using free test key `3`
**Base URL:** `https://www.thesportsdb.com/api/v1/json/{key}`
**Auth:** Key embedded in URL path (free test key: `3`, premium: same Patreon key as V2)
**Source samples:** `docs/api_samples/sportsdb/`
> **This document covers ONLY what was tested and confirmed with real API calls.**
> V2 endpoints are NOT covered — they require a premium key and have not been tested.
---
## Free Key Limitations
The free key (`3`) returns **limited results** (typically 5 rows for standings, returns wrong/random events for some lookups). The response **structure and field names** are valid regardless — only row count and relevance are affected.
---
## Validated IDs
| Entity | TheSportsDB ID | Notes |
|--------|----------------|-------|
| MLS | `4346` | Listed as "American Major League Soccer" |
| English Premier League | `4328` | "English Premier League" |
| Toronto FC | `134148` | Short: `TOR`, league: 4346 |
| Arsenal | `133604` | Short: `ARS`, league: 4328 |
| Federico Bernardeschi | `34148472` | Position: Winger |
---
## Endpoints
### 1. Search Teams
```
GET /searchteams.php?t={team_name}
```
**Tested with:** `Toronto FC`, `Arsenal`
**Response wrapper:** `{"teams": [...]}`
**Returns:** Array of matching team objects (1 exact match each)
#### Validated fields (all values are strings or null):
| Field | Example (TFC) | Example (Arsenal) | Notes |
|-------|---------------|-------------------|-------|
| `idTeam` | `"134148"` | `"133604"` | String, not int |
| `idESPN` | `null` | `"359"` | Nullable |
| `idAPIfootball` | `"1601"` | `"42"` | Cross-ref to API-Football |
| `intLoved` | `"2"` | `"9"` | String of int |
| `strTeam` | `"Toronto FC"` | `"Arsenal"` | Full name |
| `strTeamAlternate` | `""` | `"Arsenal Football Club, AFC, Arsenal FC"` | Comma-separated aliases |
| `strTeamShort` | `"TOR"` | `"ARS"` | 3-letter code |
| `intFormedYear` | `"2006"` | `"1892"` | String of int |
| `strSport` | `"Soccer"` | `"Soccer"` | |
| `strLeague` | `"American Major League Soccer"` | `"English Premier League"` | Primary league |
| `idLeague` | `"4346"` | `"4328"` | String of int |
| `strLeague2`..`strLeague7` | `"Leagues Cup"` | `"FA Cup"`, `"EFL Cup"`, etc. | Additional leagues/cups |
| `idLeague2`..`idLeague7` | `"5281"` | `"4482"`, `"4570"`, etc. | IDs for above |
| `strDivision` | `null` | `null` | |
| `idVenue` | `"16782"` | `"15528"` | String of int |
| `strStadium` | `"BMO Field"` | `"Emirates Stadium"` | |
| `strKeywords` | `""` | `"Gunners, Gooners"` | Nicknames |
| `strRSS` | `""` | `""` | |
| `strLocation` | `"Toronto, Ontario"` | `"Holloway, London, England"` | |
| `intStadiumCapacity` | `"30991"` | `"60338"` | String of int |
| `strWebsite` | `"www.torontofc.ca"` | `"www.arsenal.com"` | No protocol |
| `strFacebook` | `"www.facebook.com/torontofc"` | `"www.facebook.com/Arsenal"` | |
| `strTwitter` | `"twitter.com/torontofc"` | `"twitter.com/arsenal"` | |
| `strInstagram` | `"www.instagram.com/torontofc"` | `"instagram.com/arsenal"` | |
| `strDescriptionEN` | Full paragraph | Full paragraph | English bio, `\r\n` separated |
| `strDescriptionDE`..`strDescriptionPL` | `null` or text | `null` or text | 14 language variants |
| `strColour1` | `"#B81137"` | `"#EF0107"` | Hex color |
| `strColour2` | `"#455560"` | `"#023474"` | |
| `strColour3` | `""` | `"#9C824A"` | May be empty |
| `strGender` | `"Male"` | `"Male"` | |
| `strCountry` | `"Canada"` | `"England"` | |
| `strBadge` | URL | URL | PNG badge |
| `strLogo` | URL | URL | PNG logo |
| `strFanart1`..`strFanart4` | URL or null | URL or null | Fan art images |
| `strBanner` | URL | URL | Banner image |
| `strEquipment` | URL | URL | Kit/equipment image |
| `strYoutube` | `"www.youtube.com/torontofc"` | `"www.youtube.com/ArsenalTour"` | |
| `strLocked` | `"unlocked"` | `"unlocked"` | |
**Key observations:**
- ALL numeric values are returned as **strings** (e.g., `"134148"` not `134148`)
- Team can belong to up to 7 leagues/cups
- `idAPIfootball` provides cross-reference to API-Football IDs
---
### 2. Lookup Team by ID
```
GET /lookupteam.php?id={idTeam}
```
**Tested with:** `134148` (Toronto FC) — but free key returned Arsenal instead (known free-key bug)
**Response wrapper:** `{"teams": [...]}`
**Fields:** Identical to search response above.
**⚠ Free key pitfall:** Lookup by ID returned the wrong team (Arsenal instead of TFC). This is a free-key limitation, not a field-structure issue. The response shape is the same as search.
---
### 3. Previous Team Events (Last Results)
```
GET /eventslast.php?id={idTeam}
```
**Tested with:** `134148` (Toronto FC)
**Response wrapper:** `{"results": [...]}` ← Note: key is `results`, NOT `events`
**Returns:** Last 5 results for the team (free key returned 1)
#### Validated event fields:
| Field | Example | Notes |
|-------|---------|-------|
| `idEvent` | `"2425216"` | String |
| `idAPIfootball` | `"1514800"` | Cross-ref, nullable |
| `strEvent` | `"Toronto FC vs Polissya Zhytomyr"` | Match title |
| `strEventAlternate` | `"Polissya Zhytomyr @ Toronto FC"` | Away @ Home format |
| `strFilename` | `"Club Friendlies 2026-02-14 Toronto FC vs Polissya Zhytomyr"` | League + date + teams |
| `strSport` | `"Soccer"` | |
| `idLeague` | `"4569"` | String — league of this match |
| `strLeague` | `"Club Friendlies"` | |
| `strLeagueBadge` | URL | League badge |
| `strSeason` | `"2026"` | or `"2025-2026"` for Euro leagues |
| `strDescriptionEN` | `""` | Match description (usually empty) |
| `strHomeTeam` | `"Toronto FC"` | |
| `strAwayTeam` | `"Polissya Zhytomyr"` | |
| `intHomeScore` | `"2"` | String of int, null if not played |
| `intAwayScore` | `"1"` | String of int, null if not played |
| `intRound` | `"0"` | String, `"0"` for friendlies |
| `intSpectators` | `null` | Nullable |
| `strOfficial` | `""` | Referee, often empty |
| `strTimestamp` | `"2026-02-14T09:45:00"` | ISO-ish, no timezone |
| `dateEvent` | `"2026-02-14"` | YYYY-MM-DD |
| `dateEventLocal` | `"2026-02-14"` | Local date |
| `strTime` | `"09:45:00"` | UTC time |
| `strTimeLocal` | `"04:45:00"` | Local time |
| `strGroup` | `""` | Group stage name, nullable |
| `idHomeTeam` | `"134148"` | String |
| `strHomeTeamBadge` | URL | Team badge |
| `idAwayTeam` | `"140180"` | String |
| `strAwayTeamBadge` | URL | Team badge |
| `intScore` | `null` | Aggregate/extra score? Always null in sample |
| `intScoreVotes` | `null` | Always null |
| `strResult` | `""` | Text result summary, empty |
| `idVenue` | `"16782"` | String |
| `strVenue` | `"BMO Field"` | |
| `strCountry` | `"Canada"` | |
| `strCity` | `""` | Often empty |
| `strPoster` | `""` | Image URLs, often empty |
| `strSquare` | `""` | |
| `strFanart` | `null` | |
| `strThumb` | `""` | |
| `strBanner` | `""` | |
| `strMap` | `null` | |
| `strTweet1` | `""` | |
| `strVideo` | `""` | Highlight video URL |
| `strStatus` | `"Match Finished"` | See status values below |
| `strPostponed` | `"no"` | `"no"` or `"yes"` |
| `strLocked` | `"unlocked"` | |
**Known `strStatus` values:** `"Match Finished"`, `"Not Started"`, `null` (old events)
---
### 4. Next Team Events (Upcoming)
```
GET /eventsnext.php?id={idTeam}
```
**Tested with:** `134148` (Toronto FC)
**Response wrapper:** `{"events": [...]}` ← Note: key is `events` (different from `eventslast`)
**Returns:** Next 515 upcoming events
**Fields:** Identical to the event structure in section 3 above, except:
- `intHomeScore` / `intAwayScore` = `null` (not yet played)
- `strStatus` = `"Not Started"`
- `dateEventLocal` / `strTimeLocal` may be `null`
- `strThumb` may have a URL (match preview image)
**⚠ Free key pitfall:** Free key returned Bolton Wanderers fixtures instead of Toronto FC fixtures. The event field structure is still valid.
---
### 5. Event Lookup by ID
```
GET /lookupevent.php?id={idEvent}
```
**Tested with:** `2425216`
**Response wrapper:** `{"events": [...]}`
**Returns:** Single event in array
**Fields:** Same event structure as section 3. All fields present.
**⚠ Free key pitfall:** Free key returned a completely different event (Liverpool vs Swansea 2014 instead of TFC vs Polissya 2026). The field structure is still valid — the values are just wrong.
---
### 6. Search Players
```
GET /searchplayers.php?p={player_name}
```
**Tested with:** `Bernardeschi`
**Response wrapper:** `{"player": [...]}` ← Note: key is `player`, singular
**Returns:** Array of matching players
#### Validated fields:
| Field | Example | Notes |
|-------|---------|-------|
| `idPlayer` | `"34148472"` | String |
| `idTeam` | `"134781"` | String |
| `strPlayer` | `"Federico Bernardeschi"` | Full name |
| `strTeam` | `"Bologna"` | Current team (may not match Toronto FC if transferred) |
| `strSport` | `"Soccer"` | |
| `strThumb` | URL | Player thumbnail |
| `strCutout` | URL | Player cutout image |
| `strNationality` | `"Italy"` | |
| `dateBorn` | `"1994-02-16"` | YYYY-MM-DD |
| `strStatus` | `"Active"` | `"Active"` or `"Retired"` |
| `strGender` | `"Male"` | |
| `strPosition` | `"Winger"` | |
| `relevance` | `"28.93..."` | Float as string, search relevance score |
**Note:** V1 player search returns a **minimal** field set (13 fields). V2 `lookup/player/{id}` is expected to return full bio, height, weight, description, etc. — **not yet tested**.
---
### 7. Standings / League Table
```
GET /lookuptable.php?l={idLeague}&s={season}
```
**Tested with:** `l=4328&s=2025-2026` (EPL), `l=4346&s=2026` (MLS)
**Response wrapper:** `{"table": [...]}`
**Returns:** Standings rows (free key: 5 rows; premium: full table)
#### Validated fields:
| Field | Example (EPL) | Example (MLS) | Notes |
|-------|---------------|---------------|-------|
| `idStanding` | `"8793837"` | `"8790287"` | String |
| `intRank` | `"1"` | `"1"` | String of int |
| `idTeam` | `"133604"` | `"150261"` | String |
| `strTeam` | `"Arsenal"` | `"San Diego FC"` | |
| `strBadge` | URL `/tiny` | URL `/tiny` | Tiny badge variant |
| `idLeague` | `"4328"` | `"4346"` | String |
| `strLeague` | `"English Premier League"` | `"American Major League Soccer"` | |
| `strSeason` | `"2025-2026"` | `"2026"` | Euro vs US format |
| `strForm` | `"WWWDD"` | `"WWW"` | Recent results W/D/L |
| `strDescription` | `"Promotion - Champions League..."` | `"Promotion - MLS (Play Offs...)"` | Qualification zone |
| `intPlayed` | `"30"` | `"3"` | String of int |
| `intWin` | `"20"` | `"3"` | |
| `intLoss` | `"3"` | `"0"` | |
| `intDraw` | `"7"` | `"0"` | |
| `intGoalsFor` | `"59"` | `"8"` | |
| `intGoalsAgainst` | `"22"` | `"0"` | |
| `intGoalDifference` | `"37"` | `"8"` | |
| `intPoints` | `"67"` | `"9"` | |
| `dateUpdated` | `"2026-03-09 06:01:21"` | `"2026-03-09 06:00:50"` | Last update timestamp |
**Season format:**
- European leagues: `"2025-2026"`
- MLS / calendar-year leagues: `"2026"`
**MLS standings note:** MLS has two conferences (East & West). The API returns **both mixed together** — ranks restart at `1` for each conference. There is no explicit conference field. You must use the rank patterns or `strDescription` to infer conference grouping.
---
### 8. Events by Date
```
GET /eventsday.php?d={YYYY-MM-DD}&s=Soccer # all soccer events
GET /eventsday.php?d={YYYY-MM-DD}&l={idLeague} # filtered by league
```
**Tested with:** `d=2026-03-09&s=Soccer`, `d=2026-03-09&l=4346`
**Response wrapper:** `{"events": [...]}` or `{"events": null}`
**Fields:** Same event structure as section 3.
**Results:**
- Global soccer on 2026-03-09: Returned **1 event** (Melbourne Victory vs Western Sydney, from 2014) — free key returns stale/random data
- MLS on 2026-03-09: Returned `{"events": null}` — no matches or free key limitation
**⚠ Free key:** Events-by-date is unreliable on free key. The field structure matches section 3 but the actual events returned are wrong/old. Premium key should return correct current-day events.
---
## Response Patterns Summary
| Endpoint | Wrapper Key | Null when empty |
|----------|-------------|-----------------|
| `searchteams.php` | `teams` | Unknown (always had results) |
| `lookupteam.php` | `teams` | Unknown |
| `eventslast.php` | `results` | Unknown |
| `eventsnext.php` | `events` | Unknown |
| `lookupevent.php` | `events` | Unknown |
| `searchplayers.php` | `player` | Unknown |
| `lookuptable.php` | `table` | Unknown |
| `eventsday.php` | `events` | **Yes** — returns `{"events": null}` |
---
## Data Type Warning
**All numeric fields are returned as strings.** Every `id*`, `int*`, and numeric value must be cast to `int` before use:
```python
team_id = int(team["idTeam"]) # "134148" → 134148
capacity = int(team["intStadiumCapacity"]) # "30991" → 30991
```
---
## V1 Endpoints NOT Tested
These V1 endpoints exist in the API but were not called during discovery:
| Endpoint | Purpose |
|----------|---------|
| `searchevents.php` | Search events by name |
| `eventsnextleague.php` | Next events for a league |
| `eventspastleague.php` | Previous events for a league |
| `eventsseason.php` | All events in a season |
| `lookup_all_players.php` | Full squad (V1 version) |
| `lookupplayer.php` | Detailed player profile |
| `all_sports.php` | All sports (used only for health check) |
| `all_leagues.php` | All leagues |
| `search_all_leagues.php` | Search leagues |
---
## V2 Endpoints — NOT TESTED (Premium Key Required)
These are the V2 endpoints built into `nike/sportsdb.py` but **entirely untested**. All field names in the code are guessed from TheSportsDB OpenAPI docs and have NOT been validated against real responses.
| Endpoint | Guessed wrapper key | What we don't know |
|----------|--------------------|--------------------|
| `GET /search/league/{name}` | `search` | Actual field names |
| `GET /search/team/{name}` | `search` | May differ from V1 |
| `GET /search/player/{name}` | `search` | May differ from V1 |
| `GET /lookup/event/{id}` | `lookup` or `events` | May have extra fields vs V1 |
| `GET /lookup/event_stats/{id}` | `lookup` | Field names like `strStat`, `intHome`, `intAway` are **guesses** |
| `GET /lookup/event_timeline/{id}` | `lookup` | Field names like `intTime`, `strTimeline`, `strPlayer` are **guesses** |
| `GET /lookup/event_lineup/{id}` | `lookup` | Fields like `intSquadNumber`, `strSubstitute`, `strHome` are **guesses** |
| `GET /lookup/player/{id}` | `lookup` or `players` | May have `strHeight`, `strWeight`, `strDescriptionEN` |
| `GET /lookup/player_contracts/{id}` | Unknown | Unknown |
| `GET /lookup/player_honours/{id}` | Unknown | Unknown |
| `GET /lookup/event_tv/{id}` | Unknown | Unknown |
| `GET /lookup/venue/{id}` | Unknown | Unknown |
| `GET /list/teams/{leagueId}` | `list` | Unknown |
| `GET /list/seasons/{leagueId}` | `list` | Unknown |
| `GET /list/players/{teamId}` | `list` | Field names for squad members unknown |
| `GET /schedule/next/league/{id}` | Unknown | Unknown |
| `GET /schedule/previous/league/{id}` | Unknown | Unknown |
| `GET /schedule/next/team/{id}` | Unknown | Unknown |
| `GET /schedule/previous/team/{id}` | Unknown | May match V1 `eventsnext` or differ |
| `GET /schedule/full/team/{id}` | Unknown | Unknown |
| `GET /schedule/league/{id}/{season}` | Unknown | Unknown |
| `GET /livescore/soccer` | `livescore` | All field names **guessed** |
---
## Next Steps (After Getting Premium Key)
1. Set `SPORTSDB_KEY=your_key` in `.env`
2. Run `python scripts/discover_sportsdb.py` — it auto-detects premium and tests all V2 endpoints
3. Review saved JSON files in `docs/api_samples/sportsdb/`
4. Update this document with V2 validated fields
5. Fix `sportsdb.py` client if any URL paths or wrapper keys are wrong
6. Design `schema.sql` from real field names (current schema is drafted from guesses)
7. Fix `server.py` tool formatters to use actual field names