§ docsfoot.io api referencev1

One API.
One schema.

PostgREST-style filtering, embedding and ordering over the closed-beta REST surface. OpenAPI 3.0.3 is published at /openapi-v2.yaml. The HTTP MCP endpoint supports discovery; REST and tool execution require an issued invite key.

§01quickstart5 min

Quickstart.

Three lines.

# 1. closed beta — join the waitlist at /signup; keys are invite-only for now. # once you're invited you'll get a fio_live_ key: export FOOTIO_KEY=fio_live_xxxxxxxxxxxx # 2. the API is PostgREST over our Postgres — this is the live base URL: export FOOTIO_BASE=https://amidfjrgsztslrpeaiec.supabase.co/rest/v1 export FOOTIO_ANON=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFtaWRmanJnc3p0c2xycGVhaWVjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU3MjczMjksImV4cCI6MjA5MTMwMzMyOX0.huNv-RAjXijyiCn8p_rDLf-yD1dsKqFvGSPMExM0ILY # 3. query (both headers: apikey routes to the project, x-api-key is YOUR key) curl "$FOOTIO_BASE/players?name=ilike.*xhaka*" \ -H "apikey: $FOOTIO_ANON" \ -H "x-api-key: $FOOTIO_KEY"

That returns Granit Xhaka's profile JSON — biographics plus available external_ids cross-references. The same filtering and embedding pattern works across the documented resources. No SDK is required.

§02authenticationx-api-key

Authentication.

Reads authenticate with your x-api-key header (keys look like fio_live_… and are issued at /signup). Keys are stored only as SHA-256 hashes — shown once at creation, never re-shown.

# in any request (alongside the public apikey header from the quickstart): -H "x-api-key: fio_live_xxxxxxxxxxxx"

The public project key also permits an unsupported browser-preview path. Database RLS limits season- and match-scoped canonical rows on that path—and on issued free keys—to the current season of competitions on the free list: 196 competitions served at least one current-season match when we measured on 6 August 2026. Historical access requires a valid key assigned basic, pro or enterprise; MCP tool queries use the same database rule, and every tier reads through the same licence gate. Public catalogue and aggregate coverage views can remain broader so gaps are inspectable without implying raw historical entitlement.

Closed-beta quotas and content tiers are assigned per invite; the pricing matrices described elsewhere are not currently self-serve purchasable plans. Per-minute and monthly allowances are plan terms enforced atomically on the metered gateway (below) and on MCP tool calls. Direct PostgREST reads are not usage-metered today — no X-RateLimit-* headers and no 429s on that surface. Lost or leaked keys are rotated by emailing hello@foot.io.

Metered gateway.

Keyed requests can also go through the metered gateway, which enforces per-minute and monthly limits atomically and returns your remaining allowance on every response. Same PostgREST query syntax, same tier entitlements; only the base URL changes:

GET https://amidfjrgsztslrpeaiec.supabase.co/functions/v1/data-api/v1/matches?select=id,match_date&limit=5 -H "x-api-key: fio_live_xxxxxxxxxxxx" # response headers x-ratelimit-limit-minute / x-ratelimit-remaining-minute x-ratelimit-limit-month / x-ratelimit-remaining-month # when a monthly quota applies # 429 with Retry-After when a limit is hit (codes: RATE_LIMITED, QUOTA_EXCEEDED)

The gateway is read-only (GET/HEAD) and does not expose RPC endpoints. Direct PostgREST access keeps working during the beta; new integrations should prefer the gateway — usage-based plans will bill from its meters. Your key's per-minute and monthly allowances are set by your plan — see pricing for the current limits.

§03filteringpostgrest-style

Filtering.

Operators on selectable columns in documented resources, using standard PostgREST syntax. Machine-readable spec: openapi-v2.yaml.

OperatorMeaningExample
eqequal?country=eq.IT
gt / gte / lt / ltecompare?height_cm=gte.190
inany of?position=in.(GK,DF)
like / ilikeSQL LIKE?name=ilike.*haaland*
not.eqnegation?retired=not.eq.true
or / andcompose?or=(goals.gte.20,assists.gte.15)
ordersort?order=goals.desc,name.asc
§04embedding?select=…

Embedding.

Pull related rows in one request.

# season 1415 is the Argentine Primera División 2026 — a current season on the # free list, so this returns rows even on the anon preview (verified 6 Aug 2026): curl "$FOOTIO_BASE/matches ?season_id=eq.1415 &status=eq.finished &select=match_date,home_score,away_score, home:teams!matches_home_team_id_fkey(name,logo_url), away:teams!matches_away_team_id_fkey(name,logo_url), venue:stadiums(name,capacity), events:match_events(minute,event_type,player:players!match_events_player_id_fkey(name)) &order=match_date.desc&limit=5" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"

