api-football-v3

SKILL.md

API-Football v3 – Developer Skill

Guide for consuming the API-Football v3 API. For detailed endpoint parameters and response schemas, see endpoints.md. For complete TypeScript type definitions, see types.ts.

Quick Start

const API_KEY = process.env.API_FOOTBALL_KEY;
if (!API_KEY) throw new Error("API_FOOTBALL_KEY is not set");

const BASE = "https://v3.football.api-sports.io";

// Only GET requests. Only x-apisports-key header. No extra headers!
const res = await fetch(`${BASE}/fixtures?live=all`, {
  headers: { "x-apisports-key": API_KEY },
});
const data = await res.json();
console.log(`${data.results} live matches`);

Authentication

  • Base URL: https://v3.football.api-sports.io/{endpoint}
  • Auth Header: x-apisports-key: YOUR_API_KEY
  • Method: Only GET requests
  • CORS: Supported, but always proxy through your backend to hide the key
  • Extra headers: Do NOT add Content-Type or any other header — the API will reject it

Some frameworks (especially JS/Node) automatically add extra headers. Make sure to remove them or the API will return an error.

// api-football.client.ts
export async function apiFetch<T>(
  endpoint: string,
  params: Record<string, string | number> = {}
): Promise<ApiResponse<T>> {
  const url = new URL(`https://v3.football.api-sports.io/${endpoint}`);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v)));

  const res = await fetch(url.toString(), {
    method: "GET",
    headers: { "x-apisports-key": process.env.API_FOOTBALL_KEY! },
  });
  if (!res.ok) throw new Error(`API error ${res.status}`);
  return res.json();
}

API-SPORTS Account & Dashboard

Register and manage your API key at: dashboard.api-football.com

The dashboard allows you to:

  • Monitor your API consumption in real time
  • Manage your subscription and upgrade if needed
  • Check the status of API-Football servers
  • Test all endpoints without writing a line of code (Live Tester)

You can also check your account status programmatically via the GET /status endpoint — this call does NOT count towards your daily quota.

const { response } = await apiFetch<StatusResponse>("status");
console.log(response);

Response:

{
  "account": {
    "firstname": "John",
    "lastname": "Doe",
    "email": "john@example.com"
  },
  "subscription": {
    "plan": "Free",
    "end": "2025-04-10T23:24:27+00:00",
    "active": true
  },
  "requests": {
    "current": 12,
    "limit_day": 100
  }
}

Rate Limiting

Response Headers

Every API response includes these rate-limit headers:

Header Description
x-ratelimit-requests-limit Daily quota
x-ratelimit-requests-remaining Remaining today
X-RateLimit-Limit Max per minute
X-RateLimit-Remaining Remaining this minute

Plans

Plan Daily Per Minute
Free 100 30
Pro 300
Ultra 450
Mega 600

⚠️ Rate Limit Blocking Policy

If you exceed the allowed request rate per minute — either through continuous excessive usage or abnormal traffic spikes — your access may be temporarily or permanently blocked by their firewall without prior notice. Design your app with proper caching and background sync to avoid this.

Endpoint Quick Reference

All 38 endpoints at a glance. Detailed parameters and response schemas in endpoints.md.

Category Endpoint Required Params Best For
Reference
Timezone GET /timezone Get IANA timezones for date filters
Countries GET /countries Country list with flags
Leagues GET /leagues Leagues/cups with season coverage
Seasons GET /leagues/seasons All available years
Teams
Info GET /teams 1 of: id/name/league+season/search Team profiles
Statistics GET /teams/statistics league, season, team Season stats, form, goals per minute
Seasons GET /teams/seasons team Years team has data
Countries GET /teams/countries Countries with teams
Venues
Venues GET /venues 1 of: id/name/city/country/search Stadium info
Standings
Table GET /standings league, season League standings
Fixtures
Fixtures GET /fixtures varies (see endpoints.md) Scheduled/live/finished matches
Rounds GET /fixtures/rounds league, season Round names in a season
Head to Head GET /fixtures/headtohead h2h (e.g. 33-34) Past meetings between two teams
Statistics GET /fixtures/statistics fixture Match stats (shots, possession)
Events GET /fixtures/events fixture Goals, cards, subs, VAR
Lineups GET /fixtures/lineups fixture Starting XI, formation, bench
Players GET /fixtures/players fixture Per-player match stats
Injuries
Injuries GET /injuries league+season or fixture Injury list
Predictions
Predictions GET /predictions fixture Win probabilities, H2H, advice
Coaches
Coaches GET /coachs 1 of: id/team/search Coach profiles & career history
Players
Stats GET /players season + 1 of: id/team/league/search Player season stats (paginated)
Profiles GET /players/profiles All available players
Seasons GET /players/seasons Available player data years
Squads GET /players/squads team or player Current team roster
Career GET /players/teams player Teams played during career
Top Scorers GET /players/topscorers league, season Top 20 goals
Top Assists GET /players/topassists league, season Top 20 assists
Top Yellow GET /players/topyellowcards league, season Top 20 yellow cards
Top Red GET /players/topredcards league, season Top 20 red cards
Transfers
Transfers GET /transfers player or team Transfer records
Trophies
Trophies GET /trophies player or coach Trophies won
Sidelined
Sidelined GET /sidelined player or coach Injury/suspension history
Odds (In-Play)
Live Odds GET /odds/live In-play betting odds
Live Bet Types GET /odds/live/bets Available in-play bet types
Odds (Pre-Match)
Pre-match Odds GET /odds fixture, league, or date Betting odds (paginated)
Mapping GET /odds/mapping Fixture-to-odds mapping
Bookmakers GET /odds/bookmakers Available bookmakers
Bet Types GET /odds/bets Available pre-match bet types

