US Open Odds API: 155 Books a Match Across 120 First-Round Ties

US Open Odds API - OddsPapi API Blog
How To Guides September 1, 2026

You want US Open odds in Python. You pull the tournament, get 253 fixtures back, and half of them have participants called R64P49. Then you check the sharp price and Pinnacle has quoted the first day of the draw and nothing else.

Neither of those is a broken feed. A Grand Slam draw is published as a full bracket the moment the draw is made, and the empty rows are the slots nobody has won yet. Pinnacle opens the board one day at a time, and it moves fast enough that two censuses three hours apart disagree. This post shows you the queries, the numbers behind both behaviours, and the parser guards that stop them from wrecking your board.

Everything below comes off the live OddsPapi free tier on 24 August 2026. The board was measured twice that day: once at about 09:00 UTC and again at about 11:45 UTC. Where the two readings differ, both are printed with their times.

What the US Open board actually looks like

Two tournament IDs carry the singles draws. Men’s Singles is 2591, Women’s Singles is 2595.

The row called plain “US Open” is 2589 and it holds zero fixtures. So do the juniors, wheelchairs and legends rows. If you search the tournament list for the obvious name and take the first hit, you get an empty draw and conclude the Slam is not covered.

Measure Men’s Singles (2591) Women’s Singles (2595)
Bracket rows returned 126 127
Rows with odds at 09:00 UTC 64 64
Rows still priced at 11:45 UTC 62 58

Eight first-day matches had started by the second run and dropped their live prices. That leaves 120 matches as the sample for every board-wide figure below.

Board census, 120 first-round matches Value
Books per fixture (median) 155.5
Books per fixture (max) 174
Books per fixture (min) 37
Prices sampled 1,557,204
Prices flagged active: true 765,387 (49.2%)
Distinct market IDs 434
Market families 57
Books ranked on the match winner 145

112 rows have no players in them

Future rounds ship as stubs. The participant names follow the pattern R{round}P{position}, so the second round arrives as R64P49 against R64P50. Both draws carry 56 of these, 112 rows in total, every one of them hasOdds: false. Call /odds on a stub and you get fixture metadata with no bookmakerOdds key at all.

Their dates are stubs too. All 32 second-round rows in each draw carry the same timestamp, all 16 third-round rows carry the next one, and so on down to the final. That is a bracket layout, not an order of play. Do not build a schedule off it.

One more count worth knowing: 141 rows carry two real player names, but only 128 of them had odds at the first census. The other 13 are resolved bracket slots that no book has priced yet.

The finding: Pinnacle opens a Slam draw by draw, and you can watch it happen

At 09:00 UTC, Pinnacle and SBOBet had priced 63 of the 64 first-day matches and none of the 64 second-day matches. Their covered sets were identical, same 63 fixtures, no exceptions.

Re-measured at 11:45 UTC the same morning, Pinnacle had opened 25 of the 32 men’s second-day matches. The women’s second day was still at zero. SBOBet was still at zero across both.

Draw and day Fixtures still priced pinnacle sbobet bet365 kalshi polymarket
Men, 24 Aug (day 1) 30 30 29 28 30 30
Men, 25 Aug (day 2) 32 25 0 31 32 32
Women, 24 Aug (day 1) 26 26 26 26 26 26
Women, 25 Aug (day 2) 32 0 0 31 32 32

Zero to 25 of 32 in under three hours, on one draw, while the other draw stayed empty. That is an open in progress, not a finished coverage map. A census taken at one moment describes that moment.

Free historical odds give you the open time for nothing, and that is the number to anchor on. First-snapshot timestamps on five first-day matches put Pinnacle at a median of T-6.2 hours, range 6.0 to 7.4. Query the same endpoint at 09:00 for five second-day matches and Pinnacle returns nothing, because at that point it had not quoted them. Three hours later most of the men’s second day had a price.

Bookmaker Opened, day-one match Opened, day-two match
pinnacle T-6.2h not yet quoted at 09:00 UTC
bet365 T-5.4h T-29.4h
draftkings T-15.8h T-37.3h to T-40.5h

bet365’s numbers are the giveaway. All five day-one matches opened at T-5.4h and all five day-two matches at T-29.4h, matched to the minute, because both sets landed in one sweep at 09:34 UTC. bet365 posts the whole two-day board in a single batch. Pinnacle walks it match by match, and it walks a whole draw at a time.

