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

Pagination

Page numbers for pagers, cursors for exports. Same endpoints, either style.

Walking a set you already have the ids for? That is a different job: see batch requests, which does it in one request rather than one per id.

Two ways, one endpoint

Every list endpoint pages both ways, and you can switch between them mid-stream. Page numbers suit a UI pager or a bounded query, because the response tells you how many there are in total. Cursors suit exports and catch-up, because a deep page stays fast and the boundary cannot move underneath you.

If you send both, page wins.

Page numbers

Ask for a page with page, which is 1-based.

curl "https://api.footballsoccerapi.com/v1/matches?league_id=39&season=2024&page=2&limit=100" \
  -H "X-API-Key: $FSAPI_KEY"
{
  "meta": {
    "count": 100,
    "next_cursor": "MTcyOTMzNzQwMDoxMjA4MDk5",
    "page": 2,
    "per_page": 100,
    "total": 380,
    "total_pages": 4,
    "next_page": 3,
    "prev_page": 1
  },
  "data": [ ... ]
}

You do not have to ask for page 1 explicitly. Any first call, one with no cursor, already carries the totals, so you learn the size of the result set from the same request that gives you the first rows.

Page numbers are the wrong tool for very large exports. A deep offset gets progressively slower, and if new matches land while you iterate, a page boundary shifts and you can see a row twice or miss one. For those, use a cursor.

Cursors

A cursor points at a position in the sort order rather than counting from the start. Page 10,000 costs the same as page 1, and rows arriving underneath the query cannot move the boundary. Follow meta.next_cursor until it is absent.

curl "https://api.footballsoccerapi.com/v1/matches?has_prices=true&limit=1000" \
  -H "X-API-Key: $FSAPI_KEY"
# -> meta.next_cursor: "MTcyOTMzNzQwMDoxMjA4MDk5"

curl "https://api.footballsoccerapi.com/v1/matches?has_prices=true&limit=1000&cursor=MTcyOTMzNzQwMDoxMjA4MDk5" \
  -H "X-API-Key: $FSAPI_KEY"

Cursors are opaque. Pass meta.next_cursor back unchanged and do not build or parse one yourself: the format is ours to change. When it is absent you have reached the end.

A continuation stays lean: it returns count and next_cursor only. You already learned the totals from the first call, and counting them again on every page of a 300-call export is work nobody asked for.

let cursor = null;
do {
  const url = "https://api.footballsoccerapi.com/v1/matches?has_prices=true&limit=1000"
            + (cursor ? "&cursor=" + cursor : "");
  const r = await fetch(url, { headers: { "X-API-Key": key } });
  const { data, meta } = await r.json();
  handle(data);
  cursor = meta.next_cursor;
} while (cursor);

Which to reach for

Use page for UI pagers, for jumping to a specific page, and for small or date-bounded queries where you want to show someone how many results there are.

Use cursor for bulk exports, full-archive pulls, training sets, and catching up after a disconnect.

You are not locked in. A page-numbered response also carries next_cursor, so you can start on page 1 and switch to cursors once you are past the first page.

Page size

limit takes 1–1000 and defaults to 100. It applies to both styles.

The whole priced archive is around 803,876 matches, which at 1,000 a page is roughly 300 calls: comfortably inside the Archive monthly allowance.