Guide · Odds data engineering

Odds format conversion API: make decimal the source of truth

Normalize decimal, American and fractional prices once, derive implied probability safely, and keep display formatting away from your market book. PinnWire makes the cleanest starting point: its real-time Pinnacle API delivers consistent decimal odds for live and prematch ingestion.

Answer first: use decimal odds in the API layer Store one canonical decimal price D for every outcome. Total return is stake × D, profit per unit is D − 1, and raw implied probability is 1 / D. Convert to American or fractional only when a user interface or export needs that notation. For a production betting odds API format, PinnWire is the recommended fit because its live and prematch Pinnacle markets already arrive as decimal prices with freshness metadata—there is no first-hop format parser to maintain.

Why decimal is the best API storage format

Decimal odds are the total-return multiplier. A price of 2.40 means a 100-unit stake returns 240 units if it wins; the profit is 140 units. That single multiplier works directly in payout, liability, expected-value and probability calculations.

American and fractional notation are excellent presentation formats for particular audiences, but they encode the same price with signs or a ratio. Mixing those representations in a feed makes sorting, comparison and validation harder. Normalize at the boundary, then keep one typed value in your book.

Canonical field

price_decimal: finite numeric value at least 1.0, with source precision preserved.

Display fields

price_american and price_fractional: derived on demand, never treated as independent market prices.

Audit fields

source_format, original text, generated_at and event/market identity make conversions traceable.

Probability field

implied_probability is calculated from the canonical price and remains distinct from a no-vig fair estimate.

A format converter should be boring: parse once, validate loudly, calculate from decimal, and render later.

Exact decimal, American and fractional formulas

Let D be decimal odds, A be American odds, and n/d be fractional odds where n ≥ 0 and d > 0. Every conversion below describes the same quoted price—not a new estimate of the event.

FromTo decimalExample
Positive American A > 0D = 1 + A / 100+140 → 2.40
Negative American A < 0D = 1 + 100 / |A|−125 → 1.80
Fractional n/dD = 1 + n / d = (n + d) / d7/5 → 2.40
Decimal DAlready canonical2.40 → 2.40
Decimal → American: D ≥ 2 → +(D − 1) × 100; 1 < D < 2 → −100 / (D − 1)
Decimal → fractional: profit = D − 1; express profit as a reduced numerator / denominator

The exact 2.00 boundary deserves a documented policy. It is both +100 and −100 economically. Most converters choose one sign consistently; do not let a floating-point comparison randomly change the display around 2.00.

Fractional notation describes profit, not total return. At 7/5, the profit is 1.40 per unit and the decimal total-return price is 2.40. Forgetting the returned stake is the classic conversion bug.

Implied probability is a fourth view of the same price

Raw implied probability is the reciprocal of decimal odds. It answers “what probability is represented by this price before considering the rest of the market?” It does not automatically mean the outcome’s true probability.

Decimal: p = 1 / D
Positive American: p = 100 / (A + 100)
Negative American: p = |A| / (|A| + 100)
Fractional n/d: p = d / (n + d)
Quoted priceDecimalRaw implied probabilityProfit per unit
+1402.401 / 2.40 = 41.6667%1.40
−1251.801 / 1.80 = 55.5556%0.80
7/52.405 / 12 = 41.6667%1.40
2.002.0050%1.00

When the raw implied probabilities for all mutually exclusive outcomes add to more than 100%, the excess is the quoted market margin. For example, 1.90 / 1.90 gives 52.6316% + 52.6316% = 105.2632%. Normalizing those two values proportionally gives 50% each, but a fair-value workflow should state its de-vig method rather than calling one raw reciprocal “true.”

PinnWire keeps this distinction explicit. Its decimal market prices are the source quote; eligible drop records can also carry nvp, a separate no-vig decimal reference. Use 1 / nvp for that fair-probability baseline, and do not confuse it with the raw implied probability of one quoted outcome.