Common Tasks

Get Live Matches

const { response } = await apiFetch<Fixture[]>("fixtures", { live: "all" });
for (const match of response) {
  const { teams, goals, fixture } = match;
  console.log(`${teams.home.name} ${goals.home} - ${goals.away} ${teams.away.name} (${fixture.status.elapsed}')`);
}

Get League Standings

const { response } = await apiFetch<StandingsEntry[]>("standings", {
  league: 39, season: 2024
});
const table = response[0].league.standings[0]; // First group
for (const row of table) {
  console.log(`${row.rank}. ${row.team.name}${row.points} pts (${row.form})`);
}

Get Match Events (Goals, Cards, Subs)

const { response } = await apiFetch<FixtureEvent[]>("fixtures/events", {
  fixture: 215662
});
for (const evt of response) {
  console.log(`${evt.time.elapsed}' [${evt.type}] ${evt.player.name}${evt.detail}`);
}

Get Player Stats

const { response, paging } = await apiFetch<PlayerEntry[]>("players", {
  team: 33, season: 2024
});
// Paginated — 20 per page. paging.total shows total pages.
for (const p of response) {
  const stat = p.statistics[0];
  console.log(`${p.player.name}: ${stat.goals.total ?? 0} goals, ${stat.games.appearences ?? 0} apps`);
}

Check Coverage Before Fetching

const { response } = await apiFetch<LeagueEntry[]>("leagues", { id: 39 });
const season = response[0].seasons.find((s) => s.current);

if (season?.coverage.fixtures.lineups) { /* lineups available */ }
if (season?.coverage.standings) { /* standings available */ }
if (season?.coverage.predictions) { /* predictions available */ }
if (season?.coverage.odds) { /* odds available */ }

Fixture Status Codes

Code Meaning Code Meaning
TBD TBD NS Not Started
1H First Half HT Halftime
2H Second Half ET Extra Time
BT Break Time P Penalty Shootout
SUSP Suspended INT Interrupted
FT Full Time AET After Extra Time
PEN After Penalty PST Postponed
CANC Cancelled ABD Abandoned
AWD Tech. Loss WO Walk Over

Caching Strategy

import { Redis } from "ioredis";
const redis = new Redis(process.env.REDIS_URL!);

async function cachedFetch<T>(key: string, ttl: number, fetcher: () => Promise<T>): Promise<T> {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);
  const data = await fetcher();
  await redis.setex(key, ttl, JSON.stringify(data));
  return data;
}

Recommended TTLs

Data Type TTL Example
Live fixtures 60s cachedFetch("live", 60, ...)
Standings 1 hour cachedFetch("standings:39", 3600, ...)
Leagues / Countries 24 hours cachedFetch("leagues", 86400, ...)
Players / Transfers 24 hours cachedFetch("players:33", 86400, ...)
Predictions 24 hours cachedFetch("pred:215662", 86400, ...)

Rule: Never poll the API directly inside an HTTP request handler. Use a background cron job to populate the cache, and serve from cache in your routes.

Error Handling

export async function fetchWithRetry<T>(endpoint: string, params = {}, retries = 3): Promise<ApiResponse<T>> {
  for (let i = 0; i < retries; i++) {
    try {
      const ctrl = new AbortController();
      const timeout = setTimeout(() => ctrl.abort(), 10000);
      const url = new URL(`https://v3.football.api-sports.io/${endpoint}`);
      Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v as string)));

      const res = await fetch(url.toString(), {
        headers: { "x-apisports-key": process.env.API_FOOTBALL_KEY! },
        signal: ctrl.signal,
      });
      clearTimeout(timeout);
      if (res.ok) return res.json();
      if (res.status >= 500) throw new Error(`Server ${res.status}`);
      throw new Error(`Client ${res.status}`);
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
    }
  }
  throw new Error("Unreachable");
}

Always check errors in the response:

const data = await apiFetch<Fixture[]>("fixtures", { id: 215662 });
if (data.errors && Object.keys(data.errors).length > 0) {
  throw new Error(`API errors: ${JSON.stringify(data.errors)}`);
}

Security

Always proxy through your backend. Never expose your API key to the browser.

Browser / App → Your Backend (holds API_FOOTBALL_KEY) → v3.football.api-sports.io

Media URLs (Logos / Images)

Calls to logos and images are free and don't count towards your daily quota. However, they are subject to a rate per second & minute, so you should cache/save them on your side to avoid throttling.

