API REFERENCE · v1

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.

Free · keylessStandard · isk_dev_standard_key
bash — authenticated request
$ 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.

429 — rate_limit_exceeded
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.

404 — not_found
{
  "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"
  }
}
400invalid_requestnoMalformed query, bad id, or unknown parameter value.
401unauthorizednoMissing or invalid API key.
403forbidden_for_tiernoResource gated to a higher tier (e.g. Pinnacle on Free).
404not_foundnoNo event / entity for that id.
429rate_limit_exceededyesToken bucket drained for your key. Honor Retry-After.
500internalnoUnexpected server error.
503upstream_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.

GET /v1/leagues?sport=football&limit=2
{
  "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.

GET/v1/sportsLists supported sports: football and basketball.
GET/v1/bookmakersLists all bookmakers, including Pinnacle, with class.
GET/v1/leaguesLists leagues currently in the live snapshot. Filter with ?sport=.
GET/v1/teamsSearches teams by fuzzy name. Example: ?q=man%20city&sport=football.

GET/v1/sports

GET /v1/sports
{
  "data": [
    { "id": "sp_X4cN7m", "key": "football", "name": "Football" },
    { "id": "sp_9hT2bW", "key": "basketball", "name": "Basketball" }
  ]
}

GET/v1/bookmakers

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.

GET/v1/eventsLists and filters fixtures.
GET/v1/events/liveLists only fixtures currently in play.
GET/v1/events/{id}Returns a single event by id.
GET/v1/events/{id}/stateReturns live score, cards, and match clock.
GET/v1/events/{id}/oddsReturns all quotes for the event, in any format.
GET/v1/events/{id}/odds/{market_type}Returns quotes for one market type.

GET/v1/events

sportstringfootball | basketball
leaguestringExternal league id, e.g. lg_QPw2L8
datestringUTC date filter, YYYY-MM-DD
statusstringscheduled | live | finished
limitintPage size
cursorstringOpaque pagination cursor
GET /v1/events?sport=football&status=scheduled&limit=1
{
  "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

GET /v1/events/evt_R5fG2pq/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_time
formatstringdecimal (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.

GET /v1/events/evt_R5fG2pq/odds?bookmakers=pinnacle&format=decimal
{
  "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.

POST/v1/resolve/eventResolves a fuzzy fixture name to an event id.
POST/v1/resolve/teamResolves a fuzzy team name to a team id.
POST/v1/resolve/leagueResolves a fuzzy league name to a league id.
POST/v1/convert/oddsConverts one price across formats.
POST/v1/convert/handicapSplits a handicap line and explains its win conditions.

POST/v1/resolve/event

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

bash — 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

bash — 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.

bash — compare_lines
$ 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.

claude_desktop_config.json
{ "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.

GET /health
{
  "status": "ok",
  "feed_live": true,
  "as_of": "2026-06-11T18:30:05Z",
  "as_of_stale": false,
  "matches": 412,
  "odds": 28934
}
bash — GET /v1/usage
$ 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.

python — official SDK
# 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.