# API Football V3

> Comprehensive development guide for integrating the API-Football v3 API (api-sports.io). Covers authentication (header-based API key), rate limiting, all 38 endpoints grouped by category, TypeScript types, caching, error handling, and security. Triggers on any task involving football livescores, fixtures, standings, odds, transfers, player stats, or team data from API-Football.

- Skill: `silkyland/api-football-v3` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add silkyland/api-football-v3`
- Raw SKILL.md: https://api.skillmd.com/api/skills/silkyland/api-football-v3/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- License: MIT
- Author: silkyland (https://skillmd.com/u/silkyland)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/silkyland/api-football-v3

---


# API-Football v3 – Developer Skill

Guide for consuming the [API-Football v3](https://www.api-football.com/documentation-v3) API. For detailed endpoint parameters and response schemas, see endpoints.md. For complete TypeScript type definitions, see types.ts.

## Quick Start

```typescript
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.

```typescript
// 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](https://dashboard.api-football.com/register)

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.**

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

Response:
```json
{
  "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

```typescript
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

```typescript
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)

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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:**

```typescript
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](https://bunny.net) to cache and serve media assets. API-Football has a [setup tutorial](https://www.api-football.com/news/post/optimizing-sports-websites-bunnycdn-api-sports-image-storage-guide).

**⚠️ 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.

```typescript
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:**
```html
<script type="module" src="https://widgets.api-sports.io/3.1.0/widgets.js"></script>
```

**2. Add the config widget** (once per page):
```html
<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:**
```html
<!-- 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:

```css
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:

```html
<!-- 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:

```html
<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](https://dashboard.api-football.com/profile?access)
- Hide your key from source code: follow the [caching & security tutorial](https://www.api-football.com/news/post/how-to-optimize-widgets-cache-and-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
```bash
curl --request GET \
  --url 'https://v3.football.api-sports.io/leagues' \
  --header 'x-apisports-key: XxXxXxXxXxXxXxXxXxXxXxXx'
```

### Python
```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)
```javascript
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)
```php
$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
```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](./endpoints.md)
- For **complete TypeScript type definitions** → see [types.ts](./types.ts)
- For **official docs** → https://www.api-football.com/documentation-v3
- For **dashboard & live tester** → https://dashboard.api-football.com

