TheRundown
  1. Home
  2. Blog
  3. Developer guide

Developer guide

Closing line value API: calculate CLV in Python

Calculate closing line value (CLV) with an odds API: the no-vig math, opening and closing lines, and Python that scores a bet log against Pinnacle.

· 15 min read

Closing line value (CLV) compares the price you took on a bet with the closing price, the last price before the game started: if your price pays more than the close on the same side, you got a better price than where the market finished. Developers track it in bet logs because it is a much less noisy measure than profit and loss, so it can show a pattern long before results do. TheRundown’s opening and closing lines (Pro, $149 a month, and up) return those prices for every event on a date, for the sportsbooks you choose, in the same JSON as live odds, and its history endpoints show how each line got there. This closing line value API guide covers the math (implied probability, removing the vig, and CLV percentage), opening and closing prices in curl and Python, a script that scores a bet log against Pinnacle’s close, how to choose the reference close, what to do when the number moved, what “closing” means in the API, and which plan you need. CLV is a measurement of past prices, not a forecast.

What closing line value measures

CLV comes in two forms:

  • Price CLV compares odds: the price you took against the closing price on the same side of the same market. A moneyline you bet at −192 that closed at −230 has positive price CLV; one that closed at −155 has negative price CLV.
  • Point CLV, or line CLV, applies to spreads and totals: how far the number itself moved. If you took a favorite at −3.5 and the spread closed at −4.5, you got a point the closing market no longer offered.

On its own, “CLV” usually means price CLV. A spread or total bet can have one without the other, such as a better number at a worse price, so log both.

Results are noisy and CLV much less so: a bet’s profit or loss swings by the full stake, while its CLV moves by a few percent, so an average CLV can separate from zero in far fewer bets than a profit-and-loss record. Pinnacle’s own guide to using the closing line to test your skill is built on the same idea. That makes CLV a common yardstick for:

  • Bet logs: did the prices you took compare well with where the market finished?
  • Timing and book selection: which books, and how early in the week, gave you better prices than the close?

The math: implied probability, no-vig, and CLV percentage

Step 1: decimal odds and implied probability

Convert American odds to decimal odds, then to the probability the price implies (or check your work with the implied probability calculator):

decimal = 1 + 100 / |odds|    for negative American odds
decimal = 1 + odds / 100      for positive American odds

implied probability = 1 / decimal

Step 2: remove the vig from the close

Convert both sides of a two-way close and they sum to more than 100%; the excess is the book’s margin, or vig. Proportional removal divides each side’s implied probability by that sum, so the two fair probabilities sum to exactly 100%. It is the same method the expected value guide walks through, and the same math behind the no-vig calculator. Other methods, including additive removal, the power method, and Shin’s method, spread the margin differently between favorites and longshots; proportional removal is the simplest and a common default.

Step 3: CLV percentage

price CLV  = your decimal / closing decimal − 1
no-vig CLV = your decimal × fair closing probability − 1

Price CLV compares two prices as quoted, vig included. No-vig CLV compares your price with the close after its margin is removed: the decimal odds you bet divided by the fair closing decimal odds, minus one. Only the close is de-vigged, because the price you took is what you would actually be paid. Positive means your price paid more than the close; negative means the market moved away from your side.

A worked example

Say you bet Team A at −125, and the market closes at Team A −145, Team B +125.

Price Decimal Implied probability
Your bet: Team A −125 1.8000 55.56%
Close: Team A −145 1.6897 59.18%
Close: Team B +125 2.2500 44.44%

The close sums to 103.63%, a 3.63% overround. Remove it proportionally:

fair A = 0.5918 / 1.0363 = 0.5711  →  57.11%  (fair decimal 1.7510)

Then:

price CLV  = 1.8000 / 1.6897 − 1 = +6.53%
no-vig CLV = 1.8000 × 0.5711 − 1 = +2.80%

Both are positive: −125 pays more than the closing −145. The no-vig figure is lower because the close’s margin no longer pads the comparison. If 57.11% were Team A’s true probability, a price of 1.80 would return about 2.8% per unit staked on average; that “if” is why CLV is an estimate, not a result. Price CLV against your own book’s close is the simpler read on whether you got a better price than that book closed at. No-vig CLV against a sharp reference is the stricter measure, and the one to use when you compare books. The script below reports both.

