Flashscore Sports Results Scraper - Live Scores, 31 Sports avatar

Flashscore Sports Results Scraper - Live Scores, 31 Sports

Pricing

$1.99 / 1,000 match rows

Go to Apify Store
Flashscore Sports Results Scraper - Live Scores, 31 Sports

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

Snow Leo Data

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

7 days ago

Last modified

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

FieldWhat it does
sportsWhich sports to walk. Names or ids, or all for every one of the 31 that answer.
dayOffsetsDays relative to today. 0 today, -1 yesterday, 3 in three days. Range -7..+7.
matchStatusesKeep only scheduled, live or finished. Empty means all three.
tournamentContainsKeep matches whose tournament name contains any of these words.
countryContainsKeep matches from these countries or regions.
teamContainsKeep matches where either side matches, by full name or short name.
includeHeadToHeadAdd previous meetings and recent form. One extra request per delivered match.
includeOddsAdd the 1/X/2, handicap, over-under and moneyline numbers that travel in the same feed.
includeRawFieldsAdd every raw feed key with the constant name Flashscore's own bundle gives it.
compactOutputDrop empty fields from every row.
onlyChangedDeliver only new or changed matches. Made for scheduled monitoring.
emitUnchangedWith incremental mode on, still write unchanged matches, tagged UNCHANGED.
monitorStoreNameName of the store holding incremental memory, so several schedules stay apart.
languageLanguage of tournament and participant names. Nine to choose from.
maxItemsUpper bound on delivered rows. 0 means no limit.
detailConcurrencyHow 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) and tiebreaks (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-extractor returns 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-live returns 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_id values 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-fsign is a constant of the site's front end today. If Flashscore changes it, the feed answers 401 and the Actor says source-refused instead 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.