Skip to main content
Live and upcoming sports events, per-event markets, cross-platform matching, brackets, and reference data. Reach for this page when you are building a scoreboard, a game page, a parlay builder, or a cross-venue price comparison. Full parameter reference and a live tester: API Reference.

Base URL

All paths on this page are relative to that host.

Authentication

Every sports endpoint on this page is public and requires no credentials. Valid API-key or session credentials are accepted when supplied, but they are optional. Rate limits. /sports/tournament-bracket is 10 requests/minute (the heavy group) and /sports/game-markets is 60/minute (the sports group). Every other route on this page has no explicit decorator and falls under the service-wide default of 100/minute. Anonymous limits are keyed by client IP; authenticated requests can use the credential identity. Group defaults are adjustable at runtime by an operator.

Live events

Currently-active games with real-time scores, an additive urgency score, and cross-provider matched markets. Call it to drive a live scoreboard. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=10, s-maxage=10. Predict.fun fixtures are included only when the provider indexing gate lists predictfun as enabled. The gate is re-checked on every request (including cache hits). If the gate lookup fails, Predict.fun is excluded (fail closed) — Polymarket/Kalshi live rows still return.

Request

Each event includes sportFamily, the canonical category id from /sports/catalog (for example, cricket). The existing sport field remains the source league code (for example, cricbbl). Falls back to a live provider lookup when the per-game market cache is cold, and, when no matched entry carries a Kalshi side, synthesizes one from a team-name lookup.

Response

The response shape is described by the field table below rather than by a JSON example. Every event row also carries gameId, sport, sportFamily, homeTeam, awayTeam, homeScore, awayScore, status, period, elapsed, live, ended, slug, matchingSlug, and marketsCount. events is ordered by urgencyScore descending, then by ts descending.

Soccer and NFL fixture timing

GET /txodds/fixtures/{fixture_id}/timing returns timing evidence for NFL regulation and second-half EPL / senior World Cup fixtures. The OpenAPI reference defines the response fields. usableForLateGame indicates whether the observation supports its named lateGameBasis; it does not indicate that a game is already late. Soccer’s periodRemainingSeconds describes the nominal half clock, excluding unknown added time. NFL also exposes regulationRemainingSeconds. gameRemainingSeconds remains null: neither predicts the final whistle. Stale, missing, or unsupported evidence returns usableForLateGame=false with an unavailableReason. Consumers must check observation age and verify the fixture’s market mapping and settlement scope before using timing.

Timing and late-game strategies

Each event includes a timing object. elapsed remains an opaque, nullable upstream display string. Do not subtract it from a regulation duration or use it as seconds elapsed. A value such as 05:12 does not by itself establish clock direction, period length, or whether the clock is running. Explicit suspended/delayed/cancelled/final statuses take precedence over the broad live flag. Unrecognized statuses stay unknown. FT OT and F/OT indicate completion after overtime, not active overtime. No countdown is extrapolated during breaks, suspension, or silence. urgencyScore remains a candidate-ranking heuristic, not permission to trigger a strategy. ts records when the game state was observed. Depending on the available feed data, it can represent the source observation or the time Kairos received it. Freshness therefore measures the age of our observation, not a guarantee that the upstream score is current. The response can also be cached; neither reading it again nor receiving a WebSocket heartbeat proves that a particular game’s clock has advanced. Predict.fun and cricket schedule rows are inferred from scheduled start/end windows, not a live scoreboard. Their legacy live field can be true, but timing.status is unknown, stale is true, and unavailableReason is no_live_score_feed. The upstream sports documentation lists periods and lifecycle statuses, but does not establish the complete sport-specific elapsed contract needed to enable these remaining-time fields.
Gotcha: matchingMarkets entries are backfilled to {"providers": []} for any event that resolved a matching slug but has no cross-provider data. A present key does not imply a match was found — check providers. When the Predict.fun gate is off, predictfun entries are stripped from every providers list.

Event markets

All markets for a single sports event, resolved by game_id or by slug (which is first resolved to a gameId). Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=10, s-maxage=10.

Request

* At least one should be supplied — both are optional, unvalidated strings at the request layer.
Returns 200 with {"gameId": "<value or \"unknown\">", "markets": []} when nothing resolves — it never 404s.

Response

Array of normalized markets, same shape as events[].primaryMarket on /sports/live-events (id, question, conditionId, tokenId, outcomePrices, outcomes, volumeNum, liquidityNum, acceptingOrders, sportsMarketType, provider, …).

