Sportmonks sports data
Rate-limited Sportmonks proxies on the tournaments host — fixtures, squads, cricket, and motorsport without your own API token.
By the end: you can call Bento’s Sportmonks proxies safely (cached + rate-limited) instead of hitting Sportmonks directly.
These routes live on the tournaments host (PARLAY_TOURNAMENT_URL). They are not on createBentoSdk().tournaments today (proxy namespaces were trimmed from the public SDK surface). Use fetch (or any HTTP client) against the host origin.
Do not put a Sportmonks
api_tokenin client code. The server injects it. If you send one, it is stripped.
Setup
export PARLAY_TOURNAMENT_URL='https://bento-fun-tournaments-backend-3nku.onrender.com'
export BENTO_BUILDER_API_KEY='bnt_…' # same key as markets / createBentoSdkconst base = process.env.PARLAY_TOURNAMENT_URL!; // no trailing slashCatalog / betting still use the markets host. Sports data uses this tournaments-host proxy.
Prefer the right TTL (this is the rate limit)
Every proxy call should pass a ttl profile. The backend uses stale-while-revalidate: fresh cache is served without upstream; stale can be served while refreshing; identical in-flight requests are single-flighted.
ttl | Fresh | Stale window | Use for |
|---|---|---|---|
live | 30s | 5 min | Live scores / livescores only (see clamp below) |
ended | 60s | 24h | Finished fixtures / final stats |
news | 5 min | 1h | News-style payloads (v3 only) |
logo | 24h | 7d | Static images / logos |
default | 60s | 30 min | Schedules, squads, standings |
Server TTL clamp: ttl: 'live' is honored only on live-ish paths (livescores, fixtures/latest, inplay, paths containing /live/). On other paths the server forces default so clients can’t short-circuit the cache.
Client rules (so you don’t burn the shared Sportmonks quota):
- Always set
ttl— don’t rely on default for live data. - Poll no faster than the fresh window (e.g. live ≥ 30s).
- Reuse responses; check
_meta.fromCache/_meta.isStaleinstead of re-requesting. - Prefer the typed GET helpers below for common football needs (they’re already TTL-cached server-side).
- Use GET only on the v3 proxy (
method: 'POST'is rejected).
Backend protections (automatic)
You don’t configure these — they protect the shared upstream and the public surface:
| Layer | Limit |
|---|---|
| Per-IP proxy POST | 60 req/min on /bento/sportmonks/proxy + cricket-v2 |
| Per-IP football GET helpers | 120 req/min |
| Host-wide floor | ~300 req/min/IP |
| Per-cache-key concurrency | 8 (overflow → stale) |
| Global upstream gate | ~12 concurrent sports upstream calls |
| Circuit breaker | 5× HTTP 429 in 30s → path open 60s (stale only) |
| Path allowlist | football/ | cricket/ | motorsport/ (+ cricket-v2 roots) |
If you see 429 with Retry-After, back off. During a Sportmonks 429 storm, responses may stay stale on purpose.
Routes
1. Sportmonks v3 proxy (football / cricket path roots / motorsport)
POST ${PARLAY_TOURNAMENT_URL}/bento/sportmonks/proxy
const res = await fetch(`${base}/bento/sportmonks/proxy`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-builder-api-key': process.env.BENTO_BUILDER_API_KEY!,
},
body: JSON.stringify({
method: 'GET', // default GET
// Must start with football/ | cricket/ | motorsport/
path: 'football/fixtures/date/2026-08-05',
params: { include: 'participants' }, // api_token stripped if present
ttl: 'default',
}),
});
const body = (await res.json()) as {
data: unknown; // upstream Sportmonks JSON (usually { data: ... })
_meta?: { fromCache: boolean; isStale: boolean; fetchedAt: number; cacheKey: string };
};
// Most clients use body.data as if it came from Sportmonks directly
console.log(body.data, body._meta?.fromCache);| Body field | Notes |
|---|---|
path | Relative Sportmonks v3 path, e.g. football/leagues, motorsport/stages. Regex ^[\w\-/]+$. |
params | Query params forwarded upstream (never send api_token). |
ttl | live | ended | logo | news | default (live clamped to live paths) |
method | GET only (POST → 400) |
2. Cricket v2 proxy (separate Sportmonks host)
POST ${PARLAY_TOURNAMENT_URL}/bento/sportmonks/cricket-v2/proxy
Cricket v2 is not on the v3 host. Same envelope + similar TTL / circuit-breaker behavior. Allowed path roots include fixtures, livescores, teams, seasons, squad, etc.
const res = await fetch(`${base}/bento/sportmonks/cricket-v2/proxy`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-builder-api-key': process.env.BENTO_BUILDER_API_KEY!,
},
body: JSON.stringify({
path: 'livescores',
params: {},
ttl: 'live',
}),
});
const { data, _meta } = await res.json();3. Cached football helpers (prefer these when they fit)
| Method | Path |
|---|---|
| Fixture snapshot | GET /bento/sportmonks/football/fixture/:fixtureId/snapshot |
| Team squad | GET /bento/sportmonks/football/team/:teamId/squad |
| Team season stats | GET /bento/sportmonks/football/team/:teamId/season-stats |
const builderHeaders = {
'x-builder-api-key': process.env.BENTO_BUILDER_API_KEY!,
};
const snapshot = await fetch(
`${base}/bento/sportmonks/football/fixture/${fixtureId}/snapshot`,
{ headers: builderHeaders },
).then((r) => r.json());
const squad = await fetch(
`${base}/bento/sportmonks/football/team/${teamId}/squad`,
{ headers: builderHeaders },
).then((r) => r.json());These are server-cached aggregations — better than inventing equivalent v3 proxy paths for the same data.
Auth
Builder API key required — send the same x-builder-api-key you use on the markets host (BENTO_BUILDER_API_KEY / createBentoSdk({ apiKey })). Mint a key once on markets — tournaments validates against the same BuilderApp store (no separate registration).
No user JWT required for these Sportmonks proxy reads (the server holds the Sportmonks token). Use the tournaments host URL from Environments.
| Layer | Header | Purpose |
|---|---|---|
| Builder (app) | x-builder-api-key | Which app is calling |
| User (optional) | Authorization: Bearer … | Not needed for sports data reads |
Do not confuse this with markets sdk.user (Builder key + Bearer JWT for bets).
What not to do
| Don’t | Do instead |
|---|---|
Call api.sportmonks.com with your own key from the app | Call Bento’s proxy |
| Poll live every 1–5s | ttl: 'live' and poll ≥ 30s |
Omit ttl on hot paths | Set live / ended / default explicitly |
| Forward arbitrary paths | Stick to allowlisted roots |
Omit x-builder-api-key | Send BENTO_BUILDER_API_KEY on every sports proxy call |
| Treat proxy as a betting API | Betting stays on markets host / tournament chips |
Related
- Environments —
PARLAY_TOURNAMENT_URL - Cookbook — method shortlist (markets + tournaments)
- OpenAPI specs —
tournaments.jsonincludes these paths - Other sports proxies on the same host (NFL/MLB/NBA/OpenF1/Odds) follow the same “cache + gate” idea; Sportmonks is the primary documented surface for soccer/cricket/motorsport data