Coverage API

Soccer odds captured from Hard Rock Bet and Winamax, aligned market-by-market so the same bet can be compared across both books.

AuthGamesMarkets & odds Push lineupMapping auditLeagues Rules for consumersKnown limits

Base URL and authentication

https://datarium.top/coverage/api/

Data endpoints use HTTP Basic auth. Credentials are issued per consumer — ask the operator; they are not published here.

curl -u 'USER:PASSWORD' \
  'https://datarium.top/coverage/api/fixtures?day=2026-08-31'
EndpointAccessPurpose
GET /healthopenLiveness: archive row count, capture count for today
GET /proxyopenDaily collection spend against cap
GET /fixturesauthGames with captured odds
GET /fixtureauthEvery market and price for one game
GET /pairsauthMarket-pairing decisions and their evidence
GET /coverageauthLeagues, calendar, market vocabulary
POST /lineup/confirmauthTell the collector lineups are out for a game
GET /lineup/signalsauthAudit trail of pushed confirmations
POST /pairs/statuslocalhostApprove/reject a pairing. Not reachable externally.
Services running on the same host should call http://localhost:8010/ directly — no credential, one less hop. The paths are identical.

Games

GET /fixtures

ParamDefaultMeaning
dayExact kickoff date, YYYY-MM-DD (UTC)
days0Kickoffs within the last N days
limit2000Maximum rows
{"fixtures": [{
  "name": "Dijon vs Saint-Étienne",
  "kickoff": "2026-08-31 18:45:00",
  "competition": "France - Ligue 2",
  "books": ["hrb", "winamax"],
  "phases": ["morning", "lineup"],
  "rows": 1879, "markets": 141,
  "captured_at": "2026-08-31 11:03:51.734000",
  "lineup_confirmed": false
}]}

books tells you whether a game is comparable at all — ["hrb","winamax"] means both priced it. name and kickoff together address the game in the next call, and must be passed back verbatim.

Markets and odds

GET /fixture

curl -s -u 'USER:PASSWORD' -G \
  'https://datarium.top/coverage/api/fixture' \
  --data-urlencode 'name=Dijon vs Saint-Étienne' \
  --data-urlencode 'kickoff=2026-08-31 18:45:00'
{
  "fixture": {
    "name": "Dijon vs Saint-Étienne",
    "kickoff": "2026-08-31 18:45:00",
    "competition": "France - Ligue 2",
    "books": ["hrb", "winamax"],
    "lineup_confirmed": false,
    "lineup_source": "unknown",
    "event_ids": {"hrb": "992687190473572603", "winamax": "72572050"}
  },
  "totals": {"markets": 141, "selections": 1879, "both_books": 19, "suspect": 0},
  "markets": [ ... ]
}

Market fields

FieldMeaning
conceptThe join key. Normalised and language-neutral — total_goals_ou, btts, handicap_spread.
periodFT, H1 or H2. Always join on (concept, period).
labelEnglish display name.
hrb_name / wx_nameRaw per-book market titles. Winamax titles are French; kept verbatim as an audit trail.
both, booksWhether both books priced this market.
paired_byapproved (human-confirmed) · concept (matched by meaning) · structure (matched by shape — weakest evidence).
median_gap, suspectMedian implied-probability gap across selections, and whether that exceeds the sanity threshold.
times_seen, pair_statusHow often this pairing has been observed, and its review state.

Selection fields

FieldMeaning
roleIdentity aligned across books: home, away, draw, over, under, yes, no, named.
hrb_odds / wx_oddsDecimal odds. null when that book did not offer the selection.
hrb_label / wx_labelRaw selection text on each book.
hrb_source_age_s / wx_source_age_sAge of the price in seconds. HRB is live (0); Winamax is served from a 15-minute cache (900).
hrb_captured_at / wx_captured_atWhen each side was captured.
hrb_unbettable / wx_unbettablePresent and true when the price is ≤ 1.00 — a placeholder, not a real price.
prob_delta, suspectImplied-probability difference and its sanity flag. Both null when a placeholder is involved.
residualtrue for catch-all buckets (“Other” / “Autre”), which are not the same bet on both books.

