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
386 lines
16 KiB
Markdown
386 lines
16 KiB
Markdown
# 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 5–15 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
|