The repository or configuration that created this release differ from a previous release. View details.
Foresportia Python SDK
Official Python SDK for the Foresportia API, a football analytics API that provides match data, model probabilities, predicted picks, confidence signals, and Core Analytics payloads for building or enriching football prediction models.
The SDK targets server-side Python (3.9+) with a synchronous, typed client.
Public plans share the same SDK surface:
- Developer — free: 1 competition, up to 7 days of verified history, bulk requests up to 5 match IDs, and a limited analytics projection.
- Starter — paid: 20 competitions, up to 90 days of verified history, bulk requests up to 100 match IDs, and more complete analytics.
- Legacy beta: temporary compatibility for existing keys.
Installation
pip install foresportia
Optional extras:
pip install "foresportia[ml]" # numpy + scikit-learn for the ML example
pip install -e ".[dev]" # local development from a clone
Authentication and key safety
Authentication uses the X-API-Key header only. The SDK never puts the key in
a URL, and the key never appears in repr(), logs, or exception messages.
Store the key in an environment variable rather than in source code:
export FORES_API_KEY="fs_developer_your_key_here"
$env:FORES_API_KEY = "fs_developer_your_key_here"
from foresportia import ForesportiaClient
client = ForesportiaClient.from_env() # reads FORES_API_KEY
# or explicitly:
client = ForesportiaClient(api_key="...", timeout=10.0)
The base URL must be HTTPS. Plain HTTP is only accepted for localhost or
with allow_insecure_base_url=True (development/testing only).
Do not commit API keys to source control, notebooks, screenshots, or support tickets.
Quickstart
from foresportia import ForesportiaClient
with ForesportiaClient.from_env() as client:
leagues = client.list_leagues()
for league in leagues.data:
print(league.code, league.name, league.matches_available)
matches = client.list_league_matches(
"PREMIER_LEAGUE",
include="upcoming",
start="2026-07-15",
days=7,
)
for match in matches.data:
print(match.kickoff, match.home_team, "vs", match.away_team, match.pick)
The SDK also accepts Starter and legacy beta keys without inferring a plan from their prefix. The server remains authoritative for entitlements.
Every typed method returns an ApiResponse with:
response.data # typed result (list[League], list[MatchSummary], MatchDetail, BulkResult)
response.payload # full JSON payload as a dict
response.etag # ETag header for conditional requests
response.quota # parsed rate-limit / quota headers
response.status_code # HTTP status
response.not_modified # True for 304 responses
Available competitions
leagues = client.list_leagues()
for league in leagues.data:
print(league.code, league.name, league.country, league.activity_status)
Your API key only sees the competitions enabled for it. Use the codes exactly
as returned (for example in list_league_matches).
Match detail
Match IDs from list endpoints look like fsm:v1:<64 hex characters>. Pass
them exactly as returned:
match = client.get_match("fsm:v1:0123...abcdef")
detail = match.data
print(detail.home_team, "vs", detail.away_team)
print(detail.probabilities) # 1X2, over/under, BTTS, draw no bet, ...
print(detail.ratings) # ELO-style ratings
print(detail.raw) # complete Core Analytics payload
Bulk limits by plan
Developer accepts up to 5 match IDs per request; Starter accepts up to 100.
The SDK validates only the technical ceiling of 100. A Developer request with
more than 5 IDs is sent and the server returns bulk_limit_exceeded. Order is
preserved and per-ID failures do not hide successful results:
bulk = client.get_matches_bulk([id_1, id_2, id_3])
for detail in bulk.data.results:
print(detail.id, detail.home_team, "vs", detail.away_team)
for error in bulk.data.errors:
print("failed:", error.match_id, error.code) # e.g. match_not_found
History and pagination
Developer provides up to 7 days of verified history and Starter up to 90 days.
Each request has a maximum window of 31 days. The effective archive may start
later at response.history_available_from while it fills progressively.
page = client.list_league_matches("CHN", include="past", days=7, limit=50)
while True:
for match in page.data:
print(match.kickoff, match.home_team, match.away_team)
if not page.next_cursor:
break
page = client.list_league_matches(
"CHN", include="past", days=7, limit=50, cursor=page.next_cursor
)
# Or stream rows without loading every page:
for match in client.iter_league_matches("CHN", include="past", days=7, limit=50):
print(match.id)
The server may return history_window_exceeded when the requested dates fall
outside the key's entitlement.
History helpers
list_league_history() and iter_league_history() are thin wrappers that
always request finished matches (include="past"); they take the same
parameters, return the same types, and expose the same metadata and errors as
the methods above. match.is_final mirrors the server status == "final" and
match.predicted_outcome returns the published pre-match pick
("home", "draw", "away", or None); result_score is the final score
from the API. No live data is added.
By default days is omitted, so the server picks the window from the key's
real history entitlement, capped at 31 days per request. Pass days explicitly
to choose a shorter or specific window:
with ForesportiaClient.from_env() as client:
# Automatic window: the available history, capped at 31 days per request.
page = client.list_league_history("SUE")
# Or choose an explicit window.
page = client.list_league_history("SUE", days=3)
for match in client.iter_league_history("SUE"):
if not match.is_final:
continue
print(
match.home_team,
match.away_team,
match.result_score,
match.predicted_outcome,
)
Developer keys currently expose 7 days of history; Starter keys have a 90-day
rolling entitlement, but an automatic request stays capped at 31 days.
iter_league_history() paginates the requested window — it does not
automatically split a 90-day entitlement into several windows. The SDK never
resolves the entitlement locally and makes no extra plan-discovery call; the
server stays authoritative and a valid window may legitimately contain no
matches.
Today endpoints
today = client.list_today_matches()
picks = client.list_today_picks(limit=20)
Public health
client.health() returns the tolerant dictionary from the stable public
GET /v1/health endpoint. It reports service/data health and is not an
entitlement or account-status check. Unknown future fields are preserved.
Quotas and rate limits
The API reports quota state in response headers; the SDK parses them into
response.quota (fields are None when the header is absent):
response = client.list_leagues()
print(response.quota.remaining) # X-RateLimit-Remaining
print(response.quota.units_remaining_hour) # X-Quota-Units-Remaining-Hour
print(response.quota.units_remaining_day) # X-Quota-Units-Remaining-Day
print(response.quota.match_rows_remaining_day) # X-Match-Rows-Remaining-Day
Quota enforcement stays server-side. On HTTP 429 the SDK raises
ForesportiaRateLimitError (or ForesportiaConcurrencyLimitError) carrying
retry_after and the quota snapshot. Retrying after 429 is opt-in:
client = ForesportiaClient.from_env(retry_on_rate_limit=True, max_retries=2)
ETags and conditional requests
Pass a previous ETag to skip unchanged payloads. A 304 Not Modified is a
normal response, not an error:
first = client.get_match(match_id)
second = client.get_match(match_id, etag=first.etag)
if second.not_modified:
detail = first.data # reuse the cached payload
else:
detail = second.data
Error handling
from foresportia import (
ForesportiaAuthenticationError, # 401
ForesportiaAuthorizationError, # 403
ForesportiaNotFoundError, # 404
ForesportiaValidationError, # 400 / 422 / client-side validation
ForesportiaRateLimitError, # 429
ForesportiaConcurrencyLimitError, # 429 concurrency_limit_exceeded
ForesportiaServerError, # 5xx
ForesportiaTransportError, # network / timeout
ForesportiaAPIError, # base class for all of the above
)
try:
matches = client.list_today_matches()
except ForesportiaRateLimitError as exc:
print("rate limited, retry after", exc.retry_after, "seconds")
except ForesportiaAPIError as exc:
print(exc.status_code, exc.error_code, exc.endpoint)
Exceptions carry status_code, error_code (the API business code such as
match_not_found or bulk_limit_exceeded), endpoint, retry_after, and a
quota snapshot when available. The API key is never included.
Common business codes include competition_not_selected,
history_window_exceeded, and bulk_limit_exceeded. starter_required is
normally an availability value for a masked Developer field; if a future
endpoint returns it as an error code, it remains available through
exception.error_code. Unknown future codes use the same safe fallback.
Retries and timeouts
-
Explicit timeout per client (
timeout=10.0, default 20 s). -
Retries are disabled by default (
max_retries=0). A request that times out client-side may still have been processed and counted against your quota server-side, so each retry can consume an extra quota unit. Opt in explicitly when that trade-off is acceptable:client = ForesportiaClient.from_env(max_retries=2)
When enabled, only GET requests are retried, on network errors and 502/503/504, with bounded exponential backoff.
-
No automatic retry on 400/401/403/404, nor on 429 unless
retry_on_rate_limit=True. -
The bulk POST is never retried on 5xx.
-
Use the client as a context manager (
with ... as client:) or callclient.close().
Machine learning example
A runnable script is provided in
examples/ml_starter_features.py:
it fetches upcoming matches, extracts probabilities, markets, and confidence
into a feature matrix, and optionally fits a small scikit-learn model
(pip install "foresportia[ml]"). Foresportia outputs are model probabilities,
not guaranteed predictions; you need your own historical labels to train
anything meaningful.
rows = []
for match in client.list_league_matches("PREMIER_LEAGUE", days=14).data:
p = match.probabilities
rows.append([p.get("home"), p.get("draw"), p.get("away"),
match.markets.get("btts"), match.confidence.get("score")])
Legacy methods
The v0.1 dict-based methods keep working unchanged:
client.me()
client.usage()
client.picks_today()
client.matches_today()
client.leagues()
client.league_matches("FIN", include="all", days=14, limit=10)
client.world_cup_2026_matches(limit=10)
Prefer the typed list_* / get_* methods for new code.
Links
- API documentation: https://www.foresportia.com/api/docs/
- LLM-friendly API reference: https://www.foresportia.com/api/docs/llms-full.txt
- Homepage: https://www.foresportia.com
- Developer docs (EN): https://www.foresportia.com/en/developers.html
- API dashboard (EN): https://www.foresportia.com/en/api-dashboard.html
- Repository: https://github.com/Foresportia/foresportia-python
- Issues: https://github.com/Foresportia/foresportia-python/issues
Disclaimer
Foresportia provides football probabilities, model outputs, and analytics data. It does not provide betting advice, financial advice, bookmaker odds, guaranteed outcomes, or instructions to place wagers. Any use of Foresportia data is the responsibility of the user and should comply with applicable laws, regulations, and platform policies.
Starter prediction reliability (since 0.3.2)
Use SDK 0.3.2 or later and a Starter key with an API deployment supporting
include_unreliable. Upgrade with python -m pip install --upgrade foresportia.
Typed match methods accept include_unreliable=True to explicitly request complete,
verified calculations rejected for identified sporting quality reasons. By default the
API still returns reliable predictions only. This does not extend the match horizon.
response = client.list_league_matches("LIGUE_1", include_unreliable=True)
for match in response.data or []:
print(match.id, match.reliability, match.reliability_context)
Reason codes: elo_unreliable, shadow_insufficient_data, sparse_goal_context,
elo_missing, goal_raw_data_unreliable, uefa_qualification_mute.
These flags are not a probability of correctness. Invalid calculations and
unknown rejection reasons remain excluded; the original probabilities are unchanged.
No translated message is returned. Keep the option unchanged when paginating.
License
MIT. See LICENSE.
Metadata
Release files for foresportia 0.3.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Change of provenance
The repository or configuration that created these release files differ from that of a previous release.
We recommend inspecting the repository and configuration before proceeding. View details.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| foresportia-0.3.4.tar.gz | 42.6 kB | With provenance | Details for foresportia-0.3.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| foresportia-0.3.4-py3-none-any.whl | Python 3 | none | any | Details for foresportia-0.3.4-py3-none-any.whl |
Total release size: 62.1 kB
Release files / foresportia-0.3.4.tar.gz
Change of provenance
This file belongs to a release with changed provenance: the repository or configuration that created the release files differ from that of a previous release.
We recommend inspecting the repository and configuration before proceeding. View details.
| Download URL | foresportia-0.3.4.tar.gz |
|---|---|
| Size | 42.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aa06347ff46a38e0174da6b641f93096d41c4f1237c05204a0ced5e4214123c8
|
|
BLAKE2b-256 checksum How to use checksums |
3b3ca32f452c634e3ec3bb738f21459a49c422288640da0ca536779ae236f7b1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / foresportia-0.3.4-py3-none-any.whl
Change of provenance
This file belongs to a release with changed provenance: the repository or configuration that created the release files differ from that of a previous release.
We recommend inspecting the repository and configuration before proceeding. View details.
| Download URL | foresportia-0.3.4-py3-none-any.whl |
|---|---|
| Size | 19.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
36f26a4b306a6311de895e5aa421ed3a3b5264f7ffa9fcd79f011fd12583439b
|
|
BLAKE2b-256 checksum How to use checksums |
73e28af68c468949e1b45eeb30505ec475256df8d676c634fe773afe5cc858af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logSecurity for foresportia 0.3.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Change of provenance
The repository and configuration that created this release differ from a previous release.
While this could reflect a routine change by the project maintainers, it may also indicate that the release files have been compromised.
Repository
- Removed 0.3.2 github.com/QBarbedienne/foresportia-python
- Added 0.3.4 github.com/Foresportia/foresportia-python
Attestation for foresportia-0.3.4.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.