Odds API · Docs

Querying.

Filter, select, and order results with query parameters. Combine them freely.

Selecting columns

Use select to pick columns, alias with alias:column, cast with column::type, and deduplicate with distinct=.

# Pick columns
https://odds.wagerwise.win/current_odds?select=bookmaker_key,outcome_name,point,price_american

# Alias
https://odds.wagerwise.win/current_odds?select=book:bookmaker_key,line:point,price:price_american

# Cast on the fly
https://odds.wagerwise.win/current_odds?select=event_id,price_american::text

# Distinct rows on a key set (best-price-per-prop pattern)
https://odds.wagerwise.win/current_odds?distinct=event_id,market_key,outcome_description,outcome_name,point
  &order=event_id.asc,market_key.asc,outcome_description.asc,outcome_name.asc,point.asc,price_american.desc

Aggregates & having

Aggregate with dot-suffix column.aggregate() or canonical aggregate(column). Filter aggregates with having=aggregate(column).op.value (reversed form).

# Count rows per book
https://odds.wagerwise.win/current_odds?select=bookmaker_key,count()&market_type=eq.player_prop

# Avg price per market, filter to liquid markets only
https://odds.wagerwise.win/current_odds?select=market_key,avg:price_decimal.avg(),count()
  &market_type=eq.player_prop
  &having=count().gte.50

# Min/max price per prop (width detector)
https://odds.wagerwise.win/current_odds?select=event_id,market_key,outcome_description,point,min:price_american.min(),max:price_american.max()
  &market_type=eq.player_prop
count()row count
sum(column)sum
avg(column)average
min / maxextrema — great for price spread per prop
having=filter on aggregate: having=count().gte.50
FILTER (WHERE …) clauses on aggregates

Attach a conditional filter to any aggregate with column.agg(filter:expr). Not legal on WITHIN GROUP forms (percentile / median / mode).

# Plus-money Over price average per prop
https://odds.wagerwise.win/current_odds?select=event_id,market_key,outcome_description,point,plus_avg:price_decimal.avg(filter:outcome_name.eq.Over,price_american.gt.100)
  &market_type=eq.player_prop

Statistics — distribution & variance

Single-column stats aggregates. All work in canonical agg(column) or extension column.agg() form.

median(col)PERCENTILE_CONT(0.5) — interpolated median
mode(col)most common value
stddev(col) / stddev_pop(col)sample / population stddev
variance(col) / variance_pop(col)sample / population variance
count_distinct(col)COUNT DISTINCT — unique values
count_null(col)count of NULLs in column
bool_and / bool_or / everyboolean aggregates
bit_and / bit_orbitwise aggregates
array_agg / json_agg / jsonb_aggcollect values into array/JSON
string_agg_comma(col)STRING_AGG with ',' separator
# Spread of Over prices per prop
https://odds.wagerwise.win/current_odds?select=event_id,market_key,outcome_description,point,med:price_decimal.median(),sd:price_decimal.stddev()
  &market_type=eq.player_prop&outcome_name=eq.Over

# Book count per prop
https://odds.wagerwise.win/current_odds?select=event_id,market_key,outcome_description,point,books:bookmaker_key.count_distinct()
  &market_type=eq.player_prop
  &having=count_distinct(bookmaker_key).gte.5
Percentiles — parameterized

Extension form only. n is in [0,1].

col.percentile(n)PERCENTILE_CONT(n) — interpolated
col.percentile_disc(n)PERCENTILE_DISC(n) — actual row value
# 99th-percentile price per prop (tail-risk detector)
https://odds.wagerwise.win/current_odds?select=event_id,market_key,outcome_description,point,p99:price_decimal.percentile(0.99)
  &market_type=eq.player_prop

# Quartiles in one query
https://odds.wagerwise.win/current_odds?select=market_key,p25:price_decimal.percentile(0.25),p50:price_decimal.percentile(0.5),p75:price_decimal.percentile(0.75)
  &market_type=eq.player_prop
Two-column aggregates — correlation & regression

Canonical form only: agg(y, x). y is the dependent variable, x is independent.

corr(y, x)Pearson correlation
covar_samp(y, x) / covar_pop(y, x)sample / population covariance
regr_slope(y, x)linear regression slope
regr_intercept(y, x)regression intercept
regr_r2(y, x)coefficient of determination (R²)
regr_avgx(y, x) / regr_avgy(y, x)mean of x / y
regr_count(y, x)non-null pair count
regr_sxx / regr_syy / regr_sxysums of squares / cross-products
# Correlation between price and bet_limit per market
https://odds.wagerwise.win/current_odds?select=market_key,corr:corr(price_decimal,bet_limit),n:count()
  &bet_limit=is.not_null
  &having=count().gte.30

Window / partition

Query-level filters that slice rows by rank, percentile, peer count, or z-score within partitions. Require partition_by= and usually order=.