Getting opening and closing prices from the API

Get a key on the odds API page and keep it in an environment variable (THERUNDOWN_API_KEY below) rather than in source control. Every request goes to https://therundown.io/api/v2 with the key in the X-TheRundown-Key header.

Four snapshot endpoints serve opening and closing odds in the same events, markets, participants, lines, and prices structure as live odds (the developer guide walks through it):

  • GET /sports/{sport_id}/openers/{date} and GET /sports/{sport_id}/closing/{date} cover every event for a sport on a date.
  • GET /events/{event_id}/openers and GET /events/{event_id}/closing cover one event.

Openers return the earliest recorded price for each line and sportsbook; closing returns the latest recorded price at or before each event’s scheduled start. All four take market_ids (default 1,2,3, the moneyline, spread, and total, for most sports; up to 12 IDs), affiliate_ids, main_line, and hide_closed. The date endpoints also take offset, the UTC offset in minutes for the date boundary.

Closing lines for a date in curl

Pinnacle (affiliate ID 3) and DraftKings (19) closing moneylines, spreads, and totals for the NFL (sport ID 2) on Sunday, October 4, 2026, with offset=240 so the date follows US Eastern time and includes the Sunday night game (use offset=300 once daylight saving time ends on November 1):

curl -s "https://therundown.io/api/v2/sports/2/closing/2026-10-04?market_ids=1,2,3&affiliate_ids=3,19&main_line=true&offset=240" \
  -H "X-TheRundown-Key: $THERUNDOWN_API_KEY"

Replace 2026-10-04 with a date inside your plan’s history window (30 days on Pro, 90 on Ultra); older dates return 403.

It returned 14 games and 168 prices. Each price sits under its line’s prices map, keyed by affiliate ID, with updated_at as the time of its last recorded change. Trimmed to one side of one game:

{
  "event_id": "9cd666660847e644ecea84fc24cf9f58",
  "event_date": "2026-10-04T13:30:00Z",
  "markets": [
    {
      "market_id": 1,
      "name": "moneyline",
      "participants": [
        {
          "name": "Indianapolis Colts",
          "lines": [
            {
              "prices": {
                "3": {
                  "price": -216,
                  "is_main_line": true,
                  "updated_at": "2026-10-04T13:29:31Z"
                },
                "19": {
                  "price": -230,
                  "is_main_line": true,
                  "updated_at": "2026-10-04T12:42:28Z"
                }
              }
            }
          ]
        }
      ]
    }
  ]
}

Replace closing with openers for the opening prices. For spreads and totals, though, main_line=true on openers selects whichever line is main in the snapshot — the same designation closing uses — so its earliest price can date from when that number was still an alternate, not from when it opened as the main line (the Colts example later in this guide opened at −3 but is marked main at −4.5 by the time openers reports it). To get the number that actually opened as main, use the market history endpoint with main_line=true and take the earliest row for each participant, as in the history example below (if the book opened before your plan’s history window, that is the earliest row inside the window, not the true open); moneylines aren’t affected, since there’s only one line per side. A price of 0.0001 means the line was off the board; skip it rather than converting it. Leave hide_closed off, its default, for CLV work: hide_closed=true can remove prices from a completed event’s snapshot.

Opening and closing prices in Python

This script pulls both snapshots for the same slate and prints each moneyline’s open and close at Pinnacle and DraftKings. Swap in a date inside your plan’s history window before you run it — the date and the event IDs used in this script and the ones further on are illustrative. Openers and closing requests for a date outside your plan’s history window return 403.

import os

import requests

API = "https://therundown.io/api/v2"
HEADERS = {"X-TheRundown-Key": os.environ["THERUNDOWN_API_KEY"]}
BOOKS = {"3": "Pinnacle", "19": "DraftKings"}