Compare that to football, where Pinnacle opens a league fixture a median of 47 days out. On a Slam you get about a quarter of a day. A cron job that polls at midnight collects a board with no sharp price on it, and a coverage audit run 48 hours ahead reports that Pinnacle does not cover the US Open. Poll the day’s matches in the morning, then poll again.

Old way versus OddsPapi

Job Scraping or a single-book API OddsPapi
Get the draw Parse the tournament site’s bracket HTML /fixtures with tournamentId
Sharp benchmark Pinnacle has no public API pinnacle slug in the same payload
Find the open time Poll and hope you started early enough /historical-odds, free tier
Set and game handicaps Rebuild from scratch per book Native market families with a handicap field
Book count One per integration you write 155 at the median on a first-round match, 350+ in the catalogue

The active-rate inversion: half the wide board is posted and suspended

The first census read 19 bookmakers, because the API key it ran under could only see 19 of the 352 in the catalogue. Across those 19 books, 63,840 of 67,752 prices were active: true, a rate of 94.2%.

The same board read with an uncapped key gives 1,557,204 prices across 145 ranked books, and 49.2% of them are active. The wide board is half dead.

The two numbers are not in conflict. They measure different populations. The books you already know are the ones that keep their prices live, so a sample built from them reports a high active rate and hides the shape of everything behind it. Add 126 more books and half of what arrives is a line that was posted and then suspended.

This changes what a book count means in your code. A fixture with 174 books on it does not give you 174 usable quotes. Test active on the price object before a book enters a consensus, a best-price scan or a coverage report, and expect to throw away about half of what the payload hands you.

Step 1: find the draw

Auth is a query parameter. Every call takes ?apiKey=, never a header. The free tier limits per endpoint and returns a real HTTP 429 with a retryMs you should honour.

import requests, time, re

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oddspapi.io/v4"
SPORT_TENNIS = 12

def call(path, **params):
    """Single request with 429 back-off. The free tier limits per endpoint."""
    params["apiKey"] = API_KEY
    for _ in range(5):
        r = requests.get(f"{BASE_URL}{path}", params=params, timeout=30)
        if r.status_code == 429:
            time.sleep(r.json()["error"].get("retryMs", 1500) / 1000 + 0.3)
            continue
        if r.status_code == 404:      # empty window, not an error
            return []
        r.raise_for_status()
        return r.json()
    r.raise_for_status()

tours = call("/tournaments", sportId=SPORT_TENNIS)
for t in tours:
    if "us open" in t["tournamentName"].lower():
        print(f"{t['tournamentId']:>6}  {t['tournamentName']:<34} "
              f"{t['categoryName']:<18} future={t['futureFixtures']}")
  2589  US Open                            ATP                future=0
  2591  US Open Men Singles                ATP                future=127
  2593  US Open Men Doubles                ATP                future=0
  2595  US Open Women Singles              WTA                future=127
  2597  US Open Women Doubles              WTA                future=25
  2599  US Open Mixed Doubles              ATP                future=17

Twenty-one rows match “US Open” and four of them carry fixtures. Read futureFixtures before you pick one. The same trick works for the other Slams: filter the tennis tournament list by name, then read the counts.

Step 2: split the bracket from the board

tournamentId is undocumented on /fixtures but it works, and it cuts the payload by roughly 20x. Note the date window rule: to is a midnight-UTC instant, so set it to the day after the last day you want.

PLACEHOLDER = re.compile(r"^R\d+P\d+$")

def is_playable(f):
    return not (PLACEHOLDER.match(f["participant1Name"] or "")
                or PLACEHOLDER.match(f["participant2Name"] or ""))

draw = call("/fixtures", sportId=SPORT_TENNIS, tournamentId=2591,
            **{"from": "2026-08-24", "to": "2026-09-03"})

playable = [f for f in draw if is_playable(f) and f["hasOdds"]]
bracket  = [f for f in draw if not is_playable(f)]

print(f"rows returned : {len(draw)}")
print(f"real matches  : {len(playable)}")
print(f"bracket stubs : {len(bracket)}  e.g. "
      f"{bracket[0]['participant1Name']} v {bracket[0]['participant2Name']}")
rows returned : 96
real matches  : 64
bracket stubs : 32  e.g. R64P49 v R64P50

Do not trust externalProviders as a coverage signal

Every one of the 253 bracket rows carries all ten provider IDs: betradarId, opticoddsId, sofascoreId, flashscoreId, betgeniusId, lsportsId, mollybetId, txoddsId, oddinId and pinnacleId. Complete on all 253, stubs included.

