Growing Discord community — direct access to the developer, live coverage & picks. Join the Discord Join now →
Matches Leagues Predictions The Edge Money News Stats Coverage
Docs / Football API free

Teams, players & transfers#

Teams#

GET /api/v2/teams/
Param Type Description
country_code string ISO country code, e.g. AR
league_id / season_id int Teams competing in a league/season
in_competition bool Only teams currently in an active competition
is_women bool Women's teams
name string Name search
limit / offset int Pagination
GET /api/v2/teams/{id}/           # profile: venue, coach, colours, country
GET /api/v2/teams/{id}/squad/     # current squad, ordered by position then shirt number
GET /api/v2/teams/{id}/fixtures/  # date_from, date_to, league_id, status
GET /api/v2/teams/{id}/form/      # last, league_id, venue — see Rolling form
GET /api/v2/teams/{id}/social/    # curated tweets/videos (type filter)
curl -H "Authorization: Token YOUR_API_KEY" \
  "https://sports.bzzoiro.com/api/v2/teams/755/fixtures/?status=upcoming&limit=5"

Matching a team to a country#

Every team carries country_code next to country:

{"id": 932, "name": "Türkiye", "country": "Turkey", "country_code": "TR"}

Match on the code, not the name. The name moves — "Turkey" became "Türkiye" and every name-matching integration broke the day it did. The code does not.

Two things it is not, so nobody builds on a wrong assumption:

  • It is not strictly ISO 3166-1, and we are not going to call it that. It agrees with ISO wherever ISO has a code, and uses the customary one where it does not: England EN, Scotland SX, Wales WA, Northern Ireland NX — all four of which are GB in ISO.
  • It identifies the country, not the team. Argentina and Argentina U23 both carry AR. Use id to identify a team.

Empty string when we have no code. That is 1,856 clubs that have never appeared in a fixture carrying country data, plus a handful of historical national sides — USSR, Yugoslavia, East Germany — which have none to give.

Rolling form#

/api/v2/teams/{id}/form/ returns a team's last N finished matches already averaged, instead of you fetching the fixtures and then a stats call per match.

Param Type Description
last int Window size, 1–40. Default 10.
league_id int Restrict the window to one competition. Without it every competition counts, which is usually not what you want for league form.
venue string all (default), home or away — narrows the window itself.
{"team_id": 8, "team_name": "West Ham United", "requested": 10, "matches": 10,
 "from_date": "2026-08-16T15:00:00+00:00", "to_date": "2026-09-19T11:30:00+00:00",
 "overall": {"matches": 10, "won": 5, "drawn": 3, "lost": 2,
             "goals_for": 28, "goals_against": 15,
             "points_per_match": 1.8, "form": "DLWWWWDWLD",
             "stats": {"ball_possession": {"average": 54.2, "total": 542.0, "matches": 10},
                       "dribbles": {"average": 9.0, "total": 36.0, "matches": 4}}},
 "home": {"...": "same shape"}, "away": {"...": "same shape"},
 "vs_stronger": {"...": "same shape"}, "vs_weaker": {"...": "same shape"}}

Read the denominators. Every statistic carries its own matches count. In the example above possession is averaged over ten matches and dribbles over four, because only four reported dribbles — averaging them over ten would be inventing six zeroes. overall.matches is the window; stats.<key>.matches is how much of it that statistic covers.

form is most recent first.

vs_stronger and vs_weaker compare the two sides' published positions in the table of the competition that match belonged to. When either position is missing — a cup tie between two divisions, or a table we do not carry — the match is left out of both strength splits rather than guessed at. It still counts in overall, so the strength splits will not always add up to it.

Only finished matches count. A match in progress has half its statistics, and it would drag a form average down without saying so.

Squad availability#

Every player in the squad response carries their current availability:

Field Type Description
availability string available · injured · doubtful · suspended
injury_type string Reason, e.g. Knee Injury. Empty when available or unpublished
injury_expected_return date Expected return, or null

For an injured flag, test availability == "injured" — a boolean would fold doubtful and suspended into "not injured".

