Football & Soccer Scraper — xG, Live Scores, Odds avatar

Football & Soccer Scraper — xG, Live Scores, Odds

Pricing

from $0.60 / 1,000 match with score and competitions

Go to Apify Store
Football & Soccer Scraper — xG, Live Scores, Odds

Football & Soccer Scraper — xG, Live Scores, Odds

Football (soccer) from every league the source carries: xG, xA and xGOT split by half, about 100 metrics per player, missing players, goals and cards with minute and scorer, lineups with formation, and odds from up to 20 bookmakers per country. A week back, a week ahead. No login, no browser.

Pricing

from $0.60 / 1,000 match with score and competitions

Rating

0.0

(0)

Developer

Huyen Trang

Huyen Trang

Maintained by Community

Actor stats

0

Bookmarked

5

Total users

3

Monthly active users

3 hours ago

Last modified

Categories

Share

Every match from every league and cup the source covers with the numbers models actually run on: expected goals, expected assists and xG on target, possession, big chances, passing and duels split by half, the event timeline with minutes and scorers, about 100 metrics for every player on the pitch and full lineups with formation.

No login. No cookies. No API key. No headless browser.

Quick start

  1. Open Input: the form is ready to run as it is — the 20 matches closest to today in the 15-day window, top competitions first, with statistics and summary. To narrow it, set Days to Yesterday and Competitions to Premier League (England).
  2. Press Start and wait for the run to finish.
  3. Open Output, press Export, set View to Matches and the format to Excel, then Download: one row per match with the match ID, date and time, teams, the score as numbers and as text, status, winner and link. In CSV and Excel All fields keeps the score and teams too, but scattered among about 2,000 columns in alphabetical order, and most nested data (statistics, events, box scores) is cut off at that limit; keep it for JSON (see Getting a spreadsheet).

With the form as it ships, Matches, Statistics and Events have data; the other tabs of Output stay empty until you turn on the option named in their title.

Cost: the form as it ships comes to at most $0.01605 a run — $0.00005 for the start plus $0.0008 a match; a match without a stat sheet or summary costs less. 1 000 matches cost $0.80 at the default settings, and Apify's free plan, $5 of usage a month, covers about 6 200 of them. Every option is priced under What a run actually costs.

Recipes

Paste one into the JSON view of the Input tab or send it through the API. Prices are per match returned, at this Actor's event prices; the run start adds $0.00005.

Yesterday's Premier League results — $0.0008 a match with statistics and summary.

{"dateRange": "yesterday", "tours": ["ENGLAND: Premier League"]}

Only matches in play, with the score in play — $0.0008 a match: the result plus the live state.

{"matchStatuses": ["live"], "includeLive": true, "includeStats": false, "includeSummary": false}

A month of a season from the league page — $0.0006 a match, scores only.

{"leagueUrls": ["https://www.flashscore.com/football/england/premier-league/"], "dateFrom": "2026-09-01", "dateTo": "2026-09-30", "includeStats": false, "includeSummary": false}

A team's schedule and recent results — $0.0006 a match, scores only.

{"teams": ["Arsenal"], "teamResultsPages": 1, "includeStats": false, "includeSummary": false}

A results feed on a schedule — $0.0006 for each new or changed match plus only the parts that changed; an unchanged run pays the start and the run minimum. Run it hourly from a schedule with isExclusive on.

{"dateRange": "yesterday-today", "tours": ["ENGLAND: Premier League"], "monitorMode": true, "monitorName": "premier-league"}

Pre-match odds board — $0.001 a match with odds and lines; matches without prices are skipped and not billed.

{"dateRange": "next-3-days", "matchStatuses": ["scheduled"], "tours": ["ENGLAND: Premier League", "SPAIN: LaLiga"], "includeStats": false, "includeSummary": false, "includeOdds": true, "includeOddsMarkets": true, "onlyWithOdds": true}

Getting a spreadsheet

In Console. Open the run's Output tab and press Export. Pick a View, then the format, then Download:

View in Consoleview= in the APIOne row perNeeds
Matchesoverviewmatch—
OddsoddsmatchMatch-winner odds
Live statelivematchLive state
Statisticsstatisticsstatistic per part of the matchMatch statistics
Eventseventsevent of the timelineMatch summary
Momentummomentumminute of the momentum graphMatch momentum
Lineups: homelineupsHomeplayer or coach of the home sideLineups
Lineups: awaylineupsAwayplayer or coach of the away sideLineups
Playersplayersplayer line of the box scorePlayer box score
BookmakersbookmakersbookmakerMatch-winner odds
Every pricepricesprice of every bookmakerEvery bookmaker price
Totalstotalsover/under lineTotals and handicaps
Head-to-headh2hdirect meetingHead-to-head history
Changes (monitor runs only)changesmatch the monitor emitted—

Long views unwind a list of the record into rows, so one match gives as many rows as it has statistics, events, players or prices; a match without that part gives a single row with the match columns and empty columns of the part, and a match whose list is empty gives none. The dialog opens on All fields, which exports every nested field of the records. In CSV and Excel that stops at 2,000 columns: the top-level fields (score, teams, competition, date) stay, but in alphabetical order among the rest, while most nested data — statistics, events, box scores, head-to-head rows — is cut off. Choose Excel rather than CSV: Excel opens a CSV with its own rules, so a text score such as 6-4 can turn into a date, and with a semicolon list separator the file opens as one column.

Through the API. The same views come straight as files:

https://api.apify.com/v2/datasets/<datasetId>/items?view=overview&format=xlsx&clean=true

Use format=csv for CSV and any view= name from the table. The column headers of such a file are field paths (homeTeam, odds.margin), not the labels of the table in Console. To name the columns yourself, list the fields and give outputFields in the same order; outputFields works with fields, not with view:

https://api.apify.com/v2/datasets/<datasetId>/items?fields=matchId,startDate,homeTeam,awayTeam,homeScore,awayScore&outputFields=Match,Date,Home,Away,Home%20score,Away%20score&format=xlsx

The dataset ID is in the run's details and in defaultDatasetId of the run object. A link without a token works when your account lets anyone with the ID read your storages; with Restricted access send your token in the Authorization: Bearer header or use Export → Copy shareable link. Apify keeps your ten most recent runs with their datasets; older runs are deleted with their storages after your plan's data retention period (Apify docs), so keep the files you need or append the records to a named dataset (outputDatasetName).

A named dataset has no views. The Actor opens it without its dataset schema, so view= does not work on it and its Export offers only All fields. Name the columns with fields instead, for example those of Matches and Changes:

https://api.apify.com/v2/datasets/<dataset ID, or owner~datasetName>/items?fields=matchId,startDate,startTime,tournament,homeTeam,awayTeam,homeScore,awayScore,scoreText,status,statusDetail,winnerName,matchUrl,scrapedAt,changeType,monitorChangedFieldsText,unavailablePartsText&format=xlsx&clean=true

Output example

One record from a local run, 14 October 2026 — Lens vs Sporting CP, EUROPE: Champions League - League phase — trimmed: lists keep their first item and nested objects only some of their fields. Every field is described under Output fields.

{
"matchId": "boMhjDLM",
"sport": "football",
"startDate": "2026-10-13",
"startTime": "16:45",
"startTimeKnown": true,
"timezone": "UTC",
"homeTeam": "Lens",
"awayTeam": "Sporting CP",
"homeScore": 0,
"awayScore": 2,
"tournament": "EUROPE: Champions League - League phase",
"tournamentId": "xGrwqq16",
"startsAt": "2026-10-13T16:45:00.000Z",
"status": "finished",
"statusDetail": "finished",
"home": {"name": "Lens", "id": "IBmris38", "url": "https://www.flashscore.com/team/lens/IBmris38/"},
"away": {"name": "Sporting CP", "id": "tljXuHBC", "url": "https://www.flashscore.com/team/sporting-cp/tljXuHBC/"},
"score": {"home": 0, "away": 2},
"scoreText": "0-2",
"creditedScore": {"home": 0, "away": 2},
"winnerName": "Sporting CP",
"matchUrl": "https://www.flashscore.com/match/boMhjDLM/",
"stats": {
"headline": {
"Expected goals (xG)": {"home": "1.51", "away": "1.67"},
"Ball possession": {"home": "56%", "away": "44%"}
}
},
"odds": {
"market": "HOME_DRAW_AWAY",
"priceState": "closing",
"home": 2.45,
"draw": 3.51,
"away": 2.71,
"bestHome": 2.5,
"bestHomeBookmaker": "10bet",
"bestAway": 2.8,
"bestAwayBookmaker": "Ladbrokes",
"bookmakersCount": 20,
"margin": 6.21,
"impliedHome": 0.3843,
"impliedDraw": 0.2683,
"impliedAway": 0.3474,
"movement": {"homeDirection": "drifting"},
"bookmakers": [
{
"bookmaker": "10bet",
"country": "GB",
"homePrice": 2.5,
"drawPrice": 3.6,
"awayPrice": 2.68,
"openingHome": 2.38,
"openingAway": 2.65,
"active": false
}
]
},
"summary": {
"venue": "Stade Bollaert-Delelis",
"attendance": 37548,
"events": [
{
"periodName": "1st Half",
"minute": "22'",
"minuteNumber": 22,
"side": "away",
"type": "Goal",
"player": "Suarez L.",
"assist": "Irankunda N."
}
]
},
"playerStats": {
"players": [
{
"name": "Risser Robin",
"playerId": "zHRwZwXF",
"number": "40",
"team": "home",
"position": "Goalkeeper",
"starter": true,
"rating": 5.6,
"topRated": false
}
]
},
"scrapedAt": "2026-10-14T13:56:11.422Z"
}

