ScoreCards Widget API v1
LIVE DATA · FREE · NO KEY REQUIRED

ScoreCards Widget API

Embed live football scores on your website or mobile app with a single HTTP call. Returns JSON you can style however you like — no SDK, no login, no limits.

Endpoint

GET https://scorecards.in/api/widget/{league_id}
GET 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.

Sample Response 200 OK
{
  "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

index.html
<!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

LiveScores.jsx
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

live_scores_widget.dart
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:

Live scores powered by ScoreCards (scorecards.in)

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.