{"id":4132,"date":"2026-09-20T10:00:00","date_gmt":"2026-09-20T10:00:00","guid":{"rendered":"https:\/\/oddspapi.io\/blog\/?p=4132"},"modified":"2026-09-14T14:34:24","modified_gmt":"2026-09-14T14:34:24","slug":"odds-api-market-makers-size-at-price","status":"publish","type":"post","link":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/","title":{"rendered":"Odds API for Market Makers: Size Moves Twice as Often as Price"},"content":{"rendered":"<h2>A Price Without a Size Is Not a Quote<\/h2>\n<p>Every odds API will sell you a price. Open the docs of any feed vendor and you get decimal odds, American odds, fractional odds, a timestamp if you are lucky. For a bettor that is enough. For anyone quoting two-sided markets it is half the payload.<\/p>\n<p>A market maker cannot act on 3.571 alone. The question is whether 3.571 stands in front of $133 or $16,453, because one of those is a quote and the other is a rounding error. That number exists in the OddsPapi feed. It is called <code>limit<\/code>, it is normalised across every venue that publishes one, and this post shows you how to pull it, how to read the depth ladder underneath it, and what it does that a price column cannot.<\/p>\n<p>Everything below was measured against the live API on 14 September 2026, on a single Premier League fixture: Leeds United against Newcastle United, <code>id1000001772221262<\/code>, kick-off 19:00Z. One unfiltered <code>\/odds<\/code> call on that fixture returned <strong>286 bookmakers and 62.7 MB<\/strong>. Every number in this post comes out of that call or a second one ten minutes later.<\/p>\n<h2>The Headline: Size Moves Twice as Often as Price<\/h2>\n<p>Two snapshots of the same fixture, ten minutes apart, 14:22Z and 14:32Z. Seventy-five fully sized legs of the 1X2 market appear in both. Here is what changed between them:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>What moved<\/th>\n<th>Legs<\/th>\n<th>Share<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Price changed<\/td>\n<td>14<\/td>\n<td>19%<\/td>\n<\/tr>\n<tr>\n<td>Stake limit changed<\/td>\n<td>27<\/td>\n<td>36%<\/td>\n<\/tr>\n<tr>\n<td><strong>Limit moved while the price held still<\/strong><\/td>\n<td><strong>13<\/strong><\/td>\n<td><strong>17%<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Read that last row again. On thirteen legs the price was byte-identical across both snapshots and the size behind it was not. A feed that ships prices alone reports nothing happened on those thirteen legs. Something very much happened.<\/p>\n<p>The sharpest example is Polymarket on the away participant. At 14:22Z it quoted 3.333 with <strong>9,126<\/strong> behind it. At 14:32Z it quoted 3.333 with <strong>1,022<\/strong> behind it. The price did not move a tick. Eighty-nine percent of the size drained out from under it. ProphetX did the same thing on the same leg, 2,264 down to 695, price unchanged at 3.20.<\/p>\n<p>It runs the other way too. Polymarket&#8217;s draw leg went 5,883 to 16,453 and Kalshi&#8217;s went 9,387 to 16,659, both at a frozen price. Size is the fast variable on an exchange board. Price is the slow one.<\/p>\n<h2>Why Line Shopping Alone Does Not Survive This<\/h2>\n<p>The standard pattern is to pull every book, sort by price and take the best number. On this fixture that pattern hands you Polymarket at 3.333 on the away participant at 14:32Z, which is the best available price, sitting on 1,022 of size. Meanwhile Betfair Exchange offers 3.20 on 4,205. If you need to move 4,000 the worse price is the only real one.<\/p>\n<p>This is where a market-making read diverges from a <a href=\"https:\/\/oddspapi.io\/blog\/line-shopping-python-best-odds\/\">line shopping read<\/a>. Best price is a scalar. Best executable price is a function of the size you need to move.<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>What you need<\/th>\n<th>Building it yourself<\/th>\n<th>OddsPapi<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Stake limit per price<\/td>\n<td>Scrape each book&#8217;s bet slip, different rules per venue<\/td>\n<td><code>limit<\/code>, normalised across every book that publishes one<\/td>\n<\/tr>\n<tr>\n<td>Order book depth<\/td>\n<td>Integrate Betfair, Polymarket, Kalshi and ProphetX APIs separately<\/td>\n<td><code>exchangeMeta<\/code> back and lay ladders in the same payload<\/td>\n<\/tr>\n<tr>\n<td>Sharp reference price<\/td>\n<td>Buy a second feed for Pinnacle<\/td>\n<td>Pinnacle and SBOBET alongside the soft books<\/td>\n<\/tr>\n<tr>\n<td>Quote age you can verify<\/td>\n<td>Trust the vendor&#8217;s marketing number<\/td>\n<td>Two timestamps on every price, subtract them yourself<\/td>\n<\/tr>\n<tr>\n<td>Fill auditing after the fact<\/td>\n<td>Build and run your own tick database<\/td>\n<td>Free <code>\/historical-odds<\/code> price timelines on every tier<\/td>\n<\/tr>\n<tr>\n<td>Push rather than poll<\/td>\n<td>Write a poller, miss every in-play move<\/td>\n<td>WebSocket gateway at <code>wss:\/\/v5.oddspapi.io\/ws<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: Find the Books That Publish Size<\/h2>\n<p>Not every book tells you what it will take. The ones that do are the ones you price against: the sharp books, the exchanges and the prediction markets. Start by splitting the board.<\/p>\n<pre class=\"wp-block-code\"><code>import os, time, requests\n\nAPI_KEY  = os.environ[\"ODDSPAPI_API_KEY\"]\nBASE_URL = \"https:\/\/api.oddspapi.io\/v4\"\n\ndef get_board(fixture_id, bookmakers=None):\n    \"\"\"One \/odds call. Pass bookmakers to keep the payload small.\"\"\"\n    params = {\"apiKey\": API_KEY, \"fixtureId\": fixture_id}\n    if bookmakers:\n        params[\"bookmakers\"] = \",\".join(bookmakers)\n    r = requests.get(f\"{BASE_URL}\/odds\", params=params, timeout=300)\n    if r.status_code == 429:\n        time.sleep(r.json()[\"error\"][\"retryMs\"] \/ 1000 + 0.5)\n        return get_board(fixture_id, bookmakers)\n    r.raise_for_status()\n    return r.json()[\"bookmakerOdds\"]\n\ndef walk_prices(board):\n    \"\"\"bookmakerOdds -> markets -> outcomes -> players -> the price object.\"\"\"\n    for slug, book in board.items():\n        for market_id, market in (book.get(\"markets\") or {}).items():\n            for outcome_id, outcome in (market.get(\"outcomes\") or {}).items():\n                for player_id, price in (outcome.get(\"players\") or {}).items():\n                    yield slug, market_id, outcome_id, price\n\ndef books_with_limits(board):\n    sized, unsized = [], []\n    for slug, book in board.items():\n        has = any(p.get(\"limit\") is not None\n                  for _, _, _, p in walk_prices({slug: book}))\n        (sized if has else unsized).append(slug)\n    return sorted(sized), sorted(unsized)\n\nFIXTURE = \"id1000001772221262\"          # Leeds United v Newcastle United\nboard = get_board(FIXTURE)\nsized, unsized = books_with_limits(board)\nprint(f\"bookmakers on fixture : {len(board)}\")\nprint(f\"publish a stake limit : {len(sized)}\")\nprint(f\"publish no limit      : {len(unsized)}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>bookmakers on fixture : 286\npublish a stake limit : 29\npublish no limit      : 257<\/code><\/pre>\n<p>Twenty-nine books, and the membership is the interesting part. The whole Pinnacle network is in. The whole Betfair network is in. Every exchange and prediction market on the fixture is in: Kalshi, Polymarket, Novig US, Prophet Exchange, SX Bet and 4casters. FanDuel is partial, carrying a limit on 179 of its 851 prices. Bet365 and DraftKings carry none.<\/p>\n<p>That is the correct shape for this field. A retail book does not publish what it will take because its answer depends on who is asking. A sharp book publishes it because the number is the product.<\/p>\n<h2>Step 2: Read Every Ladder With One Function<\/h2>\n<p>Under the top-of-book price, every exchange and prediction market ships a depth ladder in <code>exchangeMeta<\/code>. Different venues built their books differently, so the field arrives in more than one arrangement. Write the reader once and you never think about it again.<\/p>\n<pre class=\"wp-block-code\"><code>def read_ladder(price):\n    \"\"\"Return (back, lay) as lists of {price, size}, best price first.\n\n    Handles every exchangeMeta arrangement in the v4 feed:\n      back \/ lay              with {cents, limit, price, size}   prediction markets\n      back \/ lay              with {cents, limit, price}         prophetx, no size key\n      availableToBack \/ Lay   with {price, size}                 betfair network\n      null                    no ladder on this venue\n    \"\"\"\n    meta = price.get(\"exchangeMeta\") or {}\n    if not isinstance(meta, dict):\n        return [], []\n\n    def rungs(*keys):\n        for key in keys:\n            levels = meta.get(key)\n            if isinstance(levels, list) and levels:\n                return [\n                    {\n                        \"price\": lv.get(\"price\"),\n                        # prophetx omits size, so fall back to limit\n                        \"size\": lv.get(\"size\", lv.get(\"limit\")),\n                    }\n                    for lv in levels if isinstance(lv, dict)\n                ]\n        return []\n\n    return rungs(\"back\", \"availableToBack\"), rungs(\"lay\", \"availableToLay\")<\/code><\/pre>\n<p>Three rules that will save you a debugging session. The ladder is <strong>live only<\/strong>: <code>exchangeMeta<\/code> is <code>null<\/code> on every historical snapshot, so for depth history you use the snapshot-level <code>limit<\/code> instead. Rung lists are ordered <strong>best price first<\/strong>, and the top of the back ladder equals the outcome&#8217;s main <code>price<\/code>. And <code>tradedVolume<\/code> on the Betfair network arrives as an integer on some rungs and a float on others inside the same fixture, so type it loosely.<\/p>\n<p>Sportsbooks ship <code>exchangeMeta: null<\/code>, which is correct and expected. They have no order book to expose. If you want the two-sided view on a retail book you build it from the opposing outcome, not from this field.<\/p>\n<h2>Step 3: Rank the Board by Size Skew<\/h2>\n<p>Here is the measurement that changes how you read a board. Take every book quoting all three legs of the 1X2 with a live limit on each, then divide the widest leg by the thinnest.<\/p>\n<pre class=\"wp-block-code\"><code>def sized_market(board, market_id, outcome_ids):\n    \"\"\"Books quoting every leg active, with a limit on each.\"\"\"\n    out = {}\n    for slug, book in board.items():\n        market = (book.get(\"markets\") or {}).get(market_id)\n        if not market:\n            continue\n        legs = {}\n        for outcome_id in outcome_ids:\n            outcome = (market.get(\"outcomes\") or {}).get(outcome_id) or {}\n            price = (outcome.get(\"players\") or {}).get(\"0\")\n            if not price or not price.get(\"active\") or price.get(\"limit\") is None:\n                legs = None\n                break\n            legs[outcome_id] = (price[\"price\"], float(price[\"limit\"]))\n        if legs and min(lim for _, lim in legs.values()) > 0:\n            out[slug] = legs\n    return out\n\ndef size_skew(legs):\n    \"\"\"Widest leg over thinnest. 1.0 means equal size on every outcome.\"\"\"\n    limits = [lim for _, lim in legs.values()]\n    return max(limits) \/ min(limits)\n\nMARKET, OUTCOMES = \"101\", [\"101\", \"102\", \"103\"]      # soccer 1X2 fulltime\nquotes = sized_market(board, MARKET, OUTCOMES)\n\nfor slug, legs in sorted(quotes.items(), key=lambda kv: -size_skew(kv[1])):\n    cells = \"  \".join(f\"{p} @ {lim:,.0f}\" for p, lim in legs.values())\n    print(f\"{slug:18}{size_skew(legs):>7.1f}x   {cells}\")<\/code><\/pre>\n<p>Twenty-five of the 286 books clear that filter. The 14:22Z snapshot ranks them like this:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Bookmaker<\/th>\n<th>Leeds<\/th>\n<th>Draw<\/th>\n<th>Newcastle<\/th>\n<th>Size skew<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>sx.bet<\/td>\n<td>2.353 @ 4,819<\/td>\n<td>3.54 @ 114<\/td>\n<td>3.226 @ 103<\/td>\n<td>47.0x<\/td>\n<\/tr>\n<tr>\n<td>novig.us<\/td>\n<td>2.353 @ 3,285<\/td>\n<td>3.571 @ 133<\/td>\n<td>3.226 @ 3,927<\/td>\n<td>29.5x<\/td>\n<\/tr>\n<tr>\n<td>polymarket<\/td>\n<td>2.381 @ 146,803<\/td>\n<td>3.571 @ 5,883<\/td>\n<td>3.333 @ 9,126<\/td>\n<td>25.0x<\/td>\n<\/tr>\n<tr>\n<td>betfair-ex<\/td>\n<td>2.38 @ 6,516<\/td>\n<td>3.6 @ 17,357<\/td>\n<td>3.25 @ 1,402<\/td>\n<td>12.4x<\/td>\n<\/tr>\n<tr>\n<td>kalshi<\/td>\n<td>2.381 @ 5,935<\/td>\n<td>3.571 @ 9,387<\/td>\n<td>3.226 @ 50,178<\/td>\n<td>8.5x<\/td>\n<\/tr>\n<tr>\n<td>prophetx<\/td>\n<td>2.38 @ 729<\/td>\n<td>3.6 @ 301<\/td>\n<td>3.2 @ 2,264<\/td>\n<td>7.5x<\/td>\n<\/tr>\n<tr>\n<td>paddypower<\/td>\n<td>2.30 @ 5,769<\/td>\n<td>3.50 @ 3,000<\/td>\n<td>3.25 @ 3,333<\/td>\n<td>1.9x<\/td>\n<\/tr>\n<tr>\n<td><strong>pinnacle<\/strong><\/td>\n<td><strong>2.34 @ 10,000<\/strong><\/td>\n<td><strong>3.50 @ 10,000<\/strong><\/td>\n<td><strong>3.17 @ 10,000<\/strong><\/td>\n<td><strong>1.0x<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Sort that table by price and the top six venues look like the same book. Every one of them quotes the home participant between 2.353 and 2.381. Sort by size and they are six different venues with six different appetites. Polymarket will take 146,803 on Leeds and 5,883 on the draw off the same screen.<\/p>\n<p>Pinnacle is the control, and it is worth saying why. A flat 10,000 across all three legs is what a book with a real risk desk looks like: it has a position it is comfortable with and it will take that position on any outcome. That symmetry is also why Pinnacle stays the de-vig anchor for <a href=\"https:\/\/oddspapi.io\/blog\/consensus-odds-fair-odds-calculator-python\/\">consensus fair value<\/a>. Its 1X2 here runs a 2.85% overround, giving no-vig probabilities of 41.5% Leeds, 27.8% draw, 30.7% Newcastle.<\/p>\n<h2>Step 4: Watch Size Move While Price Holds Still<\/h2>\n<p>Run the board twice and diff it. This is the loop that produced the headline table at the top of this post.<\/p>\n<pre class=\"wp-block-code\"><code>def sized_legs(board, market_id, outcome_ids):\n    \"\"\"{(slug, outcome_id): (price, limit)} for every active, sized leg.\"\"\"\n    out = {}\n    for slug, legs in sized_market(board, market_id, outcome_ids).items():\n        for outcome_id, (price, limit) in legs.items():\n            out[(slug, outcome_id)] = (price, limit)\n    return out\n\nfirst  = sized_legs(get_board(FIXTURE), MARKET, OUTCOMES)\ntime.sleep(600)\nsecond = sized_legs(get_board(FIXTURE), MARKET, OUTCOMES)\n\nboth = set(first) &amp; set(second)\nprice_moved = {k for k in both if first[k][0] != second[k][0]}\nlimit_moved = {k for k in both if first[k][1] != second[k][1]}\nsilent      = limit_moved - price_moved\n\nprint(f\"legs in both snapshots : {len(both)}\")\nprint(f\"price changed          : {len(price_moved)}\")\nprint(f\"limit changed          : {len(limit_moved)}\")\nprint(f\"limit moved, price did not : {len(silent)}\")\n\nfor key in sorted(silent, key=lambda k: second[k][1] \/ first[k][1])[:4]:\n    slug, outcome_id = key\n    print(f\"  {slug:14} outcome {outcome_id}  \"\n          f\"{first[key][1]:>10,.0f} -> {second[key][1]:>10,.0f}\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>legs in both snapshots : 75\nprice changed          : 14\nlimit changed          : 27\nlimit moved, price did not : 13\n  polymarket     outcome 103       9,126 ->      1,022\n  prophetx       outcome 103       2,264 ->        695\n  prophetx       outcome 101         729 ->        526\n  kalshi         outcome 103      50,178 ->     38,536<\/code><\/pre>\n<p>Ten minutes of a quiet pre-match board and a third of the sized legs repriced their size. If your signal is built on price deltas you saw four of those thirteen events, because four of them also happened to move a tick. You missed the other nine.<\/p>\n<p>This is the argument for the <a href=\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/\">WebSocket gateway<\/a> rather than a poll loop. A ten minute poll interval caught the net change on this fixture. It did not catch the path. On the v5 gateway <code>limit<\/code> changes arrive as ordinary odds updates on the odds channel, so every size move lands as its own message with its own cursor.<\/p>\n<h2>Step 5: Audit the Quote Age Yourself<\/h2>\n<p>Every price carries two timestamps. <code>bookmakerChangedAt<\/code> is the book&#8217;s own change time. <code>changedAt<\/code> is when OddsPapi observed it. Subtract them and you have the observation delay on that specific quote, computed from the payload, needing nobody&#8217;s word for it.<\/p>\n<pre class=\"wp-block-code\"><code>import statistics\nfrom datetime import datetime\n\ndef parse(stamp):\n    return datetime.fromisoformat(stamp.replace(\"Z\", \"+00:00\"))\n\ndef observation_delays(board, slug):\n    \"\"\"changedAt minus bookmakerChangedAt, per active price. Seconds.\"\"\"\n    delays = []\n    for _, _, _, price in walk_prices({slug: board[slug]}):\n        if not price.get(\"active\"):\n            continue\n        seen, moved = price.get(\"changedAt\"), price.get(\"bookmakerChangedAt\")\n        if seen and moved:\n            delays.append((parse(seen) - parse(moved)).total_seconds())\n    return delays\n\nfor slug in [\"sx.bet\", \"betfair-ex\", \"draftkings\", \"kalshi\", \"pinnacle\", \"polymarket\"]:\n    delays = observation_delays(board, slug)\n    if len(delays) &gt;= 10:\n        print(f\"{slug:14}{len(delays):>6} prices   median {statistics.median(delays):.3f}s\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>sx.bet            34 prices   median 0.101s\nbetfair-ex       417 prices   median 0.122s\ndraftkings       568 prices   median 0.140s\nkalshi           117 prices   median 0.159s\npinnacle         135 prices   median 0.284s\npolymarket       154 prices   median 0.388s<\/code><\/pre>\n<p>Read the median, not the tail. This is a pre-match board, so the upper percentiles are dominated by rungs nobody has touched for hours rather than by slow delivery. Note also that <strong>73 of the 286 books publish <code>bookmakerChangedAt<\/code> on ten or more active prices<\/strong>, so this self-audit works on the books that timestamp themselves. For the rest you compare <code>changedAt<\/code> against your own receipt time.<\/p>\n<p>If you want the vendor&#8217;s own figures rather than your own, the B2B documentation publishes per-book freshness bounds on <code>GET \/bookmakers<\/code>: <code>maxDelayLiveInSec<\/code>, <code>maxDelayPregameInSec<\/code> and <code>maxDelayPregameMainInSec<\/code>.<\/p>\n<h2>One Trap Before You Build a Consensus<\/h2>\n<p>The board looks wider than it is. On this fixture 270 books quote a full active 1X2 and they collapse to <strong>87 distinct price tuples<\/strong>, a 67.8% duplication rate. White-label platforms and crypto-casino groups share pricing engines, so a naive average weights one engine by however many skins it runs. The largest single group is 24 feeds quoting the identical 2.28 \/ 3.45 \/ 3.00.<\/p>\n<pre class=\"wp-block-code\"><code>from collections import Counter\n\ndef price_tuples(board, market_id, outcome_ids):\n    tuples = {}\n    for slug, legs in sized_market(board, market_id, outcome_ids).items():\n        tuples[slug] = tuple(p for p, _ in legs.values())\n    return tuples\n\nall_quotes = {}\nfor slug, book in board.items():\n    market = (book.get(\"markets\") or {}).get(MARKET)\n    if not market:\n        continue\n    legs = []\n    for outcome_id in OUTCOMES:\n        outcome = (market.get(\"outcomes\") or {}).get(outcome_id) or {}\n        price = (outcome.get(\"players\") or {}).get(\"0\")\n        if not price or not price.get(\"active\"):\n            legs = None\n            break\n        legs.append(price[\"price\"])\n    if legs:\n        all_quotes[slug] = tuple(legs)\n\ngroups = Counter(all_quotes.values())\nprint(f\"books quoting a full active 1X2 : {len(all_quotes)}\")\nprint(f\"distinct price tuples           : {len(groups)}\")\nprint(f\"largest identical group         : {groups.most_common(1)[0][1]} feeds\")<\/code><\/pre>\n<pre class=\"wp-block-code\"><code>books quoting a full active 1X2 : 270\ndistinct price tuples           : 87\nlargest identical group         : 24 feeds<\/code><\/pre>\n<p>Deduplicate on the price tuple before you average anything or quote a book count. The <code>cloneOf<\/code> field on <code>\/bookmakers<\/code> identifies the venue families it knows about, including the Betfair agent network and the Pinnacle network, but it is a venue relationship rather than a pricing-engine fingerprint. The tuple is what you actually care about.<\/p>\n<h2>What This Post Does Not Cover<\/h2>\n<p>Three honest boundaries, because a pre-match snapshot is not a universal claim.<\/p>\n<ul>\n<li><strong>This is pre-match.<\/strong> In-play size behaviour is a different study. Boards suspend, repost and reprice on a different clock.<\/li>\n<li><strong>This is one fixture in one tournament.<\/strong> Exchange depth varies enormously by sport. Ladders that hold tens of thousands on a Premier League fixture hold single-digit dollars on a rugby union fixture. Measure your own sport before you size a strategy on it.<\/li>\n<li><strong>Two venues need an entitlement.<\/strong> <code>betdex<\/code> and <code>matchbook<\/code> return HTTP 403 on a standard key. Plan that rather than discovering it in production.<\/li>\n<\/ul>\n<h2>Building on This<\/h2>\n<p>The pieces used here are all on the free tier: the full 350+ bookmaker catalogue, <code>limit<\/code> and <code>exchangeMeta<\/code> on every live board, and <code>\/historical-odds<\/code> price timelines for fill auditing after the fact. Native market structures mean the Asian handicap and total ladders carry the same <code>limit<\/code> treatment as the 1X2, so the same four functions work on any market ID you point them at.<\/p>\n<p>If you are running an automated strategy rather than researching one, the <a href=\"https:\/\/oddspapi.io\/blog\/odds-feed-provider-sportsbooks-trading-desks\/\">B2B feed<\/a> is where this goes next: the WebSocket gateway pushes every size change as its own message with a resume cursor, and adds two flags this post could not use. <code>staleOdds<\/code> tells you the connection to a book is interrupted so its prices are unvalidated, and <code>participantsRotated<\/code> tells you a venue calls the other participant home, which reverses your outcome mapping and flips every handicap sign.<\/p>\n<p><a href=\"https:\/\/oddspapi.io\/en\/sign-up\">Get a free API key<\/a> and run the Step 1 snippet against a fixture in your own sport. The only thing you need to change is the fixture ID.<\/p>\n<h2>Frequently Asked Questions<\/h2>\n<h3>What is the difference between <code>limit<\/code> and <code>size<\/code>?<\/h3>\n<p><code>limit<\/code> is the maximum stake still available to match at that price. <code>size<\/code>, inside an <code>exchangeMeta<\/code> rung, is the total resting at that rung including what is already matched. On a sportsbook there is no ladder, so <code>limit<\/code> is simply the maximum stake the book accepts.<\/p>\n<h3>Which bookmakers publish a stake limit?<\/h3>\n<p>On the fixture measured here, 29 of 286: the Pinnacle network, the Betfair network, and every exchange and prediction market present, including Kalshi, Polymarket, Novig US, Prophet Exchange, SX Bet and 4casters. FanDuel publishes one on part of its board. The number varies by fixture and by sport, so run the Step 1 snippet on your own fixture rather than assuming this one.<\/p>\n<h3>Is the order book ladder available on historical data?<\/h3>\n<p>No. <code>exchangeMeta<\/code> is live only and reads <code>null<\/code> on every historical snapshot. Historical depth is available one number wide through the snapshot-level <code>limit<\/code> field, which is enough to chart a liquidity ramp toward kick-off but not enough to reconstruct a full book.<\/p>\n<h3>How deep is the depth ladder?<\/h3>\n<p>Three rungs per side on the venues measured here, best price first, on both the back and the lay side. The top of the back ladder always equals the outcome&#8217;s main <code>price<\/code>.<\/p>\n<h3>Can I verify the feed latency myself?<\/h3>\n<p>Yes, on the books that timestamp their own changes. Subtract <code>bookmakerChangedAt<\/code> from <code>changedAt<\/code> on any active price and you have the observation delay for that quote. On the fixture measured here, 73 of 286 books publish <code>bookmakerChangedAt<\/code> on ten or more active prices.<\/p>\n<h3>Do I need the WebSocket feed for this?<\/h3>\n<p>Not to research it. Every measurement in this post came from the v4 REST API on a free key. You need the gateway when you want every size change delivered as it happens rather than the net change between two polls, which on this fixture was the difference between thirteen events and four.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Across 75 sized legs on one Premier League board, 36% of stake limits moved in ten minutes against 19% of prices. Pinnacle held 10,000 flat on all three.<\/p>\n","protected":false},"author":2,"featured_media":4133,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[101,9,34,11,10],"class_list":["post-4132","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to-guides","tag-betting-exchange","tag-odds-api","tag-prediction-markets","tag-python","tag-sports-betting-api"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Odds API for Market Makers: Size Moves Twice as Often as Price | OddsPapi Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Odds API for Market Makers: Size Moves Twice as Often as Price | OddsPapi Blog\" \/>\n<meta property=\"og:description\" content=\"Across 75 sized legs on one Premier League board, 36% of stake limits moved in ten minutes against 19% of prices. Pinnacle held 10,000 flat on all three.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/\" \/>\n<meta property=\"og:site_name\" content=\"OddsPapi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-20T10:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"675\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"Odds API Writer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/oddspapi.io\/logo-v2.webp\" \/>\n<meta name=\"twitter:creator\" content=\"@oddspapiapi\" \/>\n<meta name=\"twitter:site\" content=\"@oddspapiapi\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Odds API Writer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/\"},\"author\":{\"name\":\"Odds API Writer\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\"},\"headline\":\"Odds API for Market Makers: Size Moves Twice as Often as Price\",\"datePublished\":\"2026-09-20T10:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/\"},\"wordCount\":2031,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp\",\"keywords\":[\"Betting Exchange\",\"Odds API\",\"Prediction Markets\",\"Python\",\"Sports Betting API\"],\"articleSection\":[\"How To Guides\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/\",\"name\":\"Odds API for Market Makers: Size Moves Twice as Often as Price | OddsPapi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp\",\"datePublished\":\"2026-09-20T10:00:00+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#primaryimage\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp\",\"width\":1200,\"height\":675,\"caption\":\"Odds API for Market Makers - OddsPapi API Blog\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/oddspapi.io\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Odds API for Market Makers: Size Moves Twice as Often as Price\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"name\":\"OddsPapi\",\"description\":\"Sports Odds API Tutorials &amp; Guides\",\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"alternateName\":\"Odds Papi\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/oddspapi.io\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\",\"name\":\"OddsPapi\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png\",\"width\":135,\"height\":135,\"caption\":\"OddsPapi\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/oddspapiapi\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\",\"name\":\"Odds API Writer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g\",\"caption\":\"Odds API Writer\"},\"url\":\"https:\/\/oddspapi.io\/blog\/author\/andy-lavelle\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Odds API for Market Makers: Size Moves Twice as Often as Price | OddsPapi Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/","og_locale":"en_US","og_type":"article","og_title":"Odds API for Market Makers: Size Moves Twice as Often as Price | OddsPapi Blog","og_description":"Across 75 sized legs on one Premier League board, 36% of stake limits moved in ten minutes against 19% of prices. Pinnacle held 10,000 flat on all three.","og_url":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/","og_site_name":"OddsPapi Blog","article_published_time":"2026-09-20T10:00:00+00:00","og_image":[{"width":1200,"height":675,"url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp","type":"image\/webp"}],"author":"Odds API Writer","twitter_card":"summary_large_image","twitter_image":"https:\/\/oddspapi.io\/logo-v2.webp","twitter_creator":"@oddspapiapi","twitter_site":"@oddspapiapi","twitter_misc":{"Written by":"Odds API Writer","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#article","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/"},"author":{"name":"Odds API Writer","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13"},"headline":"Odds API for Market Makers: Size Moves Twice as Often as Price","datePublished":"2026-09-20T10:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/"},"wordCount":2031,"commentCount":0,"publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp","keywords":["Betting Exchange","Odds API","Prediction Markets","Python","Sports Betting API"],"articleSection":["How To Guides"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/","url":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/","name":"Odds API for Market Makers: Size Moves Twice as Often as Price | OddsPapi Blog","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#primaryimage"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp","datePublished":"2026-09-20T10:00:00+00:00","breadcrumb":{"@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#primaryimage","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/09\/odds-api-market-makers-size-at-price.webp","width":1200,"height":675,"caption":"Odds API for Market Makers - OddsPapi API Blog"},{"@type":"BreadcrumbList","@id":"https:\/\/oddspapi.io\/blog\/odds-api-market-makers-size-at-price\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oddspapi.io\/blog\/"},{"@type":"ListItem","position":2,"name":"Odds API for Market Makers: Size Moves Twice as Often as Price"}]},{"@type":"WebSite","@id":"https:\/\/oddspapi.io\/blog\/#website","url":"https:\/\/oddspapi.io\/blog\/","name":"OddsPapi","description":"Sports Odds API Tutorials &amp; Guides","publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"alternateName":"Odds Papi","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/oddspapi.io\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/oddspapi.io\/blog\/#organization","name":"OddsPapi","url":"https:\/\/oddspapi.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png","width":135,"height":135,"caption":"OddsPapi"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/oddspapiapi"]},{"@type":"Person","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13","name":"Odds API Writer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g","caption":"Odds API Writer"},"url":"https:\/\/oddspapi.io\/blog\/author\/andy-lavelle\/"}]}},"_links":{"self":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/4132","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/comments?post=4132"}],"version-history":[{"count":1,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/4132\/revisions"}],"predecessor-version":[{"id":4134,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/4132\/revisions\/4134"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media\/4133"}],"wp:attachment":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media?parent=4132"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/categories?post=4132"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/tags?post=4132"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}