What you get

CapabilityWhat it means for you
Penalty shoot-outs and both halves unpackedthe credited score never masquerades as the match score
Player stats for every player on the pitchabout 100 metrics per player — minutes, xG, xA, shots, passes, duels, tackles, saves — with the match rating, position and starter flag
Missing players and predicted lineupsinjuries and suspensions with the reason days before kick-off, the predicted XI on match day
Odds from up to 20 bookmakers per country, 17 countriesopening, current and closing prices, no-vig probabilities, every price of every bookmaker on request
Expected goals, expected assists, xGOTthe advanced model inputs, per match and per half
Event timeline: goals, cards, substitutionsminute, player and the score after each goal
Lineups with formation, positions and photosheight, age, market value and season rating for every player
Attendance and venuegate figures for matches where the source publishes them

Every field, per match

GroupMetrics
ExpectedExpected goals (xG), expected assists (xA), xG on target (xGOT), xGOT faced, goals prevented
Top statsBall possession, total shots, shots on target, corner kicks, yellow and red cards
ShotsShots off target, blocked shots, shots inside and outside the box
AttackBig chances, counter attacks, offsides, touches in the penalty box
PassesPasses completed, accuracy, final third entries, crosses, long balls
DefenseTackles, clearances, interceptions, duels won, errors leading to a goal or a shot
GoalkeepingSaves, goals prevented, punches, high claims

Every metric comes per match and per period — Match, 1st Half, 2nd Half — so you can model momentum, not just outcomes.

Every stat row carries a stable snake_case key and numbers: homeValue/awayValue, and for shares like 63% (36/57) also homePercent, homeMade, homeAttempts (and the away side) — no string parsing before the data goes into a model.

Match context, not just numbers

Alongside the stat sheet each match carries a summary: venue, town, attendance, referees, score broken down by period and match duration — whatever the source publishes for that competition. Top-tier games carry the most. Where the source publishes durations, they come both as text and as minutes (totalDurationMinutes, periodDurationsMinutes).

Every event in the timeline carries the reason in reason — Foul, Delay of game, Injury — the source's commentary line, where it publishes one, in comment, and the source's short tag in note, such as Penalty Awarded. period numbers the part of the match the event belongs to, in order — regulation periods, then overtime and the shoot-out — and periodName names it (1st Half, 2nd Half). An event with two assists lists each assistant once in assist, with their ids in assistIds.

Form history, not just the last meeting

Head-to-head returns three lists: recent matches of the first side, of the second, and their direct meetings. Each row carries date, tournament and country, the numeric score, the winner, how the match ended (regulation, extra time, penalties, walkover or awarded, in status), participant IDs and a link to the match. The default depth is 10 rows per side; set maxH2HRecords to 0 for everything the source publishes — 50 per side. Direct meetings come in full by default (maxH2HMeetings = 0), and the source keeps up to about 75 per pair. Clubs that meet several times every season reach that, and then the meetings are the longest of the three lists: 51 meetings on a Premier League match, 74 and 75 on two Brazilian derbies. maxH2HMeetings keeps fewer of them. The match itself is flagged isThisMatch and is never billed; matches played after it are left out, so the history of a past match stops at its own date. The Head-to-head view lists the direct meetings, one row each.

A history row of a match decided by a shoot-out keeps the source's credited score in score, one more for the shoot-out winner, while homeScore and awayScore are the score of play without the shoot-out: 1-1 and 6-7 on penalties comes as score "1:2" with homeScore 1 and awayScore 1. penalties holds the shoot-out itself and status is penalties.

Rows are billed one by one, at most 30 a match: the rest of the history comes in the same record for free. At the default depth a match holds about 12 to 95 rows, so it is billed 12 to 30 — the measured counts are under What a run actually costs below.

On top of that comes a form summary per side, counted only from matches played before this one: wins, draws and losses, a form string of the last 10 results, the date of the last match and rest days, the home side's record at home and the away side's record away, and which competitions come up most — a form guide for both sides without a second Actor.

Score fields

  • Ready-made score fields: score (the final score; after a shoot-out it is the score of play, with the shoot-out in penaltyShootout), scoreText ("2-1", "1-1 (pen. 4-3)"), winnerName and a profile url for each side.
  • Three scores, three meanings: score (and homeScore, awayScore) is the score of play including overtime, regulationScore the score after regular time, and creditedScore, in sports with shoot-outs, the source's credited result, one more for the winner of the series: 1-1 and 4-3 on penalties is score 1-1, creditedScore 2-1.
  • Penalty shoot-outs unpacked: regulationScore (the 90 minutes), overtimeScore and penaltyShootout as separate fields — the credited score never masquerades as the match score.
  • Both halves: period scores carry explicit period numbers, first half reconstructed even where the source omits it.
  • Red cards per side in redCards (null when the source has no live coverage of the match).
  • Player ratings in lineups (rating, ratingRank) and clean formations without the goalkeeper digit.
  • Aggregate notes: first-leg results, series standings, postponement reasons in the note field.
  • home.countryId / away.countryId are null for clubs: the source gives a numeric country id to individual players only.
  • Every match carries the tournament's stable id (tournamentId) — the key to the source's season archives — and, where the competition has seasons, tournamentSeasonId and tournamentStageId. Our League Standings Actor returns all three under the same names and values, and its teamId equals home.id and away.id, so matches join with their league table.

Player stats, missing players and predicted lineups

includePlayerStats adds a stat line for every player who took the field: about 100 metrics with the match rating, position and starter flag. stats holds each value as Flashscore shows it ("95.45%"), rawStats the raw number (0.954545). Unused substitutes are neither returned nor billed: about 32 lines a match. Two extra requests per match. The shirt number and country come from the lineup by player id: with includeLineups off that is one more request per played match, not billed.

includeLineups returns formation, starters, substitutes and coaches with shirt numbers, positions and photos, plus each player's height, age, market value and season rating and team averages. Positions are positions (Goalkeeper, Defender, Midfielder, Forward); the captain and other roles sit in captain and roles. Missing players — injured, suspended or doubtful, with the reason — appear a few days before kick-off and the predicted lineup on match day: on 13 October 2026 missing players were out two days ahead and predicted lineups on match day, while a fixture four days ahead had neither. Upcoming matches are checked from 72 hours before kick-off.

Each line also carries its main numbers as fields of their own — match_minutes_played, goals, assists_goal, expected_goals, expected_assists, shots_total and 10 more, the values of rawStats — so the Players view and its CSV or Excel export show them as columns.

Momentum and league table

includeMomentum adds momentum: the source's Match Momentum graph minute by minute — minute, addedTime, period and value from -1 to 1 — for matches in play or played that have statistics. The Momentum view gives one row per minute. Many lower leagues have no graph, and then the record has no momentum key at all. It costs one request per such match, plus one per run, and it is not billed.

includeTableContext adds tableContext: for home and away their position, points, played, wins, draws, losses, recent form (latest first, up to five results) and group, with tableDate. The table is the one on the day of the run, not as of the match, so a past match shows today's standing. Cups and play-offs have no table and give null. It costs one request per competition and stage in the run, not per match, and it is not billed. Our League Standings Actor returns the whole table under the same tournamentSeasonId and tournamentStageId.

News, highlights and where to watch

Three optional feeds, all in the same match record:

  • includeNews — news articles linked to the match: title, outlet, image and publication time. Notable matches carry dozens of articles.
  • includeHighlights — official video highlight links with preview images, available once the match has been played.
  • includeBroadcasts — TV channels and streaming services the source lists for the match, by country, plus bookmaker live streams. Each item has kind (tv or bookmaker-stream), the source's channelId or bookmakerId, and countryHint, the country as the channel name writes it in brackets (Usa, GBr), not an ISO code. The source's partner links lead to advertising trackers, so their url is null. For some matches (many football ones) the source sends the channel names only: those items have channelId and url null. When the list has no TV channel at all, one more request reads the source's shorter listing, which depends on the country the request comes from.

One request per match per option, two for broadcasts of a match without a channel list. Each is billed only when data exists; broadcasts only when at least one TV channel is listed, so bookmaker streams alone are free.

Season archives: history beyond the daily window

The daily feeds cover seven days back and seven ahead. For anything older, pass league and season URLs: https://www.flashscore.com/football/england/premier-league-2024-2025/ — or any of the 115 Premier League seasons back to 1901. Old matches return the full detail set the source still holds — verified: statistics with advanced metrics a year back, lineups and events fifteen years back. Archived matches keep their odds as well: Premier League matches from May 2012 came with the 9 bookmakers of the time, opening and closing prices and 14 to 19 total and handicap lines.

{ "leagueUrls": ["https://www.flashscore.com/football/..."] }

Archive records without a start time. Some archive pages keep only the day of a match, with a placeholder time at midnight in Europe or at midnight UTC. When at least 90% of the games on a page older than a year sit at such a midnight, those records carry startTimeKnown: false, an empty startTime and startDate set to the day of the match, while startsAt and startTimestamp keep the placeholder; dateFrom and dateTo compare the day of the match. The check goes by the times on the page, so it can be wrong for an old league that really played at those hours. Records of the daily feeds and of the past year always have startTimeKnown true.

