Building with AI? Give your coding tool the whole reference in one file: https://soccer-data.co/llms.txt
SOCCER DATA Docs
Sign in Get API key
// Core concepts

Pagination

How list endpoints split results into pages, and how to read them all.

Endpoints that can return a lot of results hand them out a page at a time. You choose the page and its size, and a meta object tells you how many pages there are.

Which endpoints are paged

Endpoint Default page size Largest page
/competitions 20 100
/teams 20 100
/players 20 100
/matches 20 100
/coverage/leagues 100 200

Everything else returns the whole list in one go: seasons, squads, standings and timelines, for example. Those endpoints don't take page or per_page, and sending them gets 400 UNKNOWN_PARAMETER.

Parameters

Parameter Meaning
page Which page you want, counting from 1. Defaults to 1.
per_page How many results on each page. Defaults to 20 (100 on /coverage/leagues).

Both must be whole numbers above zero. A zero, a negative number, or a per_page above the maximum returns 400 BAD_REQUEST.

The meta object

Paged responses put a meta object next to data:

{
  "data": [],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 204,
    "total_pages": 11
  }
}
Field Meaning
page The page you asked for.
per_page The page size you asked for. The last page can hold fewer results.
total How many results there are across all pages.
total_pages How many pages that makes at this page size. 0 when nothing matched.

Reading every page

Ask for the largest page size, then keep going until page reaches meta.total_pages.

async function fetchAllPages(path, filters = {}) {
  const results = [];

  for (let page = 1; ; page++) {
    const params = new URLSearchParams({ ...filters, page: String(page), per_page: "100" });
    const res = await fetch(`https://api.soccer-data.co/v1${path}?${params}`, {
      headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
    });

    if (!res.ok) throw new Error(`Request failed with ${res.status}`);

    const body = await res.json();
    results.push(...body.data);

    if (page >= body.meta.total_pages) return results;
  }
}

const matches = await fetchAllPages("/matches", {
  competition_id: "competition_705uv6jm8myn",
  season_id: "season_hr1ie7yrsdmt",
});
console.log(`${matches.length} matches`);
import os, requests

def fetch_all_pages(path, **filters):
    results, page = [], 1
    while True:
        res = requests.get(
            f"https://api.soccer-data.co/v1{path}",
            params={**filters, "page": page, "per_page": 100},
            headers={"X-Api-Key": os.environ["SOCCER_DATA_KEY"]},
        )
        res.raise_for_status()
        body = res.json()
        results.extend(body["data"])
        if page >= body["meta"]["total_pages"]:
            return results
        page += 1

matches = fetch_all_pages("/matches", competition_id="competition_705uv6jm8myn", season_id="season_hr1ie7yrsdmt")
print(len(matches), "matches")

Note Every page is a separate request and counts against your rate limits. Narrow the list with filters such as competition_id, season_id, team_id, date_from and date_to before you page through it, rather than downloading everything and filtering in your code.

Going past the last page

  • On /matches, asking for a page after the last one returns 400 PAGE_OUT_OF_RANGE. The message tells you the page you asked for and how many pages there are.
  • On the other paged endpoints you get 200 with an empty data list. meta still shows the real total and total_pages.

Either way, the loop above never gets there, because it stops at meta.total_pages.

Ordering

The order of results is fixed, so pages don't shuffle between requests.

  • /matches lists the latest kickoff first (sort=-utc_date), so upcoming fixtures come before past results. Use sort=utc_date for the soonest first. Matches with the same kickoff time always come back in the same order.
  • /competitions, /teams and /players put the best match first when you search, and sort A to Z by name otherwise. Add sort=name to get A to Z with a search too.

One-result shortcuts

per_page=1 combined with the right filters and order answers common questions in one request:

# A team's next match
curl "https://api.soccer-data.co/v1/matches?team_id=team_f0ibhxdw7hs5&status=scheduled&sort=utc_date&per_page=1" \
  -H "X-Api-Key: YOUR_API_KEY"

# A team's latest result (the default order already puts it first)
curl "https://api.soccer-data.co/v1/matches?team_id=team_f0ibhxdw7hs5&status=finished&per_page=1" \
  -H "X-Api-Key: YOUR_API_KEY"

Without status and sort, per_page=1 gives you the team's match furthest in the future, which is rarely what you want.

Common mistakes

  • Sending page or per_page to an endpoint that isn't paged. It returns 400 UNKNOWN_PARAMETER.
  • Setting per_page too high, or page to zero. Both return 400 BAD_REQUEST.
  • Looping past total_pages on /matches. You get PAGE_OUT_OF_RANGE, not an empty page.
  • Forgetting the stop condition. Always compare page with meta.total_pages.
  • Paging through everything to filter in code. Filter in the request instead and save requests.