Resource URL Pattern
Team Logo https://media.api-sports.io/football/teams/{id}.png
Player Photo https://media.api-sports.io/football/players/{id}.png
League Logo https://media.api-sports.io/football/leagues/{id}.png
Country Flag https://media.api-sports.io/flags/{code}.svg

CDN Recommendation: Use a CDN like BunnyCDN to cache and serve media assets. API-Football has a setup tutorial.

⚠️ Intellectual Property Notice: Logos, images, and trademarks are provided solely for identification purposes. API-Football does not own these assets. Some may be subject to third-party IP rights. You are responsible for ensuring your usage complies with applicable laws.

Pagination

Endpoints like /players, /odds, and /players/profiles return 20 items per page.

async function fetchAllPages<T>(endpoint: string, params: Record<string, string | number>): Promise<T[]> {
  const all: T[] = [];
  let page = 1, total = 1;
  do {
    const data = await apiFetch<T[]>(endpoint, { ...params, page });
    all.push(...data.response);
    total = data.paging.total;
    page++;
  } while (page <= total);
  return all;
}

Widgets (Embeddable Components)

API-Football provides ready-to-use web components (Widgets v3.1) that display live data directly on your website. They use requests from your API-SPORTS account and work with all plans including free.

Quick Setup

1. Include the script:

<script type="module" src="https://widgets.api-sports.io/3.1.0/widgets.js"></script>

2. Add the config widget (once per page):

<api-sports-widget
  data-type="config"
  data-key="Your-Api-Key-Here"
  data-sport="football"
  data-lang="en"
  data-theme="dark"
></api-sports-widget>

3. Insert widgets anywhere:

<!-- Match list -->
<api-sports-widget data-type="games"></api-sports-widget>

<!-- League standings -->
<api-sports-widget data-type="standings" data-league="39" data-season="2024"></api-sports-widget>

Available Widget Types

Widget data-type Description
Match List games Scheduled, live, finished matches
Match Detail game Single match with events, stats, lineups
Standings standings League table
Team Profile team Team stats, roster, venue
Player Profile player Player stats, injuries, trophies
League Schedule league Full season schedule & results
All Leagues leagues Browse leagues by country
Head-to-Head h2h Historical comparison between two teams

Themes

Built-in: white (default), grey, dark, blue. Or create custom themes with CSS variables:

api-sports-widget[data-theme="MyTheme"] {
  --primary-color: #18cfc0;
  --text-color: #333;
  --background-color: #fff;
  --border: 1px solid #95959530;
}

Dynamic Targeting

Widgets can open other widgets when clicked:

<!-- Click a match → opens detail in modal -->
<api-sports-widget data-type="games"></api-sports-widget>

<api-sports-widget
  data-type="config"
  data-key="Your-Key"
  data-sport="football"
  data-target-game="modal"
  data-target-team="#team-container"
></api-sports-widget>

<div id="team-container"></div>

Target options: modal or CSS selector (#id, .class).

Available targets: data-target-game, data-target-standings, data-target-team, data-target-player, data-target-league.

Multi-language

Built-in: en, fr, es, it. Custom translations via JSON:

<api-sports-widget
  data-type="config"
  data-key="Your-Key"
  data-sport="football"
  data-lang="custom"
  data-custom-lang="https://yourdomain.com/lang/custom.json"
></api-sports-widget>

Download the translation template: https://widgets.api-sports.io/3.1.0/en.json

⚠️ Widget Security & Caching

  • Your API key is visible in the HTML source. Restrict allowed domains/IPs in the dashboard
  • Hide your key from source code: follow the caching & security tutorial
  • Without caching, each visitor triggers API requests. With 60-second cache, you reduce 115,200 daily requests to just 1,440
  • Debug errors with data-show-errors="true" on any widget

Full widget docs: https://api-sports.io/documentation/widgets/v3

Sample Scripts (Other Languages)

cURL

curl --request GET \
  --url 'https://v3.football.api-sports.io/leagues' \
  --header 'x-apisports-key: XxXxXxXxXxXxXxXxXxXxXxXx'

Python

import http.client

conn = http.client.HTTPSConnection("v3.football.api-sports.io")
headers = { 'x-apisports-key': "YOUR_API_KEY" }

conn.request("GET", "/leagues", headers=headers)
res = conn.getresponse()
print(res.read().decode("utf-8"))

Node.js (Axios)

const axios = require('axios');

axios.get('https://v3.football.api-sports.io/leagues', {
  headers: { 'x-apisports-key': 'YOUR_API_KEY' }
})
.then(res => console.log(res.data))
.catch(err => console.error(err));

PHP (cURL)

$curl = curl_init();
curl_setopt_array($curl, [
  CURLOPT_URL => 'https://v3.football.api-sports.io/leagues',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ['x-apisports-key: YOUR_API_KEY'],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;

Go

req, _ := http.NewRequest("GET", "https://v3.football.api-sports.io/leagues", nil)
req.Header.Add("x-apisports-key", "YOUR_API_KEY")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))

Next Steps

Weekly Installs
1
First Seen
11 days ago
Installed on
kilo1
windsurf1
amp1
cline1
openclaw1
opencode1