Upsets
L/v1/upsets
The results nobody expected, with the reasons
The results nobody expected, each with the reasons in plain words: the chance the winner had before kickoff, the rating gap between the two sides and the gap in the table. Judged on the score after 90 minutes, the result the exchange market settles on.
With an exchange price at kickoff, an upset is a win by a side with a 30% chance or less, the three prices taken together so the margin is shared out, or a draw against a favourite with 70% or more. With no price, it is judged by our team ratings before kickoff: a win by a side expected to take 30% of the points or less. upset_size runs from 0 to 100, how far under the 30% line it fell.
The latest seven days by default, newest first; give a day or a range, a competition, a country or a club, and sort by size for the biggest first. On the Live & Archive plan only. A match whose score our sources still disagree on is left out until it is settled.
Plan
This endpoint needs Live & Archive.
Calling it on a lower plan returns a 403 naming the plan it needs and
a link to pricing, never a bare refusal.
Parameters
date
string
query
YYYY-MM-DD in UTC. Leave out date, date_from and date_to for the last seven days. A range is up to 92 days. e.g. 2026-10-25
date_from
string
query
YYYY-MM-DD, evaluated in UTC. e.g. 2024-08-01
date_to
string
query
2024-08-31
league_id
string
query
/v1/leagues: 39 is the English Premier League, 62 is Ligue 2. e.g. lg_24T9Z0G
country
string
query
/v1/countries. e.g. England
team_id
string
query
tm_1E5HXK9
min_size
integer
query
60
sort
string
query
latest (newest first, the default) or size (biggest first). e.g. size
page
integer
query
total, total_pages, next_page and prev_page, so a UI pager has everything it needs from one call. If you send both page and cursor, page wins. e.g. 2
limit
integer
query
100
Returns
data[].match_id
string
data[].kickoff_utc
string
data[].league_id
string
data[].league_name
string
data[].country_name
string
data[].home_team_id
string
data[].home_team_name
string
data[].away_team_id
string
data[].away_team_name
string
data[].home_goals
integer
data[].away_goals
integer
data[].score_after_90
string
data[].outcome
string
data[].underdog_side
string
data[].underdog_name
string
data[].chance_before
number
data[].chance_from
string
data[].price_taken
empty in this example
data[].upset_size
integer
data[].reasons
list
meta.data_as_of
string
meta.request_id
string
meta.count
integer
meta.page
integer
meta.per_page
integer
meta.sort
string
meta.min_size
integer
Request
curl "https://api.footballsoccerapi.com/v1/upsets?date=2026-10-25&date_from=2024-08-01&date_to=2024-08-31" \ -H "X-API-Key: $FSAPI_KEY"
const res = await fetch("https://api.footballsoccerapi.com/v1/upsets?date=2026-10-25&date_from=2024-08-01&date_to=2024-08-31", { headers: { "X-API-Key": process.env.FSAPI_KEY } }); const { data, meta } = await res.json();
import os, requests r = requests.get( "https://api.footballsoccerapi.com/v1/upsets?date=2026-10-25&date_from=2024-08-01&date_to=2024-08-31", headers={"X-API-Key": os.environ["FSAPI_KEY"]}, ) data = r.json()["data"]
$ch = curl_init("https://api.footballsoccerapi.com/v1/upsets?date=2026-10-25&date_from=2024-08-01&date_to=2024-08-31"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["X-API-Key: {$key}"], ]); $data = json_decode(curl_exec($ch), true)["data"];
Example response
A real row from the archive, fetched when this page rendered. Run it against your own key below.
{
"data": [
{
"match_id": "mt_1X4J1FR",
"kickoff_utc": "2026-10-28T11:30:00Z",
"league_id": "lg_34N4YNV",
"league_name": "Cup",
"country_name": "Poland",
"home_team_id": "tm_06V01EV",
"home_team_name": "Siarka Tarnobrzeg",
"away_team_id": "tm_2X3XZ1Z",
"away_team_name": "Lech Poznan",
"home_goals": 1,
"away_goals": 0,
"score_after_90": "1-0",
"outcome": "home",
"underdog_side": "home",
"underdog_name": "Siarka Tarnobrzeg",
"chance_before": 7.6,
"chance_from": "rating",
"price_taken": null,
"upset_size": 75,
"reasons": [
"Siarka Tarnobrzeg were expected to take 8% of the points by our ratings before kickoff",
"Rated 493 points below Lech Poznan before kickoff"
]
},
{
"match_id": "mt_0PJMKAG",
"kickoff_utc": "2026-10-28T11:00:00Z",
"league_id": "lg_03R7V1D",
"league_name": "Cup",
"country_name": "Ukraine",
"home_team_id": "tm_3R455NB",
"home_team_name": "LNZ Cherkasy",
"away_team_id": "tm_09KN980",
"away_team_name": "Metal Kharkiv",
"home_goals": 0,
"away_goals": 1,
"score_after_90": "0-1",
"outcome": "away",
"underdog_side": "away",
"underdog_name": "Metal Kharkiv",
"chance_before": 8.4,
"chance_from": "rating",
"price_taken": null,
"upset_size": 72,
"reasons": [
"Metal Kharkiv were expected to take 8% of the points by our ratings before kickoff",
"Rated 355 points below LNZ Cherkasy before kickoff",
"Won away from home"
]
}
],
"meta": {
"data_as_of": "2026-10-29T04:32:57Z",
"request_id": "01J9QW3C7M4KX2VB",
"count": 2,
"page": 1,
"per_page": 100,
"sort": "size",
"min_size": 0
}
}
Run it live
Paste your key and fire the real request. The key is kept in this browser only and never sent anywhere but the API.