Game markets

The full market catalog for one game, grouped for game pages and parlay builders. The response combines Polymarket’s base event and linked child events (such as corners, halves, and player props) with matched Kalshi, Predict.fun, and Hyperliquid markets when available. Auth: none — public. Rate limit: 60 requests/minute. Cache-Control: public, max-age=30.

Request

Supply exactly one identifier. Sending neither or both returns 400 {"error":"exactly one of game_id or slug is required"}. Use the game-level event slug, not an individual market ID. A valid identifier that does not resolve returns 200: team and sport metadata are null, and sections with no matched supplemental markets are empty.

Response

Gotcha: using the YES position ID for a no leg buys the opposite outcome. Always pick the position ID that matches legs[].side.
comboEligible and both position-ID fields can be absent when the combo catalog is unavailable. Absence means eligibility is unknown, not false. Supplemental venue legs explicitly return comboEligible: false because atomic combos — single positions built from several legs — are Polymarket-only. Supplemental venue enrichment is best-effort: unavailable supplemental data does not remove the Polymarket catalog.
Gotcha: an open market without a usable quote remains in the response with price: 0. Treat that as unavailable pricing, not an executable 0% price.

Matching markets

Cross-platform price matches for a set of market slugs — the same market on different providers, for side-by-side comparison. Entries may include Polymarket, Kalshi, Predict.fun, and Hyperliquid. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=30, s-maxage=60, or max-age=3 / max-age=5 when live=true.

Request

No auth headers — this route is public.
Gotcha: live is compared as a raw string, not parsed as a boolean — anything other than "true" is treated as false. live=1 and live=TRUE do nothing.
Slugs with no cross-venue match are silently omitted from the response object rather than returned as null. Event slugs and per-outcome market slugs are different namespaces; include all keys carried by a discovery card. When live=true, a correction is applied if a stale Kalshi price appears to be on the wrong side of a binary flip.

Response

Returns {} if slugs parses to no non-empty entries.
Top sports markets available on both Polymarket and Kalshi, ranked by live Kalshi volume. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=30, s-maxage=60.

Request

No auth headers — this route is public. Scans the matching cache for games with both a Polymarket and a Kalshi side, drops anything dated before yesterday (UTC), enriches Kalshi volume from the live discover cache (volume_1h falling back to total), and sorts descending.

Response

Returns {"matches": [], "count": 0} immediately if the matching cache is empty.

Upcoming events

The response also includes startingEvents: fixtures with a known kickoff in the past ten minutes, retained while a live score may still be pending. These carry awaitingLive: true; they are not confirmed live games and contain no scores. Scheduled Polymarket fixtures with a known kickoff carry startingEligible: true so a cached schedule can make the same transition. Consumers should replace a starting fixture when its venue market ID appears in the live feed, and expire it after ten minutes. Not-yet-live sports events within a lookahead window, with matched cross-provider markets attached. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=30, s-maxage=60, or public, max-age=10, s-maxage=30 when a stale cached body is served while it refreshes in the background.

Request

With none of category/league/series supplied, the query covers every league in the sports catalog, including UEFA Europa League (uel). Served from a shared cache: the window is rebuilt in the background about once a minute. The default windows (windowHours=720&limitPerSeries=200 and windowHours=168&limitPerSeries=12, every league) are kept warm and a copy is never served more than two hours old; any other window is served for at most ten minutes and kept warm only while it is being requested. The Age header gives the seconds since it was built. Only a request with no copy at all waits for a build — returning 502 {"detail": "All upstream fetches failed"} if every upstream fetch fails, or 504 if the build runs out of time. A partial upstream failure still returns 200 with whatever resolved. Loading a few days at a time: startFrom/startTo cut one slice of the window by startTime, so a schedule can fetch its next days as it scrolls. Adjacent slices never overlap. startingEvents appear only in the slice that covers the current time, and matchingMarkets holds only the entries the returned events reference.

Response

The response shape is described by the field table below rather than by a JSON example. Every event row also carries eventId, title, sport, sportFamily, league, teamA, teamB, image, primaryMarket, marketsCount, and eventSlug. sportFamily is the canonical category id from /sports/catalog and is equal to sport on this route. events is sorted by startTime ascending. Returns 200 with {"events": [], "startingEvents": [], "matchingMarkets": {}} when the filters resolve to no leagues (for example series=,,), and on a genuine no-data result.