partition_by=col1,col2partition context for window ops
rank_lte=Nkeep top-N rows per partition (needs order=)
percentile_gte=0.9keep rows ≥ Nth percentile per partition
group_count_gte=Nkeep rows whose partition has ≥ N peers
outlier=<sigma>&outlier_col=<col>keep rows ≥ σ from partition mean
# Best-3 prices per prop across books
https://odds.wagerwise.win/current_odds?market_type=eq.player_prop
  &partition_by=event_id,market_key,outcome_description,outcome_name,point
  &order=price_american.desc
  &rank_lte=3

# Props with ≥ 5 books quoting + price in top decile
https://odds.wagerwise.win/current_odds?market_type=eq.player_prop
  &partition_by=event_id,market_key,outcome_description,point
  &order=price_american.desc
  &group_count_gte=5
  &percentile_gte=0.9

# Price outliers — ≥ 2σ from partition mean
https://odds.wagerwise.win/current_odds?market_type=eq.player_prop
  &partition_by=event_id,market_key,outcome_description,point,outcome_name
  &outlier=2&outlier_col=price_decimal
Window expressions in select

Per-row window functions usable as projection columns. Require the same partition_by= + order= context.

col.row_number()1-based row index within partition
col.rank() / col.dense_rank()SQL RANK variants
col.percent_rank()relative rank in [0, 1]
col.ntile(n)bucket rows into N equal groups
col.lag() / col.lag(n)previous row's value (n rows back)
col.lead() / col.lead(n)next row's value
col.diff() / col.diff(n)col − lag(col, n)
col.pct_change()(col − lag) / lag
col.log_return()LN(col / lag) — safe NULLIF
col.rolling_mean(n) / col.sma(n)N-row trailing average
col.rolling_stddev(n)N-row trailing stddev
col.cumsum() / col.cumavg()running sum / average
col.zscore() / col.standardize()(col − μ) / σ per partition
col.normalize()(col − min) / (max − min) per partition
col.outlier(sigma)boolean — |zscore| ≥ sigma
col.median_delta()col − median(col) per partition
# Line movement — diff from 30 min ago per prop
https://odds.wagerwise.win/odds_snapshots?select=fetched_at,price_decimal,delta:price_decimal.diff(1)
  &event_id=eq.{event_id}&market_key=eq.player_points&outcome_name=eq.Over
  &partition_by=bookmaker_key,outcome_description,point
  &order=fetched_at.asc

# Price z-score vs book peers
https://odds.wagerwise.win/current_odds?select=*,z:price_decimal.zscore()
  &market_type=eq.player_prop
  &partition_by=event_id,market_key,outcome_description,point,outcome_name

Grouping sets

Compute aggregates at multiple grouping levels in one query. Use grouping=rollup, cube, or sets:(a,b),(a),().

grouping=rolluphierarchical totals
grouping=cubeall combinations of grouping cols
grouping=sets:(a,b),(a),()explicit grouping sets
# Book/market/total with subtotals
https://odds.wagerwise.win/current_odds?select=bookmaker_key,market_key,count(),avg:price_decimal.avg()
  &market_type=eq.player_prop
  &grouping=rollup

Filtering

Filters use the form column=operator.value. Repeat the parameter to AND them. Prefix any operator with not. to negate.

# Equality and range
https://odds.wagerwise.win/events?sport_key=eq.basketball_nba
https://odds.wagerwise.win/events?commence_time=gte.2026-04-17&commence_time=lt.2026-04-18

# Set membership
https://odds.wagerwise.win/current_odds?bookmaker_key=in.(draftkings,fanduel,betmgm)

# Negation
https://odds.wagerwise.win/current_odds?bookmaker_key=not.in.(pinnacle)

# Text match (case-insensitive)
https://odds.wagerwise.win/current_odds?outcome_description=ilike.*tatum*

# Null and boolean
https://odds.wagerwise.win/sports?active=is.true
https://odds.wagerwise.win/events?completed=is.false
Comparison
eq / neqequals / not equals
gt / gtegreater than (or equal)
lt / lteless than (or equal)
in.(a,b,c)value in list
isis.null, is.not_null, is.true, is.false, is.unknown
isdistinctIS DISTINCT FROM — null-safe inequality
Numeric extensions — high-leverage for odds

abs_* operators compare on absolute value — the core trick for filtering by juice magnitude regardless of sign. between takes a closed range. round_* compares after rounding to N decimals.

abs_lte.Nabs(column) ≤ N — e.g. price_american=abs_lte.110 finds all prices in [-110, +110]
abs_gte.Nabs(column) ≥ N — |spread| ≥ 7 finds big favorites either side
abs_eq.Nabs(column) = N — exact juice match
between.(lo,hi)closed interval: price_american=between.(-120,120)
nbetween.(lo,hi)outside interval
mod.(divisor,remainder)(column % divisor) = remainder — point % 0.5 = 0 for half-point lines
round_eq.(places,val)round(column, places) = val
round_gte.(places,val)round(column, places) ≥ val
round_lte.(places,val)round(column, places) ≤ val
# Near pick'em prices (|line| ≤ 110)
https://odds.wagerwise.win/current_odds?price_american=abs_lte.110