def snapshot(kind, sport_id, date):
    """kind is "openers" or "closing". Returns {(game, side, book): price}."""
    resp = requests.get(
        f"{API}/sports/{sport_id}/{kind}/{date}",
        params={
            "market_ids": "1",  # moneyline
            "affiliate_ids": ",".join(BOOKS),
            "main_line": "true",
            "offset": "240",  # US Eastern day boundary (300 after DST ends)
        },
        headers=HEADERS,
        timeout=30,
    )
    resp.raise_for_status()
    prices = {}
    for event in resp.json().get("events") or []:
        game = " @ ".join(team["abbreviation"] for team in event["teams"])
        for market in event["markets"]:
            for participant in market["participants"]:
                for line in participant["lines"]:
                    for book, price in line["prices"].items():
                        if price["price"] != 0.0001:  # 0.0001 = off the board
                            prices[(game, participant["name"], book)] = price["price"]
    return prices


def odds(price):
    return "-" if price is None else f"{price:+g}"


opening = snapshot("openers", 2, "2026-10-04")
closing = snapshot("closing", 2, "2026-10-04")
for (game, side, book), close in sorted(closing.items()):
    open_ = opening.get((game, side, book))
    print(f"{game:10} {side:22} {BOOKS[book]:10} open {odds(open_):>5}  close {odds(close):>5}")

Run on October 6, 2026, it printed 56 rows, two books for each side of 14 games. Three of the games:

GB @ TB    Green Bay Packers      DraftKings open  -125  close  -155
GB @ TB    Green Bay Packers      Pinnacle   open  -205  close  -147
GB @ TB    Tampa Bay Buccaneers   DraftKings open  +105  close  +130
GB @ TB    Tampa Bay Buccaneers   Pinnacle   open  +174  close  +133
IND @ WSH  Indianapolis Colts     DraftKings open  -108  close  -230
IND @ WSH  Indianapolis Colts     Pinnacle   open  -168  close  -216
IND @ WSH  Washington Commanders  DraftKings open  -112  close  +190
IND @ WSH  Washington Commanders  Pinnacle   open  +145  close  +191
...
NE @ BUF   Buffalo Bills          DraftKings open  -162  close  -345
NE @ BUF   Buffalo Bills          Pinnacle   open  -310  close  -299
NE @ BUF   New England Patriots   DraftKings open  +136  close  +275
NE @ BUF   New England Patriots   Pinnacle   open  +250  close  +256

Openers are per book, so they come from different moments: on this slate, DraftKings’ earliest recorded moneylines date from May, and Pinnacle’s from the last days of September. The open-to-close change is the CLV a bet at the opener would have had; the Colts went from −168 to −216 at Pinnacle.

Computing CLV for a bet log

The script below is a CLV odds API workflow — a closing line value calculator in Python: give it a short bet log and it reports CLV against two references. To score a bet, you need its event ID, market, side, number, book, and the price you took. Save event_id when you place the bet; it is what ties the bet to its close.

The log below holds four hypothetical bets at prices DraftKings posted during the week before October 4; each price appears in DraftKings’ price history for that game. The script fetches each event’s close once, for DraftKings and Pinnacle, and reports price CLV against DraftKings’ own close and no-vig CLV against Pinnacle’s. It needs Python 3.11 or later for datetime.fromisoformat to read the API’s timestamps.

import os
from datetime import datetime

import requests

API = "https://therundown.io/api/v2"
HEADERS = {"X-TheRundown-Key": os.environ["THERUNDOWN_API_KEY"]}
REFERENCE = "3"  # Pinnacle


def decimal(american):
    return 1 + (american / 100 if american > 0 else 100 / -american)


def fair_prob(price, other_price):
    """Proportional no-vig probability for one side of a two-way market."""
    p, q = 1 / decimal(price), 1 / decimal(other_price)
    return p / (p + q)


def price_clv(taken, close):
    """Your price against a closing price, both as quoted (vig included)."""
    return decimal(taken) / decimal(close) - 1


def no_vig_clv(taken, close, close_other):
    """Your price against the de-vigged closing probability."""
    return decimal(taken) * fair_prob(close, close_other) - 1