Specific matches by ID

Paste Flashscore match IDs or match URLs into matchIds and get exactly those matches, from any date the source keeps: a First Division match from 1974 is found as easily as today's. Unknown IDs and matches of another sport are listed in the run warnings and never billed. Match IDs override dateRange, league URLs, teams and every competition and status filter; maxMatches, dateFrom and dateTo still apply.

Date ranges

Set dateFrom and dateTo (YYYY-MM-DD). On their own they pick days inside the 15-day window; together with league URLs, teams or match IDs they keep only matches in that range, so a one-month slice of a season is billed as one month: September 2026 of the Premier League kept 30 of 170 matches and billed 30.

Pass names instead of URLs — teams: ['Arsenal' or 'Real Madrid'] — and the Actor resolves them through the source's own search, then pulls the full known schedule and as much history as you ask for (teamResultsPages, about 40 matches per page; a First Division match from 1974 was found through a club's history). Paste a Flashscore team or player link instead, for example https://www.flashscore.com/team/arsenal/hA1Zm19f/, or the 8-character ID from it, to skip the name search and get exactly that team or player.

Monitor mode: pay only for what changed

Turn on monitorMode and the Actor remembers every match it has already delivered in a named key-value store in your account. The next run emits — and bills — only new and changed matches: new fixtures, kickoff moves, score updates, line moves, final results. On a schedule, an unchanged day costs only the run itself: the start event and the run minimum (see the costs below), usually under half a cent a run. Use monitorName to keep separate watchlists apart. monitorKey, the older name of the same field, still works in saved tasks, schedules and API calls: the same value keeps the same memory, and when both are set monitorName is used.

Every record the monitor emits says why: changeType is new for a match it has not seen before and changed for one it has, and monitorChangedFields names what changed — score, status, schedule, odds, stats, summary, lineups and so on — so a kickoff move can be handled differently from a final score. monitorChangedFieldsText holds the same list as one string ("score,status") for a spreadsheet column, and the Changes view shows it next to changeType. A part that did not arrive because the source failed (unavailableParts) is not a change: the match is not output again for it, and when the part comes back unchanged it is not billed again. With odds on, odds.previous holds the average and best prices this monitor output last time, with the time, and odds.changePct the change of each average price in percent.

A changed match is billed for what changed, not for the whole record again. It costs the base event, plus the extras of the parts named in monitorChangedFields: a moved line bills the odds, a fuller stat sheet bills the stats, while an unchanged summary or lineup is not billed twice. Within odds, the three odds events are billed separately: a moved match-winner price (pre-match or in-play) bills match-odds, a moved total, handicap or both-teams-to-score line match-odds-markets, a changed price list match-odds-offers. Row-billed parts — head-to-head rows, completed games or periods of point by point, player lines, news articles — are billed only for rows beyond those already paid for that match. The record itself still arrives complete. Free parts do not make a match changed: a new point of the momentum graph, a new place in the league table or a changed bookmaker stream alone does not output the match again, and a match output for another reason carries them up to date.

What a monitor run checks. In monitorMode, maxMatches caps the matches a run checks, not only the ones it outputs. Matches that are over and started more than a day ago are skipped without a request while they stay the same, and do not count toward it. When more candidates are left than maxMatches, half of the checks (rounded up) go every run to matches in play, matches past their start time, finals not output yet and then the top competitions, and the other half to the rest, the ones checked longest ago first, so a big watchlist is covered over several runs while the top matches are checked in every one. The monitor remembers the unfinished matches of the first day it reads until their final is output, including matches cut by maxMatches or by the status filter while they were in play, and reads them again when they fall outside the selected days — from a feed already read, or from the feed of the day they started, one request per day — first among the checks, and billed only if they changed. Schedule it hourly with dateRange yesterday-today, or daily with last-3-days, so a match that ends after midnight still gets its final score; a monitor that reads a single day says so in its status. With matchStatuses the monitor outputs only matches in those statuses: a monitor of live matches only does not output final scores, so add finished to get them.

One monitor per name at a time. Do not run two monitors with the same monitorName at once: both would output and charge the same changes. On a frequent schedule turn on the schedule's isExclusive option, which skips a run while the previous one is still going — that is the reliable protection. The Actor also tries to detect an overlap: a run that finds the memory saved by another run of the same name within the last few minutes, while that run is still going, outputs nothing and says why in its status. It relies on that run having saved its memory midway (about once a minute while it changes something), so a short overlap can pass unnoticed; then the two memories are merged at the end, and the status warns about it.

Regular loads: deduplicate and update

Every record carries matchId, the source's own ID, which stays the same across runs, and scrapedAt, the moment the record was output. A match read twice — two runs over overlapping days, or a monitor that emits it again after a change — comes as two records: keep the one with the larger scrapedAt.

Do not let a failed part overwrite good data. When the source fails to send a part of a match (statistics, summary, lineups, box score, head-to-head, odds and so on), the record names it in unavailableParts (unavailablePartsText in the Matches and Changes views), leaves it null and is not charged for it. A later record of the same match can carry such a gap where an earlier one had the data, and in monitorMode the part that comes back unchanged is not output again. So take the newest record and fill the parts it names from the latest earlier record that has them. In pandas:

import pandas as pd
df = pd.read_json("dataset.json").sort_values("scrapedAt")
latest = []
for _, rows in df.groupby("matchId", sort=False):
row = rows.iloc[-1].copy()
gaps = row.get("unavailableParts")
for part in gaps if isinstance(gaps, list) else []:
if part in rows and row[part] is None:
filled = rows[part].dropna()
if len(filled):
row[part] = filled.iloc[-1]
latest.append(row)
latest = pd.DataFrame(latest)

In a database or a sheet, upsert on matchId when the new scrapedAt is later, and for the parts named in unavailableParts keep the stored value (COALESCE(new.stats, old.stats)). monitorMode sends only new and changed matches, and the Changes view shows changeType and monitorChangedFieldsText ("score,status") for each. outputDatasetName appends every record to one named dataset across runs, so the history stays in one place; a named dataset is kept until you delete it, Apify bills its storage, and it has no views (see Getting a spreadsheet).

Compact output

Set omitEmptyFields to drop every null, empty list and empty object from the records. Measured on 11 finished matches with statistics and summary, the form as it ships, on 14 October 2026: 50 top-level fields shrink to about 39 and the record from 35 KB to 24 KB. Items inside lists keep their positions, so period-by-period lists stay aligned. Leave it off for CSV and spreadsheet exports, where a fixed set of columns is easier. With it off, records of runs with the same options have the same keys; some keys appear only with their option or mode (odds.markets, odds.offers, odds.live, the American prices, odds.previous and the other monitor fields, seeds, tableContext, momentum (only when the source has a graph)), or only when there is something to say (unavailableParts, a lifted box-score column the match has), so a dataset that collects runs with different options has a varying set of keys.

Betting odds

Turn on includeOdds and every match carries match-winner prices from each bookmaker of the countries you pick — opening price and current price side by side — plus the market average, the best price and the bookmaker margin. Finished matches keep their closing prices, so already-played days can be backfilled.

The match-winner market is three-way — home, draw, away — in regular time. odds.market and odds.scope name it.

Every odds record adds no-vig probabilities (odds.impliedHome, odds.impliedDraw, odds.impliedAway, from 0 to 1), the best price of each outcome with the bookmaker that offers it (odds.bestHome and odds.bestHomeBookmaker, and so on), the average opening price of the same bookmakers (odds.openingHome...), how many bookmakers the averages cover (odds.bookmakersCount), the book on the best prices (odds.bestPriceOverround, percent above 100) and odds.arbitrage, true when the best prices of different bookmakers cover every outcome.

odds.movement shows where the match-winner market has gone since opening: for every outcome, how many bookmakers shortened the price and how many drifted it. odds.movement.homeDirection sums it up for the home side only, not for the whole market. It is part of the match-winner odds and is not billed separately. Set oddsFormat to american and averages, best prices, every bookmaker and in-play prices also come in American format (odds.homeAmerican: +150, -110); decimal prices always stay.

Prices come from the odds feed behind Flashscore's own Odds tab, keyed by the same match id and by the participant ids of the fixture, so a price never lands on the wrong team. oddsCountries picks whose bookmakers you see. Each country lists its own licensed bookmakers, and the count changes from match to match. The country is a parameter of the request, not of your proxy, so the list is the same from any exit address, Apify's US servers included. Pick several countries to merge their bookmakers into one record: each bookmaker appears once with its country, and averages and best prices use the merged list — Great Britain plus the United States gave 25 bookmakers on a Premier League match. One request per country per match. All 17 countries on one Ligue 1 match, read on 14 October 2026 (Ukraine listed none that day):