# Plus-money only (either +N or -N outside pick'em)
https://odds.wagerwise.win/current_odds?price_american=gt.100

# Big point lines only (|spread| ≥ 7)
https://odds.wagerwise.win/current_odds?market_key=eq.spreads&point=abs_gte.7

# Half-point lines only (catch middle candidates)
https://odds.wagerwise.win/current_odds?market_type=eq.player_prop&point=mod.(1,0)=not.eq.0
Text & length
like / ilikeSQL LIKE — * as wildcard, ilike is case-insensitive
match / imatchPOSIX regex
starts.X / ends.Xprefix / suffix match
istarts.X / iends.Xcase-insensitive prefix / suffix
has.Xsubstring contains
len_eq.N / len_gte.N / len_lte.Nstring length compare
arrlen_eq.N / arrlen_gte.N / arrlen_lte.Narray length compare
contains_all.(a,b)string contains all terms (max 8)
contains_any.(a,b)string contains any term
Arrays & ranges
cscontains (left contains right)
cdcontained by
ovoverlaps — regions=ov.{us,us2} finds books serving either
sl / srstrictly left of / right of (ranges)
nxl / nxrdoes not extend left of / right of
adjadjacent to
JSONB / JSON path

For jsonb columns, query a path with jexists.$path or compare an extracted text value with jpath_text_eq.$path,value.

jexists.$pathjsonb_path_exists — truthy if path matches
jpath_text_eq.$path,valueextracted text equals value
Relative time

Durations: digits + one of s m h d w (seconds, minutes, hours, days, weeks). Great for movement windows without computing wall-clock timestamps client-side.

within.<dur>column value within the last <dur>
age_gte.<dur>age(column) ≥ <dur>
age_lte.<dur>age(column) ≤ <dur>
# Last 15 minutes of snapshots (no timestamp math)
https://odds.wagerwise.win/odds_snapshots?fetched_at=within.15m&market_type=eq.player_prop

# Stale prices: fetched > 5 min ago
https://odds.wagerwise.win/current_odds?fetched_at=age_gte.5m
Full-text search
ftsbasic tsquery
plftsplain tsquery
phftsphrase tsquery
wftswebsearch tsquery
fts(english).wordspecify language
Quantifiers

Apply SQL ANY/ALL to array columns with op(any) or op(all). Only on comparison and text ops.

# Tag arrays that contain any large value
https://odds.wagerwise.win/some_table?points=gte(any).30

# All elements below a threshold
https://odds.wagerwise.win/some_table?prices=lt(all).0
FK exists

Restrict rows to those whose FK has a matching row in another table via column=exists.<table>.<col>.

# Only current_odds rows whose event is still pregame
https://odds.wagerwise.win/current_odds?event_id=exists.events.id
Logical groups
# OR
https://odds.wagerwise.win/current_odds?or=(price_american.gt.150,price_american.lt.-300)

# AND (explicit, same as repeating params)
https://odds.wagerwise.win/current_odds?and=(market_type.eq.player_prop,price_american.gte.-115,price_american.lte.115)

# Nested
https://odds.wagerwise.win/current_odds?or=(and(bookmaker_key.eq.pinnacle,price_american.gt.100),bookmaker_key.eq.circa)

Ordering & pagination

# Sort
https://odds.wagerwise.win/current_odds?order=last_update.desc

# Multi-column sort with null placement
https://odds.wagerwise.win/events?order=commence_time.asc.nullsfirst,home_team.asc

# Sort by JSON path
https://odds.wagerwise.win/current_odds?order=data->stats->views.desc

# Limit and offset
https://odds.wagerwise.win/events?limit=50&offset=100

# Or use Range header (returns Content-Range)
curl 'https://odds.wagerwise.win/events' -H 'Range: 0-49'
curl 'https://odds.wagerwise.win/events' -H 'Range: 0-49' -H 'Prefer: count=exact'

Response shaping

# Single object (not array) — use when you know limit=1
https://odds.wagerwise.win/current_odds?event_id=eq.{id}&limit=1
Accept: application/vnd.pgrst.object+json

# Strip nulls
Accept: application/vnd.pgrst.object+json;nulls=stripped

# CSV for spreadsheets
https://odds.wagerwise.win/current_odds?sport_key=eq.basketball_nba
Accept: text/csv

Live preview

Try the request shapes below. These are the exact URLs you'll send once you have a key.

Requesthttps://api.wagerwise.win
curl GET /sports?active=is.true&select=key,title,group_name
  -H 'Authorization: Bearer $WW_API_KEY'
Response200 OK
[
  { "key": "basketball_nba", "title": "NBA", "group_name": "Basketball" },
  { "key": "icehockey_nhl",  "title": "NHL", "group_name": "Ice Hockey" },
  { "key": "baseball_mlb",   "title": "MLB", "group_name": "Baseball" }
]