Over/Under Totals Data: Game, Period and Team Totals by API
How over/under totals are modeled in odds data, game totals by period, team totals with their team object, alternate total ladders, consistency checks between them, comparing totals across books, grading pushes and reading totals history.
Totals are the second most common market after the moneyline, and they come in more shapes than any other main line. There is the full-game total, a total for each period, a total for each team, and a ladder of alternate totals around the main number, all of them over/under markets with a line and two prices. For a data product, that means one parsing pattern that covers a lot of ground, and a few relationships between the markets that are worth checking.
This guide covers how totals are represented in the PreciseAPI schema, which leagues and periods they cover, how team totals and alternate totals differ from the main market, how to cross-check them against each other, how to compare totals across books, how to grade them, and how to read totals history. The total glossary entry has the one-paragraph definition.
How a total is represented
A game total is a market with key: "total", a period, a market-level line, and two outcomes with sides over and under. Both outcomes carry the same point:
{
"id": "mkt_0r6ak3wfz1h",
"key": "total",
"period": "full_game",
"line": 47.5,
"outcomes": [
{ "id": "sel_0f8n2yqk7vb", "name": "Over", "side": "over", "point": 47.5, "price": -105 },
{ "id": "sel_1c5tm9xe4ra", "name": "Under", "side": "under", "point": 47.5, "price": -115 }
],
"suspended": false,
"updated_at": "2026-10-13T16:48:09.552Z"
}
Unlike a spread, there is no sign convention to remember: the over and the under share one number. Join on side (over or under) and on the selection id, which is built from the event, market key, period, subject, side and point but not the book, so the same over at the same number has the same id everywhere it is priced.
What the total counts depends on the sport: combined points, runs, goals, games, rounds or maps. The total key is offered for all 17 leagues in the catalog, so the same parser works for an NFL game, a tennis match and a CS2 series.
Game totals by period
Totals follow each league's period list, so a single game can carry several total markets from the same book:
| League | Periods with totals |
|---|---|
| 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 |
Request them explicitly:
curl "https://api.preciseapi.com/v1/odds?league=nba&markets=total&periods=full_game,1h,1q&books=pinnacle,draftkings,fanduel" \
-H "Authorization: Bearer $PRECISEAPI_KEY"
/v1/odds costs 2 credits per request no matter how many events, books or periods come back, so asking for three periods at once costs the same as asking for one. /v1/leagues (free) returns each league's periods and market keys if you would rather build the filter from data.
Team totals
A team total is an over/under on one team's score. The market key is team_total, offered for the eight team-sport leagues: NFL, NCAAF, CFL, NBA, WNBA, NCAAB, MLB and NHL. Its shape is the same as a game total with one addition, a team object that says which team the line belongs to:
{
"id": "mkt_1x0qe7bdn4s",
"key": "team_total",
"period": "full_game",
"line": 24.5,
"team": { "id": "nfl_kc", "name": "Kansas City Chiefs" },
"outcomes": [
{ "id": "sel_1h2u8kd0wqe", "name": "Over", "side": "over", "point": 24.5, "price": -110 },
{ "id": "sel_0p9zr4lt6cm", "name": "Under", "side": "under", "point": 24.5, "price": -110 }
],
"suspended": false,
"updated_at": "2026-10-13T16:47:52.903Z"
}
A book that prices team totals for a game typically returns one team_total market per team, so always read team.id rather than assuming the first market is the home team. Map the id to the event's home or away object when you need the side:
const teamSide = (event, market) =>
market.team?.id === event.home.id ? 'home' : market.team?.id === event.away.id ? 'away' : null;
Alternate totals
Alternate totals are a ladder of other numbers around the main total, each with its own over and under prices. In the feed they are a single alternate_total market per book and period, offered for the same eight leagues as team totals. Every rung is an over/under pair with its own point, so read the numbers from the outcomes:
// One alternate_total market -> [{ point, over, under }] sorted by point
export function totalLadder(market) {
const rungs = new Map();
for (const o of market.outcomes) {
const r = rungs.get(o.point) ?? { point: o.point };
r[o.side] = o.price;
rungs.set(o.point, r);
}
return [...rungs.values()].sort((a, b) => a.point - b.point);
}
Merge in the main total market and you have the book's full over/under curve. Two sanity checks are cheap to run on it: the over's price should get longer as the number goes up, and the under's should get shorter. A rung that breaks the pattern is usually a stale price from a ladder that has not been fully repriced after a move. The point spread guide uses the same approach for spread ladders, including how to measure the price of each half-point.
Cross-checking totals
Because a game carries several related totals, you can check them against each other. These checks are useful as data QA in a pipeline, and as features in an analytics product.
Team totals against the game total
The two team totals at a book should sum to roughly the game total. With a game total of 47.5 and team totals of 24.5 and 22.5, the team totals sum to 47, half a point away. They need not match exactly, because each market is priced and rounded to its own half-point, but a gap of several points means one of the markets is stale or the book is shading one side.
export function teamTotalGap(bookMarkets, period = 'full_game') {
const game = bookMarkets.find((m) => m.key === 'total' && m.period === period);
const teams = bookMarkets.filter((m) => m.key === 'team_total' && m.period === period);
if (!game || teams.length !== 2) return null;
return teams.reduce((sum, m) => sum + m.line, 0) - game.line;
}
Period totals against the full game
A first-half total is some fraction of the full-game total, and that fraction is fairly stable for a league. Rather than hard-coding one, compute the ratio of 1h to full_game lines per league from your own data, then flag any book whose ratio sits far outside the distribution. That catches stale period markets that did not move when the full-game total did.
The same total across books
The simplest check of all: compare each book's main total with the median across books for the same game and period. A single book well away from the median for more than a few minutes is often slow to update. The line shopping post covers freshness filters in more depth.
Comparing totals across books
Books often disagree on the number, which makes totals harder to compare than moneylines. For an over, a lower number is better; for an under, a higher one. When the numbers are equal, compare prices in decimal odds.
/v1/best-lines does this grouping for you. It compares books only when they quote the same market, period, subject and line, so team totals are grouped by team and number, and a game with books split between 47.5 and 48 returns two total entries:
curl "https://api.preciseapi.com/v1/best-lines?league=nfl&markets=total,team_total&periods=full_game" \
-H "Authorization: Bearer $PRECISEAPI_KEY"
It costs 3 credits, needs at least two books quoting a number before that number appears, skips suspended markets, excludes DFS apps, and does not group alternate_total. For ladders, compare rungs yourself from /v1/odds by selection id. The best lines API page summarizes the endpoint.
Margin on a total
Each side's implied probability includes the book's margin, so the two sides sum to more than 1. Using the 47.5 example above, over -105 and under -115:
0.5122 + 0.5349 = 1.0471 -> 4.71% overround
over 0.5122 / 1.0471 = 48.92%
under 0.5349 / 1.0471 = 51.08%
Normalized, that is about +104 on the over and -104 on the under. Measuring this for every total, team total and period across books shows where margins are wider, which is the basis of the hold comparison post. The no-vig guide covers the other de-vig methods, and the implied probability guide shows how to compute overround across a whole response.
Grading totals and pushes
Grading a total compares the final combined score with the point:
export function gradeTotal(point, combined) {
return combined > point ? 'over' : combined < point ? 'under' : 'push';
}
Half-point totals such as 47.5 cannot push. Whole-number totals can, so your grading code, your settlement tables and any backtest need a third state. For team totals, grade against the score of team.id only. Final scores come from /v1/scores (1 credit), which returns live and recently finished events. Finished events are retained for four hours, so store results as games finish; the backtesting post covers building that archive.
Totals history
/v1/history covers the full-game total (alongside the full-game moneyline and spread). Each point has a timestamp t, a line with the total, and prices in outcome order, [over, under]. It costs 3 credits and is included on the Sandbox, Starter, Pro and Enterprise plans.
curl "https://api.preciseapi.com/v1/history?event_id=evt_0uuc6p2dsqa&book=pinnacle&market=total&format=probability" \
-H "Authorization: Bearer $PRECISEAPI_KEY"
As with spreads, record line moves (47.5 to 48) separately from price moves at the same number (over -105 to over -115). Weather in outdoor sports and lineup news in basketball can move totals sharply, so totals history is a good place to look for news-driven moves; the steam move post shows how to detect synchronized moves across books. Team totals, period totals and alternates are not in history, so if you need their movement, snapshot them yourself from /v1/odds or record them from the stream.
Live totals
The same requests cover games in progress. /v1/odds returns scheduled and live events by default, and status=live narrows it to games underway. Event ids do not change when a game starts, so pre-game rows keep joining to live ones. Live totals need a little more care than pre-game ones:
- The number moves with the score and the clock. A live total is effectively "final combined score over or under this point", so it moves every time someone scores and drifts as time runs down. Show the current score and clock next to it; the event object carries
scoreandclockonce the game has started. - Suspensions are frequent. Suspended markets are excluded unless you pass
include_suspended=true, in which case they carrysuspended: true. Never show a suspended total as available. - Polling is too slow for most live products. The WebSocket stream pushes deltas as prices change, at one credit per connection-minute, and the polling versus streaming post works through when each model fits.
Putting it together
A typical totals pipeline looks like this:
- Pull
markets=total,team_total,alternate_totalwith the periods you need from/v1/odds. - Index markets by event, book, key, period and
team.id. - Build ladders from
alternate_totaland merge in the maintotal. - Run the cross-checks above and flag outliers.
- Use
/v1/best-linesor local grouping for the comparison view. - Archive scores and grade with three states.
The game odds API page covers totals alongside moneylines and spreads, the odds screen use case shows how they fit a board, and pricing lists credits per plan. Start with the quickstart.
Frequently asked questions
Which leagues have totals, team totals and alternate totals?
total is offered for all 17 leagues. team_total and alternate_total are offered for NFL, NCAAF, CFL, NBA, WNBA, NCAAB, MLB and NHL. /v1/markets?league= (free) lists the keys for any league.
How do I know which team a team total is for?
Every team_total market carries a team object with the team's id and name. Compare team.id with the event's home.id and away.id to get the side.
Do totals use the same fields as spreads?
Nearly. Totals have a market line and outcomes with point and price, but the sides are over and under, and both outcomes share the same point. Spreads have home and away sides with opposite points.
Can I get first-half and first-quarter totals?
Yes, for leagues that list those periods. Pass periods=1h or periods=1q (NFL and NBA) to /v1/odds. MLB has f5 and the NHL has 1p.
Does line history include team totals?
No. /v1/history covers full-game moneyline, spread and total markets. Snapshot team totals from /v1/odds or record them from the stream if you need their movement.
How should I handle pushes on totals?
Treat them as a third result. Whole-number totals push when the combined score equals the point exactly; half-point totals cannot push.