One round-trip. Embedding follows foreign keys. The alias syntax (home:teams!matches_home_team_id_fkey) is required where a table has multiple FKs to the same target (matches→teams, match_events→players) — a bare embed errors with PGRST201.

§05pagination & limitsRange header

Pagination & limits.

Either ?limit=50&offset=100 or HTTP Range header (Range: 0-99). There is no 100-row default page: a query without limit returns up to the server's hard cap of 1,000 rows, and a larger limit is clamped back to 1,000 (measured 6 Aug 2026). Use Prefer: count=exact to get the total count in the Content-Range header.

§06players/v1/players

Players.

311,637 indexed profiles in one schema (measured 10 Aug 2026). Field completeness varies; handle nulls.

GET/v1/players
List players with filters.
FieldTypeDescription
idintInternal player ID.
nametextDisplay name.
date_of_birthdateYYYY-MM-DD.
country_idintFK → countries.
positionenumGK | DF | MF | FW.
detailed_positionenumCB, RB, DM, AM, RW, ST, etc.
height_cmintHeight in cm.
preferred_footenumL | R | Both.
external_idsjsonbCross-reference IDs (Wikidata + other public ID systems).
GET/player_season_stats?player_id=eq.:id
Career season-by-season stats. Always filter or group by stat_scope — see Semantics — or league-only and league+cups rows will silently mix.
GET/shot_events?player_id=eq.:idheld, not served
We hold 729,596 shots with per-shot xG and pitch coordinates (measured 6 Aug 2026), but no shot-level rows — including every xG field — currently pass the licence gate at any tier, so this endpoint returns zero rows. If shot-level licensing matters to your product, contact us.
§gatewhat a key actually readssince 2026-07-29

The licence gate.

Every tier — Enterprise included — reads only rows we hold an explicit right to redistribute. That is the single biggest gap between what we hold and what a key reads, so here it is in numbers, all measured on 10 August 2026: we hold 1,177,209 matches; every tier reads 733,863 of them (62.3%). We hold 2,302,414 match events; 42.9% pass the gate. We hold 7,459,954 lineup rows; 32.9% pass. Of the 926 competitions with recorded matches, 763 serve at least one match and 163 currently serve zero.

Not served at any tier: transfers (we hold 159,823 rows; zero pass the gate), shot-level data including every xG field, and top-5-league events and lineups. Standings do pass: we hold 58,637 standings rows and 55,391 of them (94.5%) are served (measured 6 Aug 2026). Withdrawn entirely on 28 July 2026: player market values and injuries. Historical odds are served to paid tiers — 6,222,372 quotes across 130,550 matches in 21 competitions — but only 49,929 of those matches (38.2%) currently join to a result row the gate serves.

What the gate does not do is discriminate between paid tiers: there is no competition gating between them, and the cheapest paid key reads exactly the same rows as the most expensive one. Tiers differ in rate, quota and historical depth — free reads the current season across the 196-competition free list; paid tiers read full history — never in which rows pass the gate. If an endpoint looks thinner than the totals we publish for what we hold, this section is why. The defensible share grows as licensing lands; the figures above are the served surface today, not a ceiling.

§scopestat semanticsstat_scope

stat_scope — read this before ranking anyone.

Every player_season_stats row carries stat_scope, which says what the numbers count:

ValueMeaning
leagueDomestic league only (a "23 in the Premier League" figure).
all_competitionsLeague + cups + continental (a "31 in all competitions" figure).
unknownLegacy row pending a per-source semantics audit — treat with care.

Both scopes are correct — they answer different questions. But if you order=goals.desc without pinning the scope, your "top scorer" list mixes them and is wrong. Always:

# league-only golden boot race — season 1415 (Argentine Primera División 2026) # is on the free list, so this returns rows even on the anon preview (verified 6 Aug 2026): curl "$FOOTIO_BASE/player_season_stats?season_id=eq.1415&stat_scope=eq.league&order=goals.desc&limit=20&select=goals,player:players(name)" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"

Rows also carry source (fbref, api-football, stathead, …). The same player-season can legitimately appear once per source; dedupe by picking a source or aggregating deliberately.

§provtrust the numberssource · tiers

Provenance & confidence.

Many canonical fact rows carry a source column naming the pipeline that wrote them, but legacy and entity-level provenance is not yet complete. Current cross-source tier views classify matches and player-season rows (see methodology):