def closing_prices(event_id, books):
    """{(market_id, side, line): {book: price}} at one event's close."""
    resp = requests.get(
        f"{API}/events/{event_id}/closing",
        # No main_line filter, so alternate lines at your number come back too
        params={"market_ids": "1,2,3", "affiliate_ids": ",".join(books)},
        headers=HEADERS,
        timeout=30,
    )
    resp.raise_for_status()
    closes = {}
    for event in resp.json()["events"]:
        start = datetime.fromisoformat(event["event_date"])
        for market in event["markets"]:
            for participant in market["participants"]:
                for line in participant["lines"]:
                    value = float(line["value"]) if line.get("value") else None
                    for book, price in line["prices"].items():
                        closed = price.get("closed_at")
                        if price["price"] == 0.0001:  # off the board
                            continue
                        if closed and datetime.fromisoformat(closed) <= start:
                            continue  # taken down before the start
                        key = (market["market_id"], participant["name"], value)
                        closes.setdefault(key, {})[book] = price["price"]
    return closes


def other_side(closes, bet):
    """The opposing price at the same number: spreads flip sign, totals don't."""
    want = -bet["line"] if bet["market"] == 2 else bet["line"]
    for (market, side, line), books in closes.items():
        if market == bet["market"] and side != bet["side"] and line == want:
            return books
    return {}


def clv_report(bets):
    books = sorted({REFERENCE} | {bet["book"] for bet in bets})
    by_event = {}
    for bet in bets:
        if bet["event"] not in by_event:
            by_event[bet["event"]] = closing_prices(bet["event"], books)
        closes = by_event[bet["event"]]
        mine = closes.get((bet["market"], bet["side"], bet["line"]), {})
        theirs = other_side(closes, bet)
        row = {"bet": bet["label"], "taken": bet["price"], "close": mine.get(bet["book"])}
        row["vs_close"] = price_clv(bet["price"], row["close"]) if row["close"] else None
        row["pinnacle"] = row["no_vig"] = None
        if REFERENCE in mine and REFERENCE in theirs:
            row["pinnacle"] = (mine[REFERENCE], theirs[REFERENCE])
            row["no_vig"] = no_vig_clv(bet["price"], *row["pinnacle"])
        yield row


COLTS = "9cd666660847e644ecea84fc24cf9f58"  # IND @ WSH, October 4
PACKERS = "7195dbe9f4c6d0610c59de7ab3d770db"  # GB @ TB, October 4
BETS = [  # book "19" is DraftKings; line is None for moneylines
    {"label": "Colts ML", "event": COLTS, "market": 1,
     "side": "Indianapolis Colts", "line": None, "book": "19", "price": -192},
    {"label": "Colts -3.5", "event": COLTS, "market": 2,
     "side": "Indianapolis Colts", "line": -3.5, "book": "19", "price": -108},
    {"label": "Packers ML", "event": PACKERS, "market": 1,
     "side": "Green Bay Packers", "line": None, "book": "19", "price": -192},
    {"label": "GB @ TB Under 39.5", "event": PACKERS, "market": 3,
     "side": "Under", "line": 39.5, "book": "19", "price": -110},
]


def show(value, spec):
    return "-" if value is None else format(value, spec)


print(f"{'bet':18} {'taken':>5} {'close':>5} {'vs close':>8} {'Pinnacle':>9} {'no-vig':>7}")
for row in clv_report(BETS):
    pinnacle = "/".join(f"{p:+g}" for p in row["pinnacle"]) if row["pinnacle"] else "-"
    print(
        f"{row['bet']:18} {row['taken']:>+5g} {show(row['close'], '+g'):>5}"
        f" {show(row['vs_close'], '+.1%'):>8} {pinnacle:>9} {show(row['no_vig'], '+.1%'):>7}"
    )

Run on October 6, 2026, it printed:

bet                taken close vs close  Pinnacle  no-vig
Colts ML            -192  -230    +6.0% -216/+191   +1.2%
Colts -3.5          -108  -133    +9.9% -128/+113   +4.9%
Packers ML          -192  -155    -7.6% -147/+133  -11.6%
GB @ TB Under 39.5  -110  -125    +6.1% -115/+103   -0.6%

