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 / max | extrema — great for price spread per prop |
| having= | filter on aggregate: having=count().gte.50 |
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 / every | boolean aggregates |
| bit_and / bit_or | bitwise aggregates |
| array_agg / json_agg / jsonb_agg | collect 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
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
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_sxy | sums 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,col2 | partition context for window ops |
| rank_lte=N | keep top-N rows per partition (needs order=) |
| percentile_gte=0.9 | keep rows ≥ Nth percentile per partition |
| group_count_gte=N | keep 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
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_nameGrouping sets
Compute aggregates at multiple grouping levels in one query. Use grouping=rollup, cube, or sets:(a,b),(a),().
| grouping=rollup | hierarchical totals |
| grouping=cube | all 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
| eq / neq | equals / not equals |
| gt / gte | greater than (or equal) |
| lt / lte | less than (or equal) |
| in.(a,b,c) | value in list |
| is | is.null, is.not_null, is.true, is.false, is.unknown |
| isdistinct | IS DISTINCT FROM — null-safe inequality |
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.N | abs(column) ≤ N — e.g. price_american=abs_lte.110 finds all prices in [-110, +110] |
| abs_gte.N | abs(column) ≥ N — |spread| ≥ 7 finds big favorites either side |
| abs_eq.N | abs(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
| like / ilike | SQL LIKE — * as wildcard, ilike is case-insensitive |
| match / imatch | POSIX regex |
| starts.X / ends.X | prefix / suffix match |
| istarts.X / iends.X | case-insensitive prefix / suffix |
| has.X | substring contains |
| len_eq.N / len_gte.N / len_lte.N | string length compare |
| arrlen_eq.N / arrlen_gte.N / arrlen_lte.N | array length compare |
| contains_all.(a,b) | string contains all terms (max 8) |
| contains_any.(a,b) | string contains any term |
| cs | contains (left contains right) |
| cd | contained by |
| ov | overlaps — regions=ov.{us,us2} finds books serving either |
| sl / sr | strictly left of / right of (ranges) |
| nxl / nxr | does not extend left of / right of |
| adj | adjacent to |
For jsonb columns, query a path with jexists.$path or compare an extracted text value with jpath_text_eq.$path,value.
| jexists.$path | jsonb_path_exists — truthy if path matches |
| jpath_text_eq.$path,value | extracted text equals value |
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
| fts | basic tsquery |
| plfts | plain tsquery |
| phfts | phrase tsquery |
| wfts | websearch tsquery |
| fts(english).word | specify language |
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
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
# 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/csvLive preview
Try the request shapes below. These are the exact URLs you'll send once you have a key.
curl GET /sports?active=is.true&select=key,title,group_name
-H 'Authorization: Bearer $WW_API_KEY'[ { "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" } ]