Betting Limits API: How Much Can You Actually Bet at the Best Price?

Betting Limits API - OddsPapi API Blog
How To Guides August 4, 2026

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.