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:

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:

LeaguePeriods
NFL, NBAfull_game, 1h, 1q
NCAAF, CFL, WNBA, NCAAB, soccerfull_game, 1h
MLBfull_game, f5 (first five innings)
NHLfull_game, 1p
Tennis, PGA, UFC, esportsfull_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:

formatHomeAwayRounding
american-135115Integer
decimal1.7412.15Three places
probability0.57450.4651Four 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:

Common mistakes

A short list of bugs that show up in almost every first moneyline integration:

  1. Reading outcomes by position and assuming two of them, which breaks on soccer.
  2. Joining on team names instead of side or the selection id, which breaks when books spell a team differently.
  3. Averaging American odds across books instead of implied probabilities.
  4. Computing probabilities from format=decimal output, which has already been rounded to three places.
  5. Mixing periods, for example comparing one book's 1h moneyline with another's full_game.

What moneyline data is used for

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.