oddsCountriesStorefrontBookmakersAmong them
GBGreat Britain20bet365, William Hill, Betfair, Paddy Power, Skybet
USUnited States (New Jersey)5DraftKings, Fanduel, BetMGM.us, Fanatics, bet365.us
ITItaly13Sisal, SNAI, Eurobet.it, GoldBet, Lottomatica
ZASouth Africa8Betway Africa, Sportingbet, Sunbet, Betxchange, Easybet
FRFrance8Betclic.fr, Winamax, Unibet.fr, Pmu, bwin.fr
SESweden7Unibetse, BetMGM.se, MrGreen, Coolbet, Expektse
ESSpain6Codere, Sportium.es, Luckia.es, Retabet, Versus.es
MXMexico6Calientemx, 1xBet, Novibet, Sportium.mx, Winpot
DEGermany6Interwetten, bwin, Betano.de, Oddset.de, Winamax.de
PLPoland6STS.pl, eFortuna.pl, Betclic.pl, Superbet.pl, LV BET
CACanada (Ontario)5888sport, TonyBet, PointsBet.ca, Sports Interaction, Betano.ca
CZCzech Republic4Tipsport.cz, iFortuna.cz, Betano.cz, Chance.cz
NGNigeria2Bet9ja, Betking
NLNetherlands2bet365.nl, Sevelev.nl
ARArgentina1Unibet
AUAustralia1Unibetau
UAUkraine0—

Prices of a match that has started are its closing prices. The pre-match market closes at the start, and the source keeps showing its last prices. On a match in play, finished, interrupted, abandoned or canceled, odds.priceState is closing, every bookmaker and every row of the price list carries active: false, and odds.arbitrage is null: a closed market offers no bet to place. Upcoming and postponed matches carry pre-match. odds.capturedAt says when the prices were read. When matches in play come with closing prices only, the run status says so and points to the in-play prices under Live matches.

Only matches with prices. Set onlyWithOdds and a match no bookmaker prices is neither returned nor billed; the run checks up to four times as many candidates as maxMatches to fill the rest. When the odds feed fails for a match and there is no fallback, odds is null, the record names odds in unavailableParts and the odds are not billed.

Turn on includeOddsMarkets as well and the record adds over/under and Asian handicap lines — the average price on each side and how many bookmakers stand behind each line — and both teams to score where it is priced. Each block names its part of the match in scope: of the two full-match variants, with and without overtime, the one more bookmakers price. It all comes from the same response as the match-winner prices, so there are no extra requests — on a Premier League match measured on 13 October 2026: 22 over/under and 17 handicap lines and both teams to score. A match with no total, handicap or both-teams-to-score line gets odds.markets null and is not billed for markets.

Every price, flat. Turn on includeOddsOffers for every price of every bookmaker as a flat list in odds.offers: market, part of the match, selection, line from the home side, opening and current price, movement since opening and whether it is still offered. A Premier League match carries about 2 900 rows (about 600 KB) from the British storefront; correct score alone is about 40% of them. Filter by market (oddsOfferMarkets) and part of the match (oddsOfferScopes) to keep records small. No extra request; charged once per match as match-odds-offers, only when the list is not empty. The Every price view gives one row per price and Bookmakers one row per bookmaker; the codes are listed under Output fields.

Live matches

Turn on includeLive and matches in play carry their current state: the running score, the match clock and the stage of the match in play. live.minute shows the match clock the way Flashscore shows it: "67", "45+2", "90+", "HT" at half time. live.period names the period.

Status comes in two fields: status is scheduled, live, finished, postponed, canceled, abandoned (called off, no result) or interrupted (stopped and not finished yet, so it can still resume and turn finished), and statusDetail names the period — 1st half, half time, 2nd half, extra time, penalty shootout — or how the match ended or stopped: after extra time, after penalties, walkover, awarded, interrupted, suspended. An interrupted match has neither endedAt nor winner, even when one side leads on goals: it is not over yet. A match whose start is held back stays scheduled, with statusDetail delayed or pending.

Live state costs one request for the whole run, not one per match. Once a match is over, live is null: the live feed keeps a finished match for a few minutes, and that tail is neither returned nor billed as live state. live.capturedAt says when the state was read. The live feed is read after the daily list, and its stage wins: with includeLive or the Live now filter, status, statusDetail and the clock follow it, so a match that ended in between comes as finished, with its winner and endedAt, and the Live now filter leaves it out.

Only matches in play. Set matchStatuses to Live now and the run returns just the matches in play — the Actor reads the live feed first and filters before charging you, so you don't pay for a full day of fixtures to find the handful that are on. Matches in play can only be on today's or yesterday's list, so the run reads just those days whatever dateRange says; Finished alone skips the days ahead. The filter on its own does not add the live state: turn on includeLive as well for the running score, the clock and the period, billed as match-live.

In-play prices. includeLiveOdds adds odds.live to matches in play: the average and best in-play match-winner prices with the bookmaker behind each best price (odds.live.home, odds.live.bestHome, odds.live.bestHomeBookmaker...), how many bookmakers the averages cover and how many take bets right now (odds.live.bookmakersCount, odds.live.activeBookmakers), and each bookmaker's own prices (homePrice, drawPrice, awayPrice), up to 10 bookmakers, one request per bookmaker. Each bookmaker row also carries its first in-play price (openingHome...: the first live price, not the pre-match one) and where the price stands against it (homeDirection, drawDirection, awayDirection: up above it, down below it, null when equal), and the averages count how many bookmakers hold each price above or below their first in-play price (odds.live.homeUpBookmakers, odds.live.homeDownBookmakers and so on). A Champions League match in play on 14 October 2026 had live prices from 7 British bookmakers, each with its first in-play price and the direction of the move since then. It is part of the odds charge.

In-play totals and both teams to score. With includeOddsMarkets on as well, a match in play also gets odds.live.markets: the in-play over/under lines of goals (overUnder, lowest line first) and both teams to score (bothTeamsToScore), each from one bookmaker that has a live line on it, named in bookmaker, not averaged. It takes up to four requests a match together with the bookmaker menus; the Champions League match above got the 3.5 line and both teams to score from bet365 in two of them. Prices a bookmaker has suspended are left out, and a market with only suspended prices is not returned. It is billed as match-odds-markets, once per record: a match already billed for its pre-match lines pays nothing more for these. In monitor mode a moved in-play line is a changed market and is billed again, like a moved pre-match line.

The Live state view lists the matches in play with period, clock and live score.

Round of the match

The daily feed carries a stage name for some competitions (League phase, Play Offs). Switch on resolveRounds and the round is read from the tournament itself: Final, Semi-finals, 1/8-finals. The cost is per tournament in the run, not per match:

  • League rounds such as Round 6 and cup stages come from the tournament page (a heavy one, about 1 MB; free when the same league is in leagueUrls) plus the first page of its results feed for played matches and of its fixtures feed for upcoming ones: two or three requests per tournament, a further page only for a match deep in the season.
  • Matches in play are not looked up and keep round null: the season feeds list a match before it starts and after it ends, not while it is on. A tournament with only matches in play costs nothing.
  • Competitions the source does not split into rounds keep null too — the NHL regular season, for one — and the lookup stops after the first page.

Input

{
"dateRange": "custom",
"dayOffsets": [-1, 0, 1],
"tours": ["ENGLAND: Premier League", "SPAIN: LaLiga"],
"includeStats": true,
"includeH2H": false,
"maxMatches": 200
}

dateRange accepts presets (today, yesterday, yesterday-today, last-3-days, next-week, whole-window...); with dateRange: "custom" the dayOffsets array is used instead: 0 today, -1 yesterday, 1 tomorrow. dateFrom and dateTo take calendar dates instead (see Date ranges above).

Day boundaries follow a time zone, and you choose it. timezone takes a time zone name such as America/New_York or Europe/Berlin, with daylight saving applied, or an offset such as +05:30. Left empty, days follow UTC. With UTC days a US game that starts at 8 pm Eastern or later (7 pm in winter) lands on the next calendar day, so set America/New_York for the American game day. A match is kept only if it starts on one of the chosen days in that zone: the source's day feeds overlap the neighbouring days by about an hour, and without this cut a daily run would bill such a match twice: on 14 October 2026 a yesterday run dropped 4 of 328 such matches. A match in play that started yesterday still counts as today's. The older timezoneOffset (whole hours, no daylight saving) works when timezone is empty; its 0 means this Actor's default, so ask for UTC days with timezone: "UTC".

The daily feeds reach seven days back and seven days ahead — measured on 13 October 2026 across offsets from -8 to +8: fixtures up to the seventh day, nothing on the eighth. next-week covers the seven days after today, so a weekday run reaches the weekend's fixtures. Offsets outside the window are dropped with a warning instead of silently returning nothing.

tours keeps only competitions whose name contains one of the strings as whole words, ignoring case, so ENGLAND: Premier League, SPAIN: LaLiga, ITALY: Serie A all work. A bare Premier League also catches the Premier Leagues of Egypt, Lebanon and dozens of other countries; add the country as the source writes it, and ENGLAND: Premier League then skips Premier League 2 and the Premier League Cup. The input form suggests 20 popular competitions written the way the source writes them (EUROPE: Champions League), and any other name can be typed in. Most of them are seasonal: a league between seasons has no matches, and when none of the selected competitions has any on the chosen days, the run status says why. Leave it empty for everything.

More filters for the daily list. teamFilter keeps only matches of the given teams or players among the selected days — names as whole words, or the 8-character IDs from Flashscore links, and teamSide (home, away or any) keeps only their home or away matches. startsWithinHours keeps matches that start from now until that many hours ahead, and days that cannot hold such matches are not read. These filters read nothing extra; for a team's history beyond the window use teams. Two more drop whole kinds of competition by their name and link: excludeWomen (women's competitions) and excludeYouth (U19, U21 and other youth, junior and reserve competitions, such as the UEFA Youth League and Primavera). A competition counts when its name or link says so (Women, U21, Youth, Reserve...), plus a short list of ones that do not: England's WSL and WSL 2, Premier League 2, Premier League Cup and Professional Development League, and MLS Next Pro. Another competition without such a word in its name or link is not recognised and stays. They also apply with league URLs and teams, while match IDs ignore them; on 14 October 2026 the two together dropped 49 of the 264 football matches of the day.