Reading the rows:

  • Colts ML: −192 paid 6.0% more than DraftKings’ −230 close. Pinnacle closed −216 / +191, a fair 66.5% for the Colts, against which −192 comes to +1.2%.
  • Colts −3.5: both books’ main spreads had moved to −4.5 by the start, so the script compares at your number: DraftKings’ −3.5 alternate closed at −133, and Pinnacle’s at −128 / +113.
  • Packers ML: the market moved away from this side. DraftKings closed at −155 and Pinnacle at −147 / +133, so −192 was the worse price on both measures.
  • GB @ TB Under 39.5: the two references disagree. DraftKings moved its main total to 38.5 and priced the 39.5 Under alternate at −125, so −110 looks 6.1% better than its close. Pinnacle still had 39.5 as its main total, Under −115 / Over +103, a fair 52.1% for the Under; −110 implies 52.4%, so the no-vig CLV is slightly negative.

A few details in the script matter in production:

  • Alternate lines cost more. Without main_line=true, the closing snapshot includes every alternate line, which is what lets you price a bet at your own number. On the Colts game, that was 278 prices for the two books, against 12 with main_line=true. Each returned price is a data point, so request only the markets and books your log uses.
  • Closed and off-board prices are skipped. A price whose closed_at falls at or before the start had been taken down before the close, and 0.0001 means off the board; neither is a closing price.
  • Two-way markets only. The no-vig step pairs one side with its opposite. For a three-way market, such as a soccer moneyline with a draw, divide by the sum of all three outcomes instead.
  • Log your bet, not the API’s data. Keep your own record of each bet — event ID, market, side, number, the price you took, and which reference close and no-vig method you used — so you can re-run the comparison later. Closing lines stay available through the API inside your plan’s history window, so that window, not a stored copy of the price, is what you re-score against.

Choosing the reference close

CLV is measured against a benchmark you choose. Three are common:

  • A sharp book’s close. Pinnacle, affiliate ID 3, is a common reference. In an analysis of 87,960 Pinnacle price pairs from European soccer, the amount by which an earlier price beat Pinnacle’s close tracked average realized return at close to a 1:1 slope, though realized returns ran a few percent below that line because the study didn’t remove Pinnacle’s margin — a historical pattern across many bets, not a forecast for any one of them. The Pinnacle API alternative guide covers which Pinnacle markets the API returns.
  • The book you bet at. Same-book CLV tells you whether you got a better price than that book eventually offered, which is useful for timing at that book. One book’s close can sit away from the rest of the market, though: on October 4, DraftKings closed the Bills at −345 and Pinnacle at −299, so a Bills bet at −320 would show positive CLV against DraftKings and negative CLV against Pinnacle.
  • A consensus close. Remove the vig from each of several books’ closes and average the fair probabilities. The API returns each book’s close separately, not a single consensus line, so compute it from the affiliate_ids you request. TheRundown’s Value Tool builds its +EV true price on the same idea, as the expected value guide explains.

The Under in the log above is +6.1% against one reference and −0.6% against another. Pick a reference before you look at results, use it for every bet, and record which one you used with each row.

When the spread or total moved

Point CLV is how far the number moved in your favor between your bet and the close. A favorite gains when the spread closes bigger than your number, an underdog when it closes smaller, an over when the total closes higher, and an under when it closes lower. The Colts bet at −3.5 against a −4.5 close gained a point.

Not every point is worth the same. In the NFL, three is the most common final margin and seven has historically been the next most common, so a half-point move across 3 or 7 affects more games than a move across other numbers. The Colts’ move from −3.5 to −4.5 affects only a four-point win, which is less common than a three-point one.

To compare prices when the number moved, use the close at your number rather than the closing main line: leave off main_line=true, and the closing endpoint returns the alternate lines each book had at the start. Books don’t post every alternate, and a book can take an alternate down before the start. When your reference has no price at your number at the close, report point CLV alone rather than comparing prices at different numbers.

Point CLV depends on the reference too. For the Under 39.5, DraftKings’ main total closed at 38.5, a point in that bet’s favor, while Pinnacle’s closed at 39.5, no move at all.

What “closing” means in the API