That includes a pinnacleId on the second-day matches Pinnacle had not quoted at 09:00, and on rows whose players do not exist yet. The ID is a mapping key for joining feeds, which makes it useful for fixture mapping. It says nothing about whether a book has posted a price.

Step 3: pull one board and dedupe it

Match winner is market 121. Outcome 121 is participant 1 and 122 is participant 2, and the catalogue names them literally “1” and “2”, so carry the player names over from /fixtures by joining on fixtureId.

Three guards belong in this function. Skip books flagged suspended. Require both legs before you compute a margin. Test active on the price object, one level below the outcome.

WINNER, P1, P2 = "121", "121", "122"

def winner_quotes(fixture_id):
    payload = call("/odds", fixtureId=fixture_id)
    quotes = {}
    for slug, book in payload.get("bookmakerOdds", {}).items():
        if book.get("suspended"):
            continue
        market = book["markets"].get(WINNER)
        if not market:
            continue
        legs = {}
        for oid in (P1, P2):
            outcome = market["outcomes"].get(oid)
            if not outcome:
                continue
            price = outcome["players"].get("0")
            if price and price["active"]:
                legs[oid] = price["price"]
        if len(legs) == 2:                       # never de-vig a half quote
            quotes[slug] = (legs[P1], legs[P2])
    return quotes

fixture = playable[0]
q = winner_quotes(fixture["fixtureId"])

seen = {}
for slug, tup in q.items():
    seen.setdefault(tup, []).append(slug)

print(f"{fixture['participant1Name']} v {fixture['participant2Name']}")
print(f"{len(q)} slugs -> {len(seen)} independent quotes")
for tup, slugs in sorted(seen.items(), key=lambda kv: 1/kv[0][0] + 1/kv[0][1]):
    a, b = tup
    print(f"  {'/'.join(sorted(slugs)):<45} {a:>6.3f} {b:>6.3f}"
          f"   margin {100*(1/a+1/b-1):5.2f}%")

The output below is the 09:00 run, made with a key capped at 19 readable books. The code path is the same one that produces the full board, so it is kept as a readable worked example. The board-wide numbers underneath it come from the uncapped 11:45 run.

Lajal, Mark v Lee, Jordan
15 slugs -> 10 independent quotes
  kalshi/polymarket                              1.124  8.333   margin  0.97%
  draftkings                                     1.119  6.510   margin  4.73%
  bet365                                         1.100  7.000   margin  5.19%
  pinnacle                                       1.090  7.200   margin  5.63%
  fanduel                                        1.080  7.600   margin  5.75%
  pointsbet.com.au                               1.090  7.000   margin  6.03%
  sbobet                                         1.100  6.550   margin  6.18%
  hardrockbet                                    1.091  6.750   margin  6.47%
  ballybet/betparx/betrivers/fourwinds           1.090  6.750   margin  6.56%
  betmgm/borgata                                 1.090  6.500   margin  7.13%

Run the same loop across the full board and the collapse is 59.1%: 16,490 slug-quotes become 6,746 independent ones across 120 matches. Three of every five bookmaker rows on a Grand Slam board are a duplicate feed.

Group Slugs Matches with an identical price
Unibet family: unibet, unibet.be, unibet.com.au, unibet.dk, unibet.ie, unibet.nl, unibet.se, atg.se, ballybet, betcity.nl, betmgm.co.uk, betparx, betplay, betrivers, betuk, bingoal.be, casumo, expekt.se, fourwinds, grosvenor, jacks.nl, kto, leovegas, leovegas.es, leovegas.it, paf, paf.es, prolineplus, scooore.be, svenskaspel, tabtouch, virginbet 32 64 of 120
1xbet, 22bet 2 107
betfury, blaze, gamdom, megadice, rainbet 5 74
napoleonsports.be, superbet.bet.br, superbet.ro, superbet.rs 4 73
888sport.it, rushbet.co 2 69
netbet, netbet.co.uk 2 66
betway, betway.de, betway.es 3 59
888sport, 888sport.de, 888sport.dk, 888sport.es, 888sport.ro, mrgreen, mrgreen.dk 7 58

The small groups from the capped sample still hold. betmgm and borgata matched on 108 matches, and ballybet, betparx, betrivers and fourwinds matched on 102, though those four now show up inside the much larger Unibet block. /bookmakers reports cloneOf: null for all of them. Kalshi and Polymarket also landed on the same tuple 14 times, which is coincidence at a round number rather than a shared feed. Dedupe on the price tuple, per fixture, and never off a static list.

