Syncing data
If your product stores sports data, sync it into your own database: import a season once, then refresh only what can change.
Samples use the small get() helper from Build a live score app. db.upsert stands in for your database’s insert-or-update.
What to store
Store the entities your product shows, keyed by sport and the API’s id — so a football team and a basketball team with the same number never collide. How often each one changes decides how often you refresh it:
| Entity | Endpoint | Changes |
|---|---|---|
| Leagues | /football/leagues | Rarely — mainly when a new season starts. |
| Teams | /football/teams | Rarely — names, codes and venues. |
| Players | /football/players | Transfers and season statistics. |
| Fixtures | /football/fixtures | Dates, status and scores — often on match days. |
| Standings | /football/standings | After every result. |
Initial import
Import one league-season at a time: reference data first, then fixtures. Every list is paginated, so walk every page — see Pagination.
async function* pages(path, params = {}) { for (let page = 1; ; page++) { const { data, meta } = await get(path, { ...params, page }); yield* data; if (page * meta.per_page >= meta.total) return; }}async function importSeason(leagueId, season) { const filters = { league_id: leagueId, season }; // Reference data first, then fixtures that point at it for await (const team of pages("/football/teams", filters)) { await db.upsert("football_teams", team.id, team); } for await (const fixture of pages("/football/fixtures", filters)) { await db.upsert("football_fixtures", fixture.id, fixture); }}await importSeason(39, 2026);Keeping data fresh
After the import, refresh each entity on its own schedule and only fetch what can have changed:
- Fixtures — refresh today and the next few days on a schedule. While matches are in play, follow them with the live endpoint instead (see Handling live matches).
- Standings — re-fetch a league’s table when one of its fixtures reaches
FT. - Teams and players — refresh on a slow schedule, such as nightly, and whenever you meet an ID you don’t have yet.
- Leagues — check for a new
current_seasonaround the start of each season.
// Run on a schedule: refresh fixtures for the next few daysasync function refreshUpcoming(leagueId, days) { for (let i = 0; i < days; i++) { const date = new Date(Date.now() + i * 86400000).toISOString().slice(0, 10); for await (const fixture of pages("/football/fixtures", { league_id: leagueId, date })) { await db.upsert("football_fixtures", fixture.id, { ...fixture, synced_at: new Date() }); } }}Handling changes
- Upsert, never insert. Fixtures change after you first see them: kick-off times move, statuses and scores update.
- Key by id, never by date and teams. A rescheduled fixture has a new date.
- Update, don’t delete. Postponed (
PST) and cancelled (CANC) fixtures are statuses — store them so your users see what happened. - Record when you synced. A
synced_atcolumn per row makes stale data easy to find.
Budgeting requests
Every page is one request, so you can estimate a sync before you schedule it. A 20-team league with 380 fixtures needs 1 page of teams and 8 pages of fixtures (380 ÷ 50): the initial import costs about 9 requests. Following live matches at the recommended 60-second interval costs one request a minute, however many fixtures are in play.
Compare that with your plan — the Free plan includes [X,XXX] requests / month — and see Rate Limits for the per-minute limit.