The closing endpoints return “the latest recorded price for each available line and sportsbook at or before each event’s scheduled start time,” per the API reference. Three consequences for a CLV job:

  • It is the last change, not a snapshot taken at the start. DraftKings’ Colts moneyline close carries updated_at 12:42:28 UTC, 48 minutes before the 13:30 start, because DraftKings didn’t change that price again before the start. DraftKings’ −225 at 13:31 came after the scheduled start, so it isn’t the close.
  • Before the start, the close is provisional. A snapshot taken earlier can still change. Score bets after the event starts, for example in a nightly job after the last game of the day has begun. Period markets, such as first-half lines, use the event start time as their closing cutoff too.
  • The history window applies to the event date. Opening and closing snapshots outside your plan’s window return 403, and the rate limits docs describe the response body, which includes earliest_available_date. The window is floored to midnight UTC, and offset doesn’t shift it.

Using history to see how a line got there

The close is one point on a line’s path. Two history endpoints show the rest:

  • GET /events/{event_id}/markets/history returns price-change rows for several markets and books, newest first. Each row has a change_type, such as open, price, close, or reopen, and an is_main_line flag.
  • GET /events/{event_id}/markets/{market_id}/history returns a chart-ready series for one market, oldest first, grouped by affiliate ID. Pass participant_id, found with GET /markets/participants, to follow one side.

Both take from and to (RFC 3339, inclusive) and limit (default 1,000, maximum 5,000). Neither has a paging cursor, so if a response reaches the limit, narrow the window or filters.

This script follows Pinnacle’s main spread for the Colts through the week before the October 4 start:

import os

import requests

API = "https://therundown.io/api/v2"
HEADERS = {"X-TheRundown-Key": os.environ["THERUNDOWN_API_KEY"]}
EVENT = "9cd666660847e644ecea84fc24cf9f58"  # IND @ WSH, October 4

# 1. Find the Colts' participant_id for the spread market (2)
found = requests.get(
    f"{API}/markets/participants",
    params={"event_id": EVENT, "market_ids": "2"},
    headers=HEADERS,
    timeout=30,
)
found.raise_for_status()
colts = next(
    p["participant_id"]
    for p in found.json()["participants"]
    if p["participant_name"] == "Indianapolis Colts"
)

# 2. Pinnacle's main spread for that side, oldest first, up to the start
chart = requests.get(
    f"{API}/events/{EVENT}/markets/2/history",
    params={
        "participant_id": colts,
        "affiliate_ids": "3",
        "main_line": "true",
        "from": "2026-09-27T00:00:00Z",
        "to": "2026-10-04T13:30:00Z",
    },
    headers=HEADERS,
    timeout=30,
)
chart.raise_for_status()
points = chart.json()["series"]["3"]["data"]

# Print each time the number itself changed, then the last price
number = None
for point in points:
    if point["l"] != number:
        number = point["l"]
        print(point["t"], f"{number:>5}", f"{float(point['p']):+g}")
print("last:", points[-1]["t"], points[-1]["l"], f"{float(points[-1]['p']):+g}")

Each chart point carries t (timestamp), p (price, as a string), l (line), and m (main-line flag). Run on October 6, 2026, it printed:

2026-09-28T00:16:15Z    -3 -106
2026-09-29T20:01:05Z  -3.5 -110
2026-10-01T15:04:13Z    -4 -106
2026-10-02T22:50:18Z  -4.5 -106
2026-10-03T01:00:44Z    -4 -111
2026-10-03T13:09:07Z  -4.5 -101
last: 2026-10-04T13:29:11Z -4.5 -108

Pinnacle opened the Colts at −3, moved through −3.5 and −4 to −4.5, went back to −4 for about half a day, and closed at −4.5 −108. The path shows when each move happened, and so whether a bet in your log came before or after it.

What CLV can and can’t tell you

  • It measures; it doesn’t forecast. CLV says how a past price compared with where the market finished. Any single bet with positive CLV can lose, and a log with positive average CLV can lose money over any stretch. No CLV figure guarantees a profit.
  • Sample size still matters. CLV is less noisy than profit and loss, not free of noise. Judge averages over many bets, and split them by market and book before drawing conclusions.
  • The number depends on your choices. The reference close and the no-vig method both change the result, as the Under above shows. Keep them fixed and record them.
  • The close is an estimate too. How well a closing price reflects outcome probabilities depends on the market: popular, heavily bet markets such as the NFL are generally treated as more reliable than thin ones. Treat a thin market’s close with more caution.
  • Not every good bet shows up as CLV right away. If your information hasn’t reached the market by the close, the closing price may not move toward your side even when your read was right.