Order and maxMatches. maxMatches caps the run. When matches are picked by days (no league URLs, teams or match IDs), Champions League, Premier League, LaLiga, Serie A, Bundesliga and Ligue 1 matches come first, then the other 14 competitions suggested in the form, then the competitions Flashscore itself marks as top, then its main ones, then the rest; inside each group the source's order stays: day by day, countries alphabetically. tours, the status filter and dates narrow the set and keep this order.

The order covers only the days the run reads. Without filters, days are read four at a time, and reading stops once the days read hold maxMatches matches (with any filter every selected day is read). whole-window is read from today outward — today, yesterday, tomorrow, two days back, two days ahead and so on — so a small maxMatches gets the matches closest to today, and the far edges of the window are not read at all; the other presets are read day by day in calendar order. In monitorMode the order applies day by day, so a new fixture for tomorrow never pushes today's match out of the run, and maxMatches caps the matches checked: when there are more candidates, half of the checks go to matches in play, finals not output yet and the top of this order, the other half to the rest by rotation (see Monitor mode). League URLs, teams and match IDs keep the source's order.

Run status, warnings and source failures

The run's status line says how the input was understood and how far the run got — for example "Reading days: 4 of 7 read, 3996 matches found" — and at the end what was output, with how many records carry each part, what maxMatches cut off and every warning about the input: a competition name with no match (with the closest names when it looks misspelt), a league URL that is not a league page or belongs to another sport, a team the search did not find. A run that comes back empty says why, and a run stopped by your spending limit says how much more the rest would need.

A failure of the source is kept apart from missing data. Days whose feed did not load, broken day feeds and league pages the source did not return are named in the status. So are the per-match feeds — statistics, summary, lineups, player statistics, head-to-head, news, highlights, broadcasts, odds — when they fail: such a record lists the missing parts in unavailableParts and is not billed for them. When the first batch of days does not load at all, the run fails at once instead of retrying the rest of the window, and it is charged only the start and the run minimum of a failed run.

Long runs and limits. While matches are processed the status shows the progress and about how long the rest will take, and warns when that runs past the run's timeout. Shortly before the timeout (15% of the run's time, but no less than 25 and no more than 45 seconds, and no more than half of it) the run stops starting new work: records already on their way are finished and charged, the status says how many matches were output, RUN_SUMMARY lists the rest in notOutputMatchIds, and the run ends on its own before the timeout. A second run with those IDs as matchIds gets just them, and none of them was charged in the first run. When your maximum cost per run cannot cover every selected match, the run says so before it starts on them: about how many it covers and what all of them would cost.

When there is something to explain, the same goes into the RUN_SUMMARY record of the run's key-value store — Run summary in the Output tab: status (done, empty, stopped-early, or timing-out when the run is about to reach its timeout), scope, output, selected, unchanged, cutByMaxMatches, cutNotice, emptyReason, stopReason, warnings, sourceFailures, notOutputMatchIds, and runMinimumUsd with runMinimumNote (the run minimum charged in the run). A clean run writes none. A monitor run writes it only when it stopped early, part of the source failed or it came back empty for a reason other than no changes: its input is the same in every scheduled run, so its input warnings stay in the status and the log.

What a run actually costs

Billing is per event, and an event is charged only when the data actually exists. The base event is $0.0006 per match, and every run starts with the apify-actor-start event, $0.00005; the rows below add up the events each configuration actually fires:

RunWhat is billedPer match
Default — the input form ships this waymatch, stat sheet and summary$0.0008
Typicalplus odds and head-to-head at the default depth$0.00112 to $0.0013
Everything onplus lineups, totals, handicaps and both teams to score, every bookmaker price, highlights, broadcasts, live state, the player box score$0.00562

Per 1 000 matches: scores only $0.60, the default settings $0.80, typical $1.12 to $1.30, everything on about $5.62. Apify's free plan includes $5 of usage a month (pricing): about 6 200 matches at the default settings, or 8 300 with scores only.

The last row is arithmetic on row-billed items, so treat it as an order of magnitude rather than a fixed price:

  • head-to-head — $0.00001 a row, at most 30 billed rows a match ($0.0003); rows beyond that come free in the same record. The default — 10 rows per side plus all direct meetings — holds about 12 to 95 rows; the source keeps up to about 75 meetings, so about 95 is the most. Measured on 12 and 13 October 2026: 71 rows on a Premier League match (Coventry – Newcastle, 51 meetings), 57 in the Championship, 75 in Colombia's Primera A, 94 and 95 on the Brazilian derbies Gremio – Internacional and Palmeiras – Corinthians; cup matches came to 12 to 41. Setting maxH2HRecords to 0 adds up to about 50 form rows per side on top of the meetings, up to about 175 rows a match, still billed as 30. The row of the match itself is free and does not count.
  • player stats — $0.00001 a line, one per player who took the field: about 32 a match.
  • every bookmaker price — $0.0032 a match, however many rows the list holds, and only when it is not empty.
  • news — $0.00001 an article, and notable matches carry dozens. News is not counted in the table above.
  • momentum and league table position — not billed; they only add requests.

Options you leave off cost nothing, and an option that returns no data is not billed at all: a fixture with no stat sheet, no odds and no box score is charged as a plain match, and a part that the source failed to deliver is not billed either. In monitor mode a changed match is billed only for the parts that changed (see Monitor mode above).

A spending limit is kept record by record. If you set a maximum cost per run, a match whose events no longer fit into what is left of it is not returned at all, so every record you receive is paid for in full and nothing arrives unbilled.

Run minimum. A run that reads a lot and returns little — a narrow filter over the whole 15-day window, a monitor run with nothing new, a competition between seasons — still uses compute, storage and traffic on the platform. Such a run is topped up with the run-minimum event, $0.00001 a unit, to a share of that usage at Apify's list prices (compute after the first 5 seconds, which Apify covers):

  • a run that succeeds is topped up at the end to 1.5 times its usage;
  • a run that fails, is aborted gracefully, or stops at or shortly before its timeout is topped up to 1.25 times its usage, which only covers the cost after Apify's 20% share. An abort without the graceful option (the default in Console and the API) stops the run at once, and it pays only what was charged along the way. A run still going 20 seconds before its timeout (half the time left in a short run) gets the top-up then, counting as paid only what the run will still charge in those seconds: records already on their way, and the matches its recent batches show it can still output at the base price — nothing while it is still reading the source, and for a monitor run only the records on their way. A run that finishes in time pays little or nothing for it, a run that the timeout cuts off pays it in full; whatever it charged is not refunded;
  • a run longer than a minute charges part of it as it goes, after each batch of days, league, team or batch of matches, so that a crash halfway does not leave it unpaid. Matches already selected for output count toward it (a monitor run counts only what it has already output). This part is capped at 1.25 times the usage so far, counts toward the final top-up and is not refunded if later batches bring the data that would have covered the run;
  • users on the Apify free plan never pay it, and neither do Standby requests, where you pay the platform usage directly.

Everything the run was charged, the start included, counts toward the minimum, so a run whose records cover its usage pays no final top-up, and a thin run usually comes to under half a cent in total. If you pick a residential proxy group yourself, its traffic counts at Apify's $8 per GB; the default datacenter proxy adds nothing of the kind. The run status names the amount and the reason (for example, from a local monitor run with nothing new: "Run minimum: $0.00008 was added, because the run's other charges ($0.00005) came to less than 1.5x the platform usage of this run (compute, storage and traffic, about $0.00009)"). The minimum never goes past your maximum cost per run: before each batch the run checks that the minimum for more work still fits and otherwise stops reading and says so, and a day feed that does not answer is not retried once the retry would no longer fit. The check runs between requests, so a single slow request can still take the run a little past what the limit covers; the top-up then stops at the limit.

How this compares

CapabilityThis ActorOther Store Actors (14 Oct 2026, Apify Free plan prices)
Price per match, scores only$0.0006$0.0009 to $0.01 a match on 12 other Actors; one more charges $0.008 a run for a date page of 461 matches
Price per match with detail$0.0008 at the default settings, $0.00562 with every option on$0.0009 (a match in play with statistics, events and lineups) to $0.011 (match plus details) on the others; $0.001 a record with every block plus $0.005 a run on one SofaScore-based Actor
Small request (13-14 Oct 2026 build)4.1 s (10 finished matches, scores only); 6.5 s (3 matches by ID with stats, summary and head-to-head)6.7 s on the same 10-match input on another Flashscore Actor; 3.2 s for details of the same 3 matches on another
Match statistics34 to 36 metrics in 6 groups, per match and per half; xG, xA, xGOT, goals preventedthe same set on one other Flashscore Actor; 30+ metrics described by four more
Player statsabout 100 metrics for every player who took the field (103 columns, 32 players a match); rating, position, shirt number and countryabout 100 metrics a player on one Actor, at $0.0002 a player; ratings in lineups on SofaScore-based Actors
Missing players and predicted lineupswith the reason and a doubtful flag; predicted XI before kick-offinjuries with an expected return on one; injuries and suspensions with the chance to play on another; predicted lineups described by none of the 14
Head-to-head with a form summary10 matches per side by default (up to 50) and all meetings; form, rest days, home and away50 per team and 31 to 50 meetings on one Flashscore Actor; up to 100 per team from stored snapshots on another; form up to 8 and meetings up to 12 on a third; meeting totals without a list on SofaScore-based Actors
Bookmaker odds20 British bookmakers a match; 17 countries, mergeable (25 for Great Britain plus the US); opening, current, closing; every price as a flat list1X2 from up to 42 listings without a country choice on one; every bookmaker for the server's region (EU or UK) on one odds Actor; one bookmaker a run on SofaScore-based ones
Specific matches by ID or URLyes, on any dateon seven other football Actors
Season archivesseason URLs: the Premier League back to 1901; 2011/12 with stats, lineups and 9 bookmakersseasons from 2017/18 on one; odds from 2005/06 on another; past seasons by id on SofaScore-based Actors
Monitor mode: pay only for changesyes, billed only for the parts that changedchange-only modes on two other football Actors
Standby HTTP APIyes, up to 100 matches a requestone other football Actor
Proxiesdatacenter, no extra costnone needed on most Flashscore-based Actors; residential on three SofaScore-based ones

