InferSports REST docs
A read-only REST API for live Asian and Pinnacle odds: Asian handicap, totals, and 1×2, full-time and half-time. No endpoint can place a wager. Everything below is the current v1 contract.
Introduction
InferSports is an API-first, English-facing REST service for sharp Asian odds. It is strictly read-only: it surfaces current and live prices and exposes no betting or execution surface, which is exactly why it is safe to hand to an autonomous agent. Responses are JSON over HTTPS, with Sqids-encoded ids (evt_, tm_, lg_, bk_, sp_) and ISO-8601 UTC timestamps.
- Base URL
- https://api.infersports.dev/v1
- Version
- v1
- Content-Type
- application/json
- OpenAPI
- OpenAPI 3.1 at
/openapi.json
Every list endpoint is cursor-paginated, every odds payload carries an as_of freshness stamp, and a ?format= switch re-renders prices into any supported notation. See coverage for the live book and league list.
Authentication
Authenticate with a bearer token in the Authorization header. Keys are prefixed isk_. Requests with no key fall through to the Free tier: tighter rate limits and no sharp books. Your tier sets the rate limit and whether Pinnacle and the sharp books are returned. See pricing.
isk_dev_standard_key$ curl -H "Authorization: Bearer isk_…" \ https://api.infersports.dev/v1/sports
Get your own key at signup. The dev key isk_dev_standard_key grants the Standard tier for trying the docs and Playground. Treat production keys as secrets; never embed them in client-side code.
Rate limits
Each key is metered by a per-tier token bucket. Every response carries the current budget in headers so you can back off before you run out:
- X-RateLimit-Limit
- Requests allowed in the current window.
- X-RateLimit-Remaining
- Requests left before throttling.
- X-RateLimit-Reset
- Epoch seconds when the bucket refills.
Exceeding the budget returns 429 with a retriable error envelope and a Retry-After header. Read X-RateLimit-Remaining and X-RateLimit-Reset on every response and stop before 0; on 429, wait Retry-After seconds, then retry. Every endpoint is read-only, so retries are always safe.
HTTP/1.1 429 Too Many Requests Retry-After: 30 { "error": { "code": "rate_limit_exceeded", "message": "Rate limit exceeded for your tier (Standard)", "is_retriable": true, "retry_after_seconds": 30, "documentation_url": "https://docs.infersports.dev/errors#rate_limit_exceeded" } }
Errors
All errors share one envelope: an error object with a stable code, a human message, an is_retriable flag, an optional retry_after_seconds, and a documentation_url pointing at the matching anchor.
{ "error": { "code": "not_found", "message": "event evt_zzzz not found", "is_retriable": false, "retry_after_seconds": null, "documentation_url": "https://docs.infersports.dev/errors#not_found" } }
invalid_requestnoMalformed query, bad id, or unknown parameter value.unauthorizednoMissing or invalid API key.forbidden_for_tiernoResource gated to a higher tier (e.g. Pinnacle on Free).not_foundnoNo event / entity for that id.rate_limit_exceededyesToken bucket drained for your key. Honor Retry-After.internalnoUnexpected server error.upstream_unavailableyesUpstream feed temporarily unreachable. On 503 upstream_unavailable the live snapshot may be briefly stale: check as_of for age and retry after a short delay. Reads are always safe to repeat.
Pagination
Lists that can grow — events, leagues, teams — are cursor-paginated; fixed lists (/v1/sports, /v1/bookmakers) return the full set in one page. The response wraps results in data and reports the next position under page. Pass limit to size a page and cursor to fetch the next. When next_cursor is null, you have reached the end. Odds payloads also carry an as_of freshness stamp: the timestamp of the last update applied.
{ "data": [ { "id": "lg_QPw2L8", "name": "English Premier League", "sport": "football" }, { "id": "lg_8mN3kP", "name": "La Liga", "sport": "football" } ], "page": { "next_cursor": "eyJvIjoyfQ", "limit": 2 } }
Odds formats
Prices are normalized to decimal internally. Add ?format= to any odds endpoint to re-render every price into a different notation. Conversion, including per-book Malay rounding, happens server-side, so you never do client math.
decimal2.41Decimal. Default. European decimal odds.hk1.41Hong Kong. Net return per unit staked.malay-0.709Malay. Per-book rounding applied.indonesian1.41Indonesian. Signed Indo notation.american141American. Integer moneyline (+/-).probability0.4149Probability. Implied probability, 0–1. All six examples above are the same underlying price (2.41 decimal).
Metadata
Reference endpoints that map human names and keys to ids. Use these to drive filters on the event and odds endpoints.
/v1/sportsLists supported sports: football and basketball./v1/bookmakersLists all bookmakers, including Pinnacle, with class./v1/leaguesLists leagues currently in the live snapshot. Filter with ?sport=./v1/teamsSearches teams by fuzzy name. Example: ?q=man%20city&sport=football.GET/v1/sports
{ "data": [ { "id": "sp_X4cN7m", "key": "football", "name": "Football" }, { "id": "sp_9hT2bW", "key": "basketball", "name": "Basketball" } ] }
GET/v1/bookmakers
{ "data": [ { "id": "bk_T7nJ4v", "key": "pinnacle", "name": "Pinnacle", "class": "sharp" }, { "id": "bk_Kp2mQ9", "key": "crown", "name": "Crown", "class": "asian" } ] }
GET/v1/teams?q=man%20city
Fuzzy match on q (handles abbreviations like man city → Manchester City). Optional sport filter; cursor-paginated like /v1/leagues.
Events & odds
The core surface: list fixtures, read live state, and pull normalized quotes across every bookmaker, market, and period.
/v1/eventsLists and filters fixtures./v1/events/liveLists only fixtures currently in play./v1/events/{id}Returns a single event by id./v1/events/{id}/stateReturns live score, cards, and match clock./v1/events/{id}/oddsReturns all quotes for the event, in any format./v1/events/{id}/odds/{market_type}Returns quotes for one market type.GET/v1/events
sportstringfootball | basketballleaguestringExternal league id, e.g. lg_QPw2L8datestringUTC date filter, YYYY-MM-DDstatusstringscheduled | live | finishedlimitintPage sizecursorstringOpaque pagination cursor{ "data": [{ "id": "evt_R5fG2pq", "sport": "football", "league": { "id": "lg_QPw2L8", "name": "English Premier League" }, "home_team": { "id": "tm_M3xK9p", "name": "Manchester City" }, "away_team": { "id": "tm_aB3dE9", "name": "Arsenal" }, "scheduled_at": "2026-05-30T18:00:00Z", "status": "scheduled" }], "page": { "next_cursor": "eyJvIjoxfQ", "limit": 1 } }
GET/v1/events/{id}/state
{ "event_id": "evt_R5fG2pq", "status": "live", "score": { "home": 1, "away": 0 }, "cards": { "home": 1, "away": 2 }, "clock": "1H 23", "as_of": "2026-05-30T18:24:00Z" }
GET/v1/events/{id}/odds
bookmakerscsvBookmaker keys, e.g. pinnacle,crown (max 50)marketscsv1x2,asian_handicap,totals (max 10)periodstringfull_time | half_timeformatstringdecimal (default) | hk | malay | indonesian | american | probability Returns an OddsBlock: event_id, an as_of stamp, and an odds array. Each quote is keyed by event + bookmaker + market_type + period + line, and carries its own format, a status of open or suspended, and a per-quote as_of stamp. For 1×2, line is null and prices is { home, draw, away }; totals use { over, under }; handicap_components appears on quarter lines. Append /{market_type} to scope to a single market. A suspended quote keeps its last prices verbatim (often 0), so filter on status before treating a price as live.
{ "event_id": "evt_R5fG2pq", "as_of": "2026-05-30T12:34:56Z", "odds": [{ "bookmaker": "pinnacle", "market_type": "asian_handicap", "period": "full_time", "line": -0.75, "handicap_components": [-0.5, -1.0], "prices": { "home": 1.98, "away": 1.92 }, "format": "decimal", "status": "open", "as_of": "2026-05-30T12:34:56Z" }] }
On the Free tier, sharp books, including Pinnacle, are filtered out of the odds array. Upgrade to access them.
Resolve & convert
POST helpers built to be driven by code and LLMs: fuzzy-resolve a name to an id, convert a price between formats, or split a handicap line into components with its full win conditions.
/v1/resolve/eventResolves a fuzzy fixture name to an event id./v1/resolve/teamResolves a fuzzy team name to a team id./v1/resolve/leagueResolves a fuzzy league name to a league id./v1/convert/oddsConverts one price across formats./v1/convert/handicapSplits a handicap line and explains its win conditions.POST/v1/resolve/event
# request { "query": "Man City vs Arsenal this weekend", "context": { "sport": "football" } } # response { "status": "matched", "best_match": { "event_id": "evt_R5fG2pq", "confidence": 0.97 }, "alternatives": [ { "event_id": "evt_Lk9wQ2", "confidence": 0.42 } ] }
/v1/resolve/team takes { "query", "sport"? } → { status, best_match: { team_id, confidence }, alternatives }. /v1/resolve/league takes { "query" } → the same shape with league_id.
POST/v1/convert/odds
$ curl -X POST https://api.infersports.dev/v1/convert/odds \ -H "Authorization: Bearer isk_dev_standard_key" \ -H "Content-Type: application/json" \ -d '{"value": 2.41, "from": "decimal", "to": ["american","hk","malay","probability"]}' { "decimal": 2.41, "american": 141, "hk": 1.41, "malay": -0.709, "probability": 0.4149 }
Only the source format plus requested to targets are returned. Pass an optional bookmaker key (e.g. "crown") to apply that book's Malay rounding.
POST/v1/convert/handicap
$ curl -X POST https://api.infersports.dev/v1/convert/handicap \ -H "Authorization: Bearer isk_dev_standard_key" \ -H "Content-Type: application/json" \ -d '{"line": -0.75}' { "handicap_raw": "-0.75", "handicap_decimal": -0.75, "handicap_components": [-0.5, -1.0], "explanation": "Home gives 0.75 (split -0.5 / -1.0).", "win_conditions": { "full_win": "a >= 0.5", "half_win": "a == 0.25", "push": null, "half_loss": "a == -0.25", "full_loss": "a <= -0.5" } }
MCP tools
Live today A dozen agent-first tools over the same live data, live today: match_info, get_opening_line, compare_lines, get_sharp_line, find_value, find_arbitrage, scan_slate, explain_handicap, list_bookmakers, find_match, get_match_odds, and list_today_matches. Call them as plain REST (POST /v1/mcp/<tool>) or run the MCP server and connect any MCP client. Same key, tiers, and limits as the rest of the API.
Call a tool over REST
compare_lines returns the consensus line, the best price per outcome across books, and de-vigged fair odds with the sharp book they came from.
Fair odds = the sharpest (lowest-overround) book's two-way prices with the overround removed proportionally (overround = Σ(1/decimal) − 1); edge_pct = (book price ÷ fair price − 1) × 100. Fair odds need a tier that includes the sharp book (Standard+); on the Free tier they fall back to the tightest available book.
$ curl -X POST -H "Authorization: Bearer isk_dev_standard_key" \ https://api.infersports.dev/v1/mcp/compare_lines \ -d '{"event_id":"evt_8xQ","market_type":"asian_handicap"}' { "market_type": "asian_handicap", "period": "full_time", "consensus_line": -0.25, "best_prices": [{ "outcome": "home", "bookmaker": "crown", "decimal": 1.97 }], "fair_odds": { "home": 1.95, "away": 1.98 }, "fair_from": "pinnacle", "book_count": 7, "as_of": "2026-06-11T18:30:05Z", "stale": false }
Run the MCP server
A thin client over the same API: pip install "infersports[mcp]", then point it at your key and connect it to Claude Desktop, Cursor, or any MCP host. The host spawns python -m app.mcp and talks to it over stdio.
{ "mcpServers": { "infersports": { "command": "python", "args": ["-m", "app.mcp"], "env": { "INFERSPORTS_API_KEY": "isk_…" } } } }
Without a key the server runs on the Free tier (no Pinnacle). Agents can self-discover the whole API at /llms.txt for the map and /llms-full.txt for every tool with a worked example — both keyless.
Ops
Two endpoints for wiring InferSports into your own monitoring. GET /health is keyless and reports the live snapshot; GET /v1/usage returns per-key request, error, and latency metrics for your key.
{ "status": "ok", "feed_live": true, "as_of": "2026-06-11T18:30:05Z", "as_of_stale": false, "matches": 412, "odds": 28934 }
$ curl -H "Authorization: Bearer isk_…" https://api.infersports.dev/v1/usage { "started_at": "2026-06-11T00:00:00Z", "uptime_s": 66605.0, "total_requests": 12840, "total_errors": 7, "error_rate": 0.0, "per_key": { "isk_…": { "requests": 12840, "errors": 7, "avg_latency_ms": 1.1, "max_latency_ms": 18.0 } }, "per_endpoint": { "/v1/events": { "requests": 9001, "errors": 0, "avg_latency_ms": 0.9 } } }
SDK & curl
Every endpoint is plain HTTP and JSON, so curl, httpx, fetch, or any HTTP client works out of the box. An official Python SDK wraps auth, pagination, and format conversion.
# pip install infersports from infersports import Client client = Client(api_key="isk_…") # your key from the dashboard odds = client.events.odds( "evt_R5fG2pq", bookmakers=["pinnacle"], period="full_time", format="decimal", ) print(odds.as_of, odds.odds[0].prices)
Grab a free key and make your first call in under a minute, or browse the coverage to see what's live right now.