OpenOddsAPI API Get started

Endpoints

Get Odds

Every bookmaker's price for a sport in one call, with per-book freshness.

GET/v1/sports/{sport_key}/oddsAPI key

All events for a sport with each source's prices attached. Events are matched across books, so one entry carries every book's line for the same fixture.

Path parameters

NameTypeRequiredDescription
sport_keystringrequiredOne of ufc, boxing, tennis, nfl, golf.

Query parameters

NameTypeRequiredDescription
marketsstringoptionalmoneyline, spread, total, outright. Comma-separated; defaults to all.
booksstringoptionalComma-separated sources to include, e.g. pinnacle,fanduel.
exclude_booksstringoptionalSources to leave out, e.g. kalshi,polymarket.
min_booksintegeroptionalOnly events priced by at least this many sources.
min_limitnumberoptionalOnly outcomes with a published max stake at or above this.
unknown_limitstringoptionalWhat to do with outcomes whose source publishes no limit: exclude (default) or include.
min_liquiditynumberoptionalOnly outcomes with at least this much depth behind them.
commence_beforestringoptionalISO timestamp, or a relative window like 24h / 3d.
commence_afterstringoptionalSame formats as commence_before.
fresh_onlybooleanoptionalDrop any source currently flagged stale.
includestringoptionalComputed extras: best, novig, arb. Comma-separated.
arb_onlybooleanoptionalWith include=arb, return only events where an arbitrage exists.
odds_formatstringoptionalamerican (default), decimal or probability.
sortstringoptionalcommence_time, books or arb.
limitintegeroptionalPage size. total_matching reports the unpaged count.
offsetintegeroptionalPagination offset.
Response
JSON
{
  "sport": "ufc",
  "title": "UFC",
  "generated_at": "2026-08-09T23:21:37.714Z",
  "sources": [
    {
      "book": "bovada",
      "name": "Bovada",
      "last_update": "2026-08-09T23:21:34.849Z",
      "stale": false,
      "latency_ms": 39
    },
    {
      "book": "pinnacle",
      "name": "Pinnacle",
      "last_update": "2026-08-09T23:21:24.662Z",
      "stale": false,
      "latency_ms": 76
    },
    {
      "book": "unibet",
      "name": "Unibet",
      "last_update": "2026-08-09T23:21:22.444Z",
      "stale": false,
      "latency_ms": 157
    }
  ],
  "bout_count": 29,
  "bouts": [
    {
      "home": "Jeremiah Wells",
      "away": "Myktybek Orolbai",
      "commence_time": "2026-08-15T21:30:00.000Z",
      "books": [
        {
          "book": "bovada",
          "last_update": "2026-08-09T23:21:34.849Z",
          "outcomes": [
            {
              "name": "Jeremiah Wells",
              "price": 500
            },
            {
              "name": "Myktybek Orolbai",
              "price": -700
            }
          ]
        },
        {
          "book": "pinnacle",
          "last_update": "2026-08-09T23:21:24.662Z",
          "outcomes": [
            {
              "name": "Myktybek Orolbai",
              "price": -650
            },
            {
              "name": "Jeremiah Wells",
              "price": 482
            }
          ]
        },
        {
          "book": "unibet",
          "last_update": "2026-08-09T23:21:22.444Z",
          "outcomes": [
            {
              "name": "Jeremiah Wells",
              "price": 430
            },
            {
              "name": "Myktybek Orolbai",
              "price": -625
            }
          ]
        }
      ]
    }
  ]
}

Market keys

Every outcome carries a market field. Filter with ?markets=.

KeyShapeSportsSources
moneylineTwo outcomes, one per sideUFC, boxing, tennis, NFLAll
spreadHandicap with a point per sideNFL, tennisPinnacle, Bovada
totalOver / Under with a shared pointNFL, tennisPinnacle, Bovada
outrightOne outcome per competitor in the fieldGolfBovada, Pinnacle, Kambi, FanDuel
NOTEMain lines only. Alternate spreads and totals multiply the payload several times over and will arrive behind their own parameter.

Filters that return nothing

A filter that empties the response explains itself. min_limit excludes outcomes whose source publishes no maximum stake — right for arbitrage, surprising otherwise — so the response carries filter_notes naming which sources publish limits, and filter_report counting what was dropped. Pass unknown_limit=include to keep them.

JSON
{
  "bout_count": 32,
  "filter_notes": [
    "204 outcome(s) excluded because their source does not publish a max stake. Sources that do: pinnacle. Pass unknown_limit=include to keep them."
  ],
  "filter_report": {
    "unknown_limit_outcomes": 204,
    "below_limit_outcomes": 0,
    "unknown_handling": "exclude",
    "limit_publishers": ["pinnacle"]
  }
}

Tradeability

A price you cannot actually take is worse than no price. Where a source publishes it, each outcome carries bid and ask alongside price, plus limit (maximum stake) and liquidity (depth behind the quote). Prediction-market prices are the ask, not the mid — the mid is carried separately as mid.

FieldMeaningSources that publish it
priceThe price you would take right nowAll
bid / askBoth sides of the bookKalshi, Polymarket
midMidpoint, for reference onlyPolymarket
limitMaximum stake the book will acceptPinnacle
liquidityDepth available at that priceKalshi, Polymarket, SX Bet
book_nameThe competitor label the source printedAll
WARNINGEvery price is pre-fee. Each source in sources carries a fees model — Kalshi charges per contract, SX Bet takes commission on winnings, bookmakers price margin into the line. include=arb applies these for you.

Computed extras

Pass include= to have the API do the arithmetic that every consumer would otherwise write themselves.

ValueAdds
bestBest available price per outcome and the source offering it
novigFair probabilities with the margin removed, from the tightest book
arbMargin, margin after fees, stake split per leg, and the ceiling imposed by limits and depth
JSON
{
  "arbitrage": {
    "is_arbitrage": true,
    "survives_fees": true,
    "margin": 1.958,
    "margin_after_fees": 0.387,
    "max_stake": 1394.79,
    "stake_ceiling_unknown": false,
    "legs": [
      { "name": "Jonathan Kunneman", "book": "polymarket", "price": -178,
        "stake": 653.07, "liquidity": 2830.18, "max_total_stake": 4333.63,
        "fee_model": "spread_only" },
      { "name": "Joseph Kropschot", "book": "kalshi", "price": 194,
        "stake": 346.93, "liquidity": 483.89, "max_total_stake": 1394.79,
        "fee_model": "per_contract" }
    ]
  }
}

max_stake is set by the binding leg, so the same 1.96% edge can be worth $1,394 or $26,000 depending on which side runs out first. stake_ceiling_unknown is true when a source publishes neither a limit nor depth — unknown, which is not the same as unlimited.

Freshness

Each entry in sources carries last_update, stale and latency_ms. A source is marked stale once we have not had a good read from it in 90 seconds, and stale sources are excluded from the merged prices.

WARNINGShow staleness in your interface rather than hiding it. A price displayed as live when it is minutes old is the worst failure mode in this category.

Caching

Responses carry an ETag and Cache-Control: max-age=2. Send If-None-Match and you will get a 304 when nothing has changed, which does not count against your quota differently but does save you the payload.

OpenOddsAPI API — Documentation v1Examples use illustrative data