Flashscore Sports Results Scraper - Live Scores, 31 Sports
Pricing
$1.99 / 1,000 match rows
Flashscore Sports Results Scraper - Live Scores, 31 Sports
Flashscore scraper for 31 sports: one football day feed returned 460 tournaments and 1,815 matches in a single request. Football live scores API with tennis and basketball live scores over a 15-day window, 76 columns, field names from the site's own bundle. No browser, no proxy.
Pricing
$1.99 / 1,000 match rows
Rating
0.0
(0)
Developer
Snow Leo Data
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
7 days ago
Last modified
Categories
Share
Flashscore Scraper: 31 Sports, Live Scores, Fixtures and Results
Scrape Flashscore live scores, fixtures and finished results for 31 sports over a 15-day window, without a browser, without a proxy and without an API key. One request returns an entire day of a sport: on 12 September 2026 the football day feed carried 460 tournaments and 1,815 matches in a single request weighing 2,788,777 bytes.
This is a sports data API you can schedule. It returns tournaments, countries, kick-off times, live minute, period scores, tennis tiebreaks, red cards, participant ids, team logos, head-to-head history and league metadata as structured JSON, CSV, Excel or XML.
What this Actor gives you that a hand-rolled scraper does not
Three things, and every one of them is a number you can reproduce with
python3 tools/measure.py.
1. The signature header. The Flashscore feed is not open. It
answers 401 when the x-fsign header is missing and 401 again when the
header carries a wrong value — both measured from Apify servers, not from a laptop. A naive
scraper that simply requests the feed URL gets nothing at all. This Actor sends
the header the site's own front end sends.
2. Field names lifted from the source's own build. Flashscore ships its feed
as two-letter keys: AE, AG, ZC. Most scrapers guess what those mean. This
one does not guess. The names come out of Flashscore's own JavaScript bundle,
where the webpack module that defines the keys and the core-debugger chunk
that names them can be joined: AE is FULLFEEDINDEXES_HOME_PARTICIPANT_NAME,
AG is SHAREDINDEXES_HOME_CURRENT_RESULT. That join yields 252 feed keys
carrying 265 constant names, and 9 keys that mean different things in
different sports are kept with every meaning rather than flattened to one.
Three of the ids were cross-checked against the live Premier League page, whose
own config object publishes tournamentId: "SY30SsKF",
tournamentStageId: "CfoA8Dmm" and tournamentTemplateId: "dYlOSQOD" — exactly
the values of feed keys ZE, ZC and ZEE.
3. Coverage measured, not claimed. Sport ids 1 to 45 were probed from Apify servers across three different days. 31 of them return a feed: football, tennis, basketball, hockey, american football, baseball, handball, rugby union, rugby league, floorball, bandy, futsal, volleyball, cricket, darts, snooker, boxing, aussie rules, badminton, water polo, golf, field hockey, table tennis, beach soccer, mma, pesapallo, auto racing, moto racing, cycling, horse racing and esports.
The 15-day window, and why it matters
The feed serves day offsets -7 to +7, which is 15 days in total. This is
not a guess either: at offset -8 and at offset +8 the server answers HTTP 200
with a body of 0. That is the worst kind of failure, because it is not a
failure — a scraper that checks only the status code records a successful
request and an empty result, and the buyer never learns why.
This Actor refuses an out-of-window day before sending a single request,
names the dropped offsets back to you in the run report, and writes the named
reason days-out-of-window so that a monitoring watchdog can tell a legitimate
empty run from a broken one.
Every sport gets a share of your row limit
maxItems limits delivered rows, not rows walked. That distinction is the
difference between a working Actor and one that charges for nothing.
There is a second trap behind it. Football is by far the largest feed — 1,815 matches on a normal Tuesday against 138 for tennis. Ask for all sports with a limit of 60 rows and a naive implementation hands you 60 football matches and calls it multi-sport coverage. This Actor gives each selected feed its own quota and then interleaves the feeds round-robin, so a 60-row run across all sports returns at least eight different sports, none of them taking more than a fifth of the rows. Empty feeds do not waste their slot; their share flows to the sports that have matches.
Input
| Field | What it does |
|---|---|
sports | Which sports to walk. Names or ids, or all for every one of the 31 that answer. |
dayOffsets | Days relative to today. 0 today, -1 yesterday, 3 in three days. Range -7..+7. |
matchStatuses | Keep only scheduled, live or finished. Empty means all three. |
tournamentContains | Keep matches whose tournament name contains any of these words. |
countryContains | Keep matches from these countries or regions. |
teamContains | Keep matches where either side matches, by full name or short name. |
includeHeadToHead | Add previous meetings and recent form. One extra request per delivered match. |
includeOdds | Add the 1/X/2, handicap, over-under and moneyline numbers that travel in the same feed. |
includeRawFields | Add every raw feed key with the constant name Flashscore's own bundle gives it. |
compactOutput | Drop empty fields from every row. |
onlyChanged | Deliver only new or changed matches. Made for scheduled monitoring. |
emitUnchanged | With incremental mode on, still write unchanged matches, tagged UNCHANGED. |
monitorStoreName | Name of the store holding incremental memory, so several schedules stay apart. |
language | Language of tournament and participant names. Nine to choose from. |
maxItems | Upper bound on delivered rows. 0 means no limit. |
detailConcurrency | How many head-to-head requests run in parallel. |
16 input fields, and every one of them is read by the code — a check enforces that, so the schema can never advertise a switch that does nothing.
Output
The dataset declares 76 columns. A football match on a normal day fills
about 50 of them; a tennis match fills 53, because tiebreaks and doubles
partners only exist there. Fields that a sport does not have are null, never
an empty string — an empty string in a numeric column breaks Excel, BigQuery and
pandas alike.
Groups of columns:
- Identity —
event_id,url,sport,sport_id,day_offset. - Tournament —
tournament_name,tournament_header,country,country_id,tournament_id,tournament_stage_id,tournament_template_id,tournament_url,tournament_stage_type,tournament_stats_type,tournament_source_type,tournament_stages_count. - Time and state —
start_timestamp,start_time_utc,date,end_timestamp,period_start_timestamp,status,stage_type_id,stage_id,stage_time,game_time,is_live,round_name. - Participants —
home_name,away_name, short names, country names and ids,home_participant_ids,away_participant_ids, event-participant ids, logos, slugs, and the four fields that carry doubles partners in tennis:home_first_participant,home_second_participant,away_first_participant,away_second_participant. - Scores —
home_score,away_score, full-time scores,winner,home_red_cards,away_red_cards,period_scores(up to nine periods plus an extra one) andtiebreaks(up to five). - Flags —
is_national_event,has_lineups,has_live_centre,has_highlights,has_odds_comparison,info_notice. - Extras —
head_to_head,change_type,raw, and nine odds columns.
status is derived from the feed's stage type, and the derivation was measured
rather than assumed: on a past day all 480 football matches carried stage type
3, on a day two ahead 226 of 227 carried type 1, and on the current day 98
matches carried type 2 together with the live flag. Hence exactly three values:
scheduled, live, finished. The finer stage_id is passed through as a
number without an invented name, because its values were not verified.
Scheduled monitoring
Sports data is a monitoring niche. Across 471 Actors in this niche the store reports 20,295 users and 655,998 runs — 32.3 runs per user — and the strongest monitoring neighbour runs 289.0 times per user. People put these on a schedule; they do not export once.
Monitoring only works if a repeat run is cheap. Turn on onlyChanged and the
Actor remembers what it delivered in a named key-value store — named,
because the default store is wiped at the start of every run, so incremental
mode built on it would silently degrade into a full export. Memory holds
60,000 keys, roughly 2 MB against a 9 MB record ceiling.
The fingerprint covers only fields the day feed always carries: status, stage, both scores, kick-off time, winner and clock. Head-to-head and odds are deliberately excluded — otherwise the same match fetched with and without extras would look changed every single run.
Named reasons for an empty dataset
A green run with an empty dataset is the worst thing an Actor can do to a buyer. In a monitoring niche it is also legitimate: a scheduled run that finds nothing changed should write zero rows, or you pay twice for the same data.
So this Actor never stays silent about it. Every empty outcome carries a name in the run report and a sentence in the log:
days-out-of-window— every requested day is outside -7..+7. No request sent.status-impossible-for-days— a past day cannot contain scheduled matches and a future day cannot contain finished ones. No request sent.no-sports-selected— nothing recognisable in the sports list. No request sent.charge-limit-reached— the run's charge limit is already spent. No request sent.nothing-changed— incremental mode working as asked.filtered-out— rows were collected and your filters removed all of them.source-returned-nothing— that sport has no matches on that day.source-refused— the feed stopped answering.nothing-collected— matches were seen but none survived to delivery.
The first four are decided from your input alone, before the first byte is downloaded. A run whose result is already determined by its own input should not cost you the wait, and a test enforces that those paths issue zero requests.
Honest limitations
Rule of the house: anything a competitor does that this Actor does not gets
named here rather than quietly omitted. The line-by-line comparison against the
strongest Flashscore Actor in the store lives in COMPARISON.md.
- No league standings tables. Flashscore moved standings off the feed host
this Actor uses; eleven endpoint spellings were tried from Apify servers and
all returned an empty body.
extractify-labs/flashscore-extractorreturns standings; this one does not. - No per-match statistics, incidents or lineups. The day feed flags whether
a match has lineups and a live centre, but the blocks themselves are not
fetched.
statanow/flashscore-scraper-livereturns them in its history mode. - No deep history. The window is 15 days, full stop. An Actor that claims seasons of history is reading a different set of endpoints.
- No standby HTTP endpoint. Runs are started the normal way, through the Apify API or a schedule.
stage_idvalues are not named. Only the three stage types were verified. The finer code is passed through as a number.- Odds are a by-product, not a product. The nine odds columns are whatever travels in the same feed row. This is not an odds-comparison Actor and it does not poll bookmakers.
- The signature can be rotated.
x-fsignis a constant of the site's front end today. If Flashscore changes it, the feed answers 401 and the Actor sayssource-refusedinstead of pretending everything is fine.
FAQ
Do I need a proxy? No. Every measurement in this document was taken from Apify servers with the platform's ordinary outbound address and no proxy. The feed does not challenge datacenter addresses; it checks the signature header.
Do I need a browser? No. The Actor is pure Python standard library. No Playwright, no Puppeteer, no Chrome. That is why it fits in 1,024 MB.
How many requests does a run cost? One per sport per day, plus one per
delivered match if includeHeadToHead is on. All 31 sports for today is 31
requests. That is the whole point of the day feed.
Why is the football number so much larger than the others? Because it is. 1,815 football matches against 138 tennis matches on the same day is the shape of the source, not a sampling artefact. It is also why per-sport quotas exist.
Can I get yesterday's results? Yes — dayOffsets: ["-1"]. Anything older
than seven days is outside what the source serves, and the Actor will tell you
so before spending your time.
Can I follow one team? Put its name in teamContains and schedule the run
with onlyChanged. You will get a row when the team's next match is added and
another when its score moves.
What language are the names in? English by default; nine languages are available. The ids stay identical across languages, so you can switch language without breaking a downstream join.
What happens if the run is interrupted? Rows are pushed to the dataset first, and only then marked as delivered in memory. An interrupted run may repeat a few rows on the next pass; it will never drop them.
Is the data free to use? The Actor reads publicly visible pages of a public website. What you may do with the data afterwards depends on your jurisdiction and your purpose — that part is yours to check.