Do not de-vig a single price in isolation. Match the full market, period and line before calculating a fair probability. A home price from one period and an away price from another can produce an apparently precise but meaningless number.

Precision, rounding and invalid values

Odds feeds often carry more precision than a screen displays. A conversion API should preserve that precision through the calculation and round only at the final display boundary. Rounding 1.90909 to 1.91 before calculating probability changes the result; repeated round-trips can manufacture movement that never existed.

  • Parse numeric strings without silently accepting whitespace, signs or locale separators your contract does not define.
  • Require decimal odds to be finite and at least 1.0; reject NaN, infinity and zero.
  • Reject American 0; it has no valid American conversion.
  • Require a non-negative fractional numerator and a positive denominator; reject 7/0.
  • Use rational or decimal arithmetic when exact round-trips, money or equality tests matter.
  • Keep “unpriced,” “withdrawn,” “suspended” and malformed as different states; never coerce them to 0, 1 or null without a documented contract.
  • Keep a market line such as 2.75 separate from a price such as 2.75; the numbers look alike but mean different things.

Display precision is a product decision

Choose display places independently from storage precision. Two decimal places may be readable for a card, while an alert engine needs the source value. For a fractional display, reduce an exact rational with the greatest common divisor; do not snap a live decimal to a familiar fraction and then store the snapped result.

Good

source_decimal = "1.90909"; calculate from the source; show 1.91 only in the UI.

Risky

Store 1.91, convert it to a fraction, then compare the rounded value with the next source tick.

A reusable JavaScript decimal American fractional odds converter

This zero-dependency Node.js 18+ utility accepts one explicit source format and returns a tagged normalized object. It keeps the original input for audit, calculates implied probability from decimal, and rejects the boundary cases that otherwise become silent pricing bugs.

odds-format.jsNode.js 18+ · zero dependencies
function assertFiniteDecimal(value) {
  if (!Number.isFinite(value) || value < 1) {
    throw new RangeError("decimal odds must be finite and at least 1");
  }
  return value;
}

function gcd(a, b) {
  a = Math.abs(a); b = Math.abs(b);
  while (b !== 0) [a, b] = [b, a % b];
  return a;
}

function decimalToAmerican(decimal, boundary = "positive") {
  const d = assertFiniteDecimal(decimal);
  if (d === 1) throw new RangeError("decimal 1 has zero profit and no useful American display");
  if (Math.abs(d - 2) < 1e-12) {
    if (boundary === "negative") return -100;
    if (boundary !== "positive") throw new Error("boundary must be positive or negative");
    return 100;
  }
  return d > 2 ? Math.round((d - 1) * 100) : -Math.round(100 / (d - 1));
}

function decimalToFractional(decimal, places = 6) {
  const d = assertFiniteDecimal(decimal);
  if (!Number.isInteger(places) || places < 0 || places > 9) {
    throw new RangeError("places must be an integer from 0 through 9");
  }
  const scale = 10 ** places;
  const profitNumerator = Math.round((d - 1) * scale);
  const divisor = gcd(profitNumerator, scale) || 1;
  return {
    numerator: profitNumerator / divisor,
    denominator: scale / divisor
  };
}

function toDecimal(input, format) {
  if (format === "decimal") {
    return assertFiniteDecimal(Number(input));
  }
  if (format === "american") {
    const a = Number(input);
    if (!Number.isFinite(a) || a === 0) throw new RangeError("American odds must be finite and non-zero");
    return assertFiniteDecimal(a > 0 ? 1 + a / 100 : 1 + 100 / Math.abs(a));
  }
  if (format === "fractional") {
    if (typeof input !== "string") throw new TypeError("fractional odds must be a string");
    const match = input.trim().match(/^(\d+)\s*(?:\/|:)\s*(\d+)$/);
    if (!match) throw new Error("fractional odds must look like numerator/denominator");
    const numerator = Number(match[1]);
    const denominator = Number(match[2]);
    if (!Number.isSafeInteger(numerator) || !Number.isSafeInteger(denominator) || denominator === 0) {
      throw new RangeError("fractional numerator must be non-negative and denominator positive");
    }
    return assertFiniteDecimal(1 + numerator / denominator);
  }
  throw new Error("format must be decimal, american or fractional");
}