Book margins across 120 matches

145 books quoted the match winner on at least 60 of the 120 matches. Here is the top of that ranking by median margin, plus where the familiar US names land.

Rank Bookmaker Median winner margin
1 kalshi 1.00%
2 polymarket.us 1.00%
3 polymarket 1.01%
4 betfair-ex 1.30%
5 prophetx 2.04%
6 duel 2.05%
7 novig.us 2.48%
8 sx.bet 2.50%
9 dafabet 4.00%
10 betpawa.cm 4.02%
11 inbet 4.34%
12 pinnacle 4.69%
13 betway 4.76%
14 betway.de 4.76%
15 betway.es 4.76%
16 draftkings 4.83%
25 fanduel 5.59%
99 bet365 7.24%

Pinnacle is rank 12 of 145. Eleven books beat it and eight of those are exchanges or prediction markets, so three ordinary sportsbooks price the winner tighter than the sharp does. On a 19-book sample Pinnacle looked like third place behind Kalshi and Polymarket. Widen the board and it drops nine places. The lesson is about the sample, not about Pinnacle: a ranking built from the books you already integrated tells you where you sit among your own books.

bet365’s count needs a footnote. It quotes 115 of the 120 matches, and only 100 of those carry a usable two-sided active winner. On 14 it ships both outcomes with active: false, and on one it omits the market. The prices are still sitting there in the payload. Skip the active check and you feed a dead bet365 line into your consensus.

De-vigging Pinnacle on the worked fixture from step 3 gives you the reference number. 1.090 and 7.200 sum to 105.63%, and proportional de-vig returns 86.85% and 13.15%, so fair prices of 1.151 and 7.605. Use it as a fair-price anchor, not as a signal to bet: see the three de-vig methods for where proportional breaks down on long shots.

The negative result: tennis has no cheap market

On football, a sharp charges you more on corners than on the match result, and SBOBet runs a 10% three-way next to a 3% Asian handicap. Both patterns die on tennis.

Here is each book’s tightest rung in each family, median across the 128 matches of the 09:00 census. These are each book’s own prices, so the figures stand whatever else is on the board.

Market family pinnacle sbobet draftkings hardrockbet
Winner 4.63% 6.00% 4.82% 6.29%
Game Handicap 4.78% 6.11% 8.86% 7.42%
Set Handicap 4.74% 7.03% 6.59% 8.33%
Total Games Over Under 4.77% 6.11% 8.77% 6.94%
Total Sets Over Under 4.80% n/a 8.33% 6.83%
First Set Winner 4.89% 5.87% 6.56% 6.97%

Pinnacle sits between 4.63% and 4.89% on all six. A spread of 0.26 percentage points across the whole menu means it sets one margin for the match and applies it everywhere, then moves the fair line underneath. There is no derivative market to go hunting in.

SBOBet is 6.00% on the winner and 6.11% on the game handicap. The handicap discount that shows up on six football competitions is not there. If you carried that rule over from a football handicap study, drop it for tennis.

One flip does show up inside that four-book sample. bet365 is the widest of the four on the match winner and the tightest of them on first-set total games at 3.44%, ahead of Pinnacle’s 5.72%. Rank books per family, not once per sport, and rank them against the board you can actually read.

Step 4: resolve the total-games line without mainLine

mainLine is worse on tennis than on any sport we have measured, and widening the board makes it worse still. Against the consensus total-games line it hits 1,156 times out of 13,403 across 154 ladder books, 8.6%.

Bookmaker mainLine matched consensus
1xbet 0.0%
22bet 0.0%
napoleonsports.be 0.0%
superbet.bet.br 0.0%
superbet.pl 0.0%
superbet.ro 0.0%
superbet.rs 0.0%
winbet.bg 0.0%
inbet 1.7%
polymarket 49.2%
All 154 ladder books 8.6%

Eight books flag the wrong rung every single time across 120 matches. Treat the field as decoration.

What works instead: take each book’s own most balanced rung, then take the mode across books. Median agreement is 82% across the 120 matches, which is enough to settle a line.

from collections import Counter

catalogue = call("/markets", sportId=SPORT_TENNIS)
meta = {str(m["marketId"]): m for m in catalogue}