TierMeaningWhere to query it
corroborated≥2 assessed publisher groups agree/match_fact_tiers?match_id=eq.:id
/player_season_stat_fact_tiers — licence-filtered 2026-07-29: covers every stat row you can read, and no others
/fact_tier_summary
single_sourceOne source, no contradiction
flaggedSources disagree or a QA contract flagged it — check before publishing

n_sources is the assessed independent-publisher count; raw_source_count, independence_groups, provenance notes and unassessed namespaces preserve the audit trail. Open cross-source contradictions feed source_disagreements-backed tiers. These are row-level classifications, not proof of every field; a flagged row is your cue to compare sources rather than quote a single number.

Field-level entity facts use /entity_fact_verification. Filter by subject_type and subject_id; the response labels each assertion single_source, multi_source_unverified, corroborated or conflict and retains candidate_values. Corroborated means agreement across at least two assessed publisher-independence groups. sources_agreeing, source_count_status and unassessed_sources keep the raw labels visible without overstating them. /entity_facts_resolved fails closed: it returns every verified member of a multi-value predicate, but no guessed value for a scalar conflict.

Match-event corroboration uses /match_event_verification. It returns one row per canonical event with observation_count, source_count, contributing sources, verification_status and any fail-closed field_conflicts. Raw source payloads are retained privately rather than duplicated in canonical event results.

Assist semantics are versioned at /assist_definitions. A publisher-reported assist, a potential assist (a chance-creating action whether or not the shot is scored) and a rule-derived key-pass candidate are different concepts. Candidate assertions do not become publisher facts, and models, rules or multiple workers in one managed annotation pipeline never count as independent corroborating publishers. The assertion and evidence tables remain private while licensing and editorial review are incomplete.

curl "$FOOTIO_BASE/assist_definitions?is_active=eq.true&select=slug,version,concept,description,rule_spec,reference_url" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"
curl "$FOOTIO_BASE/entity_fact_verification?subject_type=eq.match&subject_id=eq.5074&select=predicate,verification_status,candidate_values" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"

Use /entity_fact_provenance for assertion-level evidence. It exposes the registered source, exact import run when available, stable source record/page identifier, retrieval timestamp and optional SHA-256 digest. provenance_status is deliberately explicit: run_and_record, source_record, import_run_only, source_only, unregistered_source or invalid_subject. Historical facts are not upgraded to stronger labels without evidence.

curl "$FOOTIO_BASE/entity_fact_provenance?subject_type=eq.match&subject_id=eq.9441&select=predicate,source,provenance_status,effective_source_url,import_log_id" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"

/entity_fact_provenance_gap_summary groups every assertion still lacking record-level evidence by status, subject type, predicate and source. Missing canonical subjects or referenced objects are not served as facts: their full pre-images are retained internally, while /entity_fact_integrity_summary publishes the safe aggregate and the database rejects new dangling references.

/source_entity_identities maps a registered publisher record ID to one canonical entity with a uniqueness and target-existence gate. /ingestion_gate_totals reports raw staging and unresolved-quarantine states without exposing source payloads. Staging promotion is service-only and fail-closed: name-only auto-creation is retired, while identity, evidence, source/import consistency and match-result conflicts are rechecked inside the transaction.

Historical completeness uses an explicit denominator. /competition_completeness includes every public competition—even unresearched ones—while /expected_competition_seasons expands researched targets into each expected year and labels it present, missing_season, empty_season, scoreless_season, duplicate_seasons or an evidence-backed exception such as not_held. Only verified_complete=true is a completeness claim.

curl "$FOOTIO_BASE/competition_completeness?target_status=in.(verified,provisional)&problem_seasons=gt.0&order=problem_seasons.desc" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"

The measured gaps are executable queues. /competition_research_queue ranks every unresearched competition by indexed-match impact and identifies the next evidence task; last_research_method, last_research_outcome, last_researched_at and research_retry_after preserve the latest exact-identity or exact-Wikipedia-page attempt instead of repeatedly discarding failed research. Automated contracts remain provisional and require source-backed inception plus either an explicit end or stored-season evidence; a missing dissolution date does not establish current activity. Wikipedia evidence cites an immutable article revision. Inception metadata alone is a lead, never a contract. /coverage_gap_remediation_queue expands every missing or impaired contract year, routes it to an existing ingestion or integrity-repair lane, and exposes the latest exact publisher/year outcome. source_unavailable remains a measured gap and means the next acquisition must use another publisher until the recorded retry date.

curl "$FOOTIO_BASE/competition_research_queue?select=research_rank,competition_name,last_research_outcome,research_retry_after,next_action&order=research_rank&limit=25" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY" curl "$FOOTIO_BASE/coverage_gap_remediation_queue?select=remediation_rank,competition_name,season_start_year,remediation_lane,latest_attempt_source,latest_attempt_outcome,attempt_retry_after&order=remediation_rank&limit=50" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"