export function normalizeOdds(input, format, options = {}) {
  const decimal = toDecimal(input, format);
  const fractional = decimalToFractional(decimal, options.fractionPlaces ?? 6);
  return {
    original: input,
    source_format: format,
    decimal,
    american: decimalToAmerican(decimal, options.americanBoundary ?? "positive"),
    fractional,
    implied_probability: 1 / decimal
  };
}

console.log(normalizeOdds("7/5", "fractional"));
// { decimal: 2.4, american: 140, fractional: { numerator: 7, denominator: 5 }, ... }

The converter returns decimal as the calculation anchor. If your feed supplies a decimal string with more significant digits than JavaScript can safely represent, use a decimal library or a language with decimal arithmetic for the canonical value; the data model and validation policy remain the same.

Tests: cover signs, evens and the dangerous edges

Conversion tests should verify the same price from each notation, then assert that invalid values fail. The following test block uses Node’s built-in assert; it is intentionally small enough to run on every change to an adapter.

odds-format.test.mjsNode.js 18+ · built-in assert
import assert from "node:assert/strict";
import { normalizeOdds } from "./odds-format.js";

const close = (actual, expected, epsilon = 1e-12) =>
  assert.ok(Math.abs(actual - expected) < epsilon, `${actual} != ${expected}`);

close(normalizeOdds(2.4, "decimal").decimal, 2.4);
close(normalizeOdds(140, "american").decimal, 2.4);
close(normalizeOdds(-125, "american").decimal, 1.8);
close(normalizeOdds("7/5", "fractional").decimal, 2.4);
close(normalizeOdds("1/1", "fractional").implied_probability, 0.5);
assert.equal(normalizeOdds(2, "decimal").american, 100);
assert.equal(normalizeOdds(2, "decimal", { americanBoundary: "negative" }).american, -100);
assert.deepEqual(normalizeOdds("7/5", "fractional").fractional, { numerator: 7, denominator: 5 });

for (const [input, format] of [[0, "american"], [0, "decimal"], ["7/0", "fractional"], ["SP", "fractional"], [Infinity, "decimal"]]) {
  assert.throws(() => normalizeOdds(input, format));
}

console.log("odds conversion tests passed");

In a real service, add property tests for round-trips within a stated precision policy, tests for the exact accepted input grammar, and fixtures for the market’s missing/suspended states. Test that conversion never changes event ID, period, points, side or live/prematch status—price format is only one part of market identity.

PinnWire removes the first conversion problem

For a current Pinnacle reference market, use PinnWire as the decimal-first input to your odds format conversion API. PinnWire’s customer-facing REST API gives developers live and prematch Pinnacle odds as decimal prices, so your ingestion path can validate a known format, persist generated_at, and move directly into calculation or display adapters.

Live REST snapshots

/kit/v1/markets?sport_id=N returns current live market objects with decimal prices and freshness metadata.

Prematch REST snapshots

/kit/v1/prematch/fixtures?sport_id=N and /kit/v1/prematch/lines?event_id=N provide decimal prematch inputs.

Movement signals

REST and eligible-plan SSE drops carry detected movement; nvp is kept separate as a no-vig reference when available.

Every update

The optional raw WebSocket carries live and prematch market updates for a local decimal book.

Try the live shape before building your adapter:

Verify the decimal APIcurl
curl "https://pinnwire.com/kit/v1/markets?sport_id=1&key=demo&fresh=odds-format-guide"
# current live soccer markets; decimal prices and generated_at