Push a lineup confirmation

POST /lineup/confirm

Each game is captured twice: once in the morning, once near kickoff. The second capture is meant to fire when the starting lineups are published — but the upstream lineup feed is rate-limited during exactly that window, and not every game maps to it, so most captures fall back to a blind timer instead.

If your service already knows lineups are out, say so and the collector captures that game on its next poll rather than waiting.

curl -s -u 'USER:PASSWORD' -X POST -G \
  'https://datarium.top/coverage/api/lineup/confirm' \
  --data-urlencode 'name=Benfica vs Estoril' \
  --data-urlencode 'kickoff=2026-08-31 19:15:00' \
  --data-urlencode 'source=my-service'
{
  "ok": true,
  "signal": {
    "event_id": "5638672854720577792",
    "event_name": "Benfica vs Estoril",
    "kickoff": "2026-08-31 19:15:00",
    "source": "my-service",
    "received_at": "2026-08-31 18:12:32"
  },
  "minutes_to_kickoff": 62.5,
  "effect": "capture on the watcher's next poll"
}
ParamMeaning
name + kickoffThe game, verbatim from /fixtures.
event_idAlternative address — from /fixture's event_ids.
source, noteOptional. Stored so it is clear later who asserted what.

effect tells you plainly what your call will do: capture on the watcher's next poll, none - lineup snapshot already captured, or none - kickoff has passed.

Safe to retry. A repeat or late confirmation is recorded for audit and changes nothing — it cannot cause a second capture. The endpoint only records; the capture happens inside the collector behind its own spending limit, so calling this cannot cost more than the collector was going to spend anyway.

GET /lineup/signals?limit=50 returns recent confirmations and who sent them.

Mapping audit

GET /pairs?status=approved|auto|rejected

Every HRB↔Winamax market pairing with the evidence behind it: how it was matched, how many selections lined up, how often it has been seen, and whether a human confirmed or rejected it. Use it to audit any comparison that looks surprising.

Leagues

Competition coverage is returned in GET /coverage under leagues. Note its own caveat: a 0% overlap figure means discovery missed the competition, not that Winamax does not price it.

Rules for consumers

Join on (concept, period) — never on concept alone, and never on market names. A full-time market compared against a first-half one is the most common way to manufacture a disagreement that does not exist.
Check source_age_s before comparing prices. Winamax prices are always about 15 minutes old; HRB prices are live. Treating them as simultaneous is the likeliest way to act on a price that has already moved.
Skip selections flagged *_unbettable. Winamax prices near-certain legs at exactly 1.00. That is a placeholder, not a price — read as a probability it is 100%, and it will silently corrupt any edge or margin calculation.
Cache on event_ids, not display names. Books spell teams differently and rename them — “Vancouver FC” on one side is “Vancouver Langley FC” on the other.
Treat suspect: true and paired_by: "structure" as unverified. Structural matches are made on market shape rather than meaning, which is how a corners market can be lined up against a goals market.

Known limits

Market vocabulary is archive-derived. Per-concept coverage in /coverage was rebuilt on 2026-08-31 from 1,123 HRB and 412 Winamax fixtures, replacing a 60-fixture sample. Each concept carries hrb_pct / wx_pct, the fixture counts behind them, periods_seen, and observed. Read the zeroes differently: a Winamax zero means the market is not offered, since the whole match page is captured; an HRB zero may only mean it was never requested, because collection is limited to market types discovery has learned for that competition.

“Both books” overstates comparability. A game can be listed under both books while one of them priced only a handful of markets. Depth is heavily skewed — a US-facing book does not price minor European leagues deeply. Use totals.both_books, which counts genuinely paired markets, rather than counting fixtures.

History has gaps. Collection has not been continuous since inception; some days are partial and at least one is missing entirely. Time-series queries should tolerate holes rather than assume a uniform series.

Two snapshots per game, not a live feed. Each fixture is captured once in the morning and once near kickoff (phases). This is an archive for analysis, not a streaming price feed.