How the data is checked

  • Structural invariants: a sweep on 29 August 2026, on an earlier build, checked 140 matches of tennis, football, basketball and baseball: percentages never above 100, event minutes that parse as numbers (football's with added time), a side on every event, attendance inside a plausible range and a match-level slice whenever a stat sheet exists. Zero violations.
  • Odds are joined by the shared match id and participant ids, never guessed from names and kickoff times, so a price cannot land on the wrong fixture or team.

Speed and compute

Measured on the Apify platform on 13 and 14 October 2026 with 1024 MB of memory; the times include starting the container. Rows with a build number ran on that build on 14 October; the others ran on earlier builds, which did not yet read the whole window from today outward or write the records of a batch in the order the matches were selected, nor read a lineup per played match for the box score (not billed). Builds after 1.0.3 were not timed: they start Node directly without the crawlee package and set up the proxy only when a request needs it. Take the times as the order of magnitude. Compute units are what a run uses on the platform. In a regular run you do not pay for them on top of the events, except through the run minimum of a thin run; in Standby you pay the platform usage directly:

RunMatchesTime
Ten matches of one day, scores only104.1 s
Three matches by ID with stats, summary and head-to-head36.5 s
Two matches by ID with stats, summary, lineups, player stats, odds and head-to-head27.1 s
Live now only, with live state and live odds16.5 s
The Store test input on build 1.0.3: the whole window, the form as it ships206.3 s
Every football match in the 15-day window, scores only9 38422.7 s
4 583 matches with stats and summary4 583598 s

The whole window — 9 384 matches, about 24 800 a minute — took 22.7 seconds and 0.0063 CU, under a millionth of a CU a match. That compute is not billed to you separately: the data is billed per event, $5.63 for the scores of those 9 384 matches, and about $7.51 for the same matches at the default settings, with statistics and summary at $0.0008 a match.

No browser, no API key, plain HTTP requests. The default Apify datacenter proxy is enough, so no residential-proxy cost ends up in the price.

Output fields

The dataset schema of this Actor (its fields section, which the API returns with the run's dataset and MCP clients and code generators read) describes every field with its type and meaning, many of them with an example. Every type can also be null. Percentages run from 0 to 100 (homePercent, margin, bestPriceOverround), probabilities from 0 to 1 (impliedHome, impliedDraw, impliedAway); odds.changePct is a change in percent and can be negative or above 100. In box scores rawStats and the columns lifted from it give shares as fractions from 0 to 1 (passes_accuracy 0.54, save_percentage 0.865), while stats keeps the source's text ("54.35%"). live.minute is text as the source shows it ("45+2", "HT"), with live.minuteNumber and live.addedTime next to it; event minutes have minuteNumber. Times are ISO 8601 in UTC (startsAt, scrapedAt, capturedAt), except startDate and startTime, which are in the run's time zone.

Top-level fields

FieldTypeMeaning
matchIdstringFlashscore match ID, 8 characters. Stable across runs: the key to deduplicate, update and join records.
sportstringSport slug, such as football, tennis, basketball, hockey, baseball, handball or table-tennis.
startDatestringStart date in the run's time zone (see timezone), YYYY-MM-DD.
startTimestringStart time in the run's time zone, HH:MM; null when startTimeKnown is false.
startTimeKnownbooleanFalse for an archive record the source keeps without a start time, only the day (a page older than a year with at least 90% of its games at midnight in Europe or UTC): startTime is then null and startDate is the day of the match, while startsAt and startTimestamp keep the source's placeholder time. True for the daily feeds and the past year.
timezonestringTime zone of startDate and startTime: the timezone input or this Actor's default, as an IANA name or a UTC offset.
homeTeamstringName of the home side (player 1 in tennis); the same as home.name.
awayTeamstringName of the away side (player 2 in tennis); the same as away.name.
homeScorenumberScore of the home side as a number: sets won in tennis and other set sports, goals, points or runs elsewhere, without a penalty shoot-out. Null before the start.
awayScorenumberScore of the away side as a number, as homeScore.
tournamentstringCompetition as the source writes it: COUNTRY: League - Stage; in tennis TOUR - EVENT: Tournament (Country), surface.
tournamentNamestringShort name of the competition: without country and stage, in tennis without tour and surface.
tournamentIdstringStable ID of the competition at the source: the key to its season archives.
tournamentSeasonIdstringID of the competition season, under the same name in the League Standings Actor. Null where the competition has no seasons.
tournamentStageIdstringID of the season stage, under the same name in the League Standings Actor.
tournamentLogoUrlstringURL of the competition logo.
tournamentCountrystringCountry or region of the competition as the source names it.
countryIdnumberThe source's numeric ID of the competition country or region.
categoryIdnumberThe source's numeric ID of the section the competition belongs to: a country for team sports, a category such as ATP - Singles in tennis.
roundstringRound or stage: from the daily feed where it carries one (League phase, Final), otherwise only with Match round (resolveRounds).
tournamentUrlstringPage of the competition at the source.
startsAtstringScheduled start, ISO 8601 in UTC.
startTimestampnumberScheduled start, Unix seconds.
endedAtstringWhen the match ended, ISO 8601 in UTC: only for finished and abandoned matches.
statusstringscheduled, live, finished, postponed, canceled, abandoned (called off, no result) or interrupted (stopped, may still resume).
statusDetailstringPeriod in play (1st half, 2nd set, 3rd period) or how the match ended or stopped (after extra time, after penalties, retired, walkover, awarded, delayed).
homeobjectHome side (player 1 in tennis): name, IDs, links and, in tennis, ranking and seed.
awayobjectAway side (player 2 in tennis), as home.
scoreobjectFinal or current score: sets won in set sports, goals, points or runs elsewhere; after a shoot-out the score of play without it.
scoreTextstringReady-made score: "6-7(3), 4-6" in tennis, "2-1" or "1-1 (pen. 4-3)" elsewhere, " ret." after a retirement, "w/o" for a walkover.
creditedScoreobjectThe source's credited result: after a penalty or shoot-out series the winner of the series gets one more (3-3 in play becomes 4-3). Equals score otherwise. Only in sports with shoot-outs.
periodScoresarray of objectScore of every period with its number: halves, quarters, periods or innings.
redCardsobjectRed cards per side; null when the source has no live coverage of the match.
regulationScoreobjectScore after regulation time (90 minutes, three periods, nine innings); differs from score after overtime or a shoot-out.
overtimeScoreobjectScore after overtime or extra innings, before a shoot-out; null without overtime.
penaltyShootoutobjectResult of the penalty or shoot-out series; null without one.
notestringThe source's note: first-leg result, series standing, postponement reason.
winnerstringhome or away; null for a draw, an unfinished or interrupted match.
winnerNamestringName of the winner as the source writes it.
matchUrlstringPage of the match at the source.
oddsobjectMatch-winner odds with averages, best prices and every bookmaker; markets, every price and in-play prices with their options. Needs Match-winner odds (includeOdds).
liveobjectState of a match in play; null otherwise. Needs Live state (includeLive).
summaryobjectVenue, attendance, referees, score and duration by period, and the event timeline. Needs Match summary (includeSummary).
lineupsobjectStarters, substitutes and coaches, missing players and predicted lineups. Needs Lineups (includeLineups).
statsobjectTeam statistics per match and per period. Needs Match statistics (includeStats).
playerStatsobjectA line per player. Needs Player box score (includePlayerStats).
h2hobjectRecent matches of each side, direct meetings and a form summary per side. Needs Head-to-head history (includeH2H).
newsarray of objectArticles linked to the match. Needs Match news (includeNews).
highlightsarray of objectVideo highlight links, after the match. Needs Video highlights (includeHighlights).
broadcastsarray of objectTV channels and streams listed by the source. Needs TV and streaming listings (includeBroadcasts).
tableContextobjectFootball and basketball, with League table position (includeTableContext): both teams in the competition's table on the day of the run (the conference table in the NBA). Null when the competition has no table, such as cups and play-offs.
momentumarray of objectFootball, with Match momentum (includeMomentum): the source's Match Momentum graph minute by minute for a match in play or played, values from -1 to 1. Absent when the source keeps no graph for the match, as in many lower leagues.
scrapedAtstringWhen the record was output, ISO 8601 in UTC. The later of two records of one match is the newer one.
unavailablePartsarray of stringParts that were asked for but did not arrive because the source failed (stats, summary, lineups, playerStats, pointByPoint, h2h, news, highlights, broadcasts, odds); they are not billed. odds is also listed when one of several oddsCountries did not answer: the record then carries the other countries' bookmakers, and its odds are not billed. Absent when nothing failed.
unavailablePartsTextstringunavailableParts joined with commas, for one spreadsheet column (the Matches and Changes views). Absent when nothing failed.
changeTypestringnew or changed: why the monitor emitted the match. Monitor mode only.
monitorChangedFieldsarray of stringGroups that changed since the monitor last emitted the match: score, status, schedule, odds, stats, summary, lineups and others. Null for a new match. Monitor mode only.
monitorChangedFieldsTextstringmonitorChangedFields joined with commas, for one spreadsheet column. Null for a new match. Monitor mode only.

Fields of the nested parts (their descriptions are in the dataset schema):

  • home and away: name, slug, id, country, code, imageUrl, countryId, url, currentRank, currentRankPoints, currentRankDate, seed, seedNumber, entryType, partner
  • odds: home, draw, away, bestHome, bestDraw, bestAway, bestHomeBookmaker, bestDrawBookmaker, bestAwayBookmaker, openingHome, openingDraw, openingAway, bookmakersCount, margin, impliedHome, impliedDraw, impliedAway, homeAmerican, drawAmerican, awayAmerican, bestHomeAmerican, bestDrawAmerican, bestAwayAmerican, bestPriceOverround, arbitrage, priceState, capturedAt, market, scope, source, country, countries, liveBookmakerIds, bookmakers, movement, markets, offersCount, offers, live, previous, changePct
  • odds.bookmakers[] (the Bookmakers view): bookmaker, bookmakerId, homePrice, drawPrice, awayPrice, openingHome, openingDraw, openingAway, active, country, homeAmerican, drawAmerican, awayAmerican
  • odds.offers[] (the Every price view): bookmakerId, bookmaker, country, market, scope, selection, line, unit, price, opening, movement, active
  • odds.markets.overUnder[] (the Totals view): line, over, under, unit, bookmakers, scope
  • live: liveScore, minute, minuteNumber, addedTime, period, clockStale, capturedAt
  • odds.live: market, scope, home, draw, away, bestHome, bestDraw, bestAway, bestHomeBookmaker, bestDrawBookmaker, bestAwayBookmaker, homeAmerican, drawAmerican, awayAmerican, bestHomeAmerican, bestDrawAmerican, bestAwayAmerican, bookmakersCount, activeBookmakers, homeUpBookmakers, homeDownBookmakers, drawUpBookmakers, drawDownBookmakers, awayUpBookmakers, awayDownBookmakers, bookmakers, markets, fetchedAt
  • summary.events[] (the Events view): minute, minuteNumber, addedTime, shootoutOrder, side, type, player, playerId, playerUrl, assist, assistIds, playerIn, playerInId, note, reason, comment, scoreAfter, disallowed, period, periodName
  • lineups.home[] and lineups.away[] (the Lineups views): number, numberInt, name, surname, playerId, playerUrl, position, positionShort, country, rating, ratingRank, substitutedAtMinute, substitutedAtMinuteNumber, substitutedBy, cameOnAtMinute, cameOnAtMinuteNumber, replaced, incidents, role, formationPlace, captain, imageUrl, heightCm, age, marketValueEur, seasonRating, roles
  • rows of stats.scopes[].groups[].items[] (the Statistics view): name, homeText, awayText, isHeadline, key, homeValue, awayValue, homePercent, homeMade, homeAttempts, awayPercent, awayMade, awayAttempts
  • playerStats.players[] (the Players view): name, playerId, playerUrl, country, number, numberInt, teamCode, team, position, stats, starter, rating, topRated, rawStats
  • rows of h2h.homeRecent[], h2h.awayRecent[] and h2h.meetings[] (the Head-to-head view): section, playedAt, meetingId, surface, meetingTournament, country, homeName, awayName, result, score, scoreRegulation, penalties, url, tournamentCode, homeId, awayId, homeSlug, awaySlug, homeCountry, awayCountry, homePartner, awayPartner, meetingHomeScore, meetingAwayScore, scoreAfterExtraTime, meetingWinner, meetingStatus, resultDetail, side, advanced, stageWon, tabs, isThisMatch, isAfterThisMatch

Every box-score line also carries its main numbers as columns of their own, so the Players view shows them: match_minutes_played, goals, assists_goal, expected_goals, expected_assists, shots_total, shots_on_target, key_passes, passes_total, passes_accurate, passes_accuracy, tackles_won, duels_won, saves_total, cards_yellow, cards_red. They are the values of rawStats under readable names.

Market and part codes of odds.market, odds.scope and the price list (oddsOfferMarkets, oddsOfferScopes); a selection is home, draw, away, over, under, yes, no, a score such as 2:1 or a pair such as home/draw:

MarketMeaning
HOME_DRAW_AWAYMatch winner with draw (1X2)
HOME_AWAYMatch winner, no draw
OVER_UNDEROver/Under
ASIAN_HANDICAPAsian handicap
EUROPEAN_HANDICAPEuropean handicap
DOUBLE_CHANCEDouble chance
DRAW_NO_BETDraw no bet
CORRECT_SCORECorrect score
HALF_FULL_TIMEHalf-time/Full-time
ODD_OR_EVENOdd/Even
BOTH_TEAMS_TO_SCOREBoth teams to score
TO_QUALIFYTo qualify
NEXT_GOALNext goal
TOP_POSITION_MERGEDTop position
TO_WIN_AND_TOP_POSITIONTo win and top position
WIN_EACH_WAYWin each way
PartMeaning
FULL_TIMEFull time
FULL_TIME_OVER_TIMEFull time including overtime
FIRST_HALF1st half
SECOND_HALF2nd half
FIRST_PERIOD1st period
FIRST_QUARTER1st quarter
FIRST_SET1st set
SECOND_SET2nd set

Soccer API: call it from code

The API tab of this page has ready code with this Actor's full name and your token; below, <owner> stands for the owner part of that name. Called without maxMatches and dateRange, the API takes up to 200 of today's matches with statistics and summary, as the input schema defaults say, while the form starts with 20 over the whole window: set both in code. maxTotalChargeUsd caps what a run may charge you, and a run that ends without SUCCEEDED has its reason in statusMessage. A synchronous call waits for the run and returns the dataset items; a run longer than 300 seconds answers with 408, so keep synchronous calls small and use a client library for big runs. Ask the dataset for the fields you need (fields) or a view (view) to keep responses small.

curl -X POST "https://api.apify.com/v2/acts/<owner>~football-soccer-scraper/run-sync-get-dataset-items?maxTotalChargeUsd=0.50&fields=matchId,startDate,homeTeam,awayTeam,homeScore,awayScore,status" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"leagueUrls": ["https://www.flashscore.com/football/england/premier-league/"], "dateFrom": "2026-09-01", "dateTo": "2026-09-30", "includeOdds": true}'

Python, with apify-client:

from decimal import Decimal
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("<owner>/football-soccer-scraper").call(run_input={"leagueUrls": ["https://www.flashscore.com/football/england/premier-league/"], "dateFrom": "2026-09-01", "dateTo": "2026-09-30", "includeOdds": True}, max_total_charge_usd=Decimal("0.50"))
status = getattr(run.status, "value", run.status) if run else None
if status != "SUCCEEDED":
print("Run did not succeed:", status, run.status_message if run else "")
else:
for item in client.dataset(run.default_dataset_id).iterate_items(fields=["matchId", "startDate", "homeTeam", "awayTeam", "homeScore", "awayScore", "status"]):
print(item)

JavaScript, with apify-client:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('<owner>/football-soccer-scraper').call({"leagueUrls": ["https://www.flashscore.com/football/england/premier-league/"], "dateFrom": "2026-09-01", "dateTo": "2026-09-30", "includeOdds": true}, { maxTotalChargeUsd: 0.5 });
if (run.status !== 'SUCCEEDED') {
console.error('Run did not succeed:', run.status, run.statusMessage);
} else {
const { items } = await client.dataset(run.defaultDatasetId).listItems({ fields: ['matchId', 'startDate', 'homeTeam', 'awayTeam', 'homeScore', 'awayScore', 'status'] });
for (const item of items) console.log(item);
}

Use it from an MCP client

Apify's MCP server at https://mcp.apify.com exposes Store Actors as tools to MCP clients such as Cursor, VS Code or GitHub Copilot. The client can find this Actor with the search-actors tool and run it with call-actor, or you can pin it as a tool of its own: https://mcp.apify.com?tools=<owner>/football-soccer-scraper, with the full name from the API tab. Sign in with OAuth or send your Apify token as a Bearer header. The server skips Actors that need full account access; this one does not need it. The dataset schema describes every output field for the agent (see Output fields). Details: Apify MCP server.

Standby HTTP API

The Actor also runs in Apify Standby mode: send a GET request and the matches come back as JSON in the response. Take the address from the Endpoints tab of this Actor's page and send your Apify token in the Authorization: Bearer header. After a quiet spell a request can take a few seconds more while the platform starts a Standby run, and if no run becomes available within 2 minutes the platform answers 429 (Apify docs).

GET <address from the Endpoints tab>/?dateRange=today&maxMatches=25

Query parameters mirror the input fields, within these limits per request: maxMatches 25 by default and 100 at most, one league URL, three teams and two history pages per team. monitorMode, monitorName (or its older name monitorKey), outputDatasetName and proxyConfiguration work only in regular runs: a request with them returns every selected match and names them in warnings. Lists (tours, teams, matchIds, oddsCountries, matchStatuses and others) take a repeated key (tours=A&tours=B), a JSON array (tours=["A","B"]) or commas; a value with a comma inside needs one of the first two. A misspelt parameter or an invalid value answers 400 with

{"error": {"type", "parameter", "message", "allowed", "runId"}}
and charges nothing; 405, 500 and 502 use the same body.

What a request costs. Every response bills every match it returns, as a regular run would: there is no monitor in Standby. On top of the events you pay the platform usage of the Standby run ("you pay the platform usage costs of the Standby runs in addition to the event charges", Apify docs): with this Actor's Standby settings it runs with 1 GB of memory and stays up for 300 seconds after the last request. The run minimum is not charged in Standby. A poll for in-play prices:

GET <address>/?matchStatuses=live&includeLive=true&includeOdds=true&includeLiveOdds=true&includeStats=false&includeSummary=false

costs $0.001 a match per request (result, live state and odds); 10 matches every 30 seconds for two hours, 240 requests, come to $2.40 in events plus the platform usage.

The response is JSON with count, tookMs and matches (records in the same shape as the dataset), plus warnings when something was trimmed, ignored or came back empty. When fewer matches arrive than were selected, the response adds truncated: true, selected (how many were selected for output) and reason: charge limit reached (the run's maximum total charge is spent), charging unavailable or some matches could not be processed. With onlyWithOdds, selected counts the slots, up to maxMatches, and scanned how many candidates were checked for odds; when fewer matches with odds were found than maxMatches while unchecked candidates remained, the reason is not enough matches with odds among those checked. Once the charge limit is spent, every further request answers at once, without scraping, with count: 0, reason: "charge limit reached", a warning and an empty matches list. When part of the source did not answer, the response adds sourceFailures: the days whose feed did not load (days), broken day feeds (brokenDays), league pages (leagues), parts of records by name and count (parts) and matches skipped on an error (matches); the records themselves name the missing parts in unavailableParts. When no requested day could be read, the request answers 502.

Schedules, webhooks and integrations

Everything the Apify platform offers around a run works here as for any other Actor:

  • Schedules run the Actor or a saved task on a cron expression with a fixed input. Pair a schedule with monitorMode and each run delivers only what changed since the last one; set the schedule's isExclusive option so a new run does not start while the previous one is still going.
  • Webhooks call your URL when a run succeeds or fails, so a pipeline can pick up the dataset the moment it is ready.
  • Google Drive uploads a file of the results after each successful run (see the FAQ for a Google Sheet).
  • Slack posts run status to a channel; Zapier, Make and n8n pass the results to other apps without code; Airbyte loads datasets into a warehouse.

Cron expressions for the usual jobs (UTC; the schedule takes a time zone of its own):

JobCronInput
Yesterday's results every morning0 6 * * *dateRange: "yesterday"
Results and fixtures every hour with monitor mode0 * * * *dateRange: "yesterday-today", monitorMode: true
Matches in play every 5 minutes*/5 * * * *matchStatuses: ["live"], includeLive: true, monitorMode: true
The week's odds every Monday0 7 * * 1dateRange: "next-week", includeOdds: true

Good for

  • Betting models — shots, big chances and possession per half, not just the final score.
  • Scouting and analytics — player stat lines and full match sheets across hundreds of leagues, including lower tiers.
  • Fixture feeds — upcoming matches with competition, kickoff time and missing players.

Limits

  • Odds follow the bookmaker market. Upcoming, live and finished matches carry prices — finished ones keep the closing line. Minor divisions and fixtures far ahead may carry no prices at all — that is the market, not the scraper. Each country has its own licensed bookmakers, so the list changes with oddsCountries (pick several to merge them).
  • A fallback odds source covers the gaps, with less detail. When the main odds feed has no price for a match, the Actor tries BetExplorer for football. Those records carry the market average and best price only — no per-bookmaker table and no markets — and from Apify's US servers that source shows far fewer bookmakers. odds.source tells which one you got.
  • Stats exist only for matches that have started. Fixtures for tomorrow return teams and kickoff time but an empty stat sheet — that is the source, not the scraper.
  • Coverage follows Flashscore itself: strong for top competitions, thinner for minor ones.
  • Head-to-head of older matches is thin. The source publishes recent form as of today, and rows played after the match are dropped, not replaced: Corinthians – Palmeiras of 12 April 2026, read in October, kept 16 and 15 rows of recent form out of 49 and 50, so for those two clubs a match more than about eight months old keeps no recent form at all. Direct meetings played before the match stay.

FAQ

Is this a soccer API with xG and lineups? Yes. includeStats (on by default) gives expected goals (xG), expected assists and xG on target with possession, shots, passing and duels per match and per half; includeLineups adds the formation, starters, substitutes and missing players; includePlayerStats adds about 100 metrics for every player who took the field.

Can I call it as a soccer API from Python or JavaScript? Yes. Every Actor on Apify is also an HTTP API: a synchronous call returns the matches as JSON in one request, and the Apify clients for Python and JavaScript run bigger loads; see Soccer API: call it from code. Results also export to CSV and Excel.

Do I need a Flashscore account, an API key or residential proxies? No. The Actor reads public feeds with plain HTTP requests, and the default Apify datacenter proxy is enough.

How far back and ahead does it go? The daily feeds cover seven days back and seven ahead. Older matches come through leagueUrls (season archives), teams (a team's or player's history) or matchIds, which finds a single match on any date.

Why do some matches come without statistics, lineups or odds? For two reasons. Flashscore publishes these parts only for some competitions, and bookmakers do not price every fixture: a part that does not exist is not billed, and onlyWithOdds leaves unpriced matches out altogether. Or the source failed to deliver the part during the run: it is not billed either, and the record names it in unavailableParts, odds included (see Run status, warnings and source failures).

Why did my run return nothing, or fewer matches than I expected? The run status says why: no matches on the chosen days, a competition name with no match (with the closest names when it looks misspelt), a league URL that is not a league page, the maxMatches cut, the spending limit, the timeout, or days and parts the source did not deliver. The same explanation goes into RUN_SUMMARY in the run's key-value store (Run summary in the Output tab).

How do I get a spreadsheet? In the run's Output tab press Export, set View to Matches (or another table) and the format to Excel. The default All fields keeps the score and teams in CSV and Excel, but scattered among about 2,000 columns and with most nested data cut off. Through the API add view=overview&format=xlsx to the dataset items URL; see Getting a spreadsheet.

How do I get the results into a Google Sheet? Once: download the Excel file as above and open it with Google Sheets. After every run: the Google Drive integration, on the Integrations tab of the Actor or of your saved task, uploads the results to your Drive after each successful run, and Zapier or Make can pass them on to Google Sheets.

How do I keep a dataset current without paying twice for the same match? Schedule the Actor with monitorMode on: each run emits only new and changed matches, and changeType and monitorChangedFields say what changed. A changed match is billed the base event plus only the parts that changed, and row-billed parts only for rows not paid for before. Keep the latest record of each matchId, and fill the parts it names in unavailableParts from the earlier record (see Regular loads).

What happens when a run reaches my spending limit? When the limit cannot cover every selected match, the run says so first: about how many matches it covers and what all of them would cost. A match whose events no longer fit into what is left of the limit is not returned, so you never receive a record that was not paid for in full. The run status names the limit and how much more the rest would need, and RUN_SUMMARY lists the IDs of the matches not output (notOutputMatchIds): pass them as matchIds to get just those.

Which time zone are the dates in? startDate and startTime are in the run's time zone, named in timezone (empty input means UTC in this Actor), and that zone also decides where one day ends for dateRange, dateFrom and dateTo. The other timestamps (startsAt, startTimestamp, scrapedAt) are UTC.

What does a typical match cost? $0.0008 at the default settings; the cost table above shows the other configurations and the price of 1 000 matches. Options you leave off cost nothing.

Why does a run that returned little show a run-minimum charge? Reading the source costs compute, storage and traffic even when your filters keep two matches or none. Such a run is topped up to 1.5 times what it used on the platform, or 1.25 times when it fails, is aborted gracefully or reaches its timeout; see Run minimum under the costs. The run status names the amount. A run whose records cover its usage pays no final top-up, a run longer than a minute may have paid part of it along the way, and users on the Apify free plan and Standby requests never pay it.

Will the output change under my pipeline? This is version 1.1. A removed field, a field renamed without its old name still working, or a changed default comes with a new major version and a line in the changelog on the Information tab. To stay on one build, pass its build number (such as 1.0.3, shown in the run's details) as build when you start a run through the API.

Support

Open an issue in the Issues tab of this Actor's page. Paste the link to the run (from the run's page in Console): an Actor issue with a run link shares that run, its input, log and storages with the developer automatically, and nothing else in your account (Apify docs). Say what you expected and what you got instead. Do not post your API token or any other secret.

Disclaimer

This is an unofficial Actor. It is not affiliated with, endorsed by, or sponsored by Flashscore or any league, club or competition named here. All trademarks belong to their respective owners. The Actor reads publicly available pages and returns the data they already show to any visitor.