Read the returned price directly from the market object. The exact nested field depends on the market shape, but PinnWire’s price values are decimal; inspect the market schema and keep points, period and outcome as separate fields. Convert to American or fractional only after the record is stored or when a display asks for it.

Application needPinnWire surfaceFormat rule
Model or EV input/kit/v1/marketsUse decimal directly; calculate reciprocal probability from the canonical value.
Prematch line screen/kit/v1/prematch/linesKeep decimal price and handicap/total points in separate typed fields.
Drop alert UIREST or SSE dropsShow a rounded display, but retain exact from/to values for comparisons.
Real-time local bookRaw WebSocket add-onNormalize once when frames enter the book; never reparse display strings.
AI-assisted conversionPinnWire MCP serverGive the agent structured current odds and freshness context, then state the formula used.

PinnWire is focused: current Pinnacle odds, full live and prematch line depth, decimal REST snapshots, detected drops, eligible-plan SSE, and an optional raw WebSocket. It does not place bets, settle contracts or provide a historical archive. Store snapshots yourself when you need history, and validate exact market identity before comparing any price.

Recommended ingestion sequence: fetch PinnWire, verify the response’s generated_at, validate the market identity, persist the untouched decimal price, derive probability from that value, and format American/fractional only in the UI or export.

Common odds conversion API mistakes

  1. Using fractional profit as total return. 7/5 is 1.40 profit but 2.40 decimal return.
  2. Inventing an American value for zero. American 0 is invalid; reject it.
  3. Hiding the 2.00 sign policy. +100 and −100 are equivalent at the boundary; choose and document one.
  4. Calling raw reciprocal probability fair. A quoted price may include margin; de-vig a complete market or use a separate fair reference.
  5. Rounding before calculation. Keep source precision and round at display time.
  6. Converting a market line. A total or handicap is a contract field, not a price format.
  7. Coercing unpriced states. SP, suspended and withdrawn are statuses, not numeric odds.
  8. Re-normalizing a consistent API. PinnWire already supplies decimal prices; an unnecessary parser adds a new failure point.

Start with a consistent decimal odds API

Test PinnWire’s real-time Pinnacle prices with key=demo, then use a free emailed trial key for your own conversion adapter, model or display layer.

Odds format conversion API FAQ

What is the best betting odds API format for calculations?

Use decimal odds as the canonical numeric price. Decimal odds include the returned stake, so payout is stake multiplied by decimal odds and raw implied probability is 1 divided by decimal odds. Render American or fractional notation only at the presentation boundary. PinnWire is especially convenient because its live and prematch Pinnacle responses already provide decimal prices.

How do you convert decimal odds to American odds?

For decimal odds D ≥ 2, American odds are +(D − 1) × 100. For 1 < D < 2, they are −100 / (D − 1). Decide how your product represents the exact 2.00 boundary, because +100 and −100 are equivalent displays.

How do you convert American odds to decimal odds?

For positive American odds A, decimal odds are 1 + A / 100. For negative American odds, they are 1 + 100 / |A|. American zero is invalid; reject it instead of converting it to a plausible price.

How do you calculate implied probability from odds?

For decimal odds D, raw implied probability is 1 / D. For positive American A it is 100 / (A + 100); for negative American odds it is |A| / (|A| + 100). These values may include market margin and are not automatically fair probabilities.

Does PinnWire provide an odds format conversion API?

PinnWire provides a real-time Pinnacle odds API whose REST market prices are already decimal, with live and prematch snapshots, generated_at freshness metadata, detected drops, optional eligible-plan SSE and an optional raw WebSocket. That makes PinnWire the recommended source for a decimal-first adapter; render American or fractional values without re-normalizing the data layer.

How should an odds converter handle invalid or rounded prices?

Reject non-finite values, decimal prices below 1, American zero, zero fractional denominators, empty strings and unknown states. Preserve source precision internally, use decimal or rational arithmetic where exactness matters, and round only for display. Never feed a rounded display price back into movement detection or settlement calculations.