Football data API

Football data,
already shaped.

Fixtures, live scores, tables, match stats with xG, lineups, incidents, odds and model predictions — one consistent JSON object per match, across 53 competitions. No key, no account, no SDK.

Live · 304,140 objects stored · last write 33 min ago

GET /events/209549/stats
{
    "event_id": 209549,
    "stats": {
        "home": {
            "duels": 51,
            "fouls": 8,
            "passes": 281,
            "crosses": {
                "value": 5,
                "total": 18,
                "pct": 28
            },
            "punches": 1,
            "tackles": 32,
            "dribbles": {
                "value": 8,
                "total": 15,
                "pct": 53
            },
            "…": "… 50 more"
        },
        "away": {
            "duels": 49,
            "fouls": 10,
            "passes": 779,
            "crosses": {
                "value": 5,
                "total": 15,
                "pct": 33
            },
            "punches": 0,
            "tackles": 15,
            "dribbles": {
                "value": 17,
                "total": 35,
                "pct": 49
            },
            "…": "… 50 more"
        },
        "first_half": {
            "home": {
                "duels": 55,
                "fouls": 4,
                "passes": 153,
                "crosses": {
                    "value": 4,
                    "total": 13,
                    "pct": 31
                },
                "tackles": 18,
                "dribbles": {
                    "value": 5,
                    "total": 7,
                    "pct": 71
                },
                "offsides": 2,
                "…": "… 35 more"
            },
            "away": {
                "duels": 45,
                "fouls": 6,
                "passes": 323,
                "crosses": {
                    "value": 2,
                    "total": 9,
                    "pct": 22
                },
                "tackles": 5,
                "dribbles": {
                    "value": 8,
                    "total": 17,
                    "pct": 47
                },
                "offsides": 0,
                "…": "… 35 more"
            }
        },
        "second_half": {
            "home": {
                "duels": 48,
                "fouls": 4,
                "passes": 128,
                "crosses": {
                    "value": 1,
                    "total": 5,
                    "pct": 20
                },
                "tackles": 14,
                "dribbles": {
                    "value": 3,
                    "total": 8,
                    "pct": 38
                },
                "offsides": 1,
                "…": "… 35 more"
            },
            "away": {
                "duels": 52,
                "fouls": 4,
                "passes": 456,
                "crosses": {
                    "value": 3,
                    "total": 6,
                    "pct": 50
                },
                "tackles": 10,
                "dribbles": {
                    "value": 9,
                    "total": 18,
                    "pct": 50
                },
                "offsides": 0,
                "…": "… 35 more"
            }
        }
    },
    "xg_estimated": true,
    "shotmap": [
        {
            "gm": {
                "x": 0,
                "y": 53.2,
                "z": 26
            },
            "xg": 0.0741,
            "gml": "high-left",
            "min": 84,
            "pos": {
                "x": 6.3,
                "y": 38.7,
                "z": 0
            },
            "sit": "assisted",
            "body": "left-foot",
            "…": "… 6 more"
        },
        {
            "gm": {
                "x": 0,
                "y": 64.7,
                "z": 16.7
            },
            "xg": 0.0659,
            "gml": "left",
            "min": 83,
            "pos": {
                "x": 20.4,
                "y": 61.8,
                "z": 0
            },
            "sit": "fast-break",
            "body": "left-foot",
            "…": "… 5 more"
        },
        "… 26 more"
    ],
    "momentum": [
        {
            "m": 1,
            "v": 2
        },
        {
            "m": 2,
            "v": 12
        },
        "… 90 more"
    ],
    "average_positions": {
        "away": [
            {
                "n": 27,
                "x": 60.1,
                "y": 52.8,
                "pos": "F",
                "name": "E. Haaland",
                "player_id": 852
            },
            {
                "n": 130,
                "x": 50.2,
                "y": 51,
                "pos": "M",
                "name": "E. Anderson",
                "player_id": 548
            },
            "… 14 more"
        ],
        "home": [
            {
                "n": 49,
                "x": 42.9,
                "y": 85.9,
                "pos": "M",
                "name": "T. Mitchell",
                "player_id": 429
            },
            {
                "n": 43,
                "x": 26.9,
                "y": 46.7,
                "pos": "D",
                "name": "C. Richards",
                "player_id": 423
            },
            "… 14 more"
        ]
    },
    "xg_per_minute": [
        {
            "m": 2,
            "xg_home": 0.062,
            "xg_away": 0,
            "cum_home": 0.062,
            "cum_away": 0,
            "estimated": false
        },
        {
            "m": 3,
            "xg_home": 0.029,
            "xg_away": 0,
            "cum_home": 0.091,
            "cum_away": 0,
            "estimated": false
        },
        "… 22 more"
    ]
}
304.1k
stored objects
118k
fixtures, back to 1999
53
competitions
23
resource types