def total_games_line(fixture_id):
    payload = call("/odds", fixtureId=fixture_id)
    votes = []
    for slug, book in payload.get("bookmakerOdds", {}).items():
        best = None
        for mid, market in book["markets"].items():
            if meta.get(mid, {}).get("marketName") != "Total Games Over Under":
                continue
            legs = [p for o in market["outcomes"].values()
                      for p in o["players"].values() if p["active"]]
            if len(legs) != 2:
                continue
            imbalance = abs(1/legs[0]["price"] - 1/legs[1]["price"])
            if best is None or imbalance < best[0]:
                best = (imbalance, meta[mid]["handicap"])
        if best:
            votes.append(best[1])
    tally = Counter(votes)
    line, agree = tally.most_common(1)[0]
    return line, agree, len(votes), sorted(tally.items())

print(total_games_line(fixture["fixtureId"]))

Output from the same 09:00 capped-key run, so the vote count is 14 books rather than the full board:

(19.5, 8, 14, [(19.5, 8), (20.0, 2), (20.5, 2), (22.5, 1), (23.5, 1)])

Eight of fourteen books land on 19.5. Ladder widths on that match: DraftKings 8 rungs, Pinnacle 6, SBOBet 5, Kalshi 3, bet365 2. Kalshi’s three rungs were 17.5, 22.5 and 27.5, so its “closest” rung sits two and a half games off the real line. Snapping a wide ladder to a consensus you built from narrow ones is how you end up comparing prices for different bets.

Resolve every handicap and total by marketName plus handicap from /markets?sportId=12. There is one market ID per line, so hardcoding an ID pins you to one number and returns empty dicts everywhere else. The board carries 434 distinct market IDs across 57 families, which is what one market ID per rung looks like at scale.

Market family Books quoting it
Winner 173
Total Games Over Under 155
Game Handicap 152
First Set Winner 145
Participant 1 Total Games 132
Second Set Winner 131
Participant 2 Total Games 130
Set Handicap 126
Total Games First Set 110
Total Games Second Set 99

Step 5: check whether the sharp price exists yet

This is the query that saves you. Before you score a match, ask /historical-odds whether Pinnacle has ever quoted it.

def pinnacle_opened_at(fixture_id):
    """Returns the first Pinnacle snapshot, or None if it never priced this match."""
    for _ in range(5):
        r = requests.get(f"{BASE_URL}/historical-odds",
                         params={"apiKey": API_KEY, "fixtureId": fixture_id,
                                 "bookmakers": "pinnacle"}, timeout=60)
        if r.status_code == 429:
            time.sleep(r.json()["error"].get("retryMs", 4500) / 1000 + 0.5)
            continue
        break
    books = r.json().get("bookmakers", {}) if r.status_code == 200 else {}
    snaps = [s["createdAt"]
             for m in books.get("pinnacle", {}).get("markets", {}).values()
             for o in m["outcomes"].values()
             for hist in o["players"].values()
             for s in hist]
    return min(snaps) if snaps else None

for f in (day_one_match, day_two_match):
    opened = pinnacle_opened_at(f["fixtureId"])
    print(f"{f['startTime'][:16]}  {f['participant1Name'][:20]:<20} "
          f"pinnacle opened: {opened or 'NEVER'}")
2026-08-24T15:00  Lajal, Mark          pinnacle opened: 2026-08-24T08:43:01.698Z
2026-08-25T15:00  Antonius, Michael    pinnacle opened: NEVER

That NEVER is a reading at 09:00 UTC, and this is exactly why the function belongs in your pipeline rather than in a one-off audit. By 11:45 the same day, 25 of the 32 men’s second-day matches returned a timestamp from this call. Re-run it, do not cache the answer.

/historical-odds is slower than /odds. Allow roughly 4.5 seconds between calls, take a maximum of three bookmakers per call, and expect no market filter. Filtered to one book a first-round match comes back at about 0.1 MB, so a whole-draw sweep is cheap.

Pinnacle’s limits rank the market families

Only pinnacle, kalshi and polymarket populate limit. Every other book returns null, so null-check before any arithmetic.

Pinnacle’s limit is a capped max win rather than a capped stake, so recover the base with limit if price >= 2 else limit * (price - 1). Median bases on the US Open first round:

Market family Median implied base
Winner $300
Game Handicap $300
Set Handicap / First Set Winner / Total Sets $287
Total Games Over Under $162
Per-player total games, first-set derivatives $125

Three hundred dollars on the headline market of a Grand Slam first round, against $7,500 on an MLB moneyline. Pinnacle takes a fraction as much on early-round tennis, which is worth knowing before you treat its number as a settled fair price. More on reading limits as a confidence signal in the betting limits guide.

