Querying historical data
The same endpoints that serve live data also serve history. This guide focuses on the patterns you'll use to back-fill a warehouse, run season analyses, or compute team form over time.
A single day
The ?date= filter on listMatches returns every match (live, scheduled or
finished) whose kickoff falls on that UTC date.
curl "https://api.scorelytics.pro/v1/football/matches?date=2026-04-15" \
-H "X-API-Key: sk_live_..."
A range of days
The API doesn't expose a ?from=…&to=… range filter — call ?date=
in a loop and aggregate client-side.
from datetime import date, timedelta
import requests
def days_between(start: date, end: date):
d = start
while d <= end:
yield d
d += timedelta(days=1)
def fetch_day(d: date, key: str):
r = requests.get(
"https://api.scorelytics.pro/v1/football/matches",
params={"date": d.isoformat()},
headers={"X-API-Key": key},
)
r.raise_for_status()
return r.json()["data"]
all_matches = []
for d in days_between(date(2026, 4, 1), date(2026, 4, 30)):
all_matches.extend(fetch_day(d, "sk_live_..."))
For longer ranges, run those calls concurrently with a small worker pool — 6–10 parallel requests is comfortable; see Rate Limits for the exact ceiling on your plan.
A whole league season
To pull every match of a season, combine ?league_id= with date paging:
# All matches of LaLiga's 2025/26 season — paginate by date
for d in $(date -I -d "2025-08-15") ... ; do
curl -s "https://api.scorelytics.pro/v1/football/matches?league_id=LaLigaID&date=$d" \
-H "X-API-Key: sk_live_..."
done | jq -s '.[].data | add'
If the league publishes a fixture calendar, prefer iterating those calendar days. Otherwise, the safe bound is your season window.
Per-match enrichment
Once you have a list of match IDs, fan out across the enrichment endpoints in parallel. Stats and lineups are produced during the match and persist forever after, so the same calls that work for live also work historically.
import asyncio, httpx
async def enrich(match_id, client):
async def get(suffix):
r = await client.get(
f"/v1/football/matches/{match_id}{suffix}",
headers={"X-API-Key": "sk_live_..."}
)
return r.json() if r.status_code == 200 else None
events, stats, lineups = await asyncio.gather(
get("/events"),
get("/stats"),
get("/lineups"),
)
return {"id": match_id, "events": events, "stats": stats, "lineups": lineups}
async def run(ids):
async with httpx.AsyncClient(
base_url="https://api.scorelytics.pro", timeout=10.0
) as client:
sem = asyncio.Semaphore(8)
async def bound(m):
async with sem: return await enrich(m, client)
return await asyncio.gather(*(bound(m) for m in ids))
A semaphore of 8 keeps you well within typical rate limits and finishes a 380-match season in about a minute.
Standings over time
To build "league position by week" charts, query
GET /v1/football/matches/{id}/standings for the last match of each week.
The standings field returns the table as it stood at that match — exactly
what you need for time-series charts.
Idempotency for back-fills
All read endpoints are idempotent — feel free to re-run your back-fill jobs.
Use updated_at to detect changes:
-- Pseudocode for an upsert key in your warehouse
INSERT INTO matches (...)
VALUES (...)
ON CONFLICT (id) DO UPDATE
SET ... WHERE excluded.updated_at > matches.updated_at;
Storage tips
For long-term storage, normalize match IDs and team IDs into your own
dimension tables and store the raw events, stats, standings and h2h
JSON pass-through fields in JSONB columns. That's resilient to schema
additions on our side and gives you full SQL access to all the data.
What's next?
- Pagination — for
listMatcheswith a highlimit. - Rate Limits — concurrency ceilings.
- API Reference — exact response schemas.