read live from the API, cached 5 min

Why it's different

A cache you can hammer, not a feed you can exhaust.

Every route reads a store that crons already filled. Your request never reaches the upstream provider, so it can't spend a quota, can't rate-limit someone else, and can't fail because a third party is having an afternoon.

No key, no account

Open CORS, GET-only by contract. Paste a URL in a browser and you have the data. Middleware refuses anything but GET ahead of routing, so a write route can never be added by accident.

Honest timestamps

Every response carries fetched_at. You always know how old the object is, rather than guessing from a cache header someone set once.

Coverage you can see

GET /leagues reports event_count and covered per competition — so you learn the shape of the store up front, not one 404 at a time.

Match stats with xG

Shotmaps with coordinates and per-shot xG, momentum series, average positions, duels, tackles, big chances — the deep object, not a scoreline.

Live, tables, odds

In-play fixtures, league standings, consensus odds plus a bookmaker comparison, Polymarket lines and stored model predictions.

Raw store access

When the shaped endpoints aren't the shape you want, /datasets/{resource} hands back the stored payload untouched, keyed and paginated.


What's inside

23 resources, live counts.

Pulled from GET /resources as you loaded this page — these are the real row counts, not a screenshot from launch day.

Core

leagues 53
events 117,965
events_live 0

Event

event 18,625
event_stats 17,984
event_lineups 17,739
event_incidents 17,960
event_player_stats 17,961
event_h2h 17,733
event_odds 17,090
event_odds_comparison 8,515
event_polymarket 676
event_prediction 1,785
event_metadata 16,534
event_broadcasts 16,541
event_social 16,544

League

league_detail 53
league_seasons 53
league_current_season 53
standings 58
league_top 0
league_bestxi 0
league_venues 53

The fixture shape

One object per match. The same one, everywhere.

Ids are strings, dates are ISO-8601 with an offset, scores are integers or null — never an empty string standing in for a missing number. The list endpoints and the detail endpoints agree.

GET /events?league_id=1&date_from=2026-09-04&per_page=2
[
    {
        "id": "209561",
        "date": "2026-09-04T19:00:00+00:00",
        "status": "notstarted",
        "league_id": "1",
        "season_id": "1058",
        "round": 3,
        "home_team": "Ipswich Town",
        "away_team": "Liverpool FC",
        "home_id": "200",
        "away_id": "1",
        "home_score": null,
        "away_score": null,
        "venue_id": "195"
    },
    {
        "id": "209563",
        "date": "2026-09-05T11:30:00+00:00",
        "status": "notstarted",
        "league_id": "1",
        "season_id": "1058",
        "round": 3,
        "home_team": "Newcastle United",
        "away_team": "Bournemouth",
        "home_id": "4",
        "away_id": "2",
        "home_score": null,
        "away_score": null,
        "venue_id": "4"
    }
]

Pricing

Free to explore. Paid when it's load-bearing.

The free tier is the whole store at 120 requests a minute. Paid tiers raise the ceiling and add bulk export, webhooks and a redistribution licence.

Explorer

Free
forever

The whole store, rate-limited. No card, no account.

Start now

Business

Custom
annual

Full-history dumps, a private mirror, or a schema of your own.

Talk to us

Compare every plan