Metadata

Every synced team and league — logos, abbreviations, aliases, colors — for frontend lookups. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=3600, s-maxage=3600.

Request

No parameters.
Served from a shared cache refreshed every 15 minutes and never more than 24 hours old (teams and leagues sync every 12 hours); the Age header gives the seconds since the body was built.

Response

Catalog

The structured sports catalog — categories and their leagues, enriched with resolved Polymarket series IDs. Use it to populate league pickers and to discover the league values the other routes accept. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=3600, s-maxage=3600.

Request

No parameters.

Response

Gotcha: tagId is a hardcoded constant (100639) shared by every league — it is not league-specific, so it cannot be used to tell leagues apart.
seriesId is resolved via a cached lookup and may be null if unresolved. The soccer category includes UEFA Europa League as { "slug": "uel", "label": "UEFA Europa League" }.

Kalshi live games

Every currently-live Kalshi sports milestone (hockey, basketball, baseball, football/UFL, soccer, esports), with moneyline/spread/total market blocks attached. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=240.

Request

No parameters.
Building each game’s market blocks from Kalshi’s authenticated API is comparatively slow on a cold cache — caching keeps steady-state requests fast.

Response

Kalshi filters

The Kalshi sports taxonomy — Sport → Competition → Scope — backing category-filter UI. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=21600 (6 hours, in-process and Redis).

Request

No parameters.
No auth headers — this route is public.

Response

The upstream Kalshi payload is passed through verbatim, with no normalization:
A response missing filters_by_sports, or a non-200 from upstream, is treated as a fetch failure and surfaces as a 500 on a cold cache.

Futures

Sports futures — championship and award outright markets — grouped by event, across Polymarket, Predict.fun, and Hyperliquid. Auth: none — public. Rate limit: 100/minute. Cache-Control: public, max-age=60, s-maxage=300.

Request

With no parameters the whole list is returned, as before.
Stale-while-revalidate: any cached body is served immediately, with a single background rebuild kicked off once it is past the soft TTL. Only a cold cache builds synchronously, and concurrent cold builds share one in-flight build.

Response

outcomes is sorted favorites-first with unpriced contenders last, and is capped per event; events are returned richest-first by contender count. Per-game props and game lines are filtered out, and the unrecognised-sport bucket is additionally capped.

Tournament bracket

A structured tournament bracket for a league, enriched with live Polymarket/Kalshi/Predict.fun prices per tie. Auth: none — public. Rate limit: 10 requests/minute. Cache-Control: public, max-age=60, stale-while-revalidate=3600.

Request

An unrecognised league returns 200, not 404 — the body is {"leftRounds": [], "rightRounds": [], "final": null, "winner": null}. An unrecognised format is not rejected either; anything other than left-to-right renders the symmetric layout, and only the literal symmetric selects two-legged tie merging. format=left-to-right returns all rounds in leftRounds with rightRounds empty. format=groups-then-knockout adds a groups array with standings tables. thirdPlace is present only for football-data competitions that have a third-place fixture. league and format are both part of the cache key, so a novel pair pays a full synchronous build.

Response

Gotcha: winner is always null — this endpoint never resolves a champion. Read the final tie from final instead.

Errors

Errors are returned as { "detail": "<message>" }, with two exceptions: 429 uses an error key, and 422 uses FastAPI’s standard validation body. The Code column below holds the exact message string the service returns; — means the response carries no fixed string.

No route on this page returns 404

Valid but unresolvable input degrades to a 200. Do not branch on 404 — branch on the empty body:
  • /sports/event-markets → {"gameId": "<value or \"unknown\">", "markets": []}
  • /sports/matching-markets → {} when none of the requested slugs have a cross-venue match
  • /sports/trending-matched → {"matches": [], "count": 0}
  • /sports/upcoming-events → {"events": [], "startingEvents": [], "matchingMarkets": {}}
  • /sports/tournament-bracket → {"leftRounds": [], "rightRounds": [], "final": null, "winner": null} for an unknown league
  • /sports/game-markets → the catalog with null team/sport metadata and empty sections when the Polymarket event is missing
Best-effort enrichment never fails a request: cross-venue supplementation on /sports/game-markets and /sports/upcoming-events, the Predict.fun gate on /sports/live-events, combo-eligibility annotation, and background refreshes all fail open and degrade the payload rather than erroring. GET /sports/poly-kalshi-pairings and GET /sports/combo-markets are documented on the matched markets page.