Posted 15 May 2026 · Updated 6 Aug 2026 · by Mark Hahnel · 5 min read

Trust is the moat: why foot.io publishes its errors.

Implementation update: this essay states the standard foot.io is building toward. The current production database does not yet provide per-fact evidence, retrieved-at timestamps or independent corroboration for every value. Match and player-season tier views have partial coverage; see the current methodology before citing data.
Update · 6 August 2026

The licence gate this essay's principles pointed at landed on 29 July 2026. Every tier — Enterprise included — now reads only rows we hold an explicit right to redistribute: 607,925 of the 1,056,092 matches we hold (57.6%, measured on 6 August 2026). Serving less than we hold, and saying exactly how much less, is this essay put into production. The per-dataset consequences are set out on /pricing.

Every commercial football-data provider has bugs. Bad ingest joins flip thousands of rows in seconds. A scraper change at FBref silently breaks a player's career totals. A name collision merges two players. This is normal.

What isn't normal is admitting it.

foot.io maintains a public erratum log. Confirmed user-facing corrections are published with what was wrong, what changed and supporting evidence. We are extending this into a database-enforced correction ledger.

1. It compounds

Trust is the only thing that compounds. Features get cloned. Pricing gets matched. A reputation for catching your own errors before customers do takes years to build and can be lost in a single hidden bug.

A researcher who cites foot.io in a 2026 paper is implicitly trusting that the numbers won't change underneath them. We make that trust visible by maintaining a public log of changes — so if a fact got corrected after their paper went to press, they can point readers at the diff.

2. It forces our own hand

Knowing user-facing corrections land on a public page changes how we work. A silent fix is a temptation to also silently rewrite history. A public fix forces us to articulate exactly what changed and why — which means we catch the next instance of the same bug class faster, because we've already written it down.

The erratum log has a side-effect we didn't expect: it makes ingest code reviews better. Reviewers ask "what's the worst case if this is wrong?" and the answer is "we'll post it to /erratum, customer-visible, forever". That sharpens the question.

3. It's an asymmetric advantage

Public corrections are an advantage for builders, researchers and AI-agent developers because changes remain inspectable. We can make that commitment without speculating about competitors' contracts or motives.

How it works in practice

Three concrete defences:

Evidence-bearing facts. The schema supports sources, retrieval metadata, confidence tiers and a fact-evidence ledger, but coverage is incomplete. As of 21 July 2026, evidence is concentrated on matches and source URLs are sparse. API consumers must treat missing evidence as unknown.

Truth-set CI. A small hand-curated corpus checks household-name facts. It is a useful regression alarm, not evidence that the wider database is correct. Expanding both the set and its blocking coverage remains roadmap work.

Cross-source verification. Current match and player-season-stat views label rows corroborated, single-source or flagged. Correction, 22 July 2026: the earlier 24% estimate counted source labels and some metadata keys, not assessed independent publishers. It has been withdrawn; live publisher-aware totals are now exposed on Coverage and through fact_tier_summary. This is not yet field-level coverage. Disagreements and failed plausibility checks require human triage.

What we got wrong recently

Two corrections worth a paragraph each, both already on /erratum.

Career-total goals inflated 30–60% for top players (fixed 2026-05-14, commit 67fe9e8). We were storing player_season_stats rows from three upstream sources (Stathead, FBref, Wikipedia season pages) without deduplicating — so Messi's career-total goals read ~1,348 instead of ~850 because each season was counted up to three times. The fix: bucket-merge by (season_id, team_id, competition_id) keeping the highest-confidence row per bucket via a fixed SOURCE_RANK. The fix landed in production within 4 hours of detection.

Phantom matches (fixed 2026-05-12). Our Kaggle-ESPN historical importer marked rows as status='finished' whenever the home/away score was present, even if the score was 0-0 and the date was in the future. Result: 10,207 phantom 0-0 finished matches polluting team form guides. The fix: tightened the heuristic to require non-zero scores AND past date. Pre-existing phantoms nulled in the same migration.

What this means during closed beta

There is no generally purchasable plan yet. Beta users should select facts by evidence tier, preserve source metadata, report contradictions and expect gaps. The goal is for the correction and evidence trail to become enforceable across every canonical fact rather than remain a marketing promise.

See also