The free key reads yesterday again: our one-month trial of today on the free key has finished. Read the changelog

Upsets

L
GET /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
One day, ISO 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
Earliest kickoff date, inclusive. ISO YYYY-MM-DD, evaluated in UTC. e.g. 2024-08-01
date_to string query
Latest kickoff date, inclusive. e.g. 2024-08-31
league_id string query
Restrict to one competition. Ids come from /v1/leagues: 39 is the English Premier League, 62 is Ligue 2. e.g. lg_24T9Z0G
country string query
Restrict to one country, by name as returned from /v1/countries. e.g. England
team_id string query
Matches involving this club, home or away. e.g. tm_1E5HXK9
min_size integer query
Only upsets at least this big, 0 to 100. Defaults to 0, every upset. e.g. 60
sort string query
latest (newest first, the default) or size (biggest first). e.g. size
page integer query
Page number, 1-based. The response carries 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
Rows per page, 1–1000. Defaults to 100. e.g. 100

Returns

data[].match_id string
Our id for the match. Stable: what you store today still resolves later.
data[].kickoff_utc string
Kickoff, as a UTC timestamp.
data[].league_id string
Our id for the competition.
data[].league_name string
The competition's name.
data[].country_name string
The country the competition belongs to.
data[].home_team_id string
Our id for the home side.
data[].home_team_name string
The home side's name.
data[].away_team_id string
Our id for the away side.
data[].away_team_name string
The away side's name.
data[].home_goals integer
The home side's goals: the final score, after extra time where played.
data[].away_goals integer
The away side's goals: the final score, after extra time where played.
data[].score_after_90 string
The score after 90 minutes, the one upsets are judged on.
data[].outcome string
What happened: a win for the side not expected to win, or a draw against a strong favourite.
data[].underdog_side string
home or away: the side that was not expected to get the result.
data[].underdog_name string
That side's name.
data[].chance_before number
Its chance of the result before kickoff, 0 to 1.
data[].chance_from string
Where that chance came from: the exchange price at kickoff, or our team ratings.
data[].price_taken empty in this example
The exchange price behind that chance, as decimal odds.
data[].upset_size integer
0 to 100: how far under the 30% line the chance fell.
data[].reasons list
The reasons, in plain words.
meta.data_as_of string
When the data behind this answer was last brought up to date.
meta.request_id string
This request's id, to quote to support.
meta.count integer
How many items are in this answer.
meta.page integer
This page's number.
meta.per_page integer
How many items a page holds.
meta.sort string
Sort.
meta.min_size integer
Min size.

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"

Example response

A real row from the archive, fetched when this page rendered. Run it against your own key below.

200 OK application/json
{
    "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.

Get a key
GET