api-football-v3
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
GETrequests - CORS: Supported, but always proxy through your backend to hide the key
- Extra headers: Do NOT add
Content-Typeor 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
- For detailed endpoint parameters, response schemas, and use cases → see endpoints.md
- For complete TypeScript type definitions → see types.ts
- For official docs → https://www.api-football.com/documentation-v3
- For dashboard & live tester → https://dashboard.api-football.com