Tony Wang6 min readHow to Scrape SofaScore in 2026 (API & Python)
Three ways to scrape SofaScore live scores, odds, lineups, and standings in 2026 — DIY Python, no-code tools, or a structured API — and the legal basics.
The fastest way to scrape SofaScore in 2026 is to call a structured API that returns normalized JSON — live scores, match detail, lineups, odds, incidents, and standings — instead of reverse-engineering SofaScore's undocumented internal API and its Cloudflare protection yourself. DIY is possible, and a real community of unofficial clients exists, but SofaScore publishes no official public API and its own terms restrict automated access. This guide covers all three approaches, what each returns, where each breaks, and the legal basics.
Why scrape SofaScore?
Public SofaScore data powers a category of sports-data workflows:
- Live score and odds monitoring — track in-progress matches, scores, and betting markets across football, basketball, and tennis.
- Team and player research — pull rosters, team profiles, and a team's event history in one place.
- League and standings tracking — follow table position, form, and season-over-season changes.
- Sports analytics — feed event statistics, lineups, incidents, and head-to-head history into an analysis or prediction pipeline.
Is it legal to scrape SofaScore?
Option 1: DIY in Python (and why it's harder than it looks)
There's no official SDK, so DIY means calling the same internal API SofaScore's own apps use:
import requests
resp = requests.get(
"https://api.sofascore.com/api/v1/search/all",
params={"q": "real madrid"},
)
data = resp.json() # frequently a Cloudflare 403 "challenge" page, not JSON
It works until it doesn't, and the recurring pain is well documented by the community clients that reverse-engineer this API:
- No official API, no stability guarantee. SofaScore does not publish a public developer API —
api.sofascore.comis the same undocumented internal API its web and mobile apps call, and it can change shape or access rules whenever SofaScore ships a new app version, with no changelog for outside developers. - Cloudflare's challenge. Per the Sofascore-API-Bundle client docs, SofaScore's edge answers any request missing an
X-Requested-Withheader with a Cloudflare403"challenge." Clearing it consistently also needs a modern TLS handshake — older stacks get fingerprinted even from a clean IP — and, per the same docs, some IPs (certain datacenter ranges, geo-blocked regions) get a403regardless of headers. - Terms risk on top of the technical hurdles. Automated and scraping access is against SofaScore's own terms (see above), independent of whether your client can get past Cloudflare.
- No structure once you're in. A raw response mirrors SofaScore's internal app schema, not a documented public contract — every field name and nesting choice is yours to reverse-engineer and re-parse if it moves.
Option 2: No-code tools
Browser extractors and marketplace "SofaScore scraper" actors can export a single match or table to CSV, which is fine for a one-off. But live scores and odds change by the minute, and following several matches across a matchday is a monitoring pipeline, not a one-time export — that's where a scheduled API call wins.
Option 3: A structured SofaScore API
Crawlora's SofaScore API exposes documented endpoints for search, live events, match detail, odds, lineups, standings, and more, returning normalized JSON from one API key.
curl -G "https://api.crawlora.net/api/v1/sofascore/search" \
-H "x-api-key: $CRAWLORA_API_KEY" \
--data-urlencode "q=real madrid"
Then chain search → detail → odds/lineups in Python:
import requests
h = {"x-api-key": "YOUR_API_KEY"}
base = "https://api.crawlora.net/api/v1/sofascore"
# search resolves a team, player, or competition name to a numeric id
hits = requests.get(f"{base}/search", headers=h, params={"q": "real madrid"}).json()["data"]["results"]
event_id = 14025013 # from a search result's matches or the live-events endpoint
event = requests.get(f"{base}/event", headers=h, params={"id": event_id}).json()["data"]
odds = requests.get(f"{base}/event-odds", headers=h, params={"id": event_id}).json()["data"]
lineups = requests.get(f"{base}/event-lineups", headers=h, params={"id": event_id}).json()["data"]
An event response is normalized JSON (fields are illustrative — confirm the schema in the docs):
{
"code": 200,
"msg": "OK",
"data": {
"event": {
"id": 14025013,
"slug": "bournemouth-liverpool",
"start_time": "2025-08-15T19:00:00Z",
"status": { "code": 100, "description": "Ended", "type": "finished" },
"tournament": { "id": 52, "name": "Premier League", "unique_tournament_id": 17 },
"home_team": { "id": 60, "name": "Bournemouth", "country": "England" },
"away_team": { "id": 44, "name": "Liverpool", "country": "England" },
"home_score": { "current": 2, "period1": 1, "period2": 1 },
"away_score": { "current": 4, "period1": 1, "period2": 3 },
"venue": { "name": "Vitality Stadium", "city": "Bournemouth", "capacity": 11307 }
}
}
}
Standings and live events take their own required params — a season id (resolved via tournament-seasons) and a sport key, respectively:
standings = requests.get(f"{base}/standings", headers=h,
params={"id": 17, "season": 76986, "type": "total"}).json()["data"]
live = requests.get(f"{base}/live-events", headers=h, params={"sport": "football"}).json()["data"]
Store one row per event (or per standings snapshot) with its fetched_at timestamp, and re-run on a schedule — polling live-events on an interval is how you track a match as it progresses.
What you can collect
- Universal search across teams, players, and competitions, each with a numeric id, sport, and country
- Match (event) detail: scores by period, status, venue, referee, and attendance, plus the tournament and season it belongs to
- Event statistics, lineups (formation, starting XI, and substitutes), match incidents (goals, cards, substitutions), and head-to-head history between two teams
- Betting markets and odds by match — informational only (see legal section)
- Team profiles and a team's event history and roster; player profiles
- League standings (total, home, away) plus tournament seasons and round-by-round fixtures
- Live in-progress events by sport (football, basketball, tennis)
Limitations and common challenges
- No official API to fall back on. This is entirely based on SofaScore's internal app API; when SofaScore changes it, both DIY code and any scraper adapt after the fact, not before.
- Cloudflare plus IP reputation. Correct headers alone don't guarantee access from every network — datacenter ranges and some geographies get blocked outright, per the unofficial client docs cited above.
- Odds are informational, not a betting feed. SofaScore's own terms say the odds it shows are for informational purposes only, not a gambling service and not legal or tax advice — treat them as market color, not as a licensed odds feed for a wagering product.
- Not push/real-time. "Live" here means polling the live-events endpoint on a schedule; there's no websocket or streaming feed, so pick a polling interval that matches how fast the score actually needs to update.
- Season ids drift. A competition's season id changes every year (for example, a Premier League season id is different each year) — resolve it fresh via
tournament-seasonsrather than hardcoding one.
Sources
Where this fits
Try it first, free: run any public URL through the Free Web Scraper, or check whether a site blocks bots with the Anti-Bot Checker — no signup.
SofaScore data feeds live sports dashboards, team and player research, and standings tracking. For US-focused scores, standings, and rosters across other leagues, see the ESPN API, and for a single league in full depth — schedules, box scores, and player stats — see how to scrape MLB. Once you have the fixture list, how to scrape Ticketmaster covers the ticket market for those same matches. For the search and social interest around the same matches rather than the scores themselves, see what America searched for after the 2026 World Cup final. For the broader toolkit, see how to choose a web scraping API, and for the legal basics, is web scraping legal in 2026.
Get started by testing the endpoint in the Playground, reading the request and response schema in the API docs, and reviewing credit costs on the pricing page.
Part of our how-to-scrape guide series — every platform we cover, in one index.
Frequently asked questions
Does SofaScore have an official public API?
No. SofaScore does not publish a public developer API. Its data comes from an undocumented internal API (api.sofascore.com) that its own web and mobile apps call, which community projects have reverse-engineered — it can change shape or access rules whenever SofaScore ships a new app version, with no changelog for outside developers.
Can I scrape SofaScore without getting blocked?
DIY requests to api.sofascore.com that are missing an X-Requested-With header get a Cloudflare 403 "challenge," and even correct headers don't guarantee access from every network — some IPs (certain datacenter ranges, geo-blocked regions) get a 403 regardless, per third-party client documentation. A structured API handles headers, TLS, and IP routing behind one key.
Is it legal to scrape SofaScore?
SofaScore's live scores and stats are public web content, but SofaScore's own terms prohibit automated or scraping access, treat its data as a protected database, and restrict the Platform to personal, non-commercial use. Review the terms and your local law before commercial use. Not legal advice.
Can I use SofaScore odds for a betting product?
No — SofaScore's own terms state that betting odds shown on the Platform are for informational purposes only, not a gambling service, and not legal or tax advice. Treat odds data as market color for research and analytics, not as a licensed feed for a wagering product.
What SofaScore data can I collect?
Universal search across teams, players, and competitions; match detail, statistics, lineups, incidents, and head-to-head history; betting odds by match; team and player profiles; league standings (total, home, away); tournament seasons and round-by-round fixtures; and live in-progress events by sport (football, basketball, tennis).
How do I find a team, player, or event id?
Call /sofascore/search with a free-text name — it returns numeric ids typed as team, player, or competition, along with sport and country. For a specific match, pull a team's event history or the live-events feed rather than guessing an id.
How often can I refresh live scores?
The live-events endpoint returns in-progress matches for a sport (football, basketball, tennis) as of the moment you call it — there's no websocket or streaming feed, so poll it on an interval that matches how fast the score needs to update, within your plan and responsible-use limits.