SofaScore Sports Scraper: Live Scores, Timeline, Lineups
Pricing
from $0.35 / 1,000 matches
SofaScore Sports Scraper: Live Scores, Timeline, Lineups
Scrape SofaScore football, tennis and basketball matches, one row per match: teams, score, period scores, status, minute, start time and winner. Live, by round, by match link or by day. Football adds referee, venue, goals, cards, substitutions and lineups. JSON, CSV or Excel. By MrBridge
SofaScore Sports Scraper
SofaScore Sports Scraper collects football, tennis and basketball matches from SofaScore (https://www.sofascore.com), one clean row per match, ready for JSON, CSV or Excel. It reads the public pages and responses of the site, so no account and no API key are needed. Each row gives the teams or players, the tournament, the season, the round, the start time, the status, the score, the half-time score, the winner and a link to the match page. You can add the match sheet, the timeline of goals, cards and substitutions, and the line-ups.
Pick football, keep the live mode and set maxItems to 20: the Actor returns up to 20 matches in play right now, each with its tournament, elapsed minute and current score. Switch to the date mode with a league such as premier-league to get the fixtures and results of a weekend, and turn on the details to get the referee, the venue, each goal with its scorer and the starting line-ups.
What is SofaScore Sports Scraper?
SofaScore is a live-score website that lists matches of thousands of tournaments, with scores updated during play. The Actor reads those pages for you. It picks the matches according to the mode you choose, applies your league filter, removes duplicates and returns one structured row per match. The first 23 columns follow a fixed sports-matches layout, documented below, so the rows are easy to compare with other sources that use the same layout, such as Flashscore Matches Scraper.
What data can I extract with it?
One row per match: source, match ID, match link, sport, country, tournament and its ID, season, round, start time, normalized status and status as SofaScore writes it, elapsed minute of a live football match, home and away side with their IDs, score, half-time score, winner and scrape time. Then the SofaScore columns: page code and slug, tournament slug, category, season ID, score of each period, tennis surface, red cards, and the optional details.
How can I use the results?
- Follow the matches in play right now across a sport, with their score and minute.
- Build a results archive of the leagues you follow, one day or one round at a time.
- Feed a dashboard or a newsletter with the fixtures of the coming days and yesterday's scores.
- Study how often home sides win in a tournament, using the winner and half-time columns.
- Collect the goals, cards, substitutions and line-ups of finished football matches.
How to use it
- Open the Actor in Apify Console.
- Pick a mode, the sports and, if you want to narrow the list, the leagues, the dates, the rounds or the match links.
- Set
maxItems, then start the run. Rows appear in the dataset as they are delivered.
Input
| Field | Meaning | Default |
|---|---|---|
mode | live, tournament, matches or date (see below) | live |
sports | football, tennis, basketball. Used by the live and date modes | ["football"] |
tournaments | Rounds to read in the tournament mode, up to 500 | empty |
matchUrls | Match links or IDs to read in the matches mode, up to 500 | empty |
dates | Days as YYYY-MM-DD in the date mode | empty, which means today (UTC) |
leagues | Keep only these leagues in the date mode, up to 100. Empty keeps all | empty |
maxItems | Hard cap on match rows returned and billed, 1 to 10000 | 100 |
includeDetails | Add the details column to each match | false |
The Console form pre-fills mode with live, sports with football and maxItems with 20. Example input:
{"mode": "live","sports": ["football"],"maxItems": 20}
The four modes:
live: the matches in play now, for each sport ofsports.tournament: the matches of the rounds listed intournaments. An entry is{ "uniqueTournamentId": 17, "seasonId": 96668, "round": 7 }or{ "url": "https://www.sofascore.com/football/tournament/england/premier-league/17#id:96668", "round": 7 }. The round is required.matches: the matches named inmatchUrls, as SofaScore match links that end with#id:<number>or as match IDs.date: the matches of the days listed indates, for each sport ofsports, optionally limited byleagues.
A field that does not fit the mode is refused, except sports, which the tournament and matches modes ignore because the sport comes from the match.
dates accepts real calendar days from 7 days before to 7 days after the UTC day on which the run starts. A match is kept when its start falls on one of these UTC days.
leagues is a local filter. Case is ignored and a match is kept when it fits any entry. An entry takes one of these forms:
- the SofaScore ID of the unique tournament, for example
17 - the slug of the tournament, for example
premier-league - the name or the slug of the category, which is usually the country, for example
Englandorengland - a key prefixed with a sport, for example
football:17orbasketball:england, which applies to that sport only
The sport of a prefix must be football, tennis or basketball and must be in sports, otherwise the input is refused. A number alone only matches a tournament ID. A category name that contains a colon is given by its slug.
Invalid input ends the run successfully with one BAD_INPUT row in the errors dataset that lists every problem, and no match row.
Output
Matches go to the default dataset, one row per match. Problems go to a separate errors dataset and are never billed. The OUTPUT record of the key-value store summarizes the run. The dataset has two views: overview and details.
The first 23 columns are the common layout, in this order:
| Field | Type | Meaning |
|---|---|---|
source | string | Always SofaScore |
matchId | string | SofaScore match ID, the key of the row |
url | string or null | SofaScore page of the match |
sport | string | football, tennis or basketball |
country | string or null | Country of the tournament category |
tournament | string or null | Tournament name |
tournamentId | string or null | SofaScore ID of the unique tournament |
season | string or null | Season name |
round | string or null | Round name, or Round N when only the number is known |
startTime | string or null | ISO 8601 in UTC |
status | string | scheduled, live, finished, postponed, cancelled, abandoned, interrupted or unknown |
statusRaw | string or null | The status as SofaScore writes it |
minute | integer or null | Elapsed minute of a live football match in the first or second half. Null otherwise |
homeTeam, awayTeam | string or null | Team, or player in tennis |
homeTeamId, awayTeamId | string or null | SofaScore IDs |
homeScore, awayScore | integer or null | Final or current score. Sets in tennis |
homeScoreHalfTime, awayScoreHalfTime | integer or null | Half-time score in football, first two quarters added in basketball, null in tennis |
winner | string or null | home, away or draw, from the winner code of the source |
scrapedAt | string | Time the row was built, ISO 8601 in UTC |
Then the SofaScore columns:
| Field | Type | Meaning |
|---|---|---|
customId | string or null | Short code of the match page link |
slug | string or null | Match slug used in the page link |
tournamentSlug | string or null | Slug of the unique tournament |
category | string or null | Category of the tournament, usually the country |
seasonId | string or null | SofaScore season ID |
periodScores | array or null | Score of each period (half, set or quarter), up to five, objects { period, home, away }. Null when absent |
groundType | string or null | Tennis surface |
homeRedCards, awayRedCards | integer or null | Red cards of each side, football |
details | object or null | Match sheet, timeline and line-ups, see below |
Every field is always present, with null when SofaScore gives no value.
This is one real row from the example input above, built by the Actor from a stored SofaScore response:
{"source": "SofaScore","matchId": "16604724","url": "https://www.sofascore.com/football/match/cd-inca-isidro-metapan/HMjsyyVd#id:16604724","sport": "football","country": "El Salvador","tournament": "Primera División, Apertura","tournamentId": "11537","season": "Primera Division 26/27","round": "Round 19","startTime": "2026-11-07T00:00:00.000Z","status": "live","statusRaw": "1st half","minute": 11,"homeTeam": "C.D. Inca Aruba","homeTeamId": "488673","awayTeam": "Isidro Metapan","awayTeamId": "24382","homeScore": 0,"awayScore": 0,"homeScoreHalfTime": 0,"awayScoreHalfTime": 0,"winner": null,"scrapedAt": "2026-11-07T00:15:00.000Z","customId": "HMjsyyVd","slug": "cd-inca-isidro-metapan","tournamentSlug": "primera-division-apertura","category": "El Salvador","seasonId": "99021","periodScores": [{"period": 1,"home": 0,"away": 0}],"groundType": null,"homeRedCards": null,"awayRedCards": null,"details": null}
The example comes from a test fixture captured on 2026-11-07, so the scores and statuses are those of that day.
Details
Set includeDetails to true to fill details. It depends on the sport:
| Sport | Match sheet | timeline | lineups |
|---|---|---|---|
| Football | yes | yes | yes |
| Basketball | yes | null | yes |
| Tennis | none, details is null |
details holds these keys:
| Field | Meaning |
|---|---|
venue | { name, city, country, capacity } |
referee | { name, shortName, id, country } |
homeManager, awayManager | { name, shortName, id } |
injuryTime1, injuryTime2 | Added minutes announced for each half, football |
timeline | Incidents of a football match, oldest first, see below |
lineups | { confirmed, home, away } where each side is { formation, players } |
{"venue": {"name": "Craven Cottage","city": "London","country": "England","capacity": 29600},"referee": {"name": "Chris Kavanagh","shortName": null,"id": "107059","country": "England"},"homeManager": {"name": "Álvaro Arbeloa","shortName": "Á. Arbeloa","id": "798940"},"awayManager": {"name": "Sergej Jakirović","shortName": "S. Jakirović","id": "790280"},"injuryTime1": 1,"injuryTime2": 3}
Each element of timeline is one incident:
| Field | Meaning |
|---|---|
id | Incident ID |
period | 1st Half, 2nd Half or Extra Time, null for a period mark |
side | home or away |
minute, addedTime, minuteText | Minute, added time and the minute as shown, for example 90+4'. A period mark carries its label, such as HT or FT |
type | Goal, Yellow Card, Yellow Red Card, Red Card, Substitution - Out or Period |
typeCode, playerUrl | Always null, kept for the same layout as the Flashscore timeline |
player, playerId | The player concerned. For a substitution, the player going off |
reason | Reason of a card, or Injury for an injury substitution |
homeScore, awayScore | Score after a goal or at a period mark |
related | Linked player: the assist of a goal (Assistance) or the player coming on (Substitution - In) |
class | Class given by SofaScore, for example regular, penalty or ownGoal |
rescinded, injury | Flags given by SofaScore, null when absent |
{"id": "365726690","period": "1st Half","side": "home","minute": 33,"addedTime": null,"minuteText": "33'","type": "Goal","typeCode": null,"player": "Alex Iwobi","playerId": "352770","playerUrl": null,"reason": null,"homeScore": 1,"awayScore": 0,"related": [{"type": "Assistance","player": "Sander Berge","playerId": "793167","playerUrl": null}],"class": "regular","rescinded": null,"injury": null}
Each player of a line-up has a name, a short name, an ID, a shirt number, a position, a substitute flag and a captain flag:
{"name": "Bernd Leno","shortName": "B. Leno","id": "103335","shirtNumber": 1,"position": "G","substitute": false,"captain": false}
timelineis[]for a football match that is scheduled, postponed or cancelled, and no request is made for it.- Inside a non-null
details,timelineisnullfor basketball rows, and for a football match whose incidents request failed or could not be read. - A person comes out with a name, a short name and an ID only. Birth dates, heights, market values and user counts are never output.
- A line-up that SofaScore has not published gives an error row
NOT_FOUNDin theerrorsdataset, and the match row still comes out. detailsisnullwhen the option is off, for tennis rows, and when nothing could be read for the match.
Billing
One match event is billed per delivered row. One match-details event is billed on top for a row whose details hold a timeline with at least one goal, card or substitution, or a line-up with at least one player. The venue, the referee, the managers and the added time alone are not billed, and neither are the period marks (HT, FT) of a timeline. Errors are never billed: a missing line-up gives a NOT_FOUND row in the errors dataset and no charge. Duplicates, filtered-out matches and details with nothing billable are not billed either. The Pricing tab of the Actor shows the current prices.
When the platform moves a run to another server or restarts it while it works, the run picks up where it was. A match already returned is neither returned nor billed again.
Limits
- Details cover football (sheet, timeline and line-ups), basketball (sheet and line-ups,
timelinenull) and no tennis detail. - The Actor does not output match statistics or player statistics.
minuteis computed for football only, and only while the match is in its first or second half.datescovers 7 days before to 7 days after the UTC day of the launch.- In the
datemode, one football day without a filter costs about 150 requests, so a run covers about 6 such days. Aleaguesfilter cuts this sharply, one league of one country can take 2 requests. The categories that would exceed the request cap of the run are left out and counted incategoriesDroppedof theOUTPUTrecord. - In the
datemode, the basketball list of a day may be refused, and so may a single category. The run then writes aTARGET_BLOCKEDrow and goes on, so adaterun can return no basketball match at all. For basketball, thelivemode or thematchesmode stay available. In thematchesmode, basketball details come out with the venue and the line-ups, while the referee and the formation are usually null. Thetimelineis always null for basketball. - The Actor does not output betting odds. For odds, use Flashscore Matches Scraper.
- A match close to midnight can appear in two day lists. It is returned once.
- A run with
includeDetailsmakes up to three more requests per match, so it lasts longer. For large runs, raise the run timeout. - Each run is a snapshot of SofaScore at that moment. Scores move, so two runs a minute apart can differ.
Run summary and early stops
The OUTPUT record counts requests, pages, matches, errors and the details: detailRequests (the detail requests really made), rowsWithDetails and detailsCharged. categoriesDropped counts the categories left out of a date plan.
stoppedBy says why a run ended early. It keeps the first reason only.
null: all the requested work was done.maxItems: themaxItemscap was reached.chargeLimit: the maximum charge set for the run was reached.deadline: the run reached its internal time limit.circuitOpen: too many blocked responses in a row.pageBudget: the request budget of the run was spent.badInput: the Actor's access to the data source was refused. This is a configuration problem on the Actor's side and not in your input. Please report it in the Issues tab.fetchFailed: the last walk ended on a page that could not be fetched.failedWalkslists the first 20 failed walks.
When a run stops for deadline, circuitOpen or pageBudget in the middle of a page, the rest of that page gets no details and the rows already read are kept.
Every business error is written to the errors dataset with a kind: BAD_INPUT, TARGET_BLOCKED, RATE_LIMITED, FETCH_FAILED, UPSTREAM_ERROR, SCHEMA_CHANGED, LISTING_INVALID or NOT_FOUND. The OUTPUT record counts errors in errorsByKind.
Frequently asked questions
Why do I get fewer matches than maxItems?
The mode, the sports and the days you chose hold fewer matches than the cap, or your leagues narrow the list. When no more matching matches exist, the run ends with stoppedBy set to null.
Why is details null on some rows?
Tennis rows get no details, and a row also stays null when the option is off or when nothing could be read for the match. Basketball rows have a sheet and line-ups but a timeline of null.
Does it return match statistics or player statistics?
No. A row gives the score, the half-time score and the periods, and with includeDetails the match sheet, the timeline of a football match and the line-ups. Possession, shots and other match statistics are not output, and neither are player statistics.
Does it return betting odds?
No. For odds, use Flashscore Matches Scraper, which adds the prices that operators listed in the United Kingdom show for a match.
Can I combine it with Flashscore Matches Scraper?
Yes. Both Actors share the same 23 first columns, in the same order, so their rows can be merged into a single table. Keep the source column to tell which site each row came from.
Is there a similar Actor for another site?
Yes, Flashscore Matches Scraper returns the same 23 first columns for football, tennis and basketball, with a timeline and odds.
Can I use SofaScore Sports Scraper via MCP?
Yes. Any Apify Actor can be called from an MCP client through the Apify MCP server. See the Apify MCP documentation for the setup.
Your feedback
We are always working on improving our Actors. If you have technical feedback for SofaScore Sports Scraper or found a bug, please create an issue on the Actor's Issues tab.
More free tools, scrapers, MCP servers and data studies from the same team: mr-bridge.com.
Legal and responsible use
The data comes from the public pages and responses of SofaScore (https://www.sofascore.com) and belongs to its owners. SofaScore does not publish an API for it and has no link with this Actor. It has its own terms of use, and it may restrict automated access or commercial reuse. Before you rely on the output, in particular for commercial use, confirm that your use is permitted by the terms that apply. You remain responsible for how you use the results and for complying with the source's terms and with the laws that apply to you.