Skip to Content
API ReferenceLive Game State

Live Game State

Aggregated live game state — scores, periods, clocks, possession, and sport-specific situational data — merged across sportsbooks into a single authoritative view per event. One row per live fixture, consensus-selected from the books covering it.

GET /api/v1/gamestate GET /api/v1/gamestate/{sport}

AuthenticationPermalink for this section

Requires API key. Available on the Game State add-on ($79/mo) or the Enterprise tier. Keys without either receive 403 tier_restricted with addon: "game_state" in the error body. Add the add-on from the billing page  on any paid plan (Hobby / Pro / Sharp); Enterprise keys get it included.

Live game state also streams over the gamestate channel on SSE and WebSocket — see Streaming below. (Streaming access requires the WebSocket add-on or Enterprise, in addition to the Game State requirement above.)

Path ParametersPermalink for this section

ParameterTypeDescription
sportstring(optional) Single sport to return. Matches a known Atlas sport (baseball, basketball, football, hockey, soccer, tennis, table_tennis, cricket, esports, snooker, other). Case-insensitive. Unknown sports return an empty data object.

Without the path parameter, returns every sport with live events.

Response EnvelopePermalink for this section

{ "data": { "<sport>": { "<event_id>": { ...event state... } } }, "updated_at": "2026-04-23T23:55:01.234Z" }

Every event state is keyed by its canonical event_id within its sport bucket. updated_at is the server’s time when this response was built — use it to gauge freshness if polling.

Event State FieldsPermalink for this section

Every field is optional except where noted. Presence varies by sport — tennis events carry sets_home / server; hockey events carry power_play; soccer events carry corners_* / yellow_cards_*; etc.

Always presentPermalink for this section

FieldTypeNotes
home_teamstringNormalized home team name
away_teamstringNormalized away team name
sportstringSport bucket (baseball, soccer, …)
leaguestringAtlas league id (e.g. mlb, england_-_premier_league)
home_scoreintegerCurrent home score in the natural unit for the sport (runs, points, goals, sets — tennis uses summed set points)
away_scoreintegerCurrent away score
is_livebooleanAlways true in this endpoint (non-live events aren’t included)
primary_bookstringThe book the merged scores were selected from. Consensus-respecting — higher-quality books win ties
book_countintegerNumber of sportsbooks that contributed live state for this event at merge time

Temporal (most team sports)Permalink for this section

FieldTypeExample
game_periodstring"T5" (MLB top of 5th), "Q3" (NBA Q3), "2H" (soccer 2nd half), "P2" (NHL), "S2" (tennis 2nd set), "FT" (full time)
game_clockstring"49:06" for countup sports (soccer), "5:42" for countdown sports (basketball, hockey)

SituationalPermalink for this section

FieldTypeNotes
possession"home" | "away"Which team has possession (team sports) or is serving (tennis)
server"home" | "away"Tennis only — current server
last_playstringSport-specific last-play description when available
is_timeoutbooleantrue during a timeout

Sport-specificPermalink for this section

FieldSportType
fouls_home, fouls_awaybasketball, soccerinteger
corners_home, corners_awaysoccerinteger
yellow_cards_home, yellow_cards_awaysoccerinteger
red_cards_home, red_cards_awaysoccerinteger
power_playhockey"home" | "away"
sets_home, sets_awaytennisinteger (sets won)
win_prob_home, win_prob_away, win_prob_book, win_prob_at, momentum, momentum_window_s, prematch_winprob_home, prematch_winprob_away, winprob_diptennislive-only win probability + trend — Enterprise keys only; see Win probability (tennis)
hits_home, hits_awaybaseballinteger
errors_home, errors_awaybaseballinteger — fielding errors
home_pitcher, away_pitcherbaseballstring — starting pitcher display name (roster-level, not the pitcher currently on the mound — see in_play.currentPitcher below)
in_playbaseballobject — at-bat state; see Baseball in_play block
wickets_home, wickets_awaycricketinteger
overscricketfloat
batting_teamcricket"home" | "away"

Baseball in_play blockPermalink for this section

Per-at-bat state on live baseball events. Populated whenever the contributing book exposes pitch-level data (currently FanDuel, BetMGM, Betway, theScoreBet, and ProphetX); omitted otherwise. Inner field names are camelCase — they are not snake-cased by the API transform.