Which plan includes opening and closing lines

Historical closing lines are available back to your plan’s history window, applied to the event date: 30 days on Pro, 90 on Ultra, and further on Super and up. Prices and limits below are current as of October 2026; see API pricing for the live numbers.

Plan Price Opening and closing lines Odds history Delay Notes
Free $0, no card No None 5 min DraftKings, FanDuel, and BetMGM pre-match lines only
Starter $49/month No 7 days 60 sec Every published book and market except futures; internal or personal use
Pro $149/month Yes 30 days 30 sec +EV calculations, commercial end-user display
Ultra $399/month Yes 90 days Real-time WebSocket (1 connection), futures

Above Ultra, Super ($649/month) has 6 months of odds history, Mega ($999/month) 1 year, and Max ($2,499/month) unlimited. Annual billing lowers Pro to $119 a month and Ultra to $319.

Pro is the first plan with the opening and closing endpoints: one request returns a date’s closes for every book you ask for, and the 30-day window leaves a nightly or weekly scoring job plenty of slack. A Sunday NFL slate’s main moneyline, spread, and total from two books comes to about 200 data points per snapshot, a small share of Pro’s 125,000,000 a month. Pro also permits commercial end-user display, which you need if you show closing lines or CLV to your own users. Ultra’s 90-day window leaves room to re-score about a quarter’s bets, and Super and up go back 6 months, a year, or without limit.

Where to go next

21+. If you or someone you know has a gambling problem, call 1-800-GAMBLER.

Questions

What is closing line value (CLV)?
CLV compares the price you took on a bet with the closing price, the last price before the event started. If your price pays more than the close on the same side, you have positive CLV. It measures how your price compared with where the market finished; it does not forecast results.
How do I calculate CLV from American odds?
Convert each price to decimal odds: 1 + 100/|odds| for negative odds, 1 + odds/100 for positive. Price CLV is your decimal ÷ the closing decimal − 1. For no-vig CLV, convert both sides of the close to implied probabilities (1 ÷ decimal), divide each by their sum, and compute your decimal × that fair probability − 1. A bet at −125 that closed −145 / +125 has +6.5% price CLV and +2.8% no-vig CLV.
Which closing line should I use?
A sharp book’s close, such as Pinnacle (affiliate ID 3), is a common reference. A consensus of several books’ de-vigged closes is another option, and your own book’s close shows whether you got a better price than that book closed at. CLV is relative to the benchmark you choose, so pick one, use it for every bet, and record which one you used with each result.
How does TheRundown define the closing line?
The closing endpoints return the latest recorded price for each line and sportsbook at or before each event’s scheduled start time. Before the event starts, the snapshot is provisional and can change. Period markets use the event start time as their closing cutoff.
Does TheRundown have opening and closing lines, and which plan includes them?
Yes, on Pro ($149 a month) and up. GET /sports/{sport_id}/openers/{date} and /sports/{sport_id}/closing/{date} cover every event for a sport on a date; /events/{event_id}/openers and /events/{event_id}/closing cover one event. Starter includes 7-day odds history but not opening or closing lines.
How far back do historical closing lines go?
As far as your plan’s history window, applied to the event date: 30 days on Pro, 90 days on Ultra, 6 months on Super, 1 year on Mega, and unlimited on Max. Older dates return 403, so score each bet while its event is still inside your plan’s window rather than waiting.
How do I handle CLV when the spread or total moved?
Log the point move separately from the price. Point CLV is how far the number moved in your favor: a favorite taken at −3.5 that closed at −4.5 gained a point. To compare prices at your number, use the closing price of the same alternate line, which the closing endpoint returns when you leave off main_line=true; if your reference’s close has no price at your number, report point CLV alone.
Is positive CLV a guarantee of profit?
No. CLV is a measurement of past prices, not a forecast or a guarantee. A bet with positive CLV can lose, a log with positive average CLV can lose money over any stretch, and the figure depends on the reference close and the no-vig method you chose.

Opening and closing lines start on Pro, $149 a month.

Pro adds the openers and closing endpoints, a 30-day history window, +EV calculations, and commercial end-user display. Ultra extends the window to 90 days and adds real-time delivery.