# 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