The status is refreshed several times a day from the team news published for each side's next fixture. available is the default value, so it means not currently listed as missing rather than confirmed fit, and a club with no upcoming fixture in a covered competition is not re-checked until it has one.

Players#

GET /api/v2/players/
Param Type Description
name string Name search
team_id int Current club
national_team_id int Current national-team squad
nationality_code string ISO code, e.g. AR
position string G · D · M · F
limit / offset int Pagination
GET /api/v2/players/{id}/                # bio, market value, skills
GET /api/v2/players/{id}/stats/          # per-match log — season_id, team_id, league_id, date_from/to
GET /api/v2/players/{id}/transfers/      # transfer history with fees
GET /api/v2/players/{id}/career/         # season-by-season summary
GET /api/v2/players/{id}/national-team/  # international record
GET /api/v2/players/{id}/social/         # tweets/videos

The full workflow with examples: Get a football player profile.

Transfers feed#

GET /api/v2/transfers/
Param Type Description
league_id int Either club has played in this league
player_id int One player's transfer history
team_id int In or out of this club
from_team_id / to_team_id int Direction-specific
date_from / date_to date Transfer date window
min_fee int Minimum fee (EUR)
has_fee bool Only transfers with a disclosed fee
ordering string Sort field, prefix - for descending

league_id takes the id from /api/v2/leagues/ and matches a transfer when either side of the move is a club from that league — so an Argentine club selling to Europe and buying from Europe both show up:

GET /api/v2/transfers/?league_id=85&limit=10

Unknown parameters are rejected. This endpoint answers 400 if you pass a filter it does not recognise, listing what it accepts. A typo like ?league=85 fails loudly instead of quietly returning the unfiltered global feed.

Note: the dataset includes forward-dated moves (announced loans/returns that haven't happened yet). For a "latest transfers" widget, always cap date_to at today.

Transfer ledger#

The feed answers who moved. The ledger answers what it cost.

GET /api/v2/transfers/ledger/?team_id=…
GET /api/v2/transfers/ledger/?league_id=…      # every club in the league, max 40
Param Type Description
team_id int One club. Mutually exclusive with league_id
league_id int Every club in that league
date_from date Window start (ISO)
date_to date Window end. Never reaches past today whatever you pass

Each club comes back with incoming and outgoing blocks — moves, how many were permanent, loans, free and undisclosed, the fees that were published, and the average age — plus gross_spend_eur, gross_income_eur, net_spend_eur, loan_dependency_pct, squad_size, turnover_pct, age_in and age_out.

Three things decide these numbers, and you should know them before quoting one:

Money only counts where a fee was published. A move with no figure lands in undisclosed, not as a zero. Summing the blanks as zeroes reports a tenth of what the big leagues actually spend. free is the separate, known case of a fee of nothing.

A loan return is not a signing. A player coming back from a loan is the same player returning to where he already belonged. Counting him as an arrival inflates the arrivals of every club that lends anyone out, and the raw feed gives you no warning that it happened.

turnover_pct is measured against today's squad. We do not keep historical squad sizes, so a long window can exceed 100%. That is not a bug — it means the club moved more players than it currently holds.

Transfer routes#

GET /api/v2/transfers/routes/?team_id=…&min_moves=3
Param Type Description
team_id / league_id int Scope, same rules as the ledger
date_from / date_to date Window
min_moves int Minimum moves for a pair to count (default 2)
limit int Default 50, max 200

The club pairs that repeat, ranked: moves, loans, fees_eur and up to five of the players who walked it.

Only moves with a real club at both ends are counted. "No team" is how the dataset spells free agency, not a destination, and it is excluded — without that it is the busiest route of almost every club on earth.

A club's own academy does count as a route, so the top line for a big club is often its own under-21s. That is the same question asked of the one feeder it controls.

World Cup squads#

GET /api/v2/worldcup/squads/              # team, group, status, has_player
GET /api/v2/worldcup/squads/{team_id}/

Tournament squad lists with per-player status — built for tournament hub pages.

Images#

Badges and photos come from ids, no extra calls: /img/team/{id}/, /img/player/{id}/?sor=true&bg=transparent — see Working with images.

View this page as Markdown · Found a mistake? Tell us