Endpoint
https://scorecards.in/api/widget/{league_id}
https://scorecards.in/api/widget/{league_id}/{season}
If season is omitted, the latest tracked season is used automatically.
FIFA World Cup 2026
https://scorecards.in/api/widget/1/2026
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
league_id |
integer | Yes | API-Football league ID. FIFA World Cup 2026 = 1. |
season |
integer | No | 4-digit year (e.g. 2026). Defaults to the latest tracked season. |
Response
All responses are application/json. Timestamps are UTC ISO 8601.
{
"success": true,
"league": {
"id": 1,
"name": "FIFA World Cup 2026",
"season": 2026,
"logo": "https://media.api-sports.io/football/leagues/1.png",
"flag": "https://media.api-sports.io/flags/world.svg",
"country": "World"
},
"live": [
{
"fixture_id": 1234567,
"state": "live",
"status": "2H",
"status_label": "67'",
"elapsed": 67,
"round": "Group Stage - 1",
"scheduled_at": "2026-06-15T18: 00: 00Z",
"home_team": "Brazil",
"home_team_logo": "https://media.api-sports.io/football/teams/6.png",
"away_team": "Argentina",
"away_team_logo": "https://media.api-sports.io/football/teams/26.png",
"home_score": 1,
"away_score": 0,
"match_url": "https://scorecards.in/match/1234567/fifa-world-cup-2026/brazil-vs-argentina"
}
],
"recently_finished": [
{
"fixture_id": 1234500,
"state": "finished",
"status": "FT",
"status_label": "Full Time",
"elapsed": null,
"round": "Group Stage - 1",
"scheduled_at": "2026-06-15T15: 00: 00Z",
"home_team": "France",
"home_team_logo": "https://media.api-sports.io/football/teams/2.png",
"away_team": "Germany",
"away_team_logo": "https://media.api-sports.io/football/teams/25.png",
"home_score": 2,
"away_score": 1,
"match_url": "https://scorecards.in/match/1234500/fifa-world-cup-2026/france-vs-germany"
}
],
"live_count": 1,
"finished_count": 1,
"served_at": "2026-06-15T18: 12: 44+00: 00",
"cache_age_seconds": 38,
"powered_by": {
"name": "ScoreCards",
"url": "https://scorecards.in",
"attribution": "Live scores powered by ScoreCards (scorecards.in)"
}
}
Field Reference — Match Object
| Field | Type | Description |
|---|---|---|
fixture_id |
integer | Unique match ID. |
state |
string | "live" | "finished" | "upcoming" |
status |
string | Short status code: 1H, HT, 2H, ET, P, FT, AET, PEN, NS… |
status_label |
string | Human-readable: "67'", "Half Time", "Full Time", etc. |
elapsed |
int|null | Minutes played. null when not in play. |
round |
string | e.g. "Group Stage - 1", "Quarter-finals" |
scheduled_at |
string | Kickoff datetime in UTC (ISO 8601). |
home_team |
string | Home team name. |
home_team_logo |
string | URL to home team crest image. |
away_team |
string | Away team name. |
away_team_logo |
string | URL to away team crest image. |
home_score |
int|null | Home goals. null for upcoming matches. |
away_score |
int|null | Away goals. null for upcoming matches. |
match_url |
string | Full canonical URL to the match page on ScoreCards. |
Code Examples
Poll every 30 seconds while matches are live. When no matches are live, polling once per minute is sufficient.
HTML / JS Plain JavaScript — no framework needed
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Live Scores</title>
<style>
body { font-family: sans-serif; background: #0f172a; color: #e2e8f0; padding: 1rem; }
.match-card { background: #1e293b; border-radius: 12px; padding: 1rem; margin-bottom: 1rem; }
.teams { display: flex; justify-content: space-between; align-items: center; font-size: 1rem; font-weight: 700; }
.score { font-size: 1.5rem; font-weight: 900; color: #34d399; }
.badge { font-size: 0.65rem; background: #065f46; color: #6ee7b7; padding: 2px 8px;
border-radius: 100px; font-weight: 800; letter-spacing: 0.05em; }
.ft-badge { background: #78350f; color: #fde68a; }
.powered { font-size: 0.7rem; color: #475569; margin-top: 1.5rem; }
.powered a { color: #38bdf8; text-decoration: none; }
</style>
</head>
<body>
<div id="live-scores">Loading…</div>
<script>
const LEAGUE_ID = 1;
const SEASON = 2026;
const API_URL = `https://scorecards.in/api/widget/${LEAGUE_ID}/${SEASON}`;
async function fetchScores() {
try {
const res = await fetch(API_URL);
const data = await res.json();
if (!data.success) return;
const all = [...data.live, ...data.recently_finished];
if (all.length === 0) {
document.getElementById('live-scores').innerHTML =
'<p style="color:#64748b">No matches live right now.</p>';
return;
}
const html = all.map(m => `
<div class="match-card">
<div style="margin-bottom:6px">
<span class="badge ${m.state === 'finished' ? 'ft-badge' : ''}">
${m.status_label}
</span>
<span style="font-size:0.7rem;color:#64748b;margin-left:6px">${m.round ?? ''}</span>
</div>
<div class="teams">
<span>${m.home_team}</span>
<span class="score">${m.home_score} – ${m.away_score}</span>
<span>${m.away_team}</span>
</div>
<div style="margin-top:8px;font-size:0.7rem">
<a href="${m.match_url}" target="_blank"
style="color:#38bdf8;text-decoration:none">View match →</a>
</div>
</div>
`).join('');
document.getElementById('live-scores').innerHTML =
html + `<p class="powered">
Live scores powered by
<a href="${data.powered_by.url}" target="_blank">${data.powered_by.name}</a>
</p>`;
} catch (e) {
console.error('ScoreCards widget error:', e);
}
}
// Initial load, then poll every 30 seconds
fetchScores();
setInterval(fetchScores, 30_000);
</script>
</body>
</html>
REACT React Hook + Component
import { useState, useEffect } from 'react';
const WIDGET_URL = 'https://scorecards.in/api/widget/1/2026';
export default function LiveScores() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
async function load() {
try {
const res = await fetch(WIDGET_URL);
setData(await res.json());
} catch (_) {}
setLoading(false);
}
useEffect(() => {
load();
const timer = setInterval(load, 30_000);
return () => clearInterval(timer);
}, []);
if (loading) return <p>Loading scores…</p>;
if (!data?.success) return <p>Unable to load scores.</p>;
const matches = [...(data.live ?? []), ...(data.recently_finished ?? [])];
return (
<div>
<h2>{data.league.name}</h2>
{matches.length === 0 && <p>No matches live right now.</p>}
{matches.map(m => (
<div key={m.fixture_id} style={{ border: '1px solid #334155', borderRadius: 12,
padding: '12px 16px', marginBottom: 10 }}>
<div style={{ fontSize: 11, color: m.state === 'live' ? '#34d399' : '#f59e0b',
fontWeight: 800, marginBottom: 6 }}>
{m.status_label} · {m.round}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontWeight: 700 }}>{m.home_team}</span>
<span style={{ fontSize: 22, fontWeight: 900 }}>
{m.home_score} – {m.away_score}
</span>
<span style={{ fontWeight: 700 }}>{m.away_team}</span>
</div>
<a href={m.match_url} target="_blank" rel="noreferrer"
style={{ fontSize: 11, color: '#38bdf8', marginTop: 6, display: 'block' }}>
View match →
</a>
</div>
))}
<p style={{ fontSize: 11, color: '#475569', marginTop: 16 }}>
Live scores powered by{' '}
<a href={data.powered_by.url} target="_blank" rel="noreferrer"
style={{ color: '#38bdf8' }}>
{data.powered_by.name}
</a>
</p>
</div>
);
}
FLUTTER Dart — fetch + auto-refresh
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class LiveScoresWidget extends StatefulWidget {
final int leagueId;
final int season;
const LiveScoresWidget({super.key, required this.leagueId, required this.season});
@override
State<LiveScoresWidget> createState() => _LiveScoresWidgetState();
}
class _LiveScoresWidgetState extends State<LiveScoresWidget> {
Map<String, dynamic>? _data;
Timer? _timer;
@override
void initState() {
super.initState();
_load();
_timer = Timer.periodic(const Duration(seconds: 30), (_) => _load());
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
Future<void> _load() async {
final url = Uri.parse(
'https://scorecards.in/api/widget/${widget.leagueId}/${widget.season}',
);
try {
final res = await http.get(url);
if (res.statusCode == 200) {
setState(() => _data = jsonDecode(res.body));
}
} catch (_) {}
}
@override
Widget build(BuildContext context) {
if (_data == null) {
return const Center(child: CircularProgressIndicator());
}
final live = List<Map>.from(_data!['live'] ?? []);
final finished = List<Map>.from(_data!['recently_finished'] ?? []);
final matches = [...live, ...finished];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_data!['league']['name'] ?? '',
style: const TextStyle(fontWeight: FontWeight.w900, fontSize: 18),
),
const SizedBox(height: 12),
if (matches.isEmpty)
const Text('No matches live right now.',
style: TextStyle(color: Colors.grey)),
...matches.map((m) => _MatchCard(match: m)),
const SizedBox(height: 12),
Text(
'Live scores powered by ${_data!['powered_by']['name']}',
style: const TextStyle(fontSize: 11, color: Colors.grey),
),
],
);
}
}
class _MatchCard extends StatelessWidget {
final Map match;
const _MatchCard({required this.match});
@override
Widget build(BuildContext context) {
final isLive = match['state'] == 'live';
return Container(
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isLive ? const Color(0xFF34D399) : const Color(0xFFF59E0B),
width: 0.8,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
match['status_label'] ?? '',
style: TextStyle(
fontWeight: FontWeight.w800,
fontSize: 11,
color: isLive ? const Color(0xFF34D399) : const Color(0xFFF59E0B),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text(match['home_team'] ?? '',
style: const TextStyle(fontWeight: FontWeight.w700))),
Text(
'${match['home_score']} – ${match['away_score']}',
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.w900,
color: Colors.white),
),
Expanded(child: Text(match['away_team'] ?? '',
textAlign: TextAlign.right,
style: const TextStyle(fontWeight: FontWeight.w700))),
],
),
],
),
);
}
}
Requires http: ^1.2.0 in pubspec.yaml.
Attribution
Required attribution
You must display the following credit somewhere visible in your widget or app screen:
Linking to https://scorecards.in is appreciated but not mandatory.
The powered_by object in every response contains the ready-made attribution string.
Caching & Limits
How fresh is the data?
Scores are refreshed from the upstream API at most once per minute during live matches.
Check the cache_age_seconds field in the response
to know how old the current data is.
Recommended poll interval
- During live matches: every 30 seconds
- No live matches: every 60 seconds
- Off-season: once on load, no polling needed
HTTP caching
Responses carry Cache-Control: public, max-age=20.
A CDN or service worker can serve cached responses automatically within that window.
No rate limits (currently)
The API is free and open with no key required. Please be respectful — poll no faster than every 15 seconds. Abusive polling may be rate-limited without notice.