# OddAlerts API — LLM Reference Guide Audience: language models and developers integrating with the OddAlerts API. Purpose: a single, copy-pasteable bible that fits in an LLM context window and covers everything needed to call the API correctly on the first try. Canonical URL: https://data.oddalerts.com/llms.txt Human docs: https://oddalerts.com/football-data-api If something below conflicts with live behaviour, the live behaviour wins — this file is updated alongside the code but may briefly drift on bleeding-edge features. ================================================================================ TABLE OF CONTENTS ================================================================================ 1. Conventions 2. Authentication 3. Errors, pagination, caching 4. Identifiers (fixture id, league id, competition id, etc.) 5. Bookmaker IDs 6. Markets reference 7. Endpoints — quick index 8. Endpoints — detailed reference 8.1 Fixtures, Probability, Stats 8.2 Odds (history, movement, dropping, latest, upcoming, comparison) 8.3 Value Bets, Trends 8.4 Teams, Competitions, Countries, Bookmakers, Referees 8.5 Players (OA percentiles, derived stats, ranking) 8.6 Bets, Bankrolls, Betslips, CSV export 8.7 Misc (predictions, vidiprinter) 9. Recipes (end-to-end LLM-ready examples) 10. Common pitfalls ================================================================================ 1. CONVENTIONS ================================================================================ * Base URL: https://data.oddalerts.com/api (every path below is relative to this, e.g. /fixtures/live = https://data.oddalerts.com/api/fixtures/live) * Content type: JSON in, JSON out (UTF-8). * Method: GET unless explicitly stated POST/PUT/DELETE. * Time: UNIX seconds (UTC) unless a field is suffixed `_at`, in which case it is an ISO/datetime string. * Booleans: true/false (never 1/0 strings). * Pagination keys: info.page / info.per_page / info.total / info.total_pages / info.next_page_url / info.prev_page_url. * Maximum per_page: varies; commonly 250 (some endpoints 500). Larger values are silently capped. * Response envelope: most list endpoints return { info: {...}, data: [...] }. * IDs are integers. ================================================================================ 2. AUTHENTICATION ================================================================================ Every standard endpoint needs two things: * a valid `api_token` (shown on https://oddalerts.com/football-data-api), and * an account with API access: the Advanced plan, or an active 48h Advanced Day Pass. Pass the token as a query parameter on every request: GET https://data.oddalerts.com/api/fixtures/live?api_token=YOUR_TOKEN Header-based auth is NOT used. Throttling: 300 requests / minute per token (some endpoints lower; rare ones higher). When you hit the limit you receive HTTP 429. A small set of endpoints has different access rules: * /predictions/generate/{id} uses 120 req/min and a 30-second response cache. Permission failure response: { "info": "Incorrect Permissions", "more": "https://oddalerts.com/football-data-api", "code": 403 } ================================================================================ 3. ERRORS, PAGINATION, CACHING ================================================================================ ERRORS * Validation / bad input — HTTP 422 { "error": "..." } * Missing / malformed parameters — HTTP 400 { "error": "..." } * Auth missing / invalid — HTTP 401 redirect or 403 JSON * Rate limit — HTTP 429 * Server error — HTTP 500 { "error": "..." } PAGINATION Default response shape on list endpoints: { "info": { "page": 1, "per_page": 250, "total": 1840, "total_pages": 8, "count": 250, "next_page_url": "https://...&page=2", "prev_page_url": "https://...&page=1" }, "data": [ ... ] } ALWAYS prefer `next_page_url` (computed server-side, only present when more data exists) over manually incrementing `page`. Clients that auto-increment past the end trigger expensive OFFSET-past-end queries. CACHING Some endpoints are cached for a short time (seconds). You don't need to do anything; they always return fresh-enough data. For `/predictions/generate/{id}` the result is cached 30 s. ================================================================================ 4. IDENTIFIERS ================================================================================ * fixture.id — the fixture ID used across the API. Stable. Use this for /fixtures/{id}, /odds/* and /stats/fixture/{id}. * league_id — the OddAlerts league ID, found on fixture payloads as `league_id`. Each league belongs to a competition. Not the same number as `competition_id`; do not mix them up. * competition_id — the competition (Premier League, La Liga, etc.), stable across seasons. Use /competitions to discover. * season_id — a season of a competition. * team_id — a team. * country_id — a country (see /countries). When in doubt: the API accepts what it returns. If a payload exposes `fixture.id`, that's the value to feed back into other endpoints. ================================================================================ 5. BOOKMAKER IDS ================================================================================ Public bookmaker IDs (use these in query params and request bodies): 1 Pinnacle slug: pinnacle 2 Bet365 slug: bet365 3 1xBet slug: onexbet (also accepts 1xbet) 4 WilliamHill slug: williamhill 5 Betfair Exchange slug: betfair 6 Kambi Group slug: kambi 7 Betano slug: betano 8 FanDuel slug: fanduel IDs and slugs are interchangeable on fixtures include=odds, /odds/latest, and /odds/upcoming. Comma-separated. Case-insensitive. ?bookmakers=2 # Bet365 ?bookmakers=bet365 # same ?bookmakers=1,2 # Pinnacle + Bet365 ?bookmakers=pinnacle,bet365 # same ?bookmaker=2 # singular alias of bookmakers= Get the live list: GET /bookmakers Where to use them: * /fixtures/{id} is one fixture. For a season dump use /fixtures/season/{season_id}, not /fixtures/{season_id} or /fixtures/between without a guessed date range. * /fixtures/upcoming?include=odds&bookmakers=2,bet365 * /odds/upcoming?bookmakers=pinnacle,2 * /odds/latest?bookmakers=bet365 * /odds/history?bookmakers=1 * /value/{type}?bookmaker=2 On /value/you, multiple bookmakers yield BEST odds across the selection. On fixtures include=odds and /odds/upcoming, multiple bookmakers return EVERY selected book (not the best-of). ================================================================================ 6. MARKETS REFERENCE ================================================================================ Full odds market catalog (integer ids + keys): GET /odds/markets Always look markets up there rather than guessing keys. Common keys include ft_result (home, draw, away) and btts (yes, no). On fixtures include=odds, /odds/latest, and /odds/upcoming, `markets=` (or `market=`) accepts keys OR integer ids, comma-separated: ?markets=ft_result,btts ?markets=6,9 ?market=6 Other odds endpoints (/odds/history, /odds/movement, /odds/dropping) still filter by integer market ids (see GET /odds/markets). Those ids appear as `market` / `market_id` on row payloads. ================================================================================ 7. ENDPOINTS — QUICK INDEX ================================================================================ FIXTURES & STATS GET /fixtures/{id|multiple|live|upcoming|popular|between} GET /fixtures/season/{season_id} GET /fixtures/competition/{competition_id} GET /fixtures/team/{team_id} GET /stats/season/{league_id} ?teams=ID1,ID2 GET /stats/fixture/{fixture_id} GET /probability/{type} GET /correctScores ODDS GET /odds/history/{id|multiple} ?ids=,&bookmakers=,&markets= GET /odds/movement/{id|multiple} ?ids=,&page=,&per_page= GET /odds/dropping ?page=&per_page=&min_drop=&bookmakers= GET /odds/latest ?since_minutes=&bookmakers=&markets= GET /odds/upcoming ?bookmakers=&markets=&odds_type=&from=&to= VALUE BETS & TRENDS GET /value/{type} ?market=,&league_id=,&bookmaker= GET /value/you, /value/you/{id} GET /trends/{trend} REFERENCE LISTS GET /teams/{type}, /teams/{type}/{id} GET /competitions, /competitions/search?query=, /competitions/{id} GET /countries GET /bookmakers GET /referees, /referees/upcoming, /referees/{id}/... PLAYERS GET /players/meta GET /players/search ?q=&include= GET /players/rank ?stat=&form=&min_apps=&teams=&include= GET /players/percentiles ?metrics=&role=|group=&level=&window= GET /players/{id} ?include=stats,season_stats,intel GET /players/fixture/{fixture_id} ?sort_by=&include= GET /players/season/{season_id} ?teams=&sort_by=&include= GET /players/competition/{competition_id} BETS, BANKROLLS, BETSLIPS, EXPORTS GET /bets GET /bankrolls, /bankrolls/{id} POST /betslips GET /csv/probability MISC GET /predictions/generate/{id} GET /vidiprinter ?minutes=&type= ================================================================================ 8. ENDPOINTS — DETAILED REFERENCE ================================================================================ 8.1 FIXTURES, PROBABILITY, STATS -------------------------------------------------------------------------------- GET /fixtures/{id} id can be a numeric fixture.id, or one of: live currently in-play (+ recently finished) upcoming NS fixtures, soonest first. Window: now → +14 days by default; no end date when filtered by competitions=, seasons= or teams=. from= / to= (UNIX) narrow or extend the window either way. popular upcoming sorted by visits (same window rules) between requires ?from=UNIX&to=UNIX (max 365 days) multiple append ?ids=ID1,ID2,... (max 50 per page) GET /fixtures/season/{season_id} GET /fixtures/competition/{competition_id} GET /fixtures/team/{team_id} Full fixture lists without a required date window. 250 per page, same envelope as /fixtures/{id}. IDs are the ones the API already returns: season_id fixture.season_id / competitions.current_season / include=seasons competition_id fixture.competition_id / /competitions/{id} team_id fixture.home_id or away_id, or /teams/search?query= Use these for "every game in this campaign / competition / team's history". Sorted newest first (latest kick-off first). Pass order=asc for oldest first. /fixtures/{numeric_id} is still a single fixture — do not put a season or competition id there. All upcoming games for one competition / season / team (no 14-day cap): /fixtures/upcoming?competitions=8 /fixtures/upcoming?seasons=21638&to=1767225600 Extra query filters still AND on: competitions=, seasons=, teams= (comma ids, max 25 each) from=UNIX, to=UNIX optional narrowing; no 365-day cap include=, filters=hasOdds, bookmaker(s)=, markets=, odds_type=, page= Examples: /fixtures/season/21638 /fixtures/competition/8?seasons=21638,19432&teams=123 /fixtures/team/123?competitions=8&from=1700000000 Useful query params (all /fixtures/* list modes): page=1, per_page=250 (max 250) include=odds,odds.live,probability,stats,stats.periods,links,referee,h2h,timings,correctScores competitions=, seasons=, teams= (comma ids, max 25 each) filters=hasOdds or has_odds=1 (only fixtures with the has_odds flag) from=UNIX, to=UNIX (required for id=between; optional on upcoming/popular/season/competition/team) order=desc|asc (season/competition/team; default desc) bookmaker= / bookmakers= (id or slug; see include=odds below) markets= / market= (key or id, e.g. ft_result or 6) odds_type=opening|closing|peak (default closing; with include=odds) Response envelope: { info, data }. `data[].id` is fixture.id. Follow info.next_page_url. Upcoming without filters=hasOdds includes many games with empty odds — prefer hasOdds or use /odds/upcoming instead. include=odds Closing prices (or opening/peak via odds_type=). Live in-play prices are a separate include: odds.live (Bet365 only). ONE bookmaker (bookmaker=2 or bookmakers=bet365) — flat markets: "odds": { "ft_result": { "home": 2.10, "draw": 3.40, "away": 2.90 }, "btts": { "yes": 1.83, "no": 1.95 } } TWO OR MORE bookmakers (bookmakers=1,2 or bookmakers=pinnacle,bet365) — nested by slug. This is not a breaking change: comma lists did not previously return multiple books. "odds": { "pinnacle": { "ft_result": { "home": 2.05, "draw": 3.50, "away": 3.10 } }, "bet365": { "ft_result": { "home": 2.10, "draw": 3.40, "away": 2.90 } } } No bookmakers= param: one collapsed price per market (latest tick across books). Not a multi-book board. Filter the board with markets=ft_result,btts (keys or integer ids). Omit it and you get every stored market (large payload). To pull all 8 books for upcoming games, prefer /odds/upcoming (compact, odds-only, has-prices by default). Fixtures include=odds is for when you also need fixture metadata. include=stats.periods Per-half stats in `stats_periods`, alongside the full-match `stats` block (request both with include=stats,stats.periods). Works on every /fixtures endpoint, including /fixtures/live. "stats_periods": { "first_half": { "home_goals": 0, "away_goals": 4, "cards": 0, "home_yellow_cards": 0, "away_yellow_cards": 0, "corners": 4, "home_corners": 0, "away_corners": 4, ... }, "second_half": { ... }, "extra_time": { ... } (only when extra time was played) } Keys use the same names as `stats`: goals, possession, yellow/red cards, cards, corners, fouls, shots, shots_on, attacks, dang_attacks, offsides, tackles, goal_kicks, throw_ins. Pressure and xG are full-match only. Reading the values: key present with a number trusted, including 0 key present with null we hold the stat but not this half's value key absent that stat is not covered for this fixture half is null the half has not started or has no stats yet A half's numbers add up to the full-match `stats` value in almost every case. GET /stats/season/{league_id}?teams=ID1,ID2 GET /stats/fixture/{fixture_id} Beta team stats. /stats/fixture resolves the fixture's league + both teams and returns the same as the season call. Returns the full team stats (raw windows). GET /probability/{type} type ∈ { upcoming, today, live, ... }. Returns probability rows used by the value-bet model. Cached 30 s. GET /correctScores Probability of common correct-score buckets. 8.2 ODDS -------------------------------------------------------------------------------- GET /odds/history/{id} # single fixture GET /odds/history/multiple?ids=A,B,C # max 50 ids Returns OPENING / CLOSING / PEAK odds per (fixture, bookmaker, market, bet). Filter with `?markets=` / `?bookmakers=` (comma-separated). GET /odds/movement/{id|multiple} Time-series of price changes. Retained for 21 days. Pagination: per_page max 500. Response includes a `retention` block with cleanup timing. GET /odds/dropping # market-wide live drops Upcoming fixtures (next 7 days) whose price has shortened in the last 24h, ordered by drop % desc. Pagination: per_page max 250. Working set capped at 5,000 rows (largest drops). Optional filters: bookmakers=, markets=, fixtures=, seasons=, competitions=, min_drop= (0–50, default 0). GET /odds/latest DELTA / TICK feed of price changes — NOT a current board. Default window: last 5 minutes ( ?since_minutes=N , max 1440). One row per price change, most recent first. If a price did not move in the window, it is absent. Busy windows are huge (tens of thousands of rows); paginate via next_page_url (per_page max 500). Filters: bookmakers= (id or slug), markets= (key or id), outcomes=, fixtures= (max 100 ids). Row shape: fixture_id, market_key, market_id, outcome, odds, unix, datetime, bookmaker_id, bookmaker_name. Do NOT use this to load "all 8 books for upcoming games". Use /odds/upcoming for that. GET /odds/upcoming SNAPSHOT of current prices for upcoming NS fixtures that HAVE prices. This is the low-call board for multi-book polling. Defaults: now → +14 days, all 8 books, odds_type=closing. per_page / total / total_pages are FIXTURES (default/max 250 per page). info.fixture_count = fixtures on this page. info.row_count = odds rows in data (not fixtures). Follow info.next_page_url until it is false. Past the last page, next_page_url is false and data is empty. Optional: from=&to= unix (span capped at 14 days), odds_type=opening|closing|peak. Filters: bookmakers= (id or slug), markets= (key or id). Always pass markets= unless you want every stored market (very large pages). Same row shape as /odds/latest. Example (all 8 books, FT result only — typically ~5 pages for 14 days): GET /odds/upcoming?markets=ft_result&bookmakers=1,2,3,4,5,6,7,8 GET /odds/upcoming?markets=ft_result,btts&bookmakers=bet365,pinnacle 8.3 VALUE BETS & TRENDS -------------------------------------------------------------------------------- GET /value/{type} # type = homeWin, draw, awayWin, btts, # o15, u15, o25, u25, o35, u35, # o45, u45, o85Corners, # homeGoals, awayGoals, # homeWinHT, drawHT, awayWinHT Common params: market=, league_id=, bookmaker=, min_value=, min_odds= GET /value/you, /value/you/{id} # user-saved strategies GET /trends/{trend} 8.4 REFERENCE LISTS -------------------------------------------------------------------------------- GET /teams/search?query=arsenal name search, best match first (senior, top-division teams rank above youth, women's and lower-league namesakes). Each result lists current_competitions to tell similar names apart. Optional: country_id=, limit= (max 25) GET /teams/find/{id}, /teams/country/{country_id}, /teams/all GET /competitions all competitions GET /competitions/search?query=premier word-by-word LIKE search (multi-token) GET /competitions/{id} GET /countries GET /bookmakers { id, name, slug } GET /referees, /referees/upcoming, /referees/{id}/{stats|seasons|fixtures} Players have their own section: 8.5. 8.5 PLAYERS -------------------------------------------------------------------------------- Player data comes in two layers. Know which one you need before you call. stats, season_stats Provider counting stats with simple averages, per-90s and hit rates, bucketed by recent form or by season. This is the layer /players/rank and sort_by sort on. intel OddAlerts' own derived layer: the role a player actually plays, coverage-aware per-90s, efficiency rates, his share of team output, workload, and OA percentiles (0-100) against same-role players at the same level. Use it to judge how good or how involved a player is, not just how many of something he did. Nothing heavy is returned unless you ask. Every /players endpoint takes include=stats,season_stats,intel (comma list, default none). include=none forces the light identity-only shape. ENDPOINTS GET /players/meta Every value you can send or will read: forms, sortable stats, includes, positions, detailed_positions, defaults, limits, and an `intel` block listing club and international windows, percentile windows, roles, role groups, levels, percentile metric names and the lower-is-better metrics. Fetch once and cache it. GET /players/search?q=salah name search, min 2 chars, best match first. per_page max 100 (default 20). GET /players/{id} one player. GET /players/fixture/{fixture_id} both squads for a fixture: club squads, or national squads for internationals. GET /players/season/{season_id} everyone in that season's squads. GET /players/competition/{competition_id} same, for the competition's current season (info.competition.season_id). GET /players/rank?stat=... leaderboard on provider stats. GET /players/percentiles?metrics=... leaderboard on OA percentiles. See RANKING AND SORTING. teams=ID1,ID2 (max 50) narrows /players/rank, /players/season and /players/competition to those teams' squads. National teams use the current call-up (plus, on season/competition, everyone who played for them in that season). Fields on every player row: id, team_id (current club, never a national team), teams ([club, national team]), country_id, nationality, names, position_id (1 GK, 2 DEF, 3 MID, 4 ATT), detailed_position_id (labels in /players/meta), shirt_number, updated_at. OA PERCENTILES (intel.pct) OddAlerts' own player index. Each metric is a 0-100 percentile against players in the same tactical role at the same level, so a Championship winger is ranked against Championship-level wingers, not Premier League ones, and a centre-back's shots are compared with other centre-backs'. "pct": { "level": "elite", "built_at": 1759550100, "ewma": { "cohort": "role:ST|elite", "cohort_size": 212, "metrics": { "expected_goals_p90": 91, "shots_on_target_p90": 84, "xg_share": 88, "rating": 79, ... } }, "season": { ... }, "l5": { ... } } Windows ewma recency-weighted club form. The default to read and to rank on. season his current club season. l5 last five club appearances. Noisy; use for "who is hot", not for "who is good". Metric names Every intel per90 key (goals_p90, shots_on_target_p90, tackles_p90, ...), every rates and shares name (shot_accuracy, xg_per_shot, xg_share, ...), and rating. Full list: /players/meta -> intel.metrics. A metric is missing when the provider did not measure it for him. How a percentile is built 1. Cohort. Role (GK, CB, FB, WB, DM, CM, AM, W, ST) at level (elite, major, standard, lower, women). `cohort` names the pool used, e.g. "role:ST|elite". With fewer than 40 qualifying players it falls back to the role group ("group:ATT|elite"), then to role and then group across all men's football ("role:ST|men"). Players with no trusted role are scored on their group only. Check cohort and cohort_size before quoting a number. 2. Shrinkage. Each value is pulled toward the cohort median in proportion to how little evidence backs it. Volume stats (touches, passes) are pulled lightly; outcome stats that swing on a few events (goals, assists, conversion, finishing delta) are pulled hard. A two-goal cameo will not read as the 99th percentile. 3. Ties share the middle of their run: a 0 that 60% of the cohort also has scores 30. So 50 is cohort-typical and 90+ is genuinely top tier for his role and level. Percentiles are not comparable across levels: 80 at "lower" is not 80 at "elite". Levels elite Premier League, La Liga, Serie A, Bundesliga, Ligue 1, Champions League major Championship, La Liga 2, Serie B, 2. Bundesliga, Ligue 2, Europa League, and top flights with deep data coverage standard other first and second divisions lower everything else women all women's competitions (benchmarked separately) Freshness: cohort distributions are rebuilt daily (built_at); each player is re-scored against them within about an hour of every match. INTEL BLOCK (include=intel) intel { version schema version (currently 1) as_of UNIX of his latest match role { primary, share, group, mix, starts, apps } club { l3, l5, l10, season, ewma } windows, shape below international { all } national-team matches, or null workload { ... } pct OA percentiles, above } role primary the role he actually lines up in, taken from the formation slot of his recent club starts (up to 10 starts within his last 25 club appearances). Null with fewer than 3 starts. share fraction of those starts in the primary role mix { role: starts } across the same sample group GK, DEF, MID or ATT across his last 10 appearances, subs included starts / apps the sample sizes behind primary and group role.primary is often more precise than position_id: a "Midfielder" may be a DM or a W. It also decides the percentile cohort. club windows l3, l5, l10 last N club appearances season current club season; carries season_id. A season counts as current only if he played in it within 60 days of his latest club match, so a new season takes over quickly. ewma every club match, weighted by recency (~6-match half-life). The best single view of current level. Club and international matches are never mixed: international { all } keeps national-team output separate. Each window sample { apps, starts, minutes, nineties, reliability, from_unix, to_unix } reliability 0-1 is how far to trust the window (0.5 at five full matches, 0.8 at twenty). In ewma, apps and minutes are weighted, so they are not whole numbers. minutes_per_app, start_rate (0-1), rating (average match rating), points_per_game (team points while he played) per90 goals, np_goals, assists, goal_involvements, shots_total, shots_on_target, expected_goals, expected_goals_on_target, finishing_delta, shot_execution_delta, key_passes, big_chances_created, shot_involvements, passes, passes_in_final_third, touches, possession_lost, successful_dribbles, accurate_crosses, tackles, interceptions, clearances, ball_recovery, defensive_actions, duels_won, aerials_won, fouls, fouls_drawn, yellowcards, saves Every key carries a _p90 suffix: goals_p90, touches_p90, ... rates shot_accuracy, shot_conversion, xg_per_shot, xgot_per_sot, pass_completion, dribble_success, duel_win_rate, aerial_win_rate, cross_accuracy (fractions, 0-1) shots_per100_touches, shot_involvements_per100_touches, final_third_passes_per100_touches, possession_lost_per100_touches (per 100 touches) shares his share of the team total in matches he played, scaled to his minutes on the pitch (0-1): shot_share, sot_share, xg_share, key_pass_share, touch_share, goal_involvement_share basis nineties of measured data behind each stat group (shots, xg, xgot, passing, extended, touches, defensive, duels, dribbles, rating). Lower than sample.nineties when some of his matches did not report that group. Derived stats (intel only) np_goals goals minus penalties scored goal_involvements goals + assists shot_involvements shots + key passes (shots he took or created) defensive_actions tackles + interceptions finishing_delta goals - xG. Positive = finishing above chance quality; tends to regress toward 0. shot_execution_delta xGOT - xG. Positive = placing shots better than their location implies; more stable than finishing_delta. Coverage-aware. A per-90 divides only by minutes in matches that measured that stat, and a rate only uses matches where both parts were measured. A null metric means "not measured in this window", never zero. workload last_match_unix, days_since_last, matches_7d/14d/30d, minutes_7d/14d/30d, starts_l5, avg_minutes_l5, full_90_rate_l10, subbed_on_rate_l10, unused_sub_l10 Club and international matches count together, except unused_sub_l10 (club only). days_since_last is as of the last rebuild; recompute from last_match_unix when it matters. STATS AND SEASON_STATS (include=stats, include=season_stats) stats { last_5, last_10, last_15, last_20, all }, all competitions season_stats { season_id: totals }, one entry per season Each bucket holds apps, minutes, nineties_played and provider counts (goals, assists, shots_total, shots_on_target, tackles, fouls, cards, passes, crosses, dribbles, duels, saves, ...), plus: {stat}_avg per appearance {stat}_per90 per 90 minutes {stat}_oN5_per hit rate: % of appearances (0-100) with more than N.5, e.g. shots_on_target_o05_per = % of games with 1+ SoT. Built for player props. points, points_per_game, wins, win_rate (0-1), draws, losses team results while he played _per hit rates and provider *_percentage fields are 0-100. win_rate and every intel rate and share are 0-1. RANKING AND SORTING Two leaderboards: /players/percentiles OA percentiles. "Who is best / most creative / most dangerous for his role and level." /players/rank provider stats. "Who has the most shots / the best prop hit rate over his last N games." GET /players/percentiles metrics= required. One metric, or a weighted blend of up to 5 with weights 1-3: metrics=expected_goals_p90 or metrics=expected_goals_p90:3,shots_total_p90:2,xg_share Names: /players/meta -> intel.metrics. role= GK, CB, FB, WB, DM, CM, AM, W, ST \ at least group= GK, DEF, MID, ATT (its roles, plus players | one of without a trusted role in that group) | these four competitions= comma ids, max 25 (the player's current club | competition) | teams= comma club ids, max 25 / level= elite, major, standard, lower, women. Defaults to elite unless competitions= or teams= is given (then all levels in scope). window= ewma (default) | season min_nineties= default 4 for ewma, 5 for season. 0 = off. sort= desc (default) | asc page=, per_page= (default 25, max 100); follow next_page_url. Only players with a match in the last 120 days are ranked. Row: { "rank": 1, "id": 1112, "name": "Raphinha", "team_id": 10963, "competition_id": 419, "season_id": 2244302, "role": "ST", "role_group": "ATT", "level": "elite", "cohort": "role:ST|elite", "cohort_size": 224, "score": 98.3, "metrics": { "expected_goals_p90": { "pct": 100, "value": 1.09 }, "shots_total_p90": { "pct": 95, "value": 4.27 } }, "sample": { "nineties": 7.56, "reliability": 0.602, "season_apps": 7, "season_minutes": 555 }, "last_match_unix": 1789844400 } score weighted mean of the requested percentiles, 0-100. Players missing more than 40% of the requested weight are left out. metrics pct is the raw percentile (as in intel.pct); value is the raw number (per 90, rate or share). Lower-is-better metrics (possession_lost_p90, possession_lost_per100_touches) are flipped inside score, so a high score is always good. Their pct stays raw: a high pct there means he loses the ball more. info.lower_is_better lists any you requested. sample.reliability is for the ewma window (null for season). /players/percentiles?metrics=expected_goals_p90&role=ST /players/percentiles?metrics=key_passes_p90:2,big_chances_created_p90&group=MID&level=major /players/percentiles?metrics=tackles_p90,interceptions_p90&competitions=8&window=season GET /players/rank stat= required; one of /players/meta -> stats (else HTTP 400 with allowed_stats) form= last_5 (default), last_10, last_15, last_20, all sort= desc (default) | asc min_apps= minimum apps in that form bucket (default 3; 0 = off) min_minutes= default 0 position_ids= 1-4, comma list teams= comma list per_page= max 100. No paging: rank returns the top N only. include= as above Each row carries highlight { form, stat, value, apps }. /players/rank?stat=shots_on_target_o05_per&form=last_10&min_apps=6 /players/rank?stat=fouls_drawn_per90&position_ids=4&teams=12,34 Sorting list endpoints (fixture, season, competition) sort_by= (a /players/meta stat), sort=, form=, min_apps=, min_minutes=, position_ids=, page=, per_page= (max 100). Without sort_by, rows come in last-updated order, not by quality. On /players/season and /players/competition, sort_by, min_apps and min_minutes use that season's totals, not rolling form, and every row carries that season under season_stats.{season_id}. Ranking on raw intel values /players/percentiles ranks on percentiles, which only compare within a role and level. To compare raw per-90s across levels, or to rank on an intel window it does not cover (l3, l5, l10), pull the pool with include=intel and sort intel.club.{window}.per90 yourself. 8.6 BETS, BANKROLLS, BETSLIPS, EXPORTS -------------------------------------------------------------------------------- GET /bets per-user saved bets, paginated GET /bankrolls list bankrolls GET /bankrolls/{id} single bankroll POST /betslips generate betslips (accumulators) GET /csv/probability CSV export of probability rows POST /betslips body (JSON, every field optional): markets array of market settings items_per_slip legs per betslip (minimum 1) max_betslips number of betslips to return target_odds target combined odds per betslip, e.g. 2.5 odds_per_item [min, max] odds per leg, e.g. [1.2, 3] start_time UNIX seconds, earliest kick-off end_time UNIX seconds, latest kick-off competitions array of competition ids exclude_fixtures array of fixture ids to leave out locked_selections array of selections that must appear in every slip 8.7 MISC -------------------------------------------------------------------------------- GET /predictions/generate/{id|multiple} # cached 30 s, lower throttle multiple: ?ids=A,B,C (max 10). version=2 (or v=2) selects the market-anchored model; fixtures without probability/odds data are listed under `errors`. Version 2 needs the Simulations Engine v2 add-on (free trial available, added from https://oddalerts.com/dashboard#add-ons). Without it the call returns HTTP 403 with "add_on": "predictions_v2". Version 1 is unchanged. Version 2 payloads are large (~20k characters per fixture). Trim them: detail=summary headline 1X2/BTTS/overs %, expected goals, most likely result + top 5 scorelines, value bets, drivers detail=standard summary + main markets (exact goals, handicaps, result combos, halves, HT/FT, timing), corners, cards, market comparison detail=full everything (default, unchanged) include= adds sections to summary/standard (comma-separated): simulation, value, corners, cards, weather, breakdown, timing, markets Example: /predictions/generate/multiple?ids=1,2,3&version=2&detail=summary&include=weather GET /vidiprinter Live-event ticker across all in-play matches, newest first. Needs an api_token like every other endpoint. Params: minutes=5 look-back window, max 30 (default 5) type=goal only one event type (optional) Event types: goal, own_goal, penalty_scored, penalty_missed, yellow_card, red_card, second_yellow, pen_shootout_goal, pen_shootout_miss, half_time, full_time, full_time_aet, full_time_pen, extra_time, penalties Response: { "events": [ { "fixture_id": 1234, "fixture_name": "...", "home_team": "...", "away_team": "...", "event_type": "goal", "minute": 63, "extra_minute": null, "player": "...", "score": "2-1", "ht_score": "1-0", "detail": "...", "team": "...", "team_side": "home", "ts": 1735689600 } ], "count": 1, "window": "5m" } `fixture_id` is the normal fixture.id. `minute`, `player`, `team` and `team_side` are null on half_time / full_time style events. Poll it (e.g. every 30–60 s) rather than requesting a long window. ================================================================================ 9. RECIPES ================================================================================ 9.1 Drill into a specific match. (fixture.id = 1234) GET /fixtures/1234?include=odds,probability,stats GET /odds/history/1234 # all opening/closing/peak odds GET /predictions/generate/1234 # AI-generated narrative 9.2 Stream live games with stats and live odds (for a dashboard). GET /fixtures/live?include=odds,odds.live,probability,stats 9.3 All 8 bookmakers' current FT odds for upcoming games (fewest calls). Do NOT loop /fixtures/upcoming?include=odds&bookmaker=N eight times. Do NOT use /odds/latest — that is a 5-minute tick firehose, not a board. url = /odds/upcoming?markets=ft_result&bookmakers=1,2,3,4,5,6,7,8 follow info.next_page_url until it is false. Need fixture names / KO / competition as well? /fixtures/upcoming?include=odds&bookmakers=1,2,3,4,5,6,7,8&markets=ft_result&filters=hasOdds (nested by slug; still page via next_page_url) Single book, flat odds object: /fixtures/upcoming?include=odds&bookmaker=bet365&markets=ft_result 9.4 Page through a long list — ALWAYS follow next_page_url. data = [] url = first request URL while url: r = fetch(url) data.extend(r['data']) url = r['info']['next_page_url'] 9.5 Player props for a match (e.g. shots on target). GET /players/fixture/1234?include=stats,intel&sort_by=shots_on_target_per90&form=last_10 For each likely starter (intel.workload.starts_l5 high, avg_minutes_l5 70+): volume intel.club.ewma.per90.shots_on_target_p90 hit rate stats.last_10.shots_on_target_o05_per (% of games with 1+) quality intel.pct.ewma.metrics.shots_on_target_p90 and sot_share trust intel.club.ewma.sample.reliability Price the prop from the per-90 scaled to his expected minutes; use the hit rate as a sanity check. 9.6 Scout the best players in a role at a level. GET /players/percentiles?metrics=key_passes_p90:2,passes_in_final_third_p90,big_chances_created_p90&role=CM&level=elite One league only: swap level= for competitions=8. Quote score and each metric's pct with the cohort ("top 5% of elite central midfielders"), and check sample.reliability before trusting a short run. Then GET /players/{id}?include=intel for the full profile. 9.7 Who is in form vs who is good. Same pool with include=intel. Compare intel.pct.l5 against intel.pct.ewma per metric: l5 well above ewma = hot streak; ewma high and season high = established level. Check intel.club.l5.sample.reliability before trusting a hot streak. 9.8 One player, full picture. GET /players/search?q=saka -> id GET /players/{id}?include=intel,season_stats Role and usage from intel.role and intel.workload, current level from intel.pct.ewma, season totals from season_stats. ================================================================================ 10. COMMON PITFALLS ================================================================================ * Bookmaker IDs Use PUBLIC IDs (1..8) OR slugs (bet365, pinnacle, …) on fixtures include=odds, /odds/latest, and /odds/upcoming. The API maps them internally for you. /value still takes numeric public IDs. * include=odds is one book unless you pass several bookmaker=2 (or bookmakers=bet365) → flat { ft_result: { home: 2.1 } }. bookmakers=1,2 → nested { pinnacle: {...}, bet365: {...} }. Omitting the param does NOT return all books; it collapses to the latest tick. For a multi-book board use an explicit comma list or /odds/upcoming. * /odds/latest vs /odds/upcoming latest = price-change ticks in a time window (default 5 min). Huge on busy days. No fixture metadata. Missing rows ≠ no price, just no movement. upcoming = current-price snapshot of upcoming games that have prices. Use this to minimise calls for a full bookmaker board. * Odds availability Odds depend on bookmaker coverage. Lower leagues, youth fixtures, friendlies often have NO bookmaker quotes — empty odds are normal in those cases. /odds/upcoming skips those fixtures; /fixtures/upcoming does not unless you pass filters=hasOdds. * History retention Odds movement is retained for 21 days. Opening/closing/peak odds are retained indefinitely. * Pagination Don't manually increment `page` past `total_pages`. Follow `next_page_url`. `total_pages` is the source of truth. * Player data is opt-in /players endpoints return identity only unless you pass include=stats, season_stats and/or intel. For OA percentiles you need include=intel. * Player percentiles are relative to role and level intel.pct values compare a player with the same role at the same level. Do not compare a "lower" 90 with an "elite" 90; compare the raw intel.club.*.per90 values instead, and always quote the cohort. * Two player leaderboards /players/rank and sort_by take the provider stats keys in /players/meta -> stats (goals_per90, shots_on_target_o05_per, ...). /players/percentiles takes intel metric names (goals_p90, xg_share, ...). The suffixes differ: _per90 for stats, _p90 for intel. * Player null vs 0 In intel, null means the stat was not measured, not zero. Intel rates and shares are 0-1; stats _per hit rates are 0-100. * Player form vs season stats.last_N buckets span all competitions. On /players/season and /players/competition, sort_by and min_apps switch to that season's totals. ================================================================================ END OF FILE ================================================================================