Betting Limits API: How Much Can You Actually Bet at the Best Price?
Your scanner just flagged a +3.2% edge. Congratulations. Now answer the question that actually decides whether this is a business or a hobby: how much can you get down at that price?
Every odds API on the market will sell you the number. Almost none of them will tell you the size behind it. That gap is where most betting bots die. The model says stake $40,000, the book takes $375, and the spreadsheet that showed 8% ROI was describing a market that does not exist at your bet size.
The syndicates understood this before anyone was writing Python. Zeljko Ranogajec’s operation was not built on picking more winners than everyone else. It was built on getting real money onto a number before that number moved, across dozens of accounts, in the markets where the books would take size. Bill Benter’s Hong Kong operation had the same shape. The edge was distribution as much as prediction.
OddsPapi ships the size alongside the price. Every outcome in the live feed carries a limit field, and every exchange carries a full back and lay ladder with the capital sitting at each level. This guide shows you how to read both, how to reverse-engineer the rule Pinnacle uses to set its limits, and how to work out what your edge is worth after you account for what you can actually fill.
Every number below came off the live API on 28 July 2026, from a single MLB fixture: Miami Marlins at Philadelphia Phillies, fixtureId id1300010963301639.
The number your odds feed is missing
| Question | Scraping or a price-only API | OddsPapi |
|---|---|---|
| What is the best price? | Yes | Yes, across 350+ books |
| How much will they take at it? | Place the bet and find out | limit on every outcome |
| What sits behind the top of book? | No | exchangeMeta back and lay ladder |
| Did the limit move overnight? | No | Free /historical-odds snapshots carry limits |
| Cost | Enterprise contract or a scraper you maintain | Free tier |
Who publishes a limit and who does not
Thirteen bookmakers quoted the moneyline on our Marlins fixture. Three of them told us how much they would take.
| Book | Price (Miami) | Limit |
|---|---|---|
| kalshi | 1.961 | $391,922 |
| polymarket | 1.961 | $77,130 |
| pinnacle | 1.925 | $8,108 |
| fanduel | 1.91 | null |
| circasports | 1.909 | null |
| draftkings | 1.89 | null |
| betmgm, borgata, caesars, williamhill, hardrockbet, pointsbet.com.au | 1.87 | null |
The pattern is not random. Pinnacle and the exchanges publish a limit because their limit is a property of the market. They price for anyone and they cap by risk, so the number is public and identical for every account. The US retail books return null because their limit is a property of you. There is no market-wide max bet at DraftKings. There is a max bet for your account, set by how much you have won, and they are not putting that in a feed.
That asymmetry is worth internalising before you build anything. On the sharp side, the API tells you the truth. On the soft side, you have to model your own account history and accept that the ceiling drops as you win.
One housekeeping note on that table: six books quoting 1.87 is six slugs, not six independent opinions. betmgm and borgata run the same pricing, as do caesars and williamhill. Dedupe on the price tuple before you count anything.
Step 1: Read limits off the live feed
Auth is a query parameter, never a header. Pull the fixture, walk the nested payload, keep the limit alongside the price.
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oddspapi.io/v4"
FIXTURE = "id1300010963301639" # Marlins at Phillies
MONEYLINE = "131" # Winner (incl. extra innings)
def get_odds(fixture_id):
r = requests.get(f"{BASE_URL}/odds",
params={"apiKey": API_KEY, "fixtureId": fixture_id})
r.raise_for_status()
return r.json()
def read_prices(odds, market_id):
"""[{book, outcome, price, limit, ladder}] for one market."""
rows = []
for slug, book in odds.get("bookmakerOdds", {}).items():
market = book.get("markets", {}).get(market_id)
if not market:
continue
for outcome_id, outcome in market["outcomes"].items():
price = outcome["players"].get("0")
if not price or price.get("active") is False:
continue
rows.append({
"book": slug,
"outcome": outcome_id,
"price": price["price"],
"limit": price.get("limit"),
"ladder": (price.get("exchangeMeta") or {}).get("back"),
})
return rows
rows = read_prices(get_odds(FIXTURE), MONEYLINE)
for r in sorted(rows, key=lambda r: -(r["limit"] or 0)):
if r["outcome"] == "131":
print(f"{r['book']:20} {r['price']:>7} limit={r['limit']}")
Two traps in those eight lines. Filter on active is False rather than truthy active, because the feed ships active: null alongside perfectly good prices on pre-game fixtures. And treat both null and {} as “no exchange data”, because older payloads used the empty dict.
Step 2: Pinnacle publishes a max win, not a max stake
Look at the two sides of the Marlins moneyline. Miami at 1.925 has a limit of $8,108. Philadelphia at 2.0 has a limit of $7,500. Same market, same fixture, different caps. Multiply each limit by the profit per unit and the reason appears:
def max_win(price, limit):
return round(limit * (price - 1), 2)
max_win(1.925, 8108) # 7499.90
max_win(2.00, 7500) # 7500.00
max_win(1.465, 16129) # 7500.00 run line, other side
max_win(1.529, 14177) # 7499.63 alt handicap
max_win(1.18, 41666) # 7499.88 deep favourite
Pinnacle is not capping your stake. It is capping its own loss. The rule that reproduces every limit on the fixture is:
limit = max(base, base / (price - 1))
Where base is a per-market figure the book sets by how much it trusts its own number. On a 1.18 favourite that formula hands you a $41,666 limit, because you would have to stake $41,666 to win the same $7,500 that a $7,500 stake wins at evens. I checked the rule against two more MLB fixtures on the same slate and it held on the moneyline, the run line and the total every time.
Step 3: The limit tells you where the book is unsure
Run that base calculation across every Pinnacle market on one fixture and you get a confidence map, published by the book, for free:
| Market | Implied base (max win) | Limit at evens |
|---|---|---|
| Winner, full game | $7,500 | $8,108 |
| Run line -1.5 | $7,500 | $16,129 on the favourite |
| Total 8.5 | $5,625 | $5,625 |
| Handicap, first five innings | $3,750 | $3,750 |
| Alternate handicap +3 | $1,875 | $1,875 |
| Over/under, first inning | $750 | $795 |
| Exact runs | $375 | $375 |
The moneyline carries twenty-one times the size of the exact-runs market. Pinnacle has priced thousands of MLB moneylines and it knows where its number sits. It has priced far fewer exact-runs markets and it hedges by refusing size.
Now hold that against where your model probably finds an edge. Nobody beats the Pinnacle moneyline by 3%. Plenty of people find soft numbers in first-inning totals and exact-runs derivatives, which is exactly where the book will take $375 and then move. Your edge and the available size are inversely correlated, and the limit field prices that trade-off for you before you write a staking plan.
Step 4: Exchange ladders, and what size means
Exchanges go further than a single cap. exchangeMeta carries a list of price levels with the capital resting at each. Here is the Polymarket back ladder on Miami:
| Level | Price | size |
limit |
cents |
|---|---|---|---|---|
| 1 | 1.961 | 151,235.08 | 77,129.89 | 0.51 |
| 2 | 1.923 | 70,385.38 | 36,600.40 | 0.52 |
| 3 | 1.887 | 84,154.67 | 44,601.98 | 0.53 |
The two money columns mean different things and mixing them up will cost you. size is the payout available at that level. limit is the stake required to take all of it. They tie together through the native share price: 151,235.08 shares at 51 cents is $77,129.89 of stake. The relationship held on every level of both Polymarket and Kalshi that I checked.
The outcome-level limit field on an exchange is just the top level of that ladder. To fill more, you walk down and pay worse prices:
def walk_ladder(levels, target_stake):
"""Fill target_stake down a back ladder. Returns (filled, avg_price)."""
filled, payout = 0.0, 0.0
for level in levels or []:
take = min(level["limit"], target_stake - filled)
if take <= 0:
break
filled += take
payout += take * level["price"]
if filled == 0:
return 0.0, None
return round(filled, 2), round(payout / filled, 4)
Three levels is what the feed carries, and on this fixture they added up to $158,332 of back capacity. Ask for $200,000 and you get $158,332 at a blended 1.9314. The rest does not exist until someone posts it.
Parse this defensively. Kalshi and Polymarket both ship the back and lay lists shown above, but the shape varies across exchange slugs: Betfair uses availableToBack and availableToLay, some payloads carry a flat scalar, and inactive markets can return a list where you expected an object. Check the type before you iterate.
Step 5: What size does to your edge
Say your model prices Miami at 1.90, so you make the true probability 52.6%. The best available price is 1.961 and you want to know what that is worth. Run the ladder walk at increasing sizes and compute EV on the blended fill rather than the headline number:
| Target stake | Filled | Blended price | EV |
|---|---|---|---|
| $25,000 | $25,000 | 1.961 | +3.21% |
| $77,129 | $77,129 | 1.961 | +3.21% |
| $100,000 | $100,000 | 1.9523 | +2.75% |
| $150,000 | $150,000 | 1.9338 | +1.78% |
| $200,000 | $158,332 | 1.9314 | +1.65% |
Going from $25,000 to $150,000 costs you 45% of your edge, and you never got a worse price than the third level of one order book. Scale that thinking across a slate and you understand why professional operations obsess over distribution. The edge per dollar shrinks as the dollars grow, and the only fix is more venues.
Step 6: Spread the stake across books
Which is what this does. Sort by price, take what each venue will give you, walk ladders where they exist, fall back to a conservative assumption where the book hides its limit:
def allocate(rows, outcome_id, target_stake, fallback_limit=500):
"""Spread a target stake over the best-priced books that will take it."""
book_rows = sorted([r for r in rows if r["outcome"] == outcome_id],
key=lambda r: -r["price"])
plan, filled, payout = [], 0.0, 0.0
for r in book_rows:
if filled >= target_stake:
break
if r["ladder"]:
take, price = walk_ladder(r["ladder"], target_stake - filled)
else:
cap = r["limit"] if r["limit"] is not None else fallback_limit
take, price = min(cap, target_stake - filled), r["price"]
if not take:
continue
plan.append((r["book"], take, price))
filled += take
payout += take * price
return plan, round(filled, 2), round(payout / filled, 4) if filled else None
Set fallback_limit from your own account history at each soft book. That is the one input the API cannot give you, and guessing high is how you end up with a plan that assumes $5,000 at BetMGM and gets $200.
The allocator also doubles as a reality check on line shopping. Best price across 350+ books is the right target when you are staking $200. At $50,000 the question changes to best blended price, and the book with the third-best number and real depth beats the headline quote.
Step 7: Limits ramp toward first pitch
Limits are not static, and the free /historical-odds endpoint records them. Pinnacle logged 43 moneyline snapshots on this fixture, and the limit quadrupled inside 21 hours:
| Timestamp (UTC) | Price | Limit | Implied max win |
|---|---|---|---|
| 27 Jul 21:30 | 1.943 | $1,988 | $1,875 |
| 28 Jul 08:42 | 1.925 | $2,027 | $1,875 |
| 28 Jul 15:08 | 1.925 | $6,081 | $5,625 |
| 28 Jul 16:25 | 1.925 | $8,108 | $7,500 |
def limit_history(fixture_id, book, market_id, outcome_id):
r = requests.get(f"{BASE_URL}/historical-odds",
params={"apiKey": API_KEY, "fixtureId": fixture_id,
"bookmakers": book}) # max 3 books per call
r.raise_for_status()
market = r.json()["bookmakers"][book]["markets"][market_id]
snaps = market["outcomes"][outcome_id]["players"]["0"]
return [(s["createdAt"][:16], s["price"], s.get("limit")) for s in snaps]
The base moved in steps: $1,875 the night before, $5,625 by mid-afternoon, $7,500 an hour after that. Pinnacle raises its exposure as the market fills in and its number gets sharper. Polymarket did the same thing from the other direction, growing from $8 of top-of-book depth six days out to $77,000 on game day across 26,477 snapshots.
The practical consequence: an edge you find at 9am may only be fillable at 5pm, by which time the price has usually moved against you. Log limits alongside prices in your own odds database and you can measure that trade-off on your own markets instead of guessing at it.
What this changes about your staking
Most staking tutorials, including our own Kelly guide, compute a number and stop. Kelly on a $500,000 bank with a 3.2% edge will tell you to stake five figures without blinking. Whether that stake exists is a separate question with a separate data source, and now you have it.
Three changes worth making today. Clip every Kelly output to the fillable size before you log it as a bet. Compute EV on the blended fill price rather than the top-of-book quote. And when you run a risk-of-ruin simulation, feed it the sizes you can actually get on, because a strategy that needs $40,000 per bet in a market with $375 limits has a ruin probability you cannot compute from returns alone.
None of this requires an enterprise contract. The limit field, the full exchange ladders, and the historical snapshots that show limits moving are all on the free tier, alongside 350+ bookmakers and the sharps that actually publish their exposure. Ranogajec needed a network of accounts and people to answer the size question. You need one endpoint.
Get your free API key and find out what your edge is worth at scale.
Frequently Asked Questions
Which bookmakers publish a betting limit in the OddsPapi API?
Pinnacle and the prediction-market exchanges populate the limit field on every outcome. On our MLB test fixture, Pinnacle, Kalshi and Polymarket all carried a limit while the other ten books returned null. US retail books such as DraftKings, FanDuel, BetMGM and Caesars leave it empty because their maximum bet is set per account rather than per market. Check for None before doing arithmetic on it.
What does the limit field actually mean?
On a sportsbook it is the maximum stake the book will accept on that outcome at that price. On an exchange it is the stake needed to consume the top level of the order book, with deeper levels listed separately in exchangeMeta. Both are quoted in the account currency.
Why is Pinnacle’s limit different on each side of the same market?
Pinnacle caps its own maximum loss rather than your stake. The limit on any outcome is max(base, base / (price - 1)), where base is a per-market figure. A $7,500 base gives an $8,108 limit at 1.925 and a $41,666 limit at 1.18, because both stakes win the book’s maximum of $7,500.
How do I read size versus limit on an exchange ladder?
size is the payout available at that level and limit is the stake required to take it, linked by the native share price in cents. On Polymarket, 151,235 shares at 0.51 works out to $77,130 of stake. Use limit when you are walking a ladder to fill a target stake.
Can I get limit history on the free tier?
Yes. Every snapshot from /historical-odds carries the limit that applied at that timestamp, so you can chart how a book scaled its exposure into kickoff. The endpoint accepts a maximum of three bookmakers per call, so loop and merge for wider coverage.