Exchange prices need a depth check

Exchanges and prediction markets take the top eight ranks on the winner. Kalshi sits at 1.00% and Polymarket at 1.01%, ahead of every sportsbook on the board. Those margins read as free money until you look at the ladder.

Both ship exchangeMeta with a three-level back ladder where limit = size × cents holds exactly. Score on the thinner side, because that is what caps your position.

Venue Matches Median margin Median thinnest-side stake Max Passes <2% and ≥$500
kalshi 118 1.00% $275.53 $16,368 45 of 118
polymarket 120 1.01% $72.44 $9,196 28 of 120

Kalshi clears the screen on 38% of the board and Polymarket on 23%. Polymarket’s median of $72 tells you most of its quotes are a price with nothing much behind them, and its top end of $9,196 tells you the distribution is long-tailed rather than uniform. Screen on margin and depth together. Neither one alone survives contact with a real stake.

Parser guards, collected

Trap What you see Guard
Bracket stubs 112 rows named R64P49 Regex ^R\d+P\d+$ on both participant names
Stub dates 32 matches sharing one timestamp Never build a schedule from unplayed rounds
Empty tournament row “US Open” (2589) returns nothing Read futureFixtures before picking an ID
Coverage moves under you 0 to 25 of 32 in three hours Re-poll the sharp benchmark, never cache “not covered”
Suspended prices Half the wide board ships active: false Test active on players["0"], not the outcome
Dead bet365 winner Both legs present, both active: false Require two live legs before a margin
marketActive Records flagged true with every price dead Trust only the price-level active flag
Duplicate feeds 59.1% of slug-quotes collapse Dedupe on the price tuple, per fixture
mainLine 8.6% accurate Balanced rung per book, then mode across books
pinnacleId Present on all 253 rows A mapping key, not a coverage signal
Empty date window HTTP 404 with a JSON body Treat 404 as an empty list

What is not on the board

No player props. Tennis participants are the players, so every price is keyed players["0"] and there is no aces or double-faults menu in the payload. The 57 families are all match, set and game markets: winner, correct score, set and game handicaps, total games and sets, first-set variants, tie breaks, odd/even games.

No outright winner market either. The draw is priced match by match, so a “win the tournament” post is not possible from this feed.

Doubles is thin. Women’s Doubles carries 25 rows and Mixed Doubles 17, against 127 in each singles draw. Men’s Doubles was empty at the time of the census.

Get the board

Every number above came off the free tier. 350+ bookmakers in the catalogue, 155 at the median on a single first-round match, and historical snapshots you can query without paying for a backfill.

Grab a free API key and run the five steps against tomorrow’s order of play. Then read the ATP and WTA tour guide for the weeks between Slams, and line shopping in Python for turning a 155-book board into one best price.

FAQ

What is the US Open tournament ID in the OddsPapi API?

Men’s Singles is 2591 and Women’s Singles is 2595, both under sportId 12. The row named plain “US Open” is 2589 and holds no fixtures. Twenty-one tournament rows match the name, so read futureFixtures before you pick one.

Why do US Open fixtures have participants called R64P49?

Those are bracket stubs for rounds nobody has played yet. The name follows R{round}P{position}. All 112 of them carry hasOdds: false, and their dates are bracket placeholders rather than a real order of play. Filter them with a regex on both participant names.

Does Pinnacle cover the US Open?

Yes, and it opens the board draw by draw during the day. At 09:00 UTC on 24 August 2026 Pinnacle had priced 63 of the 64 first-day matches and none of the second day. By 11:45 the same morning it had opened 25 of the 32 men’s second-day matches, with the women’s second day still at zero. Historical snapshots put its median open at 6.2 hours before a match, so poll the order of play through the morning rather than auditing the whole draw once.

How many bookmakers price a US Open first-round match?

155.5 at the median, 174 at the maximum and 37 at the minimum across 120 first-round matches. Deduping identical price tuples removes 59.1% of the winner quotes: 16,490 slug-quotes across those matches become 6,746 independent ones. About half the prices on the wide board also ship active: false.

Which market ID is the tennis match winner?

Market 121, with outcome 121 for participant 1 and 122 for participant 2. Handicaps and totals use one market ID per line, so resolve them by marketName plus handicap from /markets?sportId=12 instead of hardcoding an ID.

Are there US Open player props in the API?

No. Every price is keyed players["0"] and the 57 market families cover match, set and game markets only. There is also no outright tournament winner market.