Exact fixture-count completeness has a second denominator. /competition_season_fixture_contract_queue contains every stored public competition-season, not only researched rows, and labels seasons without a cited expected count unresearched. /competition_season_fixture_contract_summary provides corpus-wide totals; source-contracted rows expose stored, orientation-independent distinct, played and scoreless counts separately.

curl "$FOOTIO_BASE/competition_season_fixture_contract_queue?contract_status=eq.unresearched&stored_match_count=gt.0&order=stored_match_count.desc&limit=50" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"

Competition labels have their own evidence contract. /competition_classification_completeness separates missing canonical values, missing evidence and source conflicts for format, gender, age group, scope, country, tier and lineage. /competition_classification_summary and /competition_classification_gap_summary provide corpus-wide aggregates without API-page truncation. Treat only classification_status=verified as fully evidenced.

curl "$FOOTIO_BASE/competition_classification_completeness?indexed_match_count=gt.0&classification_status=neq.verified&order=indexed_match_count.desc" \ -H "apikey: $FOOTIO_ANON" -H "x-api-key: $FOOTIO_KEY"

/match_source_acquisition_summary keeps the full single-source denominator intact while splitting the next run into actionable_* and deferred_* totals. verified_assertion_count includes exact same-publisher provenance recovered by stable-ID re-emitters; verified_match_count counts only matches whose assertion publisher group differs from the canonical match source group. Deferred means a specific publisher/season attempt is unsupported or unresolved until its retry date; it does not mean the match is verified or removed from the backlog.

§MCPllm-native interfacestdio + http

MCP server.

The MCP server is a deployed stateless HTTP JSON-RPC endpoint. Discovery is available; tool execution is invite-only and output quality follows the same coverage and provenance caveats as REST.

# endpoint (POST JSON-RPC, authenticate with your key): # https://amidfjrgsztslrpeaiec.supabase.co/functions/v1/mcp # header: x-api-key: $FOOTIO_KEY # # discovery (tools/list) returns the authoritative deployed tool schemas: curl -s -X POST https://amidfjrgsztslrpeaiec.supabase.co/functions/v1/mcp \ -H "content-type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' # # now Claude can answer: # "How is Saka playing lately?" → player_form # "Who's one yellow from a ban at Spurs?" → injuries_and_bans # "Everything Wikidata knows about Klose" → get_entity_wikidata # "How complete and verified is Serie A?" → get_competition_coverage # "Show the evidence for match 5074" → get_fact_evidence

31 question-shaped tools — search/detail for players, teams, matches; standings, top scorers, head-to-head, form, fixtures, honours, scope-aware season stats, current squads, the newspaper archive, Wikidata claims, and the same quality contracts served over REST. The transfers tool remains deployed but currently returns no rows: no transfer rows pass the licence gate at any tier. get_data_quality_summary, get_competition_coverage and get_fact_evidence expose unresearched denominators, publisher-aware tiers, conflicts, provenance and quarantine states to agents instead of returning data without its caveats. Account-less pay-per-call access over the x402 protocol is built and in testnet validation.

§hookpush notificationsroadmap

Webhooks (roadmap — not yet available).

Planned: POSTs to your endpoint on transfer.completed, match.finished, goal.scored and similar. Not yet shipped — today, poll the REST surface (a matches?status=eq.live query is cheap) or ask hello@foot.io to be notified when webhooks land.

§altchoosing a providerhonest

Are we the right API for you?

Often not, and it is cheaper for both of us if you work that out now. We have written the comparisons ourselves and named the places each competitor beats us — live latency, licensed depth, price, and years of proven reliability are all rows where somebody else wins:

  • foot.io vs API-Football — the live-data comparison. If your product shows in-play scores, buy theirs: their cycle is around 15 seconds and ours is five minutes.
  • foot.io vs Sportmonks — the licensing comparison. League-scoped plans with contractual depth and redistribution rights we do not currently have.
  • foot.io vs football-data.org — the free-tier comparison. If your project fits inside their 12 free competitions, keep using them.

The reasons to use us instead are narrow and specific: history back to 1871, no competition gating on any paid key, 6.2M pre-match odds quotes served to paid tiers on our match id (though only 38.2% of odds matches currently join to a served result row — measured 10 Aug 2026), free unmetered cross-provider ID resolution via resolve_entity, and the MCP server above. Every coverage number on those pages is served from the public competition_season_coverage endpoint, so you can falsify them without a key — please do.