Basketball Scraper — NBA, EuroLeague, Box Scores avatar

Basketball Scraper — NBA, EuroLeague, Box Scores

Pricing

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

Go to Apify Store
Basketball Scraper — NBA, EuroLeague, Box Scores

Basketball Scraper — NBA, EuroLeague, Box Scores

NBA, EuroLeague and NCAA basketball: live scores, player box scores, betting odds with bookmaker margin and 21 stats broken out by quarter — field goal splits, three-point and free-throw accuracy, rebounds, turnovers. Head-to-head history and arena included. No login, no browser, callable as an API.

Pricing

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

Rating

0.0

(0)

Developer

Huyen Trang

Huyen Trang

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

3 hours ago

Last modified

Categories

Share

NBA, EuroLeague and NCAA games with 21 statistics broken out by quarter — field goal splits, three-point and free-throw accuracy, rebounds and turnovers — plus head-to-head history and arena.

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 NBA.
  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 and Statistics 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.01105 a run — $0.00005 for the start plus $0.00055 a match; a match without a stat sheet or summary costs less. 1 000 matches cost $0.55 at the default settings, and Apify's free plan, $5 of usage a month, covers about 9 000 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.

Last night's NBA games — $0.00055 a match with statistics and summary.

{"dateRange": "yesterday", "tours": ["USA: NBA"]}

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

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

A month of a past NBA season — $0.00035 a match, scores only.

{"leagueUrls": ["https://www.flashscore.com/basketball/usa/nba-2024-2025/"], "dateFrom": "2025-03-01", "dateTo": "2025-03-31", "includeStats": false, "includeSummary": false}

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

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

A results feed on a schedule — $0.00035 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": ["USA: NBA"], "monitorMode": true, "monitorName": "nba"}

US odds board for the next three days — $0.00075 a match with odds and lines; matches without prices are skipped and not billed.

{"dateRange": "next-3-days", "matchStatuses": ["scheduled"], "tours": ["USA: NBA"], "includeStats": false, "includeSummary": false, "includeOdds": true, "includeOddsMarkets": true, "oddsCountries": ["US"], "oddsFormat": "american", "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
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 — Orlando Magic vs Cleveland Cavaliers, USA: NBA - Pre-season — trimmed: lists keep their first item and nested objects only some of their fields. Every field is described under Output fields.

{
"matchId": "YwkEQMle",
"sport": "basketball",
"startDate": "2026-10-13",
"startTime": "19:00",
"startTimeKnown": true,
"timezone": "America/New_York",
"homeTeam": "Orlando Magic",
"awayTeam": "Cleveland Cavaliers",
"homeScore": 132,
"awayScore": 116,
"tournament": "USA: NBA - Pre-season",
"tournamentId": "IBmris38",
"startsAt": "2026-10-13T23:00:00.000Z",
"status": "finished",
"statusDetail": "finished",
"home": {
"name": "Orlando Magic",
"id": "QZMS36Dn",
"url": "https://www.flashscore.com/team/orlando-magic/QZMS36Dn/"
},
"away": {
"name": "Cleveland Cavaliers",
"id": "xGk13Tb6",
"url": "https://www.flashscore.com/team/cleveland-cavaliers/xGk13Tb6/"
},
"score": {"home": 132, "away": 116},
"scoreText": "132-116",
"winnerName": "Orlando Magic",
"matchUrl": "https://www.flashscore.com/match/YwkEQMle/",
"stats": {
"headline": {
"Field goals %": {"home": "51.55%", "away": "45.05%"},
"3-point field goals %": {"home": "36.36%", "away": "31.43%"}
}
},
"odds": {
"market": "HOME_AWAY",
"priceState": "closing",
"home": 1.97,
"away": 1.78,
"bestHome": 2.05,
"bestHomeBookmaker": "BetMGM.us",
"bestAway": 1.86,
"bestAwayBookmaker": "DraftKings",
"bookmakersCount": 24,
"margin": 6.94,
"impliedHome": 0.4747,
"impliedAway": 0.5253,
"movement": {"homeDirection": "drifting"},
"bookmakers": [
{
"bookmaker": "Betfred",
"country": "GB",
"homePrice": 2,
"awayPrice": 1.75,
"openingHome": 1.95,
"openingAway": 1.8,
"active": false
}
]
},
"summary": {"venue": "Kia Center", "attendance": 16468},
"playerStats": {
"players": [
{
"name": "Wagner F.",
"playerId": "4fJvZAaj",
"number": "22",
"team": "home",
"points": 20,
"total_rebounds": 4,
"assists": 8,
"minutes_played": 22.13
}
]
},
"scrapedAt": "2026-10-14T13:54:42.267Z"
}

What you get

CapabilityWhat it means for you
Player box score and running scoreper-player lines plus the score point by point, quarter by quarter, with the side that scored and the lead
Quarter-by-quarter splitsscoring pace within the game, not one box score total
Shooting broken into 2-point, 3-point and free throwsattempts, makes and percentage for each
Missing players and player ratingsinjured, suspended or rested players with the reason for NBA games, match ratings in lineups
Head-to-head history between the clubsprevious meetings with competition, date and a form summary
Referees and arena where publishedcontext for officiating models

Every field, per match

GroupMetrics
ScoringField goal attempts and made, field goal %, 2-point and 3-point splits, free throws
ReboundsOffensive, defensive and total rebounds
OtherAssists, turnovers, steals, blocks, personal and technical fouls

Every metric comes per match and per period — Match, 1st Quarter to 4th Quarter and Overtime when it is played — 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).

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: 72 meetings on an NBA game, 15 in the Turkish league. 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.

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 35 to 95 rows, so it is billed 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.