"in_play": { "outs": 1, // 0, 1, or 2 "balls": 2, // 0-3 "strikes": 1, // 0-2 "runners": {"first": true, "second": false, "third": true}, "currentPitcher": "Zach Agnos", // pitcher on the mound now "currentBatter": "Tyrone Taylor" // active hitter }
FieldTypeRange / Notes
outsinteger0, 1, or 2. 3 is never emitted — by the time the third out is recorded upstream, the next half-inning has already begun (outs returns to 0 and game_period advances). The third out manifests as a transition between frames, not a steady-state value.
ballsinteger0-3. 4 is a walk; the at-bat ends and the field resets.
strikesinteger0-2. 3 is a strikeout; the at-bat ends and the field resets.
runnersobject{"first": bool, "second": bool, "third": bool} — base-by-base occupancy.
currentPitcherstringDisplay name of the pitcher currently on the mound. Distinct from the roster-level home_pitcher / away_pitcher (starting pitchers).
currentBatterstringDisplay name of the active hitter.

Detecting half-inning transitions. No discrete inning_end event is emitted. To detect the moment a half-inning ends, watch in_play.outs and game_period together across consecutive frames: the transition surfaces as outs dropping (20, or 10 if a frame was skipped) and game_period flipping (e.g. B5T6) — typically in the same frame, occasionally split across two consecutive frames. The frame’s timestamp is your half-inning-end moment.

Win probability (tennis)Permalink for this section

Enterprise only. The win-probability family (win_prob_*, momentum, momentum_window_s, prematch_winprob_*, winprob_dip) is included for Enterprise keys only. Keys with the Game State add-on alone receive every other gamestate field; these are omitted. Contact sales about Enterprise access.

Live win-probability fields on tennis events — tennis only today. The values are computed server-side at serve time from the de-vigged live moneyline (Pinnacle anchors when it prices the match, otherwise a cross-book consensus median), so the same numbers appear on REST, WebSocket, and SSE alike. They are present only on Enterprise keys, only while the event is live, and while a fresh live moneyline exists; omitted otherwise.

{ "home_team": "J. Doe", "away_team": "R. Roe", "sport": "tennis", "league": "atp", "home_score": 9, "away_score": 10, "sets_home": 1, "sets_away": 1, "game_period": "S3", "is_live": true, "server": "home", "win_prob_home": 0.6421, // P(home wins the match) "win_prob_away": 0.3579, // 1 - win_prob_home "win_prob_book": "pinnacle", // where the probability came from "win_prob_at": "2026-07-03T14:05:11Z", // freshest contributing odds time "momentum": 0.0312, // + = swinging toward home "momentum_window_s": 120, "prematch_winprob_home": 0.7104, // prematch baseline, frozen at go-live "prematch_winprob_away": 0.2896, // (present only when captured pre-match) "winprob_dip": 0.0683, // prematch favorite is 6.8pp below their prematch level "primary_book": "draftkings", "book_count": 4 }
FieldTypeNotes
win_prob_homefloatProbability (0–1) that the home side wins the match, de-vigged from the live moneyline. win_prob_home + win_prob_away = 1.
win_prob_awayfloatAway-side complement.
win_prob_bookstringSource of the probability: "pinnacle" (Pinnacle prices the match — preferred), "consensus" (median across two or more contributing books), or a single book id when only one book carries a fresh live line.
win_prob_atstringISO 8601 — timestamp of the freshest odds that fed the number, not the server clock. If it stops advancing, the underlying moneyline has gone quiet; treat the probability as aging.
momentumfloatSmoothed, signed change of win_prob_home over the last momentum_window_s seconds. Positive = the match is swinging toward home, negative = toward away. A display/trend signal, not a prediction — see below.
momentum_window_sintegerLength of the momentum window in seconds (currently 120).
prematch_winprob_homefloatPrematch baseline — the home side’s de-vigged win probability from the prematch moneyline, frozen when the match went live. Oriented home/away exactly like win_prob_home. Present only when a prematch baseline was captured before go-live; when it wasn’t, this field and the two below are omitted.
prematch_winprob_awayfloatAway-side complement of the prematch baseline (1 - prematch_winprob_home).
winprob_dipfloatHow far the prematch favorite currently sits below their own prematch win probability (favorite’s prematch_winprob_* minus their live win_prob_*). Positive = the favorite has dipped below their prematch level; 0 or negative = at or above it.

win_prob is graded continuously against final match outcomes and is sharp (Brier score ≈ 0.02–0.06 in ongoing grading, vs 0.25 for a coin-flip), with a known conservative tilt: strong favorites win slightly more often than the stated probability suggests. momentum describes what already happened to win_prob — it is intended for UI meters and trend arrows. In our grading it carries no measurable predictive signal for where win_prob goes next; do not treat it as predictive or build betting logic on it.

Absence = not computable right now. These fields are only written when the event is live and a fresh live moneyline exists. Pre-match events, non-tennis sports, and matches whose live price has gone stale simply omit them — no null placeholders.

FreshnessPermalink for this section

FieldTypeMeaning
staletrue (omitted otherwise)The primary_book’s livestate key TTL dropped below the aggregator’s freshness threshold (~10s) at merge time. Scores are the last known values; the book has gone quiet. Treat the event’s scores and period as potentially lagging the real game state.
aggregator_staletrue (omitted otherwise)The SharpAPI aggregator itself hasn’t written a new gamestate:{sport} shard for this sport in more than ~30s. Broader signal than stale — indicates a pipeline hiccup, not a single-book issue. If you see this persistently, reach out to support.

Absence = fresh. Both stale and aggregator_stale are only written when truthy, keeping the payload compact. If you don’t see them on an event, the data is considered fresh.

Example RequestsPermalink for this section

# All live events across every sport curl "https://api.sharpapi.io/api/v1/gamestate" \ -H "X-API-Key: YOUR_API_KEY" # One sport curl "https://api.sharpapi.io/api/v1/gamestate/soccer" \ -H "X-API-Key: YOUR_API_KEY"

Example ResponsePermalink for this section

{ "data": { "soccer": { "argentina_-_primera_division_bocajuniors_defensayjusticia_2026-04-23": { "home_team": "Defensa y Justicia", "away_team": "Boca Juniors", "sport": "soccer", "league": "argentina_-_primera_division", "home_score": 0, "away_score": 1, "game_period": "2H", "game_clock": "49:06", "is_live": true, "possession": "away", "corners_home": 1, "corners_away": 0, "fouls_home": 0, "fouls_away": 0, "yellow_cards_home": 0, "yellow_cards_away": 0, "red_cards_home": 0, "red_cards_away": 0, "primary_book": "draftkings", "book_count": 6 }, "chile_-_primera_division_concepcion_palestino_2026-04-24": { "home_team": "Palestino", "away_team": "Concepción", "sport": "soccer", "league": "chile_-_primera_division", "home_score": 0, "away_score": 0, "is_live": true, "primary_book": "unibet", "book_count": 1, "stale": true } } }, "updated_at": "2026-04-23T23:55:01.234Z" }

Cross-Book Merge ModelPermalink for this section

Each event’s fields are merged from every sportsbook’s livestate snapshot via a three-class algorithm:

  • Class A — Scores are picked by consensus: among books at the most advanced period rank, the highest-total score with ≥2 backers wins. A single book reporting an outlier score (common in the first seconds of a score change, or from a misbehaving adapter) is rejected.
  • Class B — Temporal fields (game_period, game_clock) are selected by sport-aware clock direction — countdown vs countup — and period rank.
  • Class C — Situational fields (possession, corners_*, etc.) are priority-filled from a fixed book ranking.

primary_book reports which book the winning scores came from. book_count is the total number of books that contributed any state for the event at merge time.

StreamingPermalink for this section

Every update that the REST endpoint would surface on the next poll also fires a gamestate:update event on the streaming channels:

Both transports open with a gamestate:snapshot carrying the full current live slate — a flat list of event rows, each with its event_id. What follows differs by transport.

Delivery semantics: WebSocket vs SSEPermalink for this section

WebSocketSSE
gamestate:updateIncremental — only the rows that changed since your previous frameFull slate — the complete current live set (matching your filters) re-emitted on every update cycle
RemovalsExplicit gamestate:removed frames carrying a list of event_idsNever sent — an event that ends simply stops appearing in the next gamestate:update (with sport/league filters there is one edge case — see the callout below)
ReconnectResumable: resume=true&from_seq=N replays missed gamestate:update and gamestate:removed frames from the replay buffer (best-effort, ~5-minute TTL-bound window; outrun it and you fall back to a fresh snapshot)Not resumableLast-Event-ID replay covers the odds channel only. A gamestate reconnect always re-bootstraps with a fresh gamestate:snapshot. Gamestate frames carry no SSE id: line, so a plain EventSource on the gamestate channel reconnects without a Last-Event-ID — its connected event carries neither resumed nor fallback_reason. Only a reconnect that does present a Last-Event-ID (e.g. on the all channel, where odds events set the resume cursor, or a hand-set header) is acknowledged with resumed: false and fallback_reason: "channel_unsupported"

Client pattern. On WebSocket, keep a map keyed by event_id and apply deltas: merge gamestate:update rows in, delete gamestate:removed ids. On SSE, build the same keyed map but replace it wholesale on every gamestate:update — anything absent from the new payload is no longer live. Don’t apply SSE payloads as deltas, or ended events will linger in your state forever.

SSE edge case with filters. An unfiltered stream receives an empty {"data": []} update when the last live event ends, but a stream with sport/league filters gets no gamestate:update at all on cycles where zero events match — so the wholesale replace never fires for the final ended event. If heartbeats keep arriving but gamestate:updates stop, treat your slate as possibly empty and expire rows that haven’t been refreshed recently.

Rate Limits & QuotasPermalink for this section

Rate limits follow your base tier, not the add-on — Hobby keys keep their 120 req/min, Pro keep 300, Sharp keep 1,000, Enterprise custom. See Pricing . /gamestate counts the same as other REST calls toward the per-minute quota.

TroubleshootingPermalink for this section

  • 403 tier_restricted with addon: "game_state" — your key doesn’t have the Game State add-on. Add it from Billing  for $79/mo, or upgrade to Enterprise.
  • Empty data object — no live events in scope. This is common between events on smaller sport schedules. Poll again.
  • Events with stale: true — the primary book has gone silent. Scores may lag; consider filtering them out or surfacing a UI indicator.
  • Events with aggregator_stale: true — the SharpAPI aggregator hasn’t refreshed that sport in >30s. If persistent, reach out to support; a brief bump can happen during redeploys.
  • Same match appears twice under different event_ids — known limitation for some regional leagues where sportsbooks use inconsistent league naming. Use (home_team, away_team) as a secondary dedup key on the client until the remaining alias gaps close.
Last updated on