Moneyline Odds API: Pull, Compare and Convert Moneylines in Code
How moneylines are modeled in an odds API, two-way and three-way markets, periods, pulling them across books, converting between American, decimal and probability, measuring margin and charting moneyline history.
The moneyline is the simplest market in sports odds data: pick the winner, no handicap, no total. It is also the market almost every odds product starts with, whether that is a scoreboard that shows prices next to each game, a model that needs win probabilities as inputs, or a research notebook that tracks how a price moved before kickoff. Because it has no line, it is the cleanest place to get the plumbing right before moving on to spreads, totals and props.
This guide covers how the moneyline is represented in the PreciseAPI schema, how to pull it for one league or many across every book, how to convert it between formats without introducing rounding errors, how to measure each book's margin, and how to chart its history. For the market itself in one paragraph, see the moneyline glossary entry.
How a moneyline is modeled
Every moneyline is a market with key: "moneyline" and a period, holding one outcome per possible winner. Each outcome has a side, a display name and a price. There is no line and no point.
{
"id": "mkt_1dougx7qnw5",
"key": "moneyline",
"period": "full_game",
"outcomes": [
{ "id": "sel_2d3ja0zugwd", "name": "Phoenix Suns", "side": "home", "price": -135 },
{ "id": "sel_1pojklgy6cc", "name": "San Antonio Spurs", "side": "away", "price": 115 }
],
"suspended": false,
"updated_at": "2026-10-14T23:33:15.863Z"
}
Three details are worth building around from the start:
- Use
side, notname. Names are for display.sideis alwayshome,awayordraw, and it is what you join on. In individual sports such as tennis and UFC the two competitors still appear as thehomeandawaysides, so the same code handles them. - Use the selection
idto join across books. The outcome id is built from the event, market, period and side, not the book, so "Phoenix to win the full game" has the same id at every source. The normalization post explains the scheme. - Don't assume two outcomes. Soccer moneylines are three-way.
Two-way and three-way
The moneyline is offered for all 17 leagues in the catalog. In 16 of them it is two-way: home or away. Soccer returns a three-way market with home, draw and away outcomes, because a draw is a common result and is priced as its own outcome. Code that reads outcomes[0] and outcomes[1] and assumes they are complementary will silently produce wrong probabilities for soccer. Loop over outcomes instead, and key everything by side.
Periods
Moneylines are priced per period, not only for the full game. Each league lists the periods it supports:
| League | Periods |
|---|---|
| NFL, NBA | full_game, 1h, 1q |
| NCAAF, CFL, WNBA, NCAAB, soccer | full_game, 1h |
| MLB | full_game, f5 (first five innings) |
| NHL | full_game, 1p |
| Tennis, PGA, UFC, esports | full_game |
Pass periods=full_game when you only want the game result, or periods=full_game,1h to get both. /v1/leagues (free) returns the periods and market keys for every league, so you can build these filters from data instead of hard-coding them.
Pulling moneylines
/v1/odds returns current prices for many events at once, grouped by source. It costs 2 credits per request, regardless of how many events or books come back. Without markets=, it returns every market for the league, so narrow it when you only need moneylines:
curl "https://api.preciseapi.com/v1/odds?league=nba,nhl&markets=moneyline&periods=full_game&books=pinnacle,draftkings,fanduel,betmgm" \
-H "Authorization: Bearer $PRECISEAPI_KEY"
league= accepts several leagues in one request, and bookmakers= works as an alias for books=. Each element of data is an event (id, league, start_time, status, home, away and so on) with a bookmakers array, and each bookmaker has its own markets. Flattening that into rows is the first thing most integrations do:
const res = await fetch(`https://api.preciseapi.com/v1/odds?${new URLSearchParams({
league: 'nba', markets: 'moneyline', periods: 'full_game',
})}`, { headers: { Authorization: `Bearer ${process.env.PRECISEAPI_KEY}` } });
const { data } = await res.json();
const rows = data.flatMap((event) =>
event.bookmakers.flatMap((book) =>
book.markets.flatMap((m) =>
m.outcomes.map((o) => ({
event_id: event.id, start: event.start_time, book: book.id,
period: m.period, side: o.side, selection_id: o.id, price: o.price, updated_at: m.updated_at,
})),
),
),
);
If you need a single game, /v1/events/{id}/odds returns every market for that event at the same price. For an always-current board, the streaming docs show how to subscribe to moneylines and receive deltas instead of polling; the WebSocket tutorial has a complete Node.js client.
Converting moneylines
The API returns American odds by default. Pass format=decimal or format=probability to receive the other formats instead. Using the -135 / +115 market above:
format | Home | Away | Rounding |
|---|---|---|---|
american | -135 | 115 | Integer |
decimal | 1.741 | 2.15 | Three places |
probability | 0.5745 | 0.4651 | Four places, margin included |
The formulas are short:
export const americanToDecimal = (a) => (a > 0 ? 1 + a / 100 : 1 + 100 / -a);
export const decimalToAmerican = (d) => (d >= 2 ? (d - 1) * 100 : -100 / (d - 1));
export const impliedProbability = (a) => 1 / americanToDecimal(a);
-135 converts to 1 + 100 / 135 = 1.7407, and its implied probability is 135 / 235 = 0.5745. +115 converts to 2.15 and 100 / 215 = 0.4651.
Two rules avoid most conversion bugs. First, store the American integer as received and convert at full precision when you need another format; converting a rounded decimal back to American can be off by a point. Second, never average American odds: the mean of -105 and +105 is zero, which is not a price. Average implied probabilities instead. The odds formats guide covers fractional odds and the edge cases, and the implied probability guide goes deeper on the probability format.
Measuring margin on a moneyline
Because each outcome's implied probability includes the book's margin, the outcomes of a market sum to more than 1. The excess is the overround, and for a moneyline it is a single, clean number per book:
0.5745 + 0.4651 = 1.0396 -> 3.96% overround
Dividing each probability by the sum gives a margin-free estimate: 55.26% for the home side and 44.74% for the away side, or about -123.5 / +123.5 in American odds. That is the multiplicative method; the no-vig guide compares it with the additive, power and Shin methods and explains why you normally de-vig a sharp book rather than a soft one.
The same arithmetic works for soccer's three outcomes. An illustrative three-way price of home +140, draw +230, away +200 has implied probabilities of 0.4167, 0.3030 and 0.3333, which sum to 1.0530, an overround of 5.30%. Normalized, that is 39.57%, 28.78% and 31.65%, or roughly +153, +248 and +216. Expect three-way markets to carry more margin than two-way ones; the hold comparison post shows how to measure that across books and markets.
export function moneylineMargins(event) {
return event.bookmakers.flatMap((book) =>
book.markets
.filter((m) => m.key === 'moneyline')
.map((m) => {
const implied = m.outcomes.map((o) => impliedProbability(o.price));
const sum = implied.reduce((a, b) => a + b, 0);
return {
book: book.id, period: m.period, overround: sum - 1,
fair: Object.fromEntries(m.outcomes.map((o, i) => [o.side, implied[i] / sum])),
};
}),
);
}
Comparing moneylines across books
For a moneyline, the best price for an outcome is the highest decimal price among the books you track; there is no line to reconcile. /v1/best-lines does this server side for 3 credits and returns the best price per outcome along with every book offering it:
curl "https://api.preciseapi.com/v1/best-lines?league=soccer&markets=moneyline&periods=full_game" \
-H "Authorization: Bearer $PRECISEAPI_KEY"
It skips suspended markets, excludes DFS apps, and only reports an outcome when at least two books quote it. If you already pull the full board from /v1/odds, you can compute the same answer locally and save the extra call. The line shopping post covers both approaches, ties and freshness, and the best lines API page summarizes the endpoint.
Moneyline history
/v1/history returns the opening value, the current value and every timestamped change for a full-game moneyline, spread or total. It costs 3 credits and is included on the Sandbox, Starter, Pro and Enterprise plans. Identify the market with a market_id from any odds response, or with event_id plus book:
curl "https://api.preciseapi.com/v1/history?event_id=evt_0uuc6p2dsqa&book=pinnacle&market=moneyline&format=probability" \
-H "Authorization: Bearer $PRECISEAPI_KEY"
Each point has a timestamp t, a line (always null for moneylines, since there is no number) and a prices array in outcome order: [home, away], or [home, draw, away] for soccer.
Suppose a book opened a game at -130 / +110 and the current price is -145 / +125. The raw implied probability of the home side went from 56.52% to 59.18%, but the overround changed too (1.0414 at the open, 1.0363 now). Normalized, the home side moved from 54.27% to 57.11%, a cleaner measure of how far the market moved. Charting the normalized series rather than raw American odds also avoids the discontinuity at even money, where -101 and +101 are nearly the same price but look 202 points apart.
// h = data from /v1/history requested with format=probability
const series = h.points.map(({ t, prices }) => {
const sum = prices.reduce((a, b) => a + b, 0);
return { t, home: prices[0] / sum };
});
Probabilities returned with format=probability are rounded to four places, which is fine for charts. For anything you store and compute on later, request American and convert in code. The steam move post and the CLV guide build on exactly these series.
Live moneylines
The same request covers in-play games. /v1/odds returns scheduled and live events by default, and status=live narrows it to games in progress. Event ids never change when a game goes in-play, so the rows you stored before the start keep joining to live updates.
Live moneylines behave differently from pre-game ones in ways your code should expect:
- They move fast and in large steps. A single score can move a live moneyline sharply within seconds. Polling every 30 seconds will miss most of the path; the stream is the right delivery model for live markets.
- They get suspended. Books commonly pull prices around scoring plays, reviews and other high-variance moments. Suspended markets are excluded unless you pass
include_suspended=true, and when included they carrysuspended: true. Never display a suspended price as available. - Freshness is per market. Each market has its own
updated_at. Compare it with the current time before you show a live price, and grey out anything older than your threshold. - Context matters. A live price without the score is hard to interpret.
/v1/scores(1 credit) returns the score and game clock for live and recently finished events, and the event object itself carriesscoreandclockonce the game has started.
Common mistakes
A short list of bugs that show up in almost every first moneyline integration:
- Reading outcomes by position and assuming two of them, which breaks on soccer.
- Joining on team names instead of
sideor the selection id, which breaks when books spell a team differently. - Averaging American odds across books instead of implied probabilities.
- Computing probabilities from
format=decimaloutput, which has already been rounded to three places. - Mixing periods, for example comparing one book's
1hmoneyline with another'sfull_game.
What moneyline data is used for
- Odds screens and scoreboards. A moneyline column per book with the best price highlighted. See the odds screen use case.
- Model inputs. De-vigged sharp moneylines are a common prior for win-probability models; see betting models.
- Media. A compact "who's favored" widget is often a moneyline from a few books; the widget post has a cached implementation.
- Market research. Tracking margin and movement across sources over a season.
The game odds API page covers moneylines alongside spreads and totals, and pricing lists credits and rate limits per plan. The quickstart gets a key working in a few minutes.
Frequently asked questions
Which leagues have moneyline odds?
All 17 leagues in the catalog. It is a two-way market (home and away) everywhere except soccer, where it is three-way with a draw. /v1/markets?league= returns the keys offered for any league.
How do I get moneylines in decimal or probability format?
Add format=decimal or format=probability to any odds request. Decimal prices are rounded to three places and probabilities to four. Probabilities include the book's margin, so they sum to more than 1 across a market.
Can I get first-half or first-five-innings moneylines?
Yes. Pass the period in periods=, for example periods=1h for basketball and football or periods=f5 for MLB. /v1/leagues lists each league's periods.
How do I pull only moneylines from /v1/odds?
Pass markets=moneyline. Without markets=, /v1/odds returns every market offered for the league, including spreads, totals, alternates, team totals and player props.
Does line history cover period moneylines?
No. /v1/history covers full-game moneyline, spread and total markets. Its prices array follows the outcome order of the market: home then away, or home, draw and away for soccer.
Why do the implied probabilities add up to more than 100%?
Each price includes the book's margin. The excess over 100% is the overround; divide each probability by the sum to remove it, as described in the no-vig guide.