Running score, point by point

includePointByPoint returns the running score point by point, quarter by quarter — every scoring step with the score after it, which side scored and the lead after it, the raw material for momentum and scoring-run models. The quarter in play is returned but not charged. One extra request per match.

Score fields

  • Ready-made score fields: score (the final score), scoreText ("112-104"), winnerName and a profile url for each side.
  • Two scores: score (and homeScore, awayScore) is the final score including overtime, regulationScore the score after regular time.
  • Period scores carry explicit period numbers; overtime and shoot-out scores come as separate fields where the sport has them.
  • 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 box score

includePlayerStats adds a stat line for every player on the floor: points, rebounds, assists, minutes, shooting splits — 21 columns per player, 21 to 31 players on the NBA pre-season games checked in October 2026, with player ids and profile links, mapped to home and away. One extra request per match.

Values come as numbers: a dash in a numeric column is 0, and playing time stays text ("mm:ss"). Made and attempted shots come as separate columns (FGM and FGA, 3PM and 3PA, FTM and FTA). 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.

With includeLineups, NBA games also carry missing players — injured, suspended or rested, with the reason — and each player's match rating.

Each line also carries its main numbers as fields of their own — points, total_rebounds, assists, minutes_played, field_goals_made, field_goals_attempted and 15 more, the values of rawStats — so the Players view and its CSV or Excel export show them as columns.

League table position and consistency checks

includeTableContext adds tableContext: for home and away their position, points, played, wins, losses, recent form (latest first, up to five results) and group, with tableDate. In the NBA the row comes from the conference table, group names the conference (Western Conference) and points is null, since the NBA ranks by wins. The table is the one on the day of the run, not as of the match, so a past match shows today's standing. 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.

Consistency checks. With includePointByPoint or includePlayerStats on, every record carries consistency: for a finished game pointByPointMatchesScore says whether the running score ends at the final score and playerPointsMatchScore whether the players' points add up to each side's score. Values are not corrected: on an NBA pre-season game of 13 October 2026, Magic – Cavaliers, the source's running score ended at 132:115 while the final was 132:116, and the flag says false. A part the run did not load or that did not arrive, and a game not finished, give null.

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.

TV listings are the source's own list by country, not a US TV guide: broadcast times are not part of it. An NBA pre-season game of 13 October 2026, Magic – Cavaliers, listed 12 channels — ESPN USA, NBCSN and regional US networks, Sky in Britain and Germany, Amazon Prime Video in Turkey and NBA Global League Pass — plus bet365 and Unibet streams.

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/basketball/usa/nba-2024-2025/ — NBA archives reach back to 1993, including pre-season, play-in and playoffs. Old matches return whatever detail the source still holds for them, which thins out the further back you go.

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

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 2000 NBA Finals came out on their real dates, 7 to 19 June. 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. 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.

Dates outside the window by competition. When dateFrom and dateTo reach outside the 15-day window and tours names a competition, the run reads that league's season archives instead of the daily feeds: tours: ["USA: NBA"] with June 2000 returned the nine NBA games of that month, the Finals included. A season is taken to start in October; dates in September and October read both neighbouring seasons, since some leagues start in September (Australia's NBL). A run reads up to six archives, the newest first, and a Standby request one; the archives left out are named in the run warnings. With only dateTo set, the season of dateTo is read. The paths of the 11 competitions suggested in the form are known in advance, though only the NBA was checked with a run; any other name is looked up in today's daily list, so it has to have a game today. A name that cannot be resolved, or an archive the source does not return, is named in the run warnings.

Pass names instead of URLs — teams: ['Lakers' or 'Celtics'] — 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). Paste a Flashscore team or player link instead, for example https://www.flashscore.com/team/

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 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. 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, consistency), 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 two-way including overtime, the moneyline most bookmakers price. odds.market and odds.scope name it.

Every odds record adds no-vig probabilities (odds.impliedHome, 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. On NBA games in runs on 13 and 14 October 2026 Great Britain listed 18 to 19 bookmakers and the United States (New Jersey) 5. 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 23 to 24 on NBA games. 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—

The default storefront of this Actor is the United States (New Jersey): with oddsCountries left as it is, the odds come from the New Jersey books in the table — DraftKings, Fanduel, BetMGM, Fanatics and bet365's New Jersey book. Prices stay decimal unless you set oddsFormat to american. For the British list, about 20 bookmakers, set oddsCountries to ["GB"], or ["US", "GB"] to merge both.

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 spread) — the average price on each side and how many bookmakers stand behind each line. 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. Here the match-winner market includes overtime, while totals and handicaps are often regulation time, because more bookmakers price them that way. It all comes from the same response as the match-winner prices, so there are no extra requests — on NBA games in runs on 13 and 14 October 2026: 66 to 71 total lines and 48 to 54 spread lines. A match with no total or handicap 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. NBA games in runs on 13 and 14 October 2026 carried 1 394 to 1 690 rows in 7 markets. 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 game clock and the stage of the match in play. live.minute is the clock inside the current period, as Flashscore shows it, and 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 quarter to 4th quarter, extra time for overtime — or how the game ended or stopped: after extra time, interrupted, suspended. An interrupted game has neither endedAt nor winner, even when one side leads on points: 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). On the British storefront an NBA game four minutes in had a live price from one bookmaker, against 18 before tip-off. It is part of the odds charge.

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": ["USA: NBA"],
"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, this Actor uses America/New_York, so yesterday is last night's American slate. Set UTC or Europe/London for European leagues. 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 in UTC dropped 8 of 82 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 USA: NBA, EUROPE: Euroleague, ACB all work. Whole words only, so NBA does not catch the WNBA, while a bare Euroleague also catches Euroleague Women. NCAA games appear once the college season starts in November. The input form suggests 11 popular competitions written the way the source writes them (USA: NBA), 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.

Order and maxMatches. maxMatches caps the run. When matches are picked by days (no league URLs, teams or match IDs), NBA games come first, then EuroLeague, then the other 9 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, box score, point by point, 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.00035 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.00055
Typicalplus odds and head-to-head at the default depth$0.00105
Everything onplus lineups, totals and handicaps, every bookmaker price, highlights, broadcasts, live state, point by point, the player box score$0.00574

Per 1 000 matches: scores only $0.35, the default settings $0.55, typical $1.05, everything on about $5.74. Apify's free plan includes $5 of usage a month (pricing): about 9 000 matches at the default settings, or 14 200 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 35 to 95 rows; the source keeps up to about 75 meetings, so about 95 is the most. Measured on 12 and 13 October 2026: 92 rows on an NBA game (Hornets – Bucks, 72 meetings since 1996), 80 on another NBA game and 35 in the Turkish league. NBA clubs meet several times every season, so most NBA games land near 90. 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.
  • running score — $0.00001 a completed period, so four to five rows a game.
  • box score — $0.00006 a line. The count follows the roster that played; the table above prices it at 30 lines.
  • every bookmaker price — $0.0019 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.
  • 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.00035$0.0004 to $0.01 a game on 12 other Actors; one more charges only the platform usage of an 8 GB run
Price per match with detail$0.00055 at the default settings, $0.00574 with every option on$0.001 to $0.01 a game with details on seven other Actors; $0.006 for a game summary with box score and play-by-play on one of them
Small request (13-14 Oct 2026 build)5.5 s (3 NBA games by ID, scores only); 7.5 s with every option on1.9 and 2.1 s for 3 NBA games, scores only, on two ESPN-based Actors; 7.9 s for 2 games with play-by-play on a third
Team statistics21 metrics per game, per quarter and for overtime25 per game, without quarters, on one ESPN-based Actor; none on four scoreboard Actors
Player box score21 to 31 players, 21 columns, numbers as numbers14 columns on one ESPN-based Actor; a line per player on two more
Scoring progressionevery scoring step with the score after it, the side that scored and the lead; no play textplay-by-play with time and player, up to 500 plays, on one ESPN-based Actor; a line per play on another
Head-to-head with a form summary10 games per side by default (up to 50) and all meetings: 64 to 75 since 1993 on NBA games; form, rest days, home and awayseason series on one; meeting totals on SofaScore-based Actors; up to 100 games per team from stored snapshots on one
Bookmaker odds23 to 24 bookmakers on NBA games (Great Britain plus the US in one record); 66 to 71 total and 48 to 54 spread lines; every price on requestone DraftKings line on two ESPN-based Actors; every ESPN bookmaker at $0.003 a line on one; every bookmaker for Europe or the UK at $0.005 a match on one odds Actor
Day boundaryAmerica/New_York by default, any IANA time zoneUS Eastern days on one other Actor; a selectable output time zone on another
Monitor mode: pay only for changesyes, billed only for the parts that changedchange-only modes on two other Actors
Standby HTTP APIyes, up to 100 games a requestone other Actor; an MCP path on one ESPN-based Actor
Proxiesdatacenter, no extra costnone on the ESPN-based Actors; residential by default on one SofaScore-based Actor

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
Three NBA games by ID, scores only35.5 s
Three NBA games of one night, every option on37.5 s
Live now only16.2 s
The Store test input on build 1.0.3: the whole window, the form as it ships206.7 s

Each detail option adds about one request per match, and requests run in parallel, so the time grows slower than the number of matches. The data is billed per event, not per second.

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 holds numbers as the source shows them, shares from 0 to 100. 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.
periodScoresarray of objectScore of every period with its number: halves, quarters, periods or innings.
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.
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).
pointByPointarray of objectEvery game (in tennis) or period with the running score point by point. Needs Point by point (includePointByPoint).
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.
consistencyobjectBasketball, when the run loads Point by point or Player box score: whether those parts agree with the final score of a finished game. Values are not corrected.
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, fetchedAt
  • 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: points, total_rebounds, assists, minutes_played, field_goals_made, field_goals_attempted, 2_point_field_goals_made, 2_point_field_goals_attempted, 3_point_field_goals_made, 3_point_field_goals_attempted, free_throws_made, free_throws_attempted, plus_minus_points, offensive_rebounds, defensive_rebounds, personal_fouls, steals, turnovers, blocked_shots, blocks_against, technical_fouls. They are the values of rawStats under readable names; other columns of the source come the same way under their snake_case 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

Basketball 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>~basketball-scraper-nba/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 '{"dateRange": "yesterday", "tours": ["USA: NBA"], "includePlayerStats": true, "maxMatches": 20}'

Python, with apify-client:

from decimal import Decimal
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("<owner>/basketball-scraper-nba").call(run_input={"dateRange": "yesterday", "tours": ["USA: NBA"], "includePlayerStats": True, "maxMatches": 20}, 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>/basketball-scraper-nba').call({"dateRange": "yesterday", "tours": ["USA: NBA"], "includePlayerStats": true, "maxMatches": 20}, { 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>/basketball-scraper-nba, 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.00075 a match per request (result, live state and odds); 10 matches every 30 seconds for two hours, 240 requests, come to $1.80 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 10 * * *dateRange: "yesterday", 10:00 UTC is 6:00 in New York in summer
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

  • Quarter-by-quarter modelling — shooting splits per quarter, not just the box score total.
  • Live betting — in-play scoring pace with the period breakdown and the game clock.
  • Team form tracking — head-to-head and recent results with competition context.

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 basketball. 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, so the older the match, the less recent form it keeps. Direct meetings played before the match stay.

FAQ

How do I get NBA box scores through an API? Turn on includePlayerStats: a stat line for every player on the floor with points, rebounds, assists, minutes and shooting splits, with player ids. Team stats per match, per quarter and for overtime come with includeStats, on by default.

Can I call it as a basketball 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 Basketball 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; and dateFrom with tours reads the season archive of the competition named (see Date ranges).

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 America/New_York 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.00055 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.