Help us shape the future of Go! Take the 2026 Go Developer Survey

owls

package module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 28, 2026 License: MIT Imports: 24 Imported by: 0

README

owls-insight-sdk-go

The Go client for the Owls Insight sports odds API: REST endpoints, history pagers, and a WebSocket stream that keeps your subscription across reconnects.

go get github.com/owlsinsight/owls-insight-sdk-go@latest

Requires Go 1.23 or later. One dependency: github.com/coder/websocket.

Use it from server-side programs only. An API key is a credential: do not ship it in code that runs on your users' machines.

REST

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	owls "github.com/owlsinsight/owls-insight-sdk-go"
)

func main() {
	c, err := owls.NewClient(os.Getenv("OWLS_INSIGHT_API_KEY"))
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()

	odds, err := c.GetOdds(ctx, "nba", &owls.OddsParams{Books: []string{"pinnacle", "fanduel"}})
	if err != nil {
		log.Fatal(err)
	}
	for book, events := range odds.Data {
		fmt.Println(book, len(events), "events")
	}
}

Every method takes a context.Context first. The method names are the TypeScript SDK's names with the first letter upper-cased (getOdds is GetOdds); the table at the end lists them all. Calls with many filters take a params struct; a nil pointer sends none.

The esports keys (cs2, lol, valorant, dota2) have their own real-time method, GetEsportsRealtime(ctx, game, league): the league is required, and each item of Data is a Pinnacle matchup exactly as Pinnacle sent it (json.RawMessage). GetRealtime refuses those keys.

GetDaySchedule returns every game of a calendar day across sports (NFL, college football, MLB, NBA, WNBA, NHL, tennis, soccer and UFC): start time, status, score and clock, both sides and Kalshi win prices. The zero DayScheduleParams asks for today in America/New_York; set Date (YYYY-MM-DD), TZ (an IANA zone), Days (1 to 10) and Sports to change that. EventID on a game is its eventId on GetOdds, or nil when the game was not found there, and Meta.Status (ok, partial, stale or no-data) says whether the answer is complete and fresh. GetSchedule(ctx, sport) is the older per-sport list of upcoming games, unchanged.

FanDuel issues a different marketId in every state while the selectionId is the same everywhere, so a bet-slip link built with New Jersey's marketId fails in another state. Set State (a two-letter code such as "co") on OddsParams (GetOdds and the per-market routes) or PropsParams (GetProps, GetBookProps for fanduel) to get that state's marketIds on the FanDuel prices. An outcome with no id for the state has its marketId omitted, and the odds routes leave the FanDuel deepLink out. Meta.FanduelMarketIds reports what was mapped (on the odds routes, Available false means no ids for that sport and state). The WebSocket events carry the New Jersey ids.

Fair line and EV

MVP and Hall of Fame plans read the fair line, anchored on Pinnacle's de-vigged price, and the prices that beat it:

fair, err := c.GetFair(ctx, "nfl") // every upcoming game's fair moneyline, spread and total
for _, g := range fair.Data {
	if g.Spread != nil && g.Spread.Home != nil { // nil when there is no current fair
		fmt.Println(*g.AwayTeam, "@", *g.HomeTeam, *g.Spread.Home.Point, *g.Spread.Home.American)
	}
}

ml, err := c.GetEV(ctx, "nfl", &owls.EVParams{MinEV: 2})                                   // moneyline
lines, err := c.GetEVLines(ctx, "nfl", &owls.EVLinesParams{Markets: []string{"spreads"}}) // spreads, totals or both
props, err := c.GetPropsEV(ctx, "nba", &owls.PropsEVParams{MinEV: 2, Player: "Tatum"})    // player props

GetEVLines scores spreads and totals at Pinnacle's main number, against books that hang exactly that number, and each opportunity carries its own Point. Its Markets takes spreads and totals only (nil means both); anything else is refused before a request, because the route would answer with the moneyline board. GetPropsEV compares a price only at the same player, category and line; All: true also lists the props with no positive-EV price, each with its fair line. Spreads, totals and props are supported for nba, ncaab, nfl, ncaaf, nhl, mlb, wnba and soccer.

Play-by-play

GetPlays(ctx, sport, eventID, params) returns every play of an NBA, NHL, NFL, NCAAF or soccer game in play (or ended within the last 6 hours), oldest first, by the event id GetScores gives the game (for soccer, the match incidents: goals, own goals, penalties, cards and substitutions); with an empty event id it lists the games that have play-by-play (res.Games), and a Since or Limit is refused. Before tip-off, and for any other id, it is a 404.

p := &owls.PlaysParams{}
res, err := c.GetPlays(ctx, "nfl", eventID, p)
for _, play := range res.Game.Plays {
	plays[*play.ID] = play // merge by id: a play can be amended after it is first published
}
p.Since = res.Game.LastSeq // next call: the new plays, plus any amended in the last 2 minutes

Play.Detail is the sport's own fields as raw JSON: decode it with owls.Decode[owls.FootballPlayDetail], HockeyPlayDetail, BasketballPlayDetail or SoccerPlayDetail. The same plays are pushed on plays-update (see WebSocket).

Options
Option Default
WithBaseURL(url) https://api.owlsinsight.com The WebSocket URL follows it unless WithWSURL is set.
WithWSURL(url) the base URL
WithHTTPClient(hc) http.DefaultClient Leave its Timeout at zero.
WithTimeout(d) 30 s Per attempt, through the request context. 0 disables it. BatchSgp and CompareSgpCombo raise a shorter value to 45 s.
WithRetry(p) no retries Retries 429 and 503 on GET, see below.
WithHistoryConcurrency(n) 3 History requests in flight at once. 0 disables the gate.
Errors

A non-2xx response is an *owls.APIError with Status, Code, Message, RetryAfter and Details (the body's details, or the whole JSON body). Match the status with errors.Is:

_, err := c.GetRealtime(ctx, "nba", "")
var apiErr *owls.APIError
switch {
case errors.Is(err, owls.ErrUnauthorized): // 401: bad or missing key
case errors.Is(err, owls.ErrForbidden):    // 403: the plan does not include it (Code FORBIDDEN, or SGP_TIER_REQUIRED for the SGP tools), or owls.DataKeyForbiddenCode
case errors.Is(err, owls.ErrNotFound):     // 404 (Code COMBO_NOT_FOUND from CompareSgpCombo, else NOT_FOUND)
case errors.Is(err, owls.ErrRateLimited) && errors.As(err, &apiErr):
	time.Sleep(apiErr.RetryAfter) // 429; Code is HISTORY_CONCURRENCY or SGP_RATE_LIMIT when the API says so (never wait out SGP_COMPARE_TOO_MANY_BOOKS or SGP_BATCH_TOO_LARGE)
case errors.Is(err, owls.ErrServiceBusy):  // 503
}

A data-only key (Sign in with Owls) is refused on the webhook API and the betslip parse with Code == owls.DataKeyForbiddenCode; use a key from your dashboard there.

Message is the body's message, else its error, else the HTTP status text. A timeout or network failure is returned wrapped, so errors.Is(err, context.DeadlineExceeded) works.

Retries

Nothing is retried unless you ask:

c, _ := owls.NewClient(key, owls.WithRetry(owls.RetryPolicy{MaxRetries: 3}))

Only 429 and 503 on GET requests are retried. The client waits for Retry-After (seconds or an HTTP date) when the server sends one, and gives up instead when it is longer than MaxRetryAfter (120 s). Without the header it backs off from BaseDelay (2 s), doubling up to MaxDelay (30 s), with full jitter. A 429 is never retried once the monthly quota is used up (X-RateLimit-Remaining-Month: 0), and a POST, PATCH or DELETE is never retried.

History

/api/v1/history/* and /api/odds/history calls pass through a client-side gate of 3 requests in flight (the MVP plan's per-key allowance), so a burst waits in your process instead of being refused by the API.

To export a game's history, page through it rather than fanning out requests:

for snap, err := range c.IterHistoryOdds(ctx, owls.HistoryOddsParams{EventID: id, Book: "pinnacle"}, nil) {
	if err != nil {
		return err
	}
	rows = append(rows, snap)
}

IterHistoryOdds and IterHistoryProps read 1000 rows per request (at most 5000), stop after a short page, and retry a 429 or 503 up to 3 times per page by default (PageOptions).

GetHistorySplits returns how a game's betting splits moved: each reading of a book's handle and ticket percentages, with the line or price at that moment, oldest first. Ask for one game by EventID (the eventId from GetOdds), or for a sport by Sport and StartDate over at most 7 days, and page with Limit (at most 500) and Offset. A market missing from a row was not quoted at that read, and a percentage missing from a market is nil, not 0: BetMGM (betmgm) publishes ticket percentages only, on the live board (GetSplits) and in the history alike.

v2 books

Every v2 book goes through two methods. Data is the book's own JSON, exactly as it was published, as a *json.RawMessage: usually an object keyed by market or event id, but some books send [] or null when they have nothing.

leagues, err := c.GetV2Leagues(ctx, "draftkings", "soccer")
board, err := c.GetV2(ctx, "draftkings", "soccer", "england-premier-league")
var markets map[string]json.RawMessage
if board.Data != nil {
	_ = json.Unmarshal(*board.Data, &markets) // stays nil when the book sent [] or null
}
for marketID, raw := range markets {
	// decode raw into your own type for this book
}

A new v2 book needs no SDK release; pass its key as the URL has it, including one that starts with a digit (4casters, 888sport). owls.V2EventName(book) gives its WebSocket event (<book>-v2-update, except BetOnline's betonline-realtime). Some books are REST only and send no event: 888sport, bookmaker, bovada, hardrock, novig, polymarketus, stake, thescore and underdog. mma is REST only on kalshi and polymarket, whose events carry their other sports. Every v2 event decodes into owls.V2Update. Books differ in what they send, so Markets (a list of {marketId, hash, kind, data} or a count), Timestamp (epoch milliseconds or an ISO string), Events, Data, Raw and the fields only some books send (Count, TotalCount, Leagues, Heartbeat, MatchupIds, RemovedIds, RemovedMatchupIds, RemovedGhids) stay json.RawMessage.

GetV2Markets(ctx, book, sport, league, marketIds) returns only the entries you name: a comma-separated list of market ids and prefixes ending in * (at most 50), or * for every entry. Every v2 book except bet365, hardrock, mybookie, stake, thunderpick and underdog reads it. It is also how you ask for the entries a book leaves out by default: DraftKings props-* (its US-sport player props), Fanatics Markets books-* (its live order books) and Polymarket US full-* (each event's full market catalog). On a default request Meta.DefaultExcluded says how many entries were left out and the marketIds value that includes them; with a filter, Meta.MarketIds echoes it and Meta.MarketsFilteredOut counts what it left out. DraftKings, Fanatics Markets and Polymarket US also send Meta.MarketAges, the seconds since each returned entry was last confirmed.

books, err := c.GetV2Markets(ctx, "fanaticsmarkets", "nfl", "", "books-*")
full, err := c.GetV2Markets(ctx, "polymarketus", "nfl", "nfl", "full-*")

Polymarket US (book polymarketus) is the CFTC-regulated US exchange, not global Polymarket: its own events, ids and prices, probabilities in 0..1 sent as strings. It is REST only, and league is required for every sport (GetV2Leagues; season-long futures sit under <league>-futures, such as nfl-futures).

Fanatics Markets order books arrive on their own event, owls.EventFanaticsMarketsBooksV2Update (fanaticsmarkets-books-v2-update, decoded into owls.V2Update), when the subscription asks for them next to its sports: V2: map[string]map[string]any{"fanaticsmarkets": {"nfl": "*", "books": true}}. Each market is a books-* entry, whole or a patch (merge by symbol; null means that book is gone), with RemovedIds. fanaticsmarkets-v2-update keeps carrying the events alone.

Prop Builder (book propbuilder; sports nfl, ncaaf, nba, wnba, nhl and mlb, each one league, so pass an empty league) is a pregame player-props board: Over/Under lines, "N+" ladders, Player Doubles, player vs player, and game markets. Several sportsbooks embed this board, and its prices were identical on every site we compared. Odds are decimal, and a market whose oddsOverride is above 1 is priced at oddsOverride instead of odds. A game leaves the board when it starts. Data is keyed by entry id, family first:

Entry id Holds
game-{gameId} The game: home, away, startTime, its tab map (categories) and entries, every entry id of the game
ou-{gameId}-{stat}-{hash} Over/Under lines for one statistic: condition 3 is over and 1 under, value is the line
ss-{gameId}-{stat}-{hash} The "N+" ladder for one statistic: value 2 means 2 or more
gfm-{gameId}-{stat}-{hash} One game-market group (to win, handicap win, total points)
ppd-{gameId} Player Doubles: two legs at one price
h2h-{gameId} Player vs player

Read the statistic from an entry's statistic, not from its id. gameId is the board's own game id, not a GetOdds event id. More families may be added, so skip one you do not handle. The game markets are sold as parlay legs only, and a leg's price is not a parlay price.

// Every Over/Under and N+ board of the sport.
nfl, err := c.GetV2Markets(ctx, "propbuilder", "nfl", "", "ou-*,ss-*")
// One game; the "-" before "*" keeps game 2816110 out.
game, err := c.GetV2Markets(ctx, "propbuilder", "nfl", "",
	"game-281611,ou-281611-*,ss-281611-*,gfm-281611-*,ppd-281611,h2h-281611")

propbuilder-v2-update pushes the entries that changed within seconds, each one whole (replace yours by marketId), and RemovedIds for the entries that left the board. Load the board with GetV2 on every connect event, the first and every reconnect, then apply the updates. Updates can arrive while the board loads: hold them, and apply them on top of the board once it is in. (Handlers run one at a time, so a GetV2 call inside the EventConnect handler holds them for you, up to the stream's 64-event queue.) Subscribe with V2: map[string]map[string]any{"propbuilder": {"nfl": "*"}}.

Same game parlays
res, err := c.BuildSgp(ctx, "mlb", owls.SgpBuildParams{
	EventID: "35989831",
	Book:    "fanduel",
	Legs: []owls.SgpLeg{
		{Type: "moneyline", Selection: "New York Yankees"},
		{Type: "player_prop", Player: "Aaron Judge", Category: "hits", Side: "over", Line: owls.Ptr(1.5)},
	},
})

The API prices parlays on fanduel, draftkings, hardrock, betrivers, ballybet and betmgm (GetSgpEvents(ctx, sport, book) lists each book's events). EventID is a string for every book: FanDuel's id is a number in GetSgpEvents, every other book's a string.

A 422 that reports unresolved legs or a refused slip is returned, not an error: check res.Success and res.ReasonCode. A 503 with Code == owls.SgpNoPriceCode means no usable price came back; wait RetryAfter (2 s when the server sends no header).

On NHL, Hard Rock also prices team_total legs ({Type: "team_total", Team: "Boston Bruins", Selection: "over", Point: owls.Ptr(2.5)}) and period legs: Period "P1", "P2" or "P3" on a moneyline, spread or total leg prices that period. A period moneyline is the period's 3-way result, so its Selection is a team or "Tie". Every other book refuses these legs: BuildSgp answers a 422 (ReasonCode unresolved), and CompareSgp and BatchSgp report that book as unsupported.

MVP and Hall of Fame plans also get:

  • Fair: true on SgpBuildParams: a priced 200 carries res.Fair (SgpFairBlock), our estimate of the parlay's price from de-vigged leg probabilities and the book's own correlation, beside the book's Sgp, never in place of it. Fair.Price is nil with Fair.Why when it cannot be made.
  • CompareSgp(ctx, sport, owls.SgpCompareParams{EventID, Legs, ...}): the same legs on every book at once, from OUR event id (as GetOdds gives it), each book's own answer in Books with a Status (priced, refused, unresolved, busy, no-price, timeout, event-not-listed, ambiguous-event, unsupported, disabled, skipped; more can be added) and Best. A book that does not price is not an error. A book that priced the same legs in the last 15 seconds may answer from that quote (Cached), unless Fresh.
  • BatchSgp(ctx, sport, owls.SgpBatchParams{Mode: "book", Book: "draftkings", Parlays: ...}): up to 10 parlays on one book, or with Mode: "compare" up to 4 on every book. Results come back in request order with your Ref; one with Status not-started was not priced: send it again. A batch can take close to 40 seconds, so it waits at least 45 s whatever a shorter WithTimeout says.
  • CompareSgpCombo(ctx, owls.SgpComboCompareParams{ComboTicker: "KXMVE..."}) (or Legs, 2 to 12 Kalshi markets with a Side): a Kalshi combo priced as the same game parlay it is. Each leg's translation is in Combo.Legs[].Translated (an SgpLeg), the game's OUR event id in EventID, and Compare is exactly what CompareSgp returns for those legs. A 422 refusal is returned, not an error: Success false and Code COMBO_NOT_A_COMBO, COMBO_LEGS_UNSUPPORTED (Combo.Legs[].Why), COMBO_MULTI_GAME (Games) or COMBO_EVENT_NOT_FOUND. It reads Kalshi before pricing, so it waits at least 45 s whatever a shorter WithTimeout says.
  • GetSgpHistory(ctx, sport, owls.SgpHistoryParams{EventID: ...}) (or Book and BookEventID): the prices YOUR account was quoted for one game, newest first, from the calls above. Every priced answer carries Meta.LegsHash; pass it as LegsHash to see one parlay's quotes. From / To (ISO 8601) set the window, at most 7 days, default the last 24 hours (From alone: the 24 hours from it, up to now); Limit is 1 to 500, default 100. Only priced answers are kept, never a cached quote, from the day recording was switched on, for 90 days.

On a lower plan those are a 403 with Code SGP_TIER_REQUIRED (below Rookie a build is a 403 FORBIDDEN whatever its options); a 503 SGP_FEATURE_DISABLED means the feature is not switched on yet. A 429 SGP_COMPARE_TOO_MANY_BOOKS or SGP_BATCH_TOO_LARGE needs more builds than the key's limit allows at all: waiting never helps, ask for fewer books or parlays. The pricing calls are never retried: every attempt is a real pricing request. GetSgpHistory is a read: WithRetry retries its 429s and 503s, including SGP_HISTORY_UNAVAILABLE (the history could not be read just then, RetryAfter 5 s).

Webhooks

Webhooks are in beta. MVP and Hall of Fame plans can register HTTPS endpoints that receive signed POSTs (MVP 3 endpoints, Hall of Fame 10): line.moved (Pinnacle's main moneyline, spread or total for a game that has not started moved by at least your step), ev.found (a price on the EV board became tradeable at or above your min_ev), game.started, game.final (never for a postponed or cancelled game) and props.graded. Each type covers its own sports; a sport an event type does not cover is refused with a 400 (sport_not_covered), and so is an event type that is not open yet (event_type_unavailable).

created, err := c.CreateWebhook(ctx, owls.CreateWebhookParams{
	URL:        "https://example.com/owls",
	EventTypes: []string{owls.WebhookEventLineMoved, owls.WebhookEventEvFound},
	Filters: &owls.WebhookFiltersInput{
		Sports:    []string{"nba", "nhl"},
		LineMoved: &owls.WebhookLineMovedFiltersInput{PriceStepPp: 2, PointStep: 1},
		EvFound:   &owls.WebhookEvFoundFiltersInput{MinEv: 3},
	},
})
if err != nil {
	log.Fatal(err)
}
secret := *created.Data.Secret // returned ONCE: store it
id := *created.Data.ID

_, _ = c.TestWebhook(ctx, id) // a signed webhook.test event
_, _ = c.ListWebhookDeliveries(ctx, id, &owls.WebhookDeliveriesParams{Limit: 10})
_, _ = c.UpdateWebhook(ctx, id, owls.UpdateWebhookParams{Enabled: owls.Ptr(false)})
rotated, _ := c.RotateWebhookSecret(ctx, id, &owls.RotateWebhookSecretParams{ExpirePreviousAfterHours: owls.Ptr(24.0)})
saveSecret(id, *rotated.Data.Secret) // also returned ONCE; the old secret keeps signing for 24 hours
_, _ = c.DeleteWebhook(ctx, id)

The calls that change something are never retried. If one times out, list your endpoints before creating again; rotate the secret of an endpoint whose secret you never saw. Verify every delivery against the raw body, then dedupe on the event id (delivery is at least once and unordered):

http.HandleFunc("/owls", func(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
	if err != nil || !owls.VerifyWebhookSignature(body, r.Header.Get(owls.WebhookSignatureHeader), secret) {
		http.Error(w, "bad signature", http.StatusBadRequest)
		return
	}
	ev, err := owls.ParseWebhookEvent(body)
	if err != nil {
		http.Error(w, "bad body", http.StatusBadRequest)
		return
	}
	if alreadyHandled(ev.EventID()) {
		w.WriteHeader(http.StatusOK)
		return
	}
	switch ev := ev.(type) {
	case *owls.WebhookLineMovedEvent:
		log.Println(*ev.Data.Market, *ev.Data.Reason)
	case *owls.WebhookEvFoundEvent:
		log.Println(*ev.Data.Signal.Venue, *ev.Data.Signal.EvPercent)
	}
	w.WriteHeader(http.StatusOK) // any 2xx within 10 s acknowledges it
})

Answer with any 2xx within 10 seconds. 410 Gone disables the endpoint. Anything else, a redirect included, is a failure: line.moved and ev.found are retried after about 5, 20 and 60 seconds, the other types up to 8 times over about a day, and nothing is sent after the event's expires_at.

Owls-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256(secret, t + "." + raw body)>, keyed with the whole secret (whsec_ included). During a secret rotation it carries a v1 for each live secret, and either one verifies. VerifyWebhookSignature rejects a t more than 5 minutes from your clock; VerifyWebhookSignatureAt takes the tolerance and the clock. ParseWebhookEvent returns an owls.WebhookEvent (EventID, EventType) holding the model of the event's type, or a *owls.WebhookDeliveryPayload (with Data left raw) for a type this version does not know.

Models

The response types are generated from the API's OpenAPI description (spec/openapi.json), with Go initialisms in field names (EventID, ID). Every field is optional: scalars are pointers (a missing spread is not a 0 spread), slices and maps are nil when absent, and enums are plain strings, so a new value never breaks decoding. Unknown fields are ignored. A value whose JSON type does not match its field is left unset instead of failing the call. Union fields, fields whose type varies (a number or a string, such as Outcome.SelectionID) and raw book data are json.RawMessage. owls.Ptr(v) builds a pointer for the optional inputs.

owls.Decode[T](raw) decodes any payload the same way, for example a WebSocket event: n, err := owls.Decode[owls.ServerNotice](raw).

A pinnacle-realtime or ps3838-realtime frame is keyed by sport. RealtimeOddsPayload has a field for each common sport; owls.RealtimeSportEvents(raw, sport) reads the events of any sport from the raw frame, including one added to the API later. pinnacle-realtime and pinnacle-realtime-sync frames can carry Feed (UpstreamAgeSeconds, Resubscribing, Stalled, nil when absent), the same as Meta.Feed on GetRealtime: Stalled is true when the sport has a game in play and Pinnacle has been silent for longer than its normal quiet spells, so prices can be out of date while frames keep arriving.

WebSocket

s := c.Stream(owls.WithSubscription(owls.Subscription{
	Sports: []string{"nba"},
	Books:  []string{"pinnacle"},
}))
s.OnOddsUpdate(func(u *owls.OddsUpdatePayload, err error) {
	// frames are partial after the first snapshot: merge by event id
})
s.On(owls.EventServerNotice, func(raw json.RawMessage) {
	log.Printf("server notice: %s", raw)
})
err := s.Run(ctx) // blocks until ctx ends, the key is refused or the server disconnects

WebSocket access needs the Hall of Fame plan, or a paid plan with the WebSocket add-on.

Subscriptions survive reconnects. The stream stores the last subscription it sent and your props subscriptions, and sends them on every connect, in the handshake and as messages. If the server does not acknowledge them within 5 s it sends them once more.

  • Subscribe(ctx, sub) replaces the subscription; fields you leave out are cleared.
  • UpdateSubscription(ctx, patch) merges the fields you set into it. A nil field is left alone; an empty one clears it: Subscription{EventIDs: []string{}} removes the event filter.
  • SubscribeProps(ctx, book, filter) and UnsubscribeProps(ctx, book) manage the props streams ("" for Pinnacle's player-props-update).

These return owls.ErrNotConnected while the stream is not connected; use WithSubscription for the first connect. The context only bounds the wait for a send already in progress. Once a call has stored its change it writes it before returning (within 10 s), so calls reach the server in the order they return.

Three Subscription fields are opt-ins rather than filters:

  • Realtime: &owls.RealtimeFeeds{PS3838: owls.Ptr(false)} stops ps3838-realtime for the connection and keeps pinnacle-realtime (default: both). Only a literal false counts. UpdateSubscription replaces the whole object, so Realtime: &owls.RealtimeFeeds{} there turns ps3838-realtime back on. The subscribed acknowledgement carries the same type in ClientSubscription.Realtime.
  • Scores: true, or a list of sport keys such as []string{"nfl"}, opts in to scores-update (owls.EventScoresUpdate, payload owls.ScoresUpdate): a full live board per sport about every 30 seconds and a delta within a few seconds of a change.
  • Plays: true, or a list of nba, nhl, nfl, ncaaf and soccer, opts in to plays-update (owls.EventPlaysUpdate, payload owls.PlaysUpdate): one message per sport within a few seconds of a new play, each game with the plays new or amended since the previous message. There is no full message: start from GetPlays and merge plays by ID.

Events. On(name, fn) receives each event's payload as json.RawMessage, in order, on one goroutine; owls.Decode[T] turns it into a model type. Up to 64 events queue for your handlers while the stream keeps answering the server's pings; after that it stops reading, and the server drops a connection that falls too far behind. Keep handlers quick: when the context ends, queued events are dropped and Run returns once the handler that is running has returned. The stream also delivers connect, disconnect and connect_error (the server's refusal, with data.code).

The server's own error event (owls.EventError, payload owls.WSError with Code and Message) is delivered like any other event and does not close the connection. The server sends it when it refuses a props subscription your plan does not include (TIER_REQUIRED), when your key is revoked, when your connection limit is lowered, and just before it disconnects a connection your account no longer allows:

s.On(owls.EventError, func(raw json.RawMessage) {
	log.Printf("server error: %s", raw)
	if e, err := owls.Decode[owls.WSError](raw); err == nil && e.Code != nil && *e.Code == "TIER_REQUIRED" {
		// the plan does not include the props stream you subscribed to
	}
})

Reconnects. After the first successful connect, Run reconnects by itself:

  • A dropped connection is retried after about 1 s. Each drop of a connection that had lasted less than 60 s doubles the delay, up to 30 s; a connection that lasts 60 s resets it.
  • A refused reconnect the server marks retryable (for example CONNECTION_LIMIT while an old connection still holds your slot) is retried on its own schedule: 5, 15, 30, then 60 s for CONNECTION_LIMIT, 2, 5, 15, 30, then 60 s for the others.
  • A reconnect refused for the account (TIER_NO_WS, SUBSCRIPTION_INACTIVE, PAYMENT_OVERDUE, TRIAL_EXPIRED, TRIAL_UNVERIFIABLE) is retried slowly, after 30 s and then every 60 s. That covers a reconnect after a network drop or a server restart: it goes through once the invoice is paid or the plan upgraded.
  • Every refusal retry is jittered, never sooner than the server's retryAfterMs, and at most 4 refusal retries of any kind are made in a minute. Each refusal is also delivered as a connect_error event, so you can see why the stream is waiting.
  • Every other refusal the server marks not retryable ends Run with an *owls.RefusalError: a bad key (MISSING_API_KEY, INVALID_API_KEY, API_KEY_DEACTIVATED), a code this SDK does not know, or no code at all (an older server, where a bad key looks the same). Waiting never fixes a bad key, and every refusal counts toward the server's per-IP block.
  • A disconnect sent by the server ends Run with owls.ErrServerDisconnect, and the stream does not reconnect after it. The server checks every connection's account every 5 minutes; when the plan, payment or trial no longer allows it, it sends an error event with the reason and then this disconnect. Call Run again once the account is fixed.

An expired trial deactivates the key, so the retry after TRIAL_EXPIRED meets API_KEY_DEACTIVATED and Run returns. A cancelled subscription deactivates the key too: until the account subscribes again, a reconnect meets API_KEY_DEACTIVATED. In both cases, call Run again once the account is active.

If the very first attempt fails, Run returns the error at once (an *owls.RefusalError, an *owls.DialError or a network error) so a wrong key or URL fails fast. RefusalError.Code is one of the owls.Refusal* constants, for example owls.RefusalConnectionLimit. The stream remembers the delay it owes before its next attempt, so calling Run again on the same stream waits it out (the refusal schedule, the server's retryAfterMs, a Retry-After, or the dropped-connection backoff): a loop around Run cannot connect faster than the stream would on its own. Network errors never contain your API key.

Connection limits. The API allows a small number of WebSocket connection attempts per IP address per minute (every connect, reconnect and refused attempt counts) and blocks the whole address, REST included, for 10 minutes when they are exceeded. Every process behind one IP address shares that budget. Run one stream per API key, keep it running, and do not restart it in a tight loop.

Method names

TypeScript Go Endpoint
listEvents ListEvents(ctx, sport, league) GET /api/v1/{sport}/events
getOdds GetOdds(ctx, sport, *OddsParams) GET /api/v1/{sport}/odds
getMoneyline GetMoneyline(ctx, sport, *OddsParams) GET /api/v1/{sport}/moneyline
getSpreads GetSpreads(ctx, sport, *OddsParams) GET /api/v1/{sport}/spreads
getTotals GetTotals(ctx, sport, *OddsParams) GET /api/v1/{sport}/totals
getRealtime GetRealtime(ctx, sport, league) GET /api/v1/{sport}/realtime
getEsportsRealtime GetEsportsRealtime(ctx, game, league) GET /api/v1/{cs2,lol,valorant,dota2}/realtime
getPS3838Realtime GetPS3838Realtime(ctx, sport, league) GET /api/v1/{sport}/ps3838-realtime
getEV GetEV(ctx, sport, *EVParams) GET /api/v1/{sport}/ev
getEVLines GetEVLines(ctx, sport, *EVLinesParams) GET /api/v1/{sport}/ev?market=spreads,totals
getFair GetFair(ctx, sport) GET /api/v1/{sport}/fair
getOneXBetSoccer GetOneXBetSoccer(ctx) GET /api/v1/1xbet/soccer
getProphetxOdds GetProphetxOdds(ctx, ProphetXOddsParams) GET /api/v1/prophetx/odds
getSchedule GetSchedule(ctx, sport) GET /api/v1/{sport}/schedule
getDaySchedule GetDaySchedule(ctx, DayScheduleParams) GET /api/v1/schedule
getResults GetResults(ctx, sport) GET /api/v1/{sport}/results
getSplits GetSplits(ctx, sport) GET /api/v1/{sport}/splits
getScores GetScores(ctx, sport) GET /api/v1/scores/live (sport ""), GET /api/v1/{sport}/scores/live
getPlays GetPlays(ctx, sport, eventID, *PlaysParams) GET /api/v1/{sport}/plays
getInjuries GetInjuries(ctx, sport, *InjuriesParams) GET /api/v1/{sport}/injuries
normalize Normalize(ctx, name, sport) GET /api/v1/normalize
normalizeBatch NormalizeBatch(ctx, names, sport) GET /api/v1/normalize/batch
getProps GetProps(ctx, sport, *PropsParams) GET /api/v1/{sport}/props
getBookProps GetBookProps(ctx, sport, book, *PropsParams) GET /api/v1/{sport}/props/{book}
getPropsEV GetPropsEV(ctx, sport, *PropsEVParams) GET /api/v1/{sport}/props/ev
getPropsHistory GetPropsHistory(ctx, sport, PropsHistoryParams) GET /api/v1/{sport}/props/history
getPropResults GetPropResults(ctx, sport, PropResultsParams) GET /api/v1/{sport}/props/results
getPropTrends GetPropTrends(ctx, sport, PropTrendsParams) GET /api/v1/{sport}/props/trends
getPropsStats GetPropsStats(ctx) GET /api/v1/props/stats
getBookPropsStats GetBookPropsStats(ctx, book) GET /api/v1/props/{book}/stats
getStats GetStats(ctx, sport, date) GET /api/v1/{sport}/stats
getMatchStats GetMatchStats(ctx, sport, eventID) GET /api/v1/{sport}/stats/match
getH2H GetH2H(ctx, sport, eventID) GET /api/v1/{sport}/stats/h2h
getPlayerAverages GetPlayerAverages(ctx, sport, PlayerAveragesParams) GET /api/v1/{sport}/stats/averages
getOddsHistory GetOddsHistory(ctx, OddsHistoryParams) GET /api/odds/history
getMoneylineHistory GetMoneylineHistory(ctx, LineHistoryParams) GET /api/odds/history, market h2h
getSpreadHistory GetSpreadHistory(ctx, LineHistoryParams) GET /api/odds/history, market spreads
getTotalsHistory GetTotalsHistory(ctx, LineHistoryParams) GET /api/odds/history, market totals
getHistoryCoverage GetHistoryCoverage(ctx) GET /api/v1/history/coverage
getHistoryGames GetHistoryGames(ctx, *HistoryGamesParams) GET /api/v1/history/games
getHistoryOdds GetHistoryOdds(ctx, HistoryOddsParams) GET /api/v1/history/odds
getHistoryProps GetHistoryProps(ctx, HistoryPropsParams) GET /api/v1/history/props
iterHistoryOdds IterHistoryOdds(ctx, HistoryOddsParams, *PageOptions) GET /api/v1/history/odds, paged
iterHistoryProps IterHistoryProps(ctx, HistoryPropsParams, *PageOptions) GET /api/v1/history/props, paged
getHistoryStats GetHistoryStats(ctx, *HistoryStatsParams) GET /api/v1/history/stats
getHistoryTennisStats GetHistoryTennisStats(ctx, eventID) GET /api/v1/history/tennis-stats
getGameStatsDetail GetGameStatsDetail(ctx, eventID) GET /api/v1/history/game-stats-detail
getClosingOdds GetClosingOdds(ctx, *ClosingOddsParams) GET /api/v1/history/closing-odds
getHistoricalPlayerProps GetHistoricalPlayerProps(ctx, *HistoricalPlayerPropsParams) GET /api/v1/history/player-props
getPublicBetting GetPublicBetting(ctx, *PublicBettingParams) GET /api/v1/history/public-betting
getHistorySplits GetHistorySplits(ctx, HistorySplitsParams) GET /api/v1/history/splits
getCS2Matches GetCS2Matches(ctx, *CS2MatchesParams) GET /api/v1/history/cs2/matches
getCS2Match GetCS2Match(ctx, matchID) GET /api/v1/history/cs2/matches/{matchId}
getCS2Players GetCS2Players(ctx, CS2PlayersParams) GET /api/v1/history/cs2/players
getHardRockLeagues GetHardRockLeagues(ctx, state, sport) GET /api/v2/hardrock/{state}/{sport}/leagues
getHardRockEvents GetHardRockEvents(ctx, state, sport, league) GET /api/v2/hardrock/{state}/{sport}
getHardRockLadder GetHardRockLadder(ctx) GET /api/v2/hardrock/ladder
getStakeBets GetStakeBets(ctx) GET /api/v2/stake/bets
get<Book>V2 GetV2(ctx, book, sport, league), GetV2Markets(ctx, book, sport, league, marketIds) GET /api/v2/{book}/{sport}
get<Book>V2Leagues GetV2Leagues(ctx, book, sport) GET /api/v2/{book}/{sport}/leagues
getSgpEvents GetSgpEvents(ctx, sport, book) GET /api/v1/{sport}/sgp/events
buildSgp BuildSgp(ctx, sport, SgpBuildParams) POST /api/v1/{sport}/sgp/build
compareSgp CompareSgp(ctx, sport, SgpCompareParams) POST /api/v1/{sport}/sgp/compare
compareSgpCombo CompareSgpCombo(ctx, SgpComboCompareParams) POST /api/v1/sgp/combo-compare
batchSgp BatchSgp(ctx, sport, SgpBatchParams) POST /api/v1/{sport}/sgp/batch
getSgpHistory GetSgpHistory(ctx, sport, SgpHistoryParams) GET /api/v1/{sport}/sgp/history
createWebhook CreateWebhook(ctx, CreateWebhookParams) POST /api/v1/webhooks
listWebhooks ListWebhooks(ctx) GET /api/v1/webhooks
getWebhook GetWebhook(ctx, id) GET /api/v1/webhooks/{id}
updateWebhook UpdateWebhook(ctx, id, UpdateWebhookParams) PATCH /api/v1/webhooks/{id}
deleteWebhook DeleteWebhook(ctx, id) DELETE /api/v1/webhooks/{id}
testWebhook TestWebhook(ctx, id) POST /api/v1/webhooks/{id}/test
rotateWebhookSecret RotateWebhookSecret(ctx, id, *RotateWebhookSecretParams) POST /api/v1/webhooks/{id}/rotate-secret
listWebhookDeliveries ListWebhookDeliveries(ctx, id, *WebhookDeliveriesParams) GET /api/v1/webhooks/{id}/deliveries
verifyWebhookSignature VerifyWebhookSignature(payload, header, secret) checks a delivery's Owls-Signature

Four methods answer with one of several shapes and return a small result struct with exactly one field set: GetScores (All or Sport), GetBookProps (Props, or BetMGM for that book), GetPropResults (Game, or Day when you pass only a date) and GetPlays (Game, or Games for an empty event id). The bet365 props were retired: GetBookProps for bet365 returns an *owls.APIError with status 410. Bet365 game lines are on GetV2.

Development

make conformance       # npm ci in conformance/: the WebSocket test server's socket.io
go test ./...          # unit tests, WebSocket conformance tests, drift checks
go test -race ./...
make test              # make conformance, then go test ./... with the WebSocket tests required
go generate ./...      # regenerate models_gen.go from spec/openapi.json

The WebSocket tests start conformance/server.mjs, a local server on the same socket.io version (4.8.1, engine.io 6.6.4, ws 8.17.1) and transport settings as the API, so they need Node.js. The server loads socket.io from $OWLS_API_REPO/node_modules when that is set, else from conformance/node_modules (pinned by conformance/package-lock.json; make conformance or npm ci in conformance/ installs it), else from a sibling ../nba-odds-app checkout. Without node or socket.io the WebSocket tests are skipped with a message, except under CI (CI set) or with OWLS_REQUIRE_WS_SERVER_TESTS=1, where they fail.

The drift tests compare the SDK with the API manifest (OWLS_API_MANIFEST, else ../nba-odds-app/docs/api-manifest.json) and the vendored spec with its source (OWLS_OPENAPI_DIR, else ../owls-insight-sdk-js/openapi), and are skipped with a message when those are missing.

The live tests call the production API and are off unless OWLS_INSIGHT_LIVE=1 and OWLS_INSIGHT_API_KEY are set. Run them at most once a minute.

License

MIT

Documentation

Overview

Package owls is the Go client for the Owls Insight sports odds API.

It is meant for server-side programs: an API key does not belong in code that runs on someone else's machine.

REST

Create a Client with your API key and call its methods. Every call takes a context first; the method names are the TypeScript SDK's names with the first letter upper-cased (getOdds is GetOdds).

c, err := owls.NewClient(os.Getenv("OWLS_INSIGHT_API_KEY"))
if err != nil { ... }
odds, err := c.GetOdds(ctx, "nba", &owls.OddsParams{Books: []string{"pinnacle"}})

A failed call returns an *APIError. Match it with errors.Is against ErrUnauthorized, ErrForbidden, ErrNotFound, ErrRateLimited or ErrServiceBusy, or read its fields with errors.As. Nothing is retried unless you ask for it with WithRetry; the history pagers retry by default.

Responses decode leniently. Every field is optional (a pointer, or a nil slice or map), unknown fields are ignored, enums are plain strings, and a field whose JSON type does not match the model is left unset rather than failing the call. Raw pass-through data (the v2 books) is kept as encoding/json.RawMessage.

Webhooks

MVP and Hall of Fame plans can register HTTPS endpoints that receive signed event notifications (Client.CreateWebhook and the other Webhook methods; beta). Check every delivery against its raw body with VerifyWebhookSignature, then read it with ParseWebhookEvent and dedupe on WebhookEvent.EventID.

WebSocket

A Stream keeps one WebSocket connection open and delivers server events to the handlers you register with Stream.On. It stores your subscription and sends it again on every reconnect, and reconnects with a growing delay when connections do not last. A reconnect after a network drop or a server restart that is refused for the account (plan, payment, trial) is retried slowly, so it goes through once the account is fixed. Any other non-retryable refusal, such as a bad key, ends Stream.Run, and so does a disconnect from the server (its periodic account check): call Run again once the account is fixed.

s := c.Stream(owls.WithSubscription(owls.Subscription{Sports: []string{"nba"}}))
s.OnOddsUpdate(func(u *owls.OddsUpdatePayload, err error) { ... })
err := s.Run(ctx) // returns when ctx ends, the key is refused or the server disconnects

The API allows a small number of WebSocket connection attempts per IP address per minute and blocks the whole address, REST included, when they are exceeded. Run one Stream per API key and per process.

Index

Examples

Constants

View Source
const (
	// Not retryable: the key is missing, wrong or deactivated. Each of these
	// counts toward the server's per-IP block, so Stream.Run stops on them.
	RefusalMissingAPIKey     = "MISSING_API_KEY"
	RefusalInvalidAPIKey     = "INVALID_API_KEY"
	RefusalAPIKeyDeactivated = "API_KEY_DEACTIVATED"
	// Not retryable until the account changes (plan, payment, trial). A running
	// Stream still retries a reconnect refused with one of these five, after 30 s
	// and then every 60 s. Any other non-retryable code, including one added
	// later, ends Stream.Run.
	RefusalTierNoWS             = "TIER_NO_WS"
	RefusalSubscriptionInactive = "SUBSCRIPTION_INACTIVE"
	RefusalPaymentOverdue       = "PAYMENT_OVERDUE"
	RefusalTrialExpired         = "TRIAL_EXPIRED"
	RefusalTrialUnverifiable    = "TRIAL_UNVERIFIABLE"
	// Retryable.
	RefusalSubscriptionCheckFailed = "SUBSCRIPTION_CHECK_FAILED"
	RefusalInternal                = "INTERNAL"
	RefusalIPBlocked               = "IP_BLOCKED"
	RefusalConnectionLimit         = "CONNECTION_LIMIT" // every connection the plan allows is in use
	RefusalClientClosed            = "CLIENT_CLOSED"
)

Refusal codes: the RefusalError.Code values the server sends. More can be added; RefusalError.Retryable says whether waiting helps, and Stream.Run says what a running stream does with each.

View Source
const (
	EventOddsUpdate           = "odds-update"
	EventEsportsUpdate        = "esports-update"
	EventPinnacleRealtime     = "pinnacle-realtime"
	EventPinnacleRealtimeSync = "pinnacle-realtime-sync"
	EventPS3838Realtime       = "ps3838-realtime"
	EventServerHeartbeat      = "server-heartbeat"
	EventServerNotice         = "server-notice"
	EventProphetXUpdate       = "prophetx-update"
	EventOneXBetUpdate        = "1xbet-update"
	// EventScoresUpdate is live scores for one sport, payload ScoresUpdate: a full
	// live board about every 30 seconds and a delta within a few seconds of a
	// change. Opt in with Subscription.Scores.
	EventScoresUpdate = "scores-update"
	// EventPlaysUpdate is play-by-play for one sport (nba, nhl, nfl, ncaaf, soccer),
	// payload PlaysUpdate: within a few seconds of a new play, each game with the
	// plays new or amended since the previous message. There is no full message:
	// start from GetPlays and merge plays by ID. Opt in with Subscription.Plays.
	EventPlaysUpdate = "plays-update"
	// EventFanaticsMarketsBooksV2Update is the Fanatics Markets live order books,
	// payload V2Update: books-* entries, whole or patch (merge by symbol; null means
	// that book is gone), RemovedIds, and about once a minute the league's full list
	// of books entries. Opt in with "books": true next to the sports of the
	// fanaticsmarkets v2 subscription, for example
	// map[string]map[string]any{"fanaticsmarkets": {"nfl": "*", "books": true}}.
	// The events alone stay on V2EventName("fanaticsmarkets").
	EventFanaticsMarketsBooksV2Update = "fanaticsmarkets-books-v2-update"
	// EventSubscribed acknowledges subscribe and update-subscription with the
	// subscription in force.
	EventSubscribed = "subscribed"
	// EventError is the server's application-level error, payload WSError: a
	// props subscription the plan does not include (code TIER_REQUIRED), a
	// revoked key, a reduced connection limit, or an account that no longer
	// allows the connection (sent just before the server disconnects it). It is an
	// ordinary event, not a connection failure (see EventConnectError for a
	// refused connection).
	EventError = "error"
)

Server events. Any name works with Stream.On; these are the fixed ones.

View Source
const (
	// EventConnect: a connection is open and the stored subscriptions were sent.
	// Payload {"sid": "..."}.
	EventConnect = "connect"
	// EventDisconnect: the connection closed. Payload {"reason": "..."}.
	EventDisconnect = "disconnect"
	// EventConnectError: the server refused a connection. Payload is the
	// server's {"message": "...", "data": {"code", "retryable", ...}}.
	EventConnectError = "connect_error"
)

Connection events, produced by the SDK and delivered through Stream.On in order with the server's events.

View Source
const (
	WebhookEventLineMoved   = "line.moved"
	WebhookEventEvFound     = "ev.found"
	WebhookEventGameStarted = "game.started"
	WebhookEventGameFinal   = "game.final"
	WebhookEventPropsGraded = "props.graded"
	WebhookEventTest        = "webhook.test"
)

The webhook event types. WebhookEventTest is sent only by Client.TestWebhook.

View Source
const DataKeyForbiddenCode = "data_key_forbidden"

DataKeyForbiddenCode is the Code of a 403 refusing a data-only key (one minted by Sign in with Owls) on the webhook API or the betslip parse, which take a key from the dashboard. Every other 403 carries FORBIDDEN (the plan does not include the endpoint); both match ErrForbidden.

View Source
const DefaultWebhookTolerance = 5 * time.Minute

DefaultWebhookTolerance is how far a delivery's timestamp may be from your clock, either side, for VerifyWebhookSignature.

View Source
const SDKLabel = "owls-insight-go/" + Version

SDKLabel is sent as the User-Agent of every request and as auth.sdk in the WebSocket handshake: owls-insight-go/<Version>. The server keeps printable ASCII up to 48 characters of it.

View Source
const SgpNoPriceCode = "no-price"

SgpNoPriceCode is the Code of the SGP builder's 503 when the price request produced no usable answer. Retry after RetryAfter (2 s when the server sent no Retry-After header).

View Source
const Version = "0.10.0"

Version is the SDK version. It is part of the label the SDK identifies itself with (see SDKLabel).

View Source
const WebhookSignatureHeader = "Owls-Signature"

WebhookSignatureHeader is the header that carries a delivery's signature.

Variables

View Source
var (
	ErrUnauthorized = errors.New("owls: unauthorized (401)")
	ErrForbidden    = errors.New("owls: forbidden (403)")
	ErrNotFound     = errors.New("owls: not found (404)")
	ErrRateLimited  = errors.New("owls: rate limited (429)")
	ErrServiceBusy  = errors.New("owls: service busy (503)")
)

Sentinel errors an *APIError matches with errors.Is, one per HTTP status the API gives a meaning to.

View Source
var ErrNotConnected = errors.New("owls: stream is not connected")

ErrNotConnected is returned by the Stream methods that send to the server while the stream has no open connection.

View Source
var ErrServerDisconnect = errors.New("owls: the server disconnected the stream (io server disconnect)")

ErrServerDisconnect is returned by Stream.Run when the server closed the stream with a Socket.IO disconnect ("io server disconnect"). The server does that when its periodic check (every 5 minutes) finds that the account no longer allows the connection (plan, payment, trial) or that the connection limit was lowered, after an EventError event that says why. The stream does not reconnect after it: call Run again once the account is fixed.

Functions

func ComputeWebhookSignature added in v0.5.0

func ComputeWebhookSignature(secret string, timestamp int64, payload []byte) string

ComputeWebhookSignature returns hex HMAC-SHA256 of timestamp, a period and the raw body, keyed with the whole secret (whsec_ included) as UTF-8.

func Decode

func Decode[T any](raw json.RawMessage) (*T, error)

Decode decodes an event payload (or any API JSON) into a model type the way the REST methods do: a value whose JSON type does not fit its field is left unset instead of failing, unknown fields are ignored, and only a payload that is not JSON, or whose top level does not fit T, is an error.

s.On(owls.EventServerNotice, func(raw json.RawMessage) {
	n, err := owls.Decode[owls.ServerNotice](raw)
	...
})

func Ptr

func Ptr[T any](v T) *T

Ptr returns a pointer to v, for the optional pointer fields of the models and of Subscription.

func V2EventName

func V2EventName(book string) string

V2EventName returns the WebSocket event a v2 book's deltas arrive on: "<book>-v2-update", except BetOnline, whose event predates the convention and is "betonline-realtime". Subscribe with Subscription.V2. Every one of them decodes into V2Update. Fanatics Markets also has a second event for its live order books, EventFanaticsMarketsBooksV2Update.

func VerifyWebhookSignature added in v0.5.0

func VerifyWebhookSignature(payload []byte, header, secret string) bool

VerifyWebhookSignature reports whether header (the Owls-Signature value) carries a v1 signature of payload (the RAW body, exactly as received) made with secret, with a timestamp within DefaultWebhookTolerance of the clock. Reject the delivery when it is false, and dedupe accepted ones on the event id (delivery is at least once).

body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
if err != nil || !owls.VerifyWebhookSignature(body, r.Header.Get(owls.WebhookSignatureHeader), secret) {
	http.Error(w, "bad signature", http.StatusBadRequest)
	return
}
Example
package main

import (
	"io"
	"log"
	"net/http"
	"os"

	owls "github.com/owlsinsight/owls-insight-sdk-go"
)

func main() {
	secret := os.Getenv("OWLS_WEBHOOK_SECRET") // CreateWebhook's Data.Secret
	http.HandleFunc("/owls", func(w http.ResponseWriter, r *http.Request) {
		body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
		if err != nil || !owls.VerifyWebhookSignature(body, r.Header.Get(owls.WebhookSignatureHeader), secret) {
			http.Error(w, "bad signature", http.StatusBadRequest)
			return
		}
		ev, err := owls.ParseWebhookEvent(body)
		if err != nil {
			http.Error(w, "bad body", http.StatusBadRequest)
			return
		}
		log.Println("event", ev.EventID()) // the same on every retry: dedupe on it
		switch ev := ev.(type) {
		case *owls.WebhookLineMovedEvent:
			log.Println(*ev.Data.Market, *ev.Data.Reason)
		case *owls.WebhookEvFoundEvent:
			log.Println(*ev.Data.Signal.Venue, *ev.Data.Signal.EvPercent)
		}
		w.WriteHeader(http.StatusOK) // any 2xx within 10 s acknowledges it; 410 Gone disables the endpoint
	})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func VerifyWebhookSignatureAt added in v0.5.0

func VerifyWebhookSignatureAt(payload []byte, header, secret string, tolerance time.Duration, now time.Time) bool

VerifyWebhookSignatureAt is VerifyWebhookSignature with the tolerance and the clock given. During a secret rotation the header carries one v1 per live secret, and a match with any of them is enough; every candidate is compared, each in constant time. An empty secret, or a negative tolerance, gives false.

Types

type APIError

type APIError struct {
	// Status is the HTTP status code.
	Status int
	// Code is the API's own code when it sent one. 401 always carries
	// UNAUTHORIZED, and 404 NOT_FOUND except CompareSgpCombo's unknown combo, which
	// carries COMBO_NOT_FOUND. 403 carries FORBIDDEN, except a data-only key's
	// refusal, which carries DataKeyForbiddenCode, and a same game parlay call or
	// fair price the plan does not include, which carries SGP_TIER_REQUIRED; a 429
	// without a code is RATE_LIMITED (HISTORY_CONCURRENCY, SGP_RATE_LIMIT,
	// SGP_COMPARE_TOO_MANY_BOOKS and SGP_BATCH_TOO_LARGE are the API's own), a 503
	// without one is SERVICE_BUSY.
	Code string
	// Message is the body's message, else its error, else the HTTP status text.
	Message string
	// RetryAfter is how long the server asked the client to wait, from the
	// Retry-After header (seconds or an HTTP date). Set on 429 (60 s when the
	// header is missing) and 503 (0 when it is missing).
	RetryAfter time.Duration
	// RemainingMinute and RemainingMonth are the X-RateLimit-Remaining-Minute and
	// X-RateLimit-Remaining-Month headers of a 429; nil when absent or "unlimited".
	RemainingMinute *int
	RemainingMonth  *int
	// Details is the body's details field, else the whole body when it was a JSON
	// object. Nil when the body was not JSON (an edge error page).
	Details json.RawMessage
}

APIError is a response the API answered with a non-2xx status.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) Is

func (e *APIError) Is(target error) bool

Is reports whether target is the sentinel error for e's status.

type AlternateLine

type AlternateLine struct {
	// LastSeen When the book last CONFIRMED this rung, as an ISO timestamp, whether or not
	// its price moved. Pinnacle realtime only (`getRealtime` and `pinnacle-realtime`);
	// absent on `/odds` and on feeds that do not stamp rungs.
	//
	// Pinnacle does not resend every rung in every message, so a rung whose
	// `last_seen` is well behind its market's `last_seen` is a hint that the rung has
	// been taken down, not proof. Rungs are never flagged `suspended` one by one.
	LastSeen *string  `json:"last_seen,omitempty"`
	Point    *float64 `json:"point,omitempty"`
	Price    *float64 `json:"price,omitempty"`
}

AlternateLine defines model for AlternateLine.

type ArchivedGame

type ArchivedGame struct {
	ArchivedAt *string  `json:"archivedAt,omitempty"`
	AwayScore  *float64 `json:"awayScore,omitempty"`
	AwayTeam   *string  `json:"awayTeam,omitempty"`

	// AwayTeamID Stable roster id for `awayTeam`; same rules.
	AwayTeamID *string `json:"awayTeamId,omitempty"`

	// CommenceTime Scheduled start of the game. Cut `recordedAtUtc` (not the stored `recordedAt`) against this to separate
	// pre-game odds from in-play ones exactly, rather than by date.
	//
	// Absent whenever the start time was not captured: every game archived
	// before 2026-09-20, and any game since whose start we could not resolve.
	// Treat its absence as "unknown", never as a start of midnight.
	CommenceTime *string `json:"commenceTime,omitempty"`
	EventID      *string `json:"eventId,omitempty"`

	// GameDate The DATE the game is filed under, which is a UTC date and not the start time.
	GameDate *string `json:"gameDate,omitempty"`
	GameType *string `json:"gameType,omitempty"`

	// HomeScore Final score, absent when the archive did not capture one.
	HomeScore *float64 `json:"homeScore,omitempty"`
	HomeTeam  *string  `json:"homeTeam,omitempty"`

	// HomeTeamID Stable roster id for `homeTeam`, absent when the name is not on a roster
	// we carry. Note the casing: camelCase here, `home_team_id` on `/odds`.
	HomeTeamID *string `json:"homeTeamId,omitempty"`

	// OddsSnapshots Stored odds rows for the eventId, both team orderings. `null` when the count
	// could not be taken when the game was archived ("not counted", never a false 0);
	// a later recount fills it.
	OddsSnapshots *float64 `json:"oddsSnapshots,omitempty"`

	// PeriodScores Per-period scores, absent when none were captured. `period` is a label,
	// not an index: `"Q1"`..`"Q4"` then `"OT1"`, `"OT2"` for the quarter sports
	// (nba, wnba, nfl, ncaaf), and the plain period or inning number for
	// everything else. A bare `"OT"` is never sent.
	PeriodScores []struct {
		Away   *float64 `json:"away,omitempty"`
		Home   *float64 `json:"home,omitempty"`
		Period *string  `json:"period,omitempty"`
	} `json:"periodScores,omitempty"`
	PlayerStats *float64 `json:"playerStats,omitempty"`

	// PropsSnapshots Stored props rows for the eventId; `null` when not counted (as oddsSnapshots).
	PropsSnapshots *float64 `json:"propsSnapshots,omitempty"`
	Season         *string  `json:"season,omitempty"`

	// Source How the row got here: the live pipeline, or a backfill run.
	Source *string `json:"source,omitempty"`
	Sport  *string `json:"sport,omitempty"`
	Venue  *string `json:"venue,omitempty"`
}

ArchivedGame defines model for ArchivedGame.

type BallybetV2BookSubscription

type BallybetV2BookSubscription = BetriversV2BookSubscription

BallybetV2BookSubscription defines model for BallybetV2BookSubscription.

type BaseballScoreDetail

type BaseballScoreDetail struct {
	// Errors Team errors.
	Errors *struct {
		Away *float64 `json:"away,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"errors,omitempty"`

	// Half Half of the current inning. Live games only. Absent when the source does not say.
	Half *string `json:"half,omitempty"`

	// Hits Team hits.
	Hits *struct {
		Away *float64 `json:"away,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"hits,omitempty"`

	// Inning Current inning, 1-indexed. Live games only.
	Inning *float64 `json:"inning,omitempty"`

	// Pitcher Pitcher the source currently lists for each side.
	Pitcher *struct {
		Away *string `json:"away,omitempty"`
		Home *string `json:"home,omitempty"`
	} `json:"pitcher,omitempty"`
}

BaseballScoreDetail Baseball-specific live detail.

`inning`/`half` are present for live games only. `hits`/`errors` complete the R-H-E line - runs are `home.score`/`away.score`, and per-inning runs are `home.linescores`/`away.linescores`.

Outs and runners on base are NOT available: the upstream source does not carry them.

type BasketballPlayDetail added in v0.9.0

type BasketballPlayDetail struct {
	Descriptor  *string  `json:"descriptor,omitempty"`
	IsFieldGoal *bool    `json:"isFieldGoal,omitempty"`
	PlayerID    *float64 `json:"playerId,omitempty"`
	PlayerName  *string  `json:"playerName,omitempty"`
	Possession  *string  `json:"possession,omitempty"`
	Qualifiers  []string `json:"qualifiers,omitempty"`

	// ShotDistance Feet.
	ShotDistance *float64 `json:"shotDistance,omitempty"`

	// ShotResult "Made" or "Missed" on shots.
	ShotResult  *string  `json:"shotResult,omitempty"`
	SubType     *string  `json:"subType,omitempty"`
	TeamTricode *string  `json:"teamTricode,omitempty"`
	X           *float64 `json:"x,omitempty"`
	Y           *float64 `json:"y,omitempty"`
}

BasketballPlayDetail NBA: the action sub type, the player, the shot.

type BasketballStats

type BasketballStats struct {
	Assists    *float64       `json:"assists,omitempty"`
	Blocks     *float64       `json:"blocks,omitempty"`
	Fg         *ShootingSplit `json:"fg,omitempty"`
	Fouls      *float64       `json:"fouls,omitempty"`
	FreeThrow  *ShootingSplit `json:"freeThrow,omitempty"`
	Minutes    *string        `json:"minutes,omitempty"`
	PlusMinus  *float64       `json:"plusMinus,omitempty"`
	Points     *float64       `json:"points,omitempty"`
	Rebounds   *float64       `json:"rebounds,omitempty"`
	Steals     *float64       `json:"steals,omitempty"`
	ThreePoint *ShootingSplit `json:"threePoint,omitempty"`
	Turnovers  *float64       `json:"turnovers,omitempty"`
}

BasketballStats defines model for BasketballStats.

type BattingStats

type BattingStats struct {
	AtBats     *float64 `json:"atBats,omitempty"`
	BattingAvg *string  `json:"battingAvg,omitempty"`
	Hits       *float64 `json:"hits,omitempty"`
	HomeRuns   *float64 `json:"homeRuns,omitempty"`
	Obp        *string  `json:"obp,omitempty"`
	Rbi        *float64 `json:"rbi,omitempty"`
	Runs       *float64 `json:"runs,omitempty"`
	Slg        *string  `json:"slg,omitempty"`
	Strikeouts *float64 `json:"strikeouts,omitempty"`
	Walks      *float64 `json:"walks,omitempty"`
}

BattingStats defines model for BattingStats.

type Bet105V2BookSubscription

type Bet105V2BookSubscription struct {
	AlpineSkiing  *json.RawMessage `json:"alpine-skiing,omitempty"`
	Archery       *json.RawMessage `json:"archery,omitempty"`
	Athletics     *json.RawMessage `json:"athletics,omitempty"`
	AussieRules   *json.RawMessage `json:"aussie-rules,omitempty"`
	Badminton     *json.RawMessage `json:"badminton,omitempty"`
	Bandy         *json.RawMessage `json:"bandy,omitempty"`
	Baseball      *json.RawMessage `json:"baseball,omitempty"`
	Basketball    *json.RawMessage `json:"basketball,omitempty"`
	Basketball3X3 *json.RawMessage `json:"basketball-3x3,omitempty"`
	Biathlon      *json.RawMessage `json:"biathlon,omitempty"`
	Bowls         *json.RawMessage `json:"bowls,omitempty"`
	Boxing        *json.RawMessage `json:"boxing,omitempty"`
	Cfl           *json.RawMessage `json:"cfl,omitempty"`
	Chess         *json.RawMessage `json:"chess,omitempty"`
	Cricket       *json.RawMessage `json:"cricket,omitempty"`
	Curling       *json.RawMessage `json:"curling,omitempty"`
	Cycling       *json.RawMessage `json:"cycling,omitempty"`
	Darts         *json.RawMessage `json:"darts,omitempty"`
	Esports       *json.RawMessage `json:"esports,omitempty"`
	Fencing       *json.RawMessage `json:"fencing,omitempty"`
	FieldHockey   *json.RawMessage `json:"field-hockey,omitempty"`
	Floorball     *json.RawMessage `json:"floorball,omitempty"`
	Football      *json.RawMessage `json:"football,omitempty"`
	Formula1      *json.RawMessage `json:"formula1,omitempty"`
	Futsal        *json.RawMessage `json:"futsal,omitempty"`
	GaelicSports  *json.RawMessage `json:"gaelic-sports,omitempty"`
	Golf          *json.RawMessage `json:"golf,omitempty"`
	Greyhounds    *json.RawMessage `json:"greyhounds,omitempty"`
	Handball      *json.RawMessage `json:"handball,omitempty"`
	Hockey        *json.RawMessage `json:"hockey,omitempty"`
	HorseRacing   *json.RawMessage `json:"horse-racing,omitempty"`
	Hurling       *json.RawMessage `json:"hurling,omitempty"`
	Judo          *json.RawMessage `json:"judo,omitempty"`
	Kabaddi       *json.RawMessage `json:"kabaddi,omitempty"`
	Kbo           *json.RawMessage `json:"kbo,omitempty"`
	Lacrosse      *json.RawMessage `json:"lacrosse,omitempty"`
	Mlb           *json.RawMessage `json:"mlb,omitempty"`
	Mma           *json.RawMessage `json:"mma,omitempty"`
	Motorbikes    *json.RawMessage `json:"motorbikes,omitempty"`
	Motorsport    *json.RawMessage `json:"motorsport,omitempty"`
	Nascar        *json.RawMessage `json:"nascar,omitempty"`
	Nba           *json.RawMessage `json:"nba,omitempty"`
	Ncaab         *json.RawMessage `json:"ncaab,omitempty"`
	Ncaaf         *json.RawMessage `json:"ncaaf,omitempty"`
	Netball       *json.RawMessage `json:"netball,omitempty"`
	Nfl           *json.RawMessage `json:"nfl,omitempty"`
	Nhl           *json.RawMessage `json:"nhl,omitempty"`
	Npb           *json.RawMessage `json:"npb,omitempty"`
	Olympics      *json.RawMessage `json:"olympics,omitempty"`
	Padel         *json.RawMessage `json:"padel,omitempty"`
	Pool          *json.RawMessage `json:"pool,omitempty"`
	RugbyLeague   *json.RawMessage `json:"rugby-league,omitempty"`
	RugbyUnion    *json.RawMessage `json:"rugby-union,omitempty"`
	Sailing       *json.RawMessage `json:"sailing,omitempty"`
	SkiJumping    *json.RawMessage `json:"ski-jumping,omitempty"`
	Snooker       *json.RawMessage `json:"snooker,omitempty"`
	Soccer        *json.RawMessage `json:"soccer,omitempty"`
	Softball      *json.RawMessage `json:"softball,omitempty"`
	Speedway      *json.RawMessage `json:"speedway,omitempty"`
	Squash        *json.RawMessage `json:"squash,omitempty"`
	Sumo          *json.RawMessage `json:"sumo,omitempty"`
	Supercars     *json.RawMessage `json:"supercars,omitempty"`
	Surfing       *json.RawMessage `json:"surfing,omitempty"`
	TableTennis   *json.RawMessage `json:"table-tennis,omitempty"`
	Taekwondo     *json.RawMessage `json:"taekwondo,omitempty"`
	Tennis        *json.RawMessage `json:"tennis,omitempty"`
	Triathlon     *json.RawMessage `json:"triathlon,omitempty"`
	Trotting      *json.RawMessage `json:"trotting,omitempty"`
	Ultimate      *json.RawMessage `json:"ultimate,omitempty"`
	Volleyball    *json.RawMessage `json:"volleyball,omitempty"`
	WaterPolo     *json.RawMessage `json:"water-polo,omitempty"`
	Wnba          *json.RawMessage `json:"wnba,omitempty"`
}

Bet105V2BookSubscription Per-sport league filter for `v2: { bet105: {...} }`. Any Bet105 sport.

type Bet365V2BookSubscription

type Bet365V2BookSubscription struct {
	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`
}

Bet365V2BookSubscription Per-book subscription shape for Bet365 v2 events.

Bet365 v2 supports both multi-league sports (soccer, requires specific league keys) and single-league sports (nba, mlb, nhl, nfl, where the entire sport is one stream). For single-league sports pass `'*'`; arrays are accepted and coerced to `'*'` server-side.

type BetMGMGameProps

type BetMGMGameProps struct {
	AwayTeam   *string            `json:"awayTeam,omitempty"`
	FixtureID  *string            `json:"fixtureId,omitempty"`
	HomeTeam   *string            `json:"homeTeam,omitempty"`
	LastUpdate *string            `json:"lastUpdate,omitempty"`
	Props      []BetMGMPlayerProp `json:"props,omitempty"`
}

BetMGMGameProps BetMGM game props - flat props[] instead of books[] wrapper.

type BetMGMPlayerProp

type BetMGMPlayerProp struct {
	Category      *string  `json:"category,omitempty"`
	FixtureID     *string  `json:"fixtureId,omitempty"`
	Line          *float64 `json:"line,omitempty"`
	MarketStatus  *string  `json:"marketStatus,omitempty"`
	OverPrice     *float64 `json:"overPrice,omitempty"`
	PlayerID      *float64 `json:"playerId,omitempty"`
	PlayerName    *string  `json:"playerName,omitempty"`
	PropID        *string  `json:"propId,omitempty"`
	RawMarketName *string  `json:"rawMarketName,omitempty"`
	UnderPrice    *float64 `json:"underPrice,omitempty"`
}

BetMGMPlayerProp BetMGM-specific player prop (flat structure, no over/under pairing).

type BetMGMPropsResponse

type BetMGMPropsResponse struct {
	Data []BetMGMGameProps `json:"data,omitempty"`
	Meta *struct {
		Book          *string  `json:"book,omitempty"`
		Filters       *string  `json:"filters,omitempty"`
		GamesReturned *float64 `json:"gamesReturned,omitempty"`
		Message       *string  `json:"message,omitempty"`
		PropsReturned *float64 `json:"propsReturned,omitempty"`
		Sport         *string  `json:"sport,omitempty"`
		Timestamp     *string  `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

BetMGMPropsResponse Response from GET /{sport}/props/betmgm.

type BetOnlineV2BookSubscription

type BetOnlineV2BookSubscription struct {
	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`
}

BetOnlineV2BookSubscription Per-book subscription shape for BetOnline v2 events (`betonline-realtime`).

Every sport takes a V2LeagueFilter, and every sport on this book is competition-keyed - there is no single-league sport here. So unlike bet365, mybookie and thunderpick, an array filter is NOT coerced to `'*'` server-side: BetOnline is deliberately excluded from that coercion list, because its rooms really are per-competition and coercing would hand you a blend of competitions you did not ask for. An array is a real filter, and a key that matches no competition simply receives nothing.

Use `'*'` to take the whole sport (all competitions), or list exact league keys from OwlsInsightRest.getBetOnlineV2Leagues - passing the sport name as a league key (e.g. `{ mlb: ['mlb'] }`) is not a wildcard and will miss Liga Mexicana, MiLB and anything else the sport carries.

type BetriversV2BookSubscription

type BetriversV2BookSubscription struct {
	// AussieRules Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	AussieRules *V2LeagueFilter `json:"aussie-rules,omitempty"`

	// Boxing Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Boxing *V2LeagueFilter `json:"boxing,omitempty"`

	// Cricket Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Cricket *V2LeagueFilter `json:"cricket,omitempty"`

	// Cycling Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Cycling *V2LeagueFilter `json:"cycling,omitempty"`

	// Darts Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Darts *V2LeagueFilter `json:"darts,omitempty"`

	// Formula1 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Formula1 *V2LeagueFilter `json:"formula1,omitempty"`

	// Golf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Golf *V2LeagueFilter `json:"golf,omitempty"`

	// Handball Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Handball *V2LeagueFilter `json:"handball,omitempty"`

	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Mma Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mma *V2LeagueFilter `json:"mma,omitempty"`

	// Motorsport Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Motorsport *V2LeagueFilter `json:"motorsport,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaab Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaab *V2LeagueFilter `json:"ncaab,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// RugbyLeague Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	RugbyLeague *V2LeagueFilter `json:"rugby-league,omitempty"`

	// RugbyUnion Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	RugbyUnion *V2LeagueFilter `json:"rugby-union,omitempty"`

	// Snooker Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Snooker *V2LeagueFilter `json:"snooker,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Specials Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Specials *V2LeagueFilter `json:"specials,omitempty"`

	// Surfing Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Surfing *V2LeagueFilter `json:"surfing,omitempty"`

	// TableTennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	TableTennis *V2LeagueFilter `json:"table-tennis,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`

	// Wnba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Wnba *V2LeagueFilter `json:"wnba,omitempty"`
}

BetriversV2BookSubscription defines model for BetriversV2BookSubscription.

type BetusV2BookSubscription

type BetusV2BookSubscription struct {
	AlpineSkiing  *json.RawMessage `json:"alpine-skiing,omitempty"`
	Archery       *json.RawMessage `json:"archery,omitempty"`
	Athletics     *json.RawMessage `json:"athletics,omitempty"`
	AussieRules   *json.RawMessage `json:"aussie-rules,omitempty"`
	Badminton     *json.RawMessage `json:"badminton,omitempty"`
	Bandy         *json.RawMessage `json:"bandy,omitempty"`
	Baseball      *json.RawMessage `json:"baseball,omitempty"`
	Basketball    *json.RawMessage `json:"basketball,omitempty"`
	Basketball3X3 *json.RawMessage `json:"basketball-3x3,omitempty"`
	Biathlon      *json.RawMessage `json:"biathlon,omitempty"`
	Bowls         *json.RawMessage `json:"bowls,omitempty"`
	Boxing        *json.RawMessage `json:"boxing,omitempty"`
	Cfl           *json.RawMessage `json:"cfl,omitempty"`
	Chess         *json.RawMessage `json:"chess,omitempty"`
	Cricket       *json.RawMessage `json:"cricket,omitempty"`
	Curling       *json.RawMessage `json:"curling,omitempty"`
	Cycling       *json.RawMessage `json:"cycling,omitempty"`
	Darts         *json.RawMessage `json:"darts,omitempty"`
	Esports       *json.RawMessage `json:"esports,omitempty"`
	Fencing       *json.RawMessage `json:"fencing,omitempty"`
	FieldHockey   *json.RawMessage `json:"field-hockey,omitempty"`
	Floorball     *json.RawMessage `json:"floorball,omitempty"`
	Football      *json.RawMessage `json:"football,omitempty"`
	Formula1      *json.RawMessage `json:"formula1,omitempty"`
	Futsal        *json.RawMessage `json:"futsal,omitempty"`
	GaelicSports  *json.RawMessage `json:"gaelic-sports,omitempty"`
	Golf          *json.RawMessage `json:"golf,omitempty"`
	Greyhounds    *json.RawMessage `json:"greyhounds,omitempty"`
	Handball      *json.RawMessage `json:"handball,omitempty"`
	Hockey        *json.RawMessage `json:"hockey,omitempty"`
	HorseRacing   *json.RawMessage `json:"horse-racing,omitempty"`
	Hurling       *json.RawMessage `json:"hurling,omitempty"`
	Judo          *json.RawMessage `json:"judo,omitempty"`
	Kabaddi       *json.RawMessage `json:"kabaddi,omitempty"`
	Kbo           *json.RawMessage `json:"kbo,omitempty"`
	Lacrosse      *json.RawMessage `json:"lacrosse,omitempty"`
	Mlb           *json.RawMessage `json:"mlb,omitempty"`
	Mma           *json.RawMessage `json:"mma,omitempty"`
	Motorbikes    *json.RawMessage `json:"motorbikes,omitempty"`
	Motorsport    *json.RawMessage `json:"motorsport,omitempty"`
	Nascar        *json.RawMessage `json:"nascar,omitempty"`
	Nba           *json.RawMessage `json:"nba,omitempty"`
	Ncaab         *json.RawMessage `json:"ncaab,omitempty"`
	Ncaaf         *json.RawMessage `json:"ncaaf,omitempty"`
	Netball       *json.RawMessage `json:"netball,omitempty"`
	Nfl           *json.RawMessage `json:"nfl,omitempty"`
	Nhl           *json.RawMessage `json:"nhl,omitempty"`
	Npb           *json.RawMessage `json:"npb,omitempty"`
	Olympics      *json.RawMessage `json:"olympics,omitempty"`
	Padel         *json.RawMessage `json:"padel,omitempty"`
	Pool          *json.RawMessage `json:"pool,omitempty"`
	RugbyLeague   *json.RawMessage `json:"rugby-league,omitempty"`
	RugbyUnion    *json.RawMessage `json:"rugby-union,omitempty"`
	Sailing       *json.RawMessage `json:"sailing,omitempty"`
	SkiJumping    *json.RawMessage `json:"ski-jumping,omitempty"`
	Snooker       *json.RawMessage `json:"snooker,omitempty"`
	Soccer        *json.RawMessage `json:"soccer,omitempty"`
	Softball      *json.RawMessage `json:"softball,omitempty"`
	Speedway      *json.RawMessage `json:"speedway,omitempty"`
	Squash        *json.RawMessage `json:"squash,omitempty"`
	Sumo          *json.RawMessage `json:"sumo,omitempty"`
	Supercars     *json.RawMessage `json:"supercars,omitempty"`
	Surfing       *json.RawMessage `json:"surfing,omitempty"`
	TableTennis   *json.RawMessage `json:"table-tennis,omitempty"`
	Taekwondo     *json.RawMessage `json:"taekwondo,omitempty"`
	Tennis        *json.RawMessage `json:"tennis,omitempty"`
	Triathlon     *json.RawMessage `json:"triathlon,omitempty"`
	Trotting      *json.RawMessage `json:"trotting,omitempty"`
	Ultimate      *json.RawMessage `json:"ultimate,omitempty"`
	Volleyball    *json.RawMessage `json:"volleyball,omitempty"`
	WaterPolo     *json.RawMessage `json:"water-polo,omitempty"`
	Wnba          *json.RawMessage `json:"wnba,omitempty"`
}

BetusV2BookSubscription Per-sport league filter for `v2: { betus: {...} }`. Any BetUS sport.

type Book

type Book = string

Book defines model for Book.

type BookProps

type BookProps struct {
	EventLink  *string      `json:"event_link,omitempty"`
	Key        *string      `json:"key,omitempty"`
	LastUpdate *string      `json:"lastUpdate,omitempty"`
	Props      []PlayerProp `json:"props,omitempty"`
	Title      *string      `json:"title,omitempty"`
}

BookProps defines model for BookProps.

type BookPropsResult

type BookPropsResult struct {
	// Props is set for every book except betmgm.
	Props *PropsResponse
	// BetMGM is set for book "betmgm".
	BetMGM *BetMGMPropsResponse
}

BookPropsResult is the result of GetBookProps. BetMGM answers with its own shape; every other book with the common one.

type BookPropsStatsResponse

type BookPropsStatsResponse struct {
	Book *string `json:"book,omitempty"`
	Data *struct {
		BySport map[string]SportPropsCount `json:"bySport,omitempty"`
		Total   *float64                   `json:"total,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

BookPropsStatsResponse defines model for BookPropsStatsResponse.

type BookStatusData

type BookStatusData struct {
	AgeSeconds *float64 `json:"ageSeconds,omitempty"`
	Available  *bool    `json:"available,omitempty"`
	LastUpdate *string  `json:"lastUpdate,omitempty"`
	Stale      *bool    `json:"stale,omitempty"`
}

BookStatusData Per-book data freshness status sent with odds-update payloads.

type Bookmaker

type Bookmaker struct {
	// EventLink Deep link to event page on sportsbook website (REST only, stripped from WebSocket).
	EventLink   *string  `json:"event_link,omitempty"`
	EventTicker *string  `json:"event_ticker,omitempty"`
	Key         *string  `json:"key,omitempty"`
	LastUpdate  *string  `json:"last_update,omitempty"`
	Markets     []Market `json:"markets,omitempty"`
	Title       *string  `json:"title,omitempty"`
}

Bookmaker defines model for Bookmaker.

type CS2MapResult

type CS2MapResult struct {
	MapName     *string         `json:"mapName,omitempty"`
	MapNumber   *float64        `json:"mapNumber,omitempty"`
	PlayerStats []CS2PlayerStat `json:"playerStats,omitempty"`
	Team1Score  *float64        `json:"team1Score,omitempty"`
	Team2Score  *float64        `json:"team2Score,omitempty"`
}

CS2MapResult defines model for CS2MapResult.

type CS2Match

type CS2Match struct {
	BestOf    *float64 `json:"bestOf,omitempty"`
	EventName *string  `json:"eventName,omitempty"`
	IsLan     *bool    `json:"isLan,omitempty"`
	MatchDate *string  `json:"matchDate,omitempty"`
	MatchID   *float64 `json:"matchId,omitempty"`
	MatchURL  *string  `json:"matchUrl,omitempty"`
	Score1    *float64 `json:"score1,omitempty"`
	Score2    *float64 `json:"score2,omitempty"`
	Stars     *float64 `json:"stars,omitempty"`
	Team1     *string  `json:"team1,omitempty"`
	Team2     *string  `json:"team2,omitempty"`
}

CS2Match defines model for CS2Match.

type CS2MatchDetail

type CS2MatchDetail struct {
	BestOf       *float64        `json:"bestOf,omitempty"`
	EventName    *string         `json:"eventName,omitempty"`
	IsLan        *bool           `json:"isLan,omitempty"`
	Maps         []CS2MapResult  `json:"maps,omitempty"`
	MatchDate    *string         `json:"matchDate,omitempty"`
	MatchID      *float64        `json:"matchId,omitempty"`
	MatchURL     *string         `json:"matchUrl,omitempty"`
	OverallStats []CS2PlayerStat `json:"overallStats,omitempty"`
	Score1       *float64        `json:"score1,omitempty"`
	Score2       *float64        `json:"score2,omitempty"`
	Stars        *float64        `json:"stars,omitempty"`
	Team1        *string         `json:"team1,omitempty"`
	Team2        *string         `json:"team2,omitempty"`
}

CS2MatchDetail defines model for CS2MatchDetail.

type CS2MatchDetailResponse

type CS2MatchDetailResponse struct {
	Data    *CS2MatchDetail `json:"data,omitempty"`
	Success *bool           `json:"success,omitempty"`
}

CS2MatchDetailResponse defines model for CS2MatchDetailResponse.

type CS2MatchesParams

type CS2MatchesParams struct {
	Team      string
	Event     string
	StartDate string
	EndDate   string
	Stars     int
	LAN       bool
	Limit     int
	Offset    int
}

CS2MatchesParams filters GetCS2Matches.

type CS2MatchesResponse

type CS2MatchesResponse struct {
	Data *struct {
		Matches    []CS2Match `json:"matches,omitempty"`
		Pagination *struct {
			Limit  *float64 `json:"limit,omitempty"`
			Offset *float64 `json:"offset,omitempty"`

			// Total Total matching rows, or null when no filters applied (for performance).
			Total *float64 `json:"total,omitempty"`
		} `json:"pagination,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

CS2MatchesResponse defines model for CS2MatchesResponse.

type CS2PlayerMatchStat

type CS2PlayerMatchStat struct {
	Adr        *float64 `json:"adr,omitempty"`
	Deaths     *float64 `json:"deaths,omitempty"`
	EventName  *string  `json:"eventName,omitempty"`
	Kast       *float64 `json:"kast,omitempty"`
	Kills      *float64 `json:"kills,omitempty"`
	MapName    *string  `json:"mapName,omitempty"`
	MapNumber  *float64 `json:"mapNumber,omitempty"`
	MatchDate  *string  `json:"matchDate,omitempty"`
	MatchID    *float64 `json:"matchId,omitempty"`
	PlayerID   *float64 `json:"playerId,omitempty"`
	PlayerName *string  `json:"playerName,omitempty"`
	Rating     *float64 `json:"rating,omitempty"`
	Score1     *float64 `json:"score1,omitempty"`
	Score2     *float64 `json:"score2,omitempty"`
	Team1      *string  `json:"team1,omitempty"`
	Team2      *string  `json:"team2,omitempty"`
	TeamName   *string  `json:"teamName,omitempty"`
}

CS2PlayerMatchStat defines model for CS2PlayerMatchStat.

type CS2PlayerStat

type CS2PlayerStat struct {
	Adr        *float64 `json:"adr,omitempty"`
	Deaths     *float64 `json:"deaths,omitempty"`
	Kast       *float64 `json:"kast,omitempty"`
	Kills      *float64 `json:"kills,omitempty"`
	PlayerID   *float64 `json:"playerId,omitempty"`
	PlayerName *string  `json:"playerName,omitempty"`
	Rating     *float64 `json:"rating,omitempty"`
	TeamName   *string  `json:"teamName,omitempty"`
}

CS2PlayerStat defines model for CS2PlayerStat.

type CS2PlayersParams

type CS2PlayersParams struct {
	PlayerName string
	Team       string
	Event      string
	StartDate  string
	EndDate    string
	MapName    string
	MinRating  float64
	Limit      int
	Offset     int
}

CS2PlayersParams filters GetCS2Players; give at least PlayerName or Team.

type CS2PlayersResponse

type CS2PlayersResponse struct {
	Data *struct {
		Pagination *struct {
			Limit  *float64 `json:"limit,omitempty"`
			Offset *float64 `json:"offset,omitempty"`
			Total  *float64 `json:"total,omitempty"`
		} `json:"pagination,omitempty"`
		Stats []CS2PlayerMatchStat `json:"stats,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

CS2PlayersResponse defines model for CS2PlayersResponse.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client calls the Owls Insight REST API and opens WebSocket streams. It is safe for concurrent use.

func NewClient

func NewClient(apiKey string, opts ...Option) (*Client, error)

NewClient returns a client for apiKey.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"

	owls "github.com/owlsinsight/owls-insight-sdk-go"
)

func main() {
	c, err := owls.NewClient(os.Getenv("OWLS_INSIGHT_API_KEY"),
		owls.WithRetry(owls.RetryPolicy{MaxRetries: 2}))
	if err != nil {
		log.Fatal(err)
	}
	odds, err := c.GetOdds(context.Background(), "nba", &owls.OddsParams{Books: []string{"pinnacle"}})
	var apiErr *owls.APIError
	switch {
	case errors.Is(err, owls.ErrRateLimited) && errors.As(err, &apiErr):
		log.Printf("rate limited, retry in %v", apiErr.RetryAfter)
		return
	case err != nil:
		log.Fatal(err)
	}
	for book, events := range odds.Data {
		fmt.Println(book, len(events))
	}
}

func (*Client) BatchSgp added in v0.10.0

func (c *Client) BatchSgp(ctx context.Context, sport string, p SgpBatchParams) (*SgpBatchResponse, error)

BatchSgp prices several same game parlays in one call (MVP and Hall of Fame).

  • Mode "book": each parlay on the one p.Book (at most 10 parlays). Each result carries Book, BookStatus and, when the book answered, Result: exactly the body BuildSgp returns for it.
  • Mode "compare": each parlay on every book, or on p.Books (at most 4 parlays). Each result carries Best and Books: exactly what CompareSgp returns for it.

Every parlay names OUR event id for its game, as on CompareSgp. Parlays are priced one after the other, in request order (in mode "compare" the books price each parlay side by side), and results come back in that order with your Ref. A result's Status is "done", or "not-started" when that parlay's pricing would have begun more than about 20 seconds after the call began: it was not priced (except by any book answered from a cached quote) and carries no fair block, so send it again. BookStatus and Books[].Status take the values a CompareSgp book's Status takes. A repeated parlay (same EventID, same legs in the same order) is priced once and reported for each copy.

Every quote not answered from a cached quote counts one build against the key's per-minute SGP build limit (one per parlay in mode "book", one per book and parlay in mode "compare"), reserved for the whole batch up front. A quote that never reaches the book (not listed, a different game, a leg that does not resolve, no time left, a parlay not started) is given back at once, while the minute it was counted in is still running.

A batch can rightly take close to 40 seconds, so each attempt waits at least 45 s whatever a shorter client timeout (WithTimeout) says; a longer one, or none, is kept. A deadline on ctx still applies.

Errors are *APIError values with these codes: a 403 SGP_TIER_REQUIRED (the plan is below MVP); a 503 SGP_FEATURE_DISABLED (batches, or with p.Fair the fair price, are not switched on yet; RetryAfter 60 s); a 429 SGP_RATE_LIMIT (the builds this batch needs exceed what is left of the per-minute limit: wait RetryAfter; Details carries unitsNeeded); a 429 SGP_BATCH_TOO_LARGE (the batch needs more builds than the limit allows at all, sent without Retry-After, so waiting never helps: send fewer parlays or books; Details carries unitsNeeded and limit). Mode "book" checks p.Book before pricing, as BuildSgp does: a 400 SGP_BOOK_UNSUPPORTED (Details lists supportedBooks) or SGP_BOOK_SPORT_UNSUPPORTED (Details lists the sports that book prices), and a 503 SGP_BOOK_DISABLED when that book's pricing is paused (60 s). A 503 SGP_RATE_LIMIT_UNAVAILABLE (2 s) or a code-less 503 (SERVICE_BUSY): retry later. A 400 for a malformed body (Message says which field).

It is never retried: every attempt is a real pricing request.

func (*Client) BuildSgp

func (c *Client) BuildSgp(ctx context.Context, sport string, p SgpBuildParams) (*SgpBuildResponse, error)

BuildSgp prices a same game parlay on one book: "fanduel", "draftkings", "hardrock", "betrivers", "ballybet" or "betmgm". It is never retried: every attempt is a real pricing request.

A 422 whose body has success false and a legs array is RETURNED, not an error: branch on ReasonCode ("unresolved" means a leg did not resolve and legs[i].reason says why; any other code means the book refused the slip). Every other 422 is an *APIError. A 503 with Code SgpNoPriceCode means no usable price came back; its RetryAfter is the server's, or 2 s when the header is missing.

With p.Fair (MVP and Hall of Fame plans) a priced 200 also carries Fair, our estimate of the parlay's price from de-vigged leg probabilities and the book's own correlation (SgpFairBlock), beside the book's Sgp, never in place of it. Fair.Price is nil, with Fair.Why, when the estimate cannot be made. On the Rookie plan the call is a 403 with Code SGP_TIER_REQUIRED, refused before anything is priced (below Rookie the builder itself is a 403 FORBIDDEN); a 503 with Code SGP_FEATURE_DISABLED means the fair price is not switched on yet. To price the same legs on every book at once, see CompareSgp.

func (*Client) CompareSgp added in v0.10.0

func (c *Client) CompareSgp(ctx context.Context, sport string, p SgpCompareParams) (*SgpCompareResponse, error)

CompareSgp prices one same game parlay on every SGP book at once (FanDuel, DraftKings, Hard Rock, BetRivers, Bally Bet and BetMGM), from OUR event id, and returns each book's own answer side by side with the best price (MVP and Hall of Fame).

p.EventID is our id for the game, as GetOdds gives it ({sport}:{Away}@{Home}-{YYYYMMDD}, -G2 for the second game of a doubleheader), not a book's id from GetSgpEvents. The same legs go to every book; books price game legs at the lines they list (the main line, and on NHL the alternates Hard Rock, Bally Bet and, before the game, DraftKings list), so a spread or total at a number one book does not list comes back "unresolved" on that book. A team_total or period leg is "unsupported" on every book except Hard Rock on NHL.

A 200 always carries every book asked, each with a Status: "priced" books have Result.Sgp (exactly the body BuildSgp returns for that book), "refused" and "unresolved" books have the 422 body in Result, and the others a Reason. Best is the highest decimal price among the priced books (nil when none priced). Branch on each book's Status; a book that did not price is not an error. New statuses can appear: treat one you do not know as no price.

Before a game starts, a book that priced exactly these legs for this game in the last 15 seconds may be answered from that quote (Cached, Result.Meta.CacheAgeMs), which costs no build; p.Fresh prices every book now. Every other book asked counts one build against the key's per-minute SGP build limit; a book that is never asked for a price (not listed, a different game, not reached in time, a leg that does not resolve) is given back at once, while the minute it was counted in is still running. p.Fair adds our estimate (SgpFairBlock), built from every book that priced.

The call can take up to p.DeadlineMs (default 6000; FanDuel, unless p.FanDuel is "skip", is given at least 12000) plus about a second: keep the client's timeout (WithTimeout, default 30 s) above that.

Errors are *APIError values with these codes: a 403 SGP_TIER_REQUIRED (the plan is below MVP); a 503 SGP_FEATURE_DISABLED (compare, or with p.Fair the fair price, is not switched on yet; RetryAfter 60 s); a 429 SGP_RATE_LIMIT (the books this call needs exceed what is left of the per-minute build limit: wait RetryAfter; Details carries unitsNeeded); a 429 SGP_COMPARE_TOO_MANY_BOOKS (the comparison needs more builds than the limit allows at all, sent without Retry-After, so RetryAfter is the 60 s default and waiting never helps: resend with at most Details' maxBooks books); a 503 SGP_RATE_LIMIT_UNAVAILABLE (2 s) or a code-less 503 (SERVICE_BUSY): retry later; a 400 for a malformed body (Message says which field).

It is never retried: every attempt is a real pricing request.

func (*Client) CompareSgpCombo added in v0.10.0

CompareSgpCombo prices a Kalshi combo as the same game parlay it is, on every SGP book at once (MVP and Hall of Fame). It is not sport-scoped: the sport comes from the legs.

Set p.ComboTicker (a Kalshi combo market: its legs are read from Kalshi) OR p.Legs (2 to 12 markets, each with a side), never both, plus any of CompareSgp's Books, DeadlineMs, FanDuel, Fair and Fresh. p.Venue is "kalshi" (empty sends "kalshi"). Each Kalshi leg is translated to the leg a sportsbook prices (Combo.Legs[].Translated), the game is matched to OUR event id (EventID), and the translated legs are priced exactly as CompareSgp prices them: Compare is its 200 body, with the same book statuses, quote cache, build counting and fair price. It never returns a Kalshi price for the combo: VenuePrice is the combo market's own quote on Kalshi, for context only (its prices are nil without a resting quote, and it is nil itself when Legs were sent). The priced quotes join your GetSgpHistory with Via "compare".

A game winner YES is that team's moneyline, a spread YES the team at minus the line (NO: the other team at plus it), a total or team total YES the over (NO the under), and a player "N+" market YES the over N - 0.5 (NO the under); a line market must be greater-than at a half point. A NO on a game winner, other periods, soccer and non-sports markets are not translated.

A 422 whose body carries a code and an error is RETURNED, not an error: Success is false and Code says why. COMBO_NOT_A_COMBO: the market does not list 2 to 12 legs. COMBO_LEGS_UNSUPPORTED: a leg has no sportsbook equivalent (Combo.Legs[].Why says why). COMBO_MULTI_GAME: the legs span games (Games groups them by index into Combo.Legs: send one game's legs). COMBO_EVENT_NOT_FOUND: the game is not on our pregame board, or cannot be told from another game of the pair that day. A code added later is returned the same way. The API sends COMBO_NOT_A_COMBO without success; the SDK sets it to false. A 422 that is not JSON, lacks a string code or error, or claims success is an *APIError.

Errors are *APIError values with these codes: a 400 COMBO_INVALID (a malformed body: Message says which field); a 404 COMBO_NOT_FOUND (Kalshi has no market p.ComboTicker; it matches ErrNotFound; with p.Legs, a leg market Kalshi does not have is a returned COMBO_LEGS_UNSUPPORTED instead); a 503 COMBO_VENUE_UNAVAILABLE (Kalshi could not be read just then: wait RetryAfter); a 403 SGP_TIER_REQUIRED (the plan is below MVP, refused before anything is read from Kalshi); a 503 SGP_FEATURE_DISABLED (combos, compare or, with p.Fair, the fair price are not switched on yet; Details names the feature; RetryAfter 60 s); a 429 as on CompareSgp (SGP_RATE_LIMIT, SGP_COMPARE_TOO_MANY_BOOKS, or the plan's limits); a 503 SGP_RATE_LIMIT_UNAVAILABLE (2 s) or a code-less 503 (SERVICE_BUSY): retry later.

Kalshi is read before the books are priced, so each attempt waits at least 45 s whatever a shorter client timeout (WithTimeout) says; a longer one, or none, is kept. A deadline on ctx still applies. It is never retried: every attempt is a real pricing request.

func (*Client) CreateWebhook added in v0.5.0

CreateWebhook registers an HTTPS endpoint for signed event notifications: line.moved, ev.found, game.started, game.final and props.graded (beta; MVP and Hall of Fame; MVP 3 endpoints, Hall of Fame 10). Data.Secret, the signing secret, is returned ONCE: store it, then check every delivery with VerifyWebhookSignature.

The calls that change something (create, update, delete, test, rotate-secret) are never retried, whatever RetryPolicy says. If one times out, list your endpoints before creating again, and rotate the secret of one whose secret you never saw. A 400 or 409 is an *APIError whose Code says why, such as invalid_body, https_required, url_not_allowed, url_unresolvable, sport_not_covered, filter_never_matches, event_type_unavailable (400), endpoint_limit or endpoint_disabled (409), and whose Details is the body. A 429 carries the code rate_limited. Other plans get a 403 (ErrForbidden).

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"

	owls "github.com/owlsinsight/owls-insight-sdk-go"
)

func main() {
	c, err := owls.NewClient(os.Getenv("OWLS_INSIGHT_API_KEY"))
	if err != nil {
		log.Fatal(err)
	}
	created, err := c.CreateWebhook(context.Background(), owls.CreateWebhookParams{
		URL:        "https://example.com/owls",
		EventTypes: []string{owls.WebhookEventLineMoved},
		Filters:    &owls.WebhookFiltersInput{Sports: []string{"nba"}, LineMoved: &owls.WebhookLineMovedFiltersInput{PointStep: 1}},
	})
	var apiErr *owls.APIError
	if errors.As(err, &apiErr) && apiErr.Status == http.StatusBadRequest {
		log.Fatalf("refused: %s (%s)", apiErr.Message, apiErr.Code)
	} else if err != nil {
		log.Fatal(err)
	}
	fmt.Println(*created.Data.ID, "store the secret now:", len(*created.Data.Secret) > 0)
}

func (*Client) DeleteWebhook added in v0.5.0

func (c *Client) DeleteWebhook(ctx context.Context, id string) (*WebhookDeleteResponse, error)

DeleteWebhook deletes an endpoint. Its undelivered events are cancelled and nothing more is sent. Never retried.

func (*Client) GetBookProps

func (c *Client) GetBookProps(ctx context.Context, sport, book string, p *PropsParams) (*BookPropsResult, error)

GetBookProps returns one book's player props for a sport. p.Books is not sent; p.State serves another state's FanDuel marketIds on book "fanduel" (see PropsParams.State). The bet365 props were retired: that book answers 410 Gone, an *APIError (Bet365 game lines are on GetV2).

func (*Client) GetBookPropsStats

func (c *Client) GetBookPropsStats(ctx context.Context, book string) (*BookPropsStatsResponse, error)

GetBookPropsStats returns one book's prop counts.

func (*Client) GetCS2Match

func (c *Client) GetCS2Match(ctx context.Context, matchID string) (*CS2MatchDetailResponse, error)

GetCS2Match returns one CS2 match with map scores and player stats.

func (*Client) GetCS2Matches

func (c *Client) GetCS2Matches(ctx context.Context, p *CS2MatchesParams) (*CS2MatchesResponse, error)

GetCS2Matches searches archived CS2 matches.

func (*Client) GetCS2Players

func (c *Client) GetCS2Players(ctx context.Context, p CS2PlayersParams) (*CS2PlayersResponse, error)

GetCS2Players searches CS2 player stats across matches.

func (*Client) GetClosingOdds

func (c *Client) GetClosingOdds(ctx context.Context, p *ClosingOddsParams) (*ClosingOddsResponse, error)

GetClosingOdds returns historical closing lines, paginated.

func (*Client) GetDaySchedule added in v0.3.0

func (c *Client) GetDaySchedule(ctx context.Context, p DayScheduleParams) (*DayScheduleResponse, error)

GetDaySchedule returns every game of a calendar day in a time zone, across sports: start time, status, score and clock, both sides and Kalshi win prices. The zero DayScheduleParams asks for today in America/New_York, every sport; Days extends the range up to 10 days. Meta.Status says whether the answer is complete and fresh ('ok', 'partial', 'stale' or 'no-data').

func (*Client) GetEV

func (c *Client) GetEV(ctx context.Context, sport string, p *EVParams) (*EVResponse, error)

GetEV returns the positive-EV prices of a sport's upcoming games (MVP and above): moneyline prices that beat the fair, Pinnacle's de-vigged line, which the other books do not move (they only veto a side they disagree with). EV is computed at the fair minus 1 percentage point (FairProbability); the fair before that margin is FairUnadjusted on each opportunity and Fair on each event. Set Venues to also list Kalshi and Polymarket.

func (*Client) GetEVLines added in v0.9.0

func (c *Client) GetEVLines(ctx context.Context, sport string, p *EVLinesParams) (*EVLinesResponse, error)

GetEVLines returns the positive-EV spreads and totals of a sport's upcoming games (MVP and above): GET /api/v1/{sport}/ev with market=spreads, totals or both (the default). The same math as GetEV, scored at Pinnacle's main number against the books that hang exactly that number; every opportunity carries its own Point. Supported for nba, ncaab, nfl, ncaaf, nhl, mlb, wnba and soccer; elsewhere Meta.Unsupported says why nothing is scored.

The route answers with the moneyline board (GetEV's shape) unless every market it is sent is spreads or totals, so GetEVLines checks Markets before any request, as the route reads them: split on commas, trimmed and lower-cased, empty entries dropped. Anything other than spreads or totals ("h2h", "spread", "moneyline") is an error, as is a Markets with no entry left. A nil or empty Markets asks for both. An answer that still reports the moneyline market is an error too.

func (*Client) GetEsportsRealtime

func (c *Client) GetEsportsRealtime(ctx context.Context, game, league string) (*EsportsRealtimeResponse, error)

GetEsportsRealtime returns Pinnacle real-time odds for one esport (cs2, lol, valorant, dota2), MVP and above. league is required: a substring of the Pinnacle league name such as "BLAST" or "LCK" (the server never serves the whole feed). Each item of Data is one Pinnacle matchup exactly as Pinnacle sent it: no team-name normalization, prices in American odds.

func (*Client) GetFair added in v0.9.0

func (c *Client) GetFair(ctx context.Context, sport string) (*FairResponse, error)

GetFair returns the Owls fair line of a sport (MVP and above): every upcoming game's fair moneyline, spread and total, each side as a probability and as American odds, anchored on Pinnacle (GET /api/v1/{sport}/fair). These are the fairs before the 1 percentage point margin GetEV scores against. A market is nil when it has no current fair (Meta.Boards says which board is old). Spreads and totals are priced at Pinnacle's main number for nba, ncaab, nfl, ncaaf, nhl, mlb, wnba and soccer (Meta.Markets); other sports carry the moneyline only.

func (*Client) GetGameStatsDetail

func (c *Client) GetGameStatsDetail(ctx context.Context, eventID string) (*GameStatsDetailResponse, error)

GetGameStatsDetail returns team stats, lineups, head-to-head and incidents of an archived game.

func (*Client) GetH2H

func (c *Client) GetH2H(ctx context.Context, sport, eventID string) (*H2HResponse, error)

GetH2H returns head-to-head data for one event.

func (*Client) GetHardRockEvents

func (c *Client) GetHardRockEvents(ctx context.Context, state, sport, league string) (*HardRockEventsResponse, error)

GetHardRockEvents returns raw Hard Rock events with all their markets. Pass a league slug for the big sports; leave it empty for the small ones.

func (*Client) GetHardRockLadder

func (c *Client) GetHardRockLadder(ctx context.Context) (*HardRockLadderResponse, error)

GetHardRockLadder returns the rootIdx to American odds ladder that decodes Hard Rock selections.

func (*Client) GetHardRockLeagues

func (c *Client) GetHardRockLeagues(ctx context.Context, state, sport string) (*HardRockLeaguesResponse, error)

GetHardRockLeagues lists the leagues of a Hard Rock sport in a state.

func (*Client) GetHistoricalPlayerProps

func (c *Client) GetHistoricalPlayerProps(ctx context.Context, p *HistoricalPlayerPropsParams) (*HistoricalPlayerPropsResponse, error)

GetHistoricalPlayerProps returns historical closing player-prop lines, paginated.

func (*Client) GetHistoryCoverage

func (c *Client) GetHistoryCoverage(ctx context.Context) (*HistoryCoverageResponse, error)

GetHistoryCoverage returns how far back each historical dataset goes, per sport.

func (*Client) GetHistoryGames

func (c *Client) GetHistoryGames(ctx context.Context, p *HistoryGamesParams) (*HistoryGamesResponse, error)

GetHistoryGames lists archived games.

func (*Client) GetHistoryOdds

func (c *Client) GetHistoryOdds(ctx context.Context, p HistoryOddsParams) (*HistoryOddsResponse, error)

GetHistoryOdds returns one page of an archived game's odds snapshots. To read them all, use IterHistoryOdds.

func (*Client) GetHistoryProps

func (c *Client) GetHistoryProps(ctx context.Context, p HistoryPropsParams) (*HistoryPropsResponse, error)

GetHistoryProps returns one page of an archived game's prop snapshots. To read them all, use IterHistoryProps.

func (*Client) GetHistorySplits added in v0.2.0

func (c *Client) GetHistorySplits(ctx context.Context, p HistorySplitsParams) (*HistorySplitsResponse, error)

GetHistorySplits returns one page of the movement history of betting splits (MVP and above): each book's handle and ticket percentages on a game's spread, total and moneyline, with the line or price shown, recorded whenever they changed, oldest first. Ask for one game by EventID, or for a sport by Sport and StartDate; page with Limit and Offset until Pagination.HasMore is false.

func (*Client) GetHistoryStats

func (c *Client) GetHistoryStats(ctx context.Context, p *HistoryStatsParams) (*HistoryStatsResponse, error)

GetHistoryStats returns archived player stats.

func (*Client) GetHistoryTennisStats

func (c *Client) GetHistoryTennisStats(ctx context.Context, eventID string) (*TennisStatsResponse, error)

GetHistoryTennisStats returns the archived match statistics of a tennis event.

func (*Client) GetInjuries

func (c *Client) GetInjuries(ctx context.Context, sport string, p *InjuriesParams) (*InjuriesResponse, error)

GetInjuries returns player injury designations for a sport (nfl, mlb), one record per source.

func (*Client) GetMatchStats

func (c *Client) GetMatchStats(ctx context.Context, sport, eventID string) (*MatchStatsResponse, error)

GetMatchStats returns live match statistics for one event.

func (*Client) GetMoneyline

func (c *Client) GetMoneyline(ctx context.Context, sport string, p *OddsParams) (*OddsResponse, error)

GetMoneyline returns the moneyline board for one sport.

func (*Client) GetMoneylineHistory

func (c *Client) GetMoneylineHistory(ctx context.Context, p LineHistoryParams) (*OddsHistoryResponse, error)

GetMoneylineHistory is GetOddsHistory for the h2h market.

func (*Client) GetOdds

func (c *Client) GetOdds(ctx context.Context, sport string, p *OddsParams) (*OddsResponse, error)

GetOdds returns the merged board for one sport. A parameter the endpoint does not read, or a market value it does not know, is reported in meta.ignored_params rather than rejected.

func (*Client) GetOddsHistory

func (c *Client) GetOddsHistory(ctx context.Context, p OddsHistoryParams) (*OddsHistoryResponse, error)

GetOddsHistory returns the line movement of one book, market and side of a live or recent event.

func (*Client) GetOneXBetSoccer

func (c *Client) GetOneXBetSoccer(ctx context.Context) (*OneXBetSoccerResponse, error)

GetOneXBetSoccer returns the isolated 1xBet soccer feed, which is not part of GetOdds.

func (*Client) GetPS3838Realtime

func (c *Client) GetPS3838Realtime(ctx context.Context, sport, league string) (*RealtimeResponse, error)

GetPS3838Realtime returns PS3838 real-time odds for one sport (MVP and above).

func (*Client) GetPlayerAverages

func (c *Client) GetPlayerAverages(ctx context.Context, sport string, p PlayerAveragesParams) (*PlayerAveragesResponse, error)

GetPlayerAverages returns a player's season and rolling averages.

func (*Client) GetPlays added in v0.9.0

func (c *Client) GetPlays(ctx context.Context, sport, eventID string, p *PlaysParams) (*Plays, error)

GetPlays returns the play-by-play of one NBA, NHL, NFL, NCAAF or soccer game in play (or ended within the last 6 hours), oldest play first, by the event id GetScores gives the game (GET /api/v1/{sport}/plays?eventId=). For soccer the plays are the match incidents (goals, own goals, penalties, cards, substitutions) of every game on the live board, whatever its league. With an empty eventID it returns the games that have play-by-play; p must then be nil or set neither Since nor Limit (they apply to one game, so GetPlays refuses them without a request). A game with no play-by-play (before tip-off, or any other id) is a 404, an *APIError.

Poll with Since set to the LastSeq you have, at least every 2 minutes, to receive the new plays and every amended one, or opt in to plays-update with Subscription.Plays. Merge plays by ID: a play can be amended after it is first published. Play.Detail is the sport's own fields as raw JSON: decode it with Decode[FootballPlayDetail], Decode[HockeyPlayDetail], Decode[BasketballPlayDetail] or Decode[SoccerPlayDetail].

Example
package main

import (
	"context"
	"log"
	"os"
	"time"

	owls "github.com/owlsinsight/owls-insight-sdk-go"
)

func main() {
	c, err := owls.NewClient(os.Getenv("OWLS_INSIGHT_API_KEY"))
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.Background()
	plays := map[string]owls.Play{} // by play id: a play can be amended after it is first published
	p := &owls.PlaysParams{}
	for {
		res, err := c.GetPlays(ctx, "nfl", "nfl:Cincinnati Bengals@Baltimore Ravens-20261025", p)
		if err != nil {
			log.Fatal(err) // a 404 once the game is no longer covered
		}
		for _, play := range res.Game.Plays {
			plays[*play.ID] = play
		}
		if res.Game.State != nil && *res.Game.State == "post" {
			return
		}
		p.Since = res.Game.LastSeq // the new plays and the amended ones next time
		time.Sleep(10 * time.Second)
	}
}

func (*Client) GetPropResults

func (c *Client) GetPropResults(ctx context.Context, sport string, p PropResultsParams) (*PropResults, error)

GetPropResults returns graded player-prop outcomes for one completed game, or with only p.Date, the graded games of that day.

func (*Client) GetPropTrends

func (c *Client) GetPropTrends(ctx context.Context, sport string, p PropTrendsParams) (*PropTrendsResponse, error)

GetPropTrends returns one player's recent form in one prop category.

func (*Client) GetProphetxOdds

func (c *Client) GetProphetxOdds(ctx context.Context, p ProphetXOddsParams) (*ProphetXOddsResponse, error)

GetProphetxOdds returns a raw ProphetX exchange snapshot for one or more sports.

func (*Client) GetProps

func (c *Client) GetProps(ctx context.Context, sport string, p *PropsParams) (*PropsResponse, error)

GetProps returns player props for a sport across books.

func (*Client) GetPropsEV added in v0.9.0

func (c *Client) GetPropsEV(ctx context.Context, sport string, p *PropsEVParams) (*PropsEVResponse, error)

GetPropsEV returns the fair line and the positive-EV prices of a sport's player props (MVP and above): GET /api/v1/{sport}/props/ev. For each upcoming prop Pinnacle prices both ways, the fair over and under, and every sportsbook price at the same player, category and line that beats it. Set All to also list the props with no positive-EV price. Supported for nba, ncaab, wnba, nfl, ncaaf, nhl, mlb and soccer.

func (*Client) GetPropsHistory

func (c *Client) GetPropsHistory(ctx context.Context, sport string, p PropsHistoryParams) (*PropsHistoryResponse, error)

GetPropsHistory returns the line history of one player prop.

func (*Client) GetPropsStats

func (c *Client) GetPropsStats(ctx context.Context) (*PropsStatsResponse, error)

GetPropsStats returns prop counts per sport and book.

func (*Client) GetPublicBetting

func (c *Client) GetPublicBetting(ctx context.Context, p *PublicBettingParams) (*PublicBettingResponse, error)

GetPublicBetting returns historical public betting percentages, paginated.

func (*Client) GetRealtime

func (c *Client) GetRealtime(ctx context.Context, sport, league string) (*RealtimeResponse, error)

GetRealtime returns Pinnacle real-time odds for one sport (MVP and above). The esports keys (cs2, lol, valorant, dota2) answer with another shape: use GetEsportsRealtime for them; GetRealtime refuses them without a request.

func (*Client) GetResults

func (c *Client) GetResults(ctx context.Context, sport string) (*ResultsResponse, error)

GetResults returns today's completed games for a sport.

func (*Client) GetSchedule

func (c *Client) GetSchedule(ctx context.Context, sport string) (*ScheduleResponse, error)

GetSchedule returns today's upcoming games for a sport.

func (*Client) GetScores

func (c *Client) GetScores(ctx context.Context, sport string) (*Scores, error)

GetScores returns live scores: every sport when sport is empty (/api/v1/scores/live), else one sport (/api/v1/{sport}/scores/live).

func (*Client) GetSgpEvents

func (c *Client) GetSgpEvents(ctx context.Context, sport, book string) (*SgpEventsResponse, error)

GetSgpEvents lists the events a book can price parlays for: fanduel (the server's default when book is empty), draftkings, hardrock, betrivers, ballybet or betmgm.

func (*Client) GetSgpHistory added in v0.10.0

func (c *Client) GetSgpHistory(ctx context.Context, sport string, p SgpHistoryParams) (*SgpHistoryResponse, error)

GetSgpHistory returns the same game parlay prices YOUR account was quoted for one game, newest first: the legs, the book's price and each leg's own price, from BuildSgp, CompareSgp, CompareSgpCombo and BatchSgp (MVP and Hall of Fame).

Name the game by OUR event id (p.EventID, as GetOdds gives it), or by a book and that book's own event id (p.Book and p.BookEventID, as GetSgpEvents lists it), never both. p.LegsHash narrows it to one parlay: every priced answer carries it as Meta.LegsHash (on CompareSgp and BatchSgp also beside the results), and the same parlay sent the same way always has the same hash. p.From and p.To (ISO 8601) set the window, at most 7 days, by default the last 24 hours (p.From alone: the 24 hours from it, up to now, so set p.To too for a longer window); p.Limit is 1 to 500, default 100. Meta echoes the query as the API applied it.

Only the calling account's own quotes are returned, and only priced answers are kept: a refusal, a no-price or a busy answer is not, and a quote served from the short quote cache is not a new quote. The history holds quotes from the day recording was switched on (nothing earlier is filled in), for 90 days. QuotedAt is when the book priced the parlay (Meta.PricedAt on the answer). Fair is our estimate only on a quote from BuildSgp with Fair set whose answer carried a fair price (Fair.Price not nil); otherwise it is nil (CompareSgp, CompareSgpCombo and BatchSgp record each book's quote without it, whatever their Fair). Via is "build", "compare" (CompareSgpCombo's quotes too) or "batch"; more can be added.

Errors are *APIError values: a 403 SGP_TIER_REQUIRED (the plan is below MVP); a 503 SGP_FEATURE_DISABLED (the history is not switched on yet; RetryAfter 60 s); a 503 SGP_HISTORY_UNAVAILABLE (the history could not be read just then; RetryAfter 5 s); a 429 (the plan rate limit, or the concurrent request limit); a 400 for a parameter the API cannot read, for example the game named both ways or not at all, a window over 7 days, a Limit outside 1 to 500 or a malformed LegsHash (Message says which).

It is a read, so with WithRetry its 429s and 503s are retried like any other GET.

func (*Client) GetSplits

func (c *Client) GetSplits(ctx context.Context, sport string) (*SplitsResponse, error)

GetSplits returns betting splits for a sport.

func (*Client) GetSpreadHistory

func (c *Client) GetSpreadHistory(ctx context.Context, p LineHistoryParams) (*OddsHistoryResponse, error)

GetSpreadHistory is GetOddsHistory for the spreads market.

func (*Client) GetSpreads

func (c *Client) GetSpreads(ctx context.Context, sport string, p *OddsParams) (*OddsResponse, error)

GetSpreads returns the spreads board for one sport.

func (*Client) GetStakeBets

func (c *Client) GetStakeBets(ctx context.Context) (*StakeBetsResponse, error)

GetStakeBets returns a sample of Stake's public bet feed. It is a sample, not a ledger: see meta.sampling.

func (*Client) GetStats

func (c *Client) GetStats(ctx context.Context, sport, date string) (*StatsResponse, error)

GetStats returns box scores for a sport; date (YYYY-MM-DD) is optional.

func (*Client) GetTotals

func (c *Client) GetTotals(ctx context.Context, sport string, p *OddsParams) (*OddsResponse, error)

GetTotals returns the totals board for one sport.

func (*Client) GetTotalsHistory

func (c *Client) GetTotalsHistory(ctx context.Context, p LineHistoryParams) (*OddsHistoryResponse, error)

GetTotalsHistory is GetOddsHistory for the totals market (Side "over" or "under").

func (*Client) GetV2

func (c *Client) GetV2(ctx context.Context, book, sport, league string) (*V2BookResponse, error)

GetV2 returns a v2 book's raw markets for a sport: GET /api/v2/{book}/{sport}. Data holds each market exactly as the book published it, keyed by market id. Multi-league sports need a league key (see GetV2Leagues); leave league empty for the others. Every v2 book goes through this one method.

func (*Client) GetV2Leagues

func (c *Client) GetV2Leagues(ctx context.Context, book, sport string) (*V2LeaguesResponse, error)

GetV2Leagues returns the league index of a v2 book and sport: GET /api/v2/{book}/{sport}/leagues.

func (*Client) GetV2Markets added in v0.7.0

func (c *Client) GetV2Markets(ctx context.Context, book, sport, league, marketIds string) (*V2BookResponse, error)

GetV2Markets is GetV2 with a marketIds filter: a comma-separated list of market ids and prefixes ending in *, for example "props-*", "main,props-*" or "*" for every entry. Only those entries are returned (and dated and hashed into the ETag); Meta.MarketIds echoes the filter and Meta.MarketsFilteredOut counts what it left out. It is also how the entries a book leaves out by default are asked for (on a default request Meta.DefaultExcluded says how many were left out and the marketIds value that includes them):

  • DraftKings stores its US-sport player props one entry per subcategory, "props-<subcategoryId>": GetV2Markets(ctx, "draftkings", "nfl", "", "props-*").
  • Fanatics Markets keeps its live order books as "books-<eventId>-<group>" entries: GetV2Markets(ctx, "fanaticsmarkets", "nfl", "", "books-*").
  • Polymarket US ("polymarketus") serves an event's full market catalog as "full-<eventSlug>": GetV2Markets(ctx, "polymarketus", "nfl", "nfl", "full-*").

Prop Builder ("propbuilder") keys its entries family first, so GetV2Markets(ctx, "propbuilder", "nfl", "", "ou-*,ss-*") returns every Over/Under and N+ board of the sport. An empty marketIds behaves like GetV2. Books with their own routes (bet365, hardrock, mybookie, stake, thunderpick, underdog) ignore it and return their whole board.

func (*Client) GetWebhook added in v0.5.0

func (c *Client) GetWebhook(ctx context.Context, id string) (*WebhookResponse, error)

GetWebhook returns one endpoint with its delivery health (ConsecutiveFailures, FailingSince, LastSuccessAt). An id that is not one of your endpoints is a 404 (ErrNotFound).

func (*Client) IterHistoryOdds

func (c *Client) IterHistoryOdds(ctx context.Context, p HistoryOddsParams, o *PageOptions) iter.Seq2[OddsSnapshot, error]

IterHistoryOdds yields every odds snapshot of one archived game, one page at a time, in order. This is the intended way to export a game's history: do not fan out one request per book or market in parallel. p.Limit and p.Offset are ignored.

for s, err := range c.IterHistoryOdds(ctx, owls.HistoryOddsParams{EventID: id}, nil) {
	if err != nil { return err }
	rows = append(rows, s)
}
Example
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	owls "github.com/owlsinsight/owls-insight-sdk-go"
)

func main() {
	c, err := owls.NewClient(os.Getenv("OWLS_INSIGHT_API_KEY"))
	if err != nil {
		log.Fatal(err)
	}
	p := owls.HistoryOddsParams{EventID: "nba:Boston Celtics@New York Knicks-20260310", Book: "pinnacle"}
	for snap, err := range c.IterHistoryOdds(context.Background(), p, nil) {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(*snap.RecordedAt, *snap.Market, *snap.Price)
	}
}

func (*Client) IterHistoryProps

func (c *Client) IterHistoryProps(ctx context.Context, p HistoryPropsParams, o *PageOptions) iter.Seq2[PropsSnapshot, error]

IterHistoryProps yields every prop snapshot of one archived game, one page at a time. Same contract as IterHistoryOdds.

func (*Client) ListEvents

func (c *Client) ListEvents(ctx context.Context, sport, league string) (*EventsResponse, error)

ListEvents lists a sport's current events without odds. The event's eventId is the id the history endpoints take.

func (*Client) ListWebhookDeliveries added in v0.5.0

func (c *Client) ListWebhookDeliveries(ctx context.Context, id string, p *WebhookDeliveriesParams) (*WebhookDeliveriesResponse, error)

ListWebhookDeliveries returns recent deliveries to one endpoint, newest first: status, attempts, your receiver's last answer and the exact payload sent. Page with StartingAfter (the last ID of the previous page) while HasMore. Deliveries are kept for 14 days. A delivery's Payload is the body as stored; to type it, pass its JSON to ParseWebhookEvent.

func (*Client) ListWebhooks added in v0.5.0

func (c *Client) ListWebhooks(ctx context.Context) (*WebhookListResponse, error)

ListWebhooks returns your webhook endpoints, oldest first, never their secrets. Meta.Limit is how many endpoints your plan allows.

func (*Client) Normalize

func (c *Client) Normalize(ctx context.Context, name, sport string) (*NormalizeResponse, error)

Normalize maps a sportsbook team name to its canonical form.

func (*Client) NormalizeBatch

func (c *Client) NormalizeBatch(ctx context.Context, names []string, sport string) (*NormalizeBatchResponse, error)

NormalizeBatch normalizes up to 25 team names at once.

func (*Client) RotateWebhookSecret added in v0.5.0

RotateWebhookSecret issues a new signing secret, returned ONCE. The previous one keeps signing beside it for p.ExpirePreviousAfterHours (nil p: 24 hours), so a receiver holding either secret verifies meanwhile. Never retried.

func (*Client) Stream

func (c *Client) Stream(opts ...StreamOption) *Stream

Stream returns a new, unconnected stream. Call Run to connect.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"

	owls "github.com/owlsinsight/owls-insight-sdk-go"
)

func main() {
	c, err := owls.NewClient(os.Getenv("OWLS_INSIGHT_API_KEY"))
	if err != nil {
		log.Fatal(err)
	}
	s := c.Stream(owls.WithSubscription(owls.Subscription{
		Sports: []string{"nba"},
		Books:  []string{"pinnacle", "fanduel"},
	}))
	s.OnOddsUpdate(func(u *owls.OddsUpdatePayload, err error) {
		if err != nil {
			log.Printf("odds-update: %v", err)
			return
		}
		for sport, events := range u.Sports {
			fmt.Println(sport, len(events), "events changed")
		}
	})
	for {
		// Run keeps its own backoff across calls, so calling it again after a
		// retryable refusal waits as long as the server asked.
		err := s.Run(context.Background())
		var refused *owls.RefusalError
		if errors.As(err, &refused) && refused.Retryable {
			log.Printf("refused, retrying: %v", err)
			continue
		}
		log.Fatal(err)
	}
}

func (*Client) TestWebhook added in v0.5.0

func (c *Client) TestWebhook(ctx context.Context, id string) (*WebhookTestResponse, error)

TestWebhook queues a webhook.test event to the endpoint, signed like every other event (202). One per endpoint every 10 seconds; the endpoint must be enabled. Follow it with Client.ListWebhookDeliveries. Never retried.

func (*Client) UpdateWebhook added in v0.5.0

func (c *Client) UpdateWebhook(ctx context.Context, id string, p UpdateWebhookParams) (*WebhookResponse, error)

UpdateWebhook changes an endpoint; only the fields set in p are sent. Never retried.

type ClientSubscription

type ClientSubscription struct {
	Alternates *bool            `json:"alternates,omitempty"`
	Books      []Book           `json:"books,omitempty"`
	Esports    *json.RawMessage `json:"esports,omitempty"`

	// EsportsRealtime Per-game subscription filter for the Pinnacle esports realtime
	// WebSocket events (OwlsInsightEvents).
	//
	// Each entry MUST include a `league` substring - the server never broadcasts
	// the firehose unfiltered. Mirrors the REST `/api/v1/{game}/realtime?league=`
	// required query param.
	EsportsRealtime  *EsportsRealtimeSubscription `json:"esportsRealtime,omitempty"`
	EventIds         []string                     `json:"eventIds,omitempty"`
	ExcludeExchanges *bool                        `json:"exclude_exchanges,omitempty"`

	// OneXBetSoccer Opt-in to the isolated 1xBet SOCCER feed (`1xbet-update` events). Default:
	// not subscribed. 1xBet soccer is NOT in /odds - it's a scrape-based feed on
	// its own channel. (1xBet esports is unaffected and stays in the odds stream.)
	OneXBetSoccer *bool `json:"oneXBetSoccer,omitempty"`

	// Plays Play-by-play subscription (`plays-update`) as the server kept it: `true`, or the
	// valid play-by-play sports you sent. Absent when nothing valid was sent.
	Plays *json.RawMessage `json:"plays,omitempty"`

	// Prophetx ProphetX WebSocket subscription filter.
	//
	//   - omitted / false    → do NOT receive prophetx-update events (default)
	//   - true               → receive every ProphetX delta (all sports + kinds)
	//   - object with filters → receive only matching (sport, kind) deltas
	//
	// The object form is optional in both dimensions:
	//   - `sports: ['basketball']` alone → all kinds for basketball
	//   - `kinds: ['prop']` alone        → all sports' props
	//   - both specified                 → intersection
	//
	// Invalid filter values (unknown sport / kind) are silently dropped by the
	// server AFTER being received - the server never widens the subscription
	// to "all". See the server's validateSubscription() for exact semantics.
	Prophetx *ProphetXSubscription `json:"prophetx,omitempty"`

	// Realtime See SubscribeOptions.realtime.
	Realtime *RealtimeFeedsSubscription `json:"realtime,omitempty"`

	// Scores Live scores subscription (`scores-update`; opt-in, default: undefined/no events).
	Scores *json.RawMessage `json:"scores,omitempty"`
	Sports []Sport          `json:"sports,omitempty"`

	// V2 Opt-in subscription for raw pass-through Source API (v2) WebSocket
	// events. Pass on `subscribe()` to start receiving the corresponding
	// `<book>-v2-update` events. Omit (or set the book to `undefined`) to
	// receive nothing for that book - these events are opt-in only.
	V2 *V2Subscription `json:"v2,omitempty"`

	// V2Advisory Set by the server, never sent: what happened to each per-league v2 filter you
	// sent (accepted but unverified, dropped, or widened to `'*'`). League keys are
	// not checked at subscribe time, so read this when a v2 subscription stays silent.
	V2Advisory []V2LeagueFilterAdvisory `json:"v2Advisory,omitempty"`

	// V2OptionsAdvisory Set by the server, never sent: what happened to the book-level v2 options
	// (`v2.polymarket.eventTypes` and `marketTypes`), including an event type that
	// this connection will not receive.
	V2OptionsAdvisory []V2OptionAdvisory `json:"v2OptionsAdvisory,omitempty"`
}

ClientSubscription defines model for ClientSubscription.

type ClosingOddsDataQuality

type ClosingOddsDataQuality struct {
	Note *string `json:"note,omitempty"`

	// RowsExcluded Always 0 - rows are never excluded by this endpoint.
	RowsExcluded *float64 `json:"rowsExcluded,omitempty"`

	// SuppressedFields Count of individual price fields nulled across this response.
	SuppressedFields *float64 `json:"suppressedFields,omitempty"`
}

ClosingOddsDataQuality Per-response data quality for /history/closing-odds.

This endpoint never withholds rows. A price that cannot be a valid American odd (|price| < 100, which indicates a decimal coefficient stored in an integer column) is omitted per FIELD, leaving the row and its other markets intact.

type ClosingOddsParams

type ClosingOddsParams struct {
	EventID   string
	Sport     string
	Book      string
	Source    string
	StartDate string
	EndDate   string
	Season    string
	Limit     int
	Offset    int
}

ClosingOddsParams filters GetClosingOdds. Season does not work for soccer, tennis or cs2; narrow those with StartDate.

type ClosingOddsResponse

type ClosingOddsResponse struct {
	Data *struct {
		// DataQuality Per-response data quality for /history/closing-odds.
		//
		// This endpoint never withholds rows. A price that cannot be a valid American odd
		// (|price| < 100, which indicates a decimal coefficient stored in an integer
		// column) is omitted per FIELD, leaving the row and its other markets intact.
		DataQuality *ClosingOddsDataQuality `json:"dataQuality,omitempty"`
		Odds        []ClosingOddsRow        `json:"odds,omitempty"`
		Pagination  *struct {
			HasMore *bool    `json:"hasMore,omitempty"`
			Limit   *float64 `json:"limit,omitempty"`
			Offset  *float64 `json:"offset,omitempty"`
			Total   *float64 `json:"total,omitempty"`
		} `json:"pagination,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

ClosingOddsResponse defines model for ClosingOddsResponse.

type ClosingOddsRow

type ClosingOddsRow struct {
	AwayTeam *string `json:"awayTeam,omitempty"`
	Book     *string `json:"book,omitempty"`
	EventID  *string `json:"eventId,omitempty"`
	GameDate *string `json:"gameDate,omitempty"`
	HomeTeam *string `json:"homeTeam,omitempty"`

	// LastUpdate Epoch SECONDS from the source. The column is BIGINT UNSIGNED, so this is a number.
	LastUpdate *float64 `json:"lastUpdate,omitempty"`

	// Moneyline `draw` is present ONLY for three-way sports (soccer). Two-outcome sports omit
	// the key entirely rather than sending `draw: null`, so `Object.keys(moneyline)`
	// is safe and an MLB row never advertises an outcome that does not exist.
	//
	// For soccer, computing implied probability from home+away alone will NOT
	// reconcile - you must include the draw.
	Moneyline *struct {
		Away *float64 `json:"away,omitempty"`
		Draw *float64 `json:"draw,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"moneyline,omitempty"`

	// Source Where a closing-odds row came from. These are NOT interchangeable:
	//  - `football-data`  the bookmaker's FINAL published closing price
	//  - `live-derived`   the last snapshot our own capture saw, which is only as
	//                     close to the close as capture ran that day
	//  - `yahoo`/`oddsshark`/`espn`  the original third-party backfill (MLB + NBA)
	// For closing-line-value work prefer `football-data` where present.
	Source *ClosingOddsSource `json:"source,omitempty"`
	Sport  *string            `json:"sport,omitempty"`

	// Spread Every field null means the spread was captured but withheld: on a live-derived
	// close, a spread whose two points do not cancel or one known to be wrong for that
	// book and date (counted in `dataQuality.suppressedFields`). Absent means none was captured.
	Spread *struct {
		Away      *float64 `json:"away,omitempty"`
		AwayPrice *float64 `json:"awayPrice,omitempty"`
		Home      *float64 `json:"home,omitempty"`
		HomePrice *float64 `json:"homePrice,omitempty"`
	} `json:"spread,omitempty"`
	Total *struct {
		// Line The totals line. Null only when the whole total was withheld (a live-derived
		// close known to be wrong for that book and date); the prices are then null too.
		// The API omits `total` when none was captured.
		Line       *float64 `json:"line,omitempty"`
		OverPrice  *float64 `json:"overPrice,omitempty"`
		UnderPrice *float64 `json:"underPrice,omitempty"`
	} `json:"total,omitempty"`
}

ClosingOddsRow defines model for ClosingOddsRow.

type ClosingOddsSource

type ClosingOddsSource = string

ClosingOddsSource Where a closing-odds row came from. These are NOT interchangeable:

  • `football-data` the bookmaker's FINAL published closing price
  • `live-derived` the last snapshot our own capture saw, which is only as close to the close as capture ran that day
  • `yahoo`/`oddsshark`/`espn` the original third-party backfill (MLB + NBA)

For closing-line-value work prefer `football-data` where present.

type ComputedAverages

type ComputedAverages struct {
	Apg          *float64 `json:"apg,omitempty"`
	Bpg          *float64 `json:"bpg,omitempty"`
	FgPct        *float64 `json:"fgPct,omitempty"`
	FtPct        *float64 `json:"ftPct,omitempty"`
	GamesPlayed  *float64 `json:"gamesPlayed,omitempty"`
	MinutesAvg   *float64 `json:"minutesAvg,omitempty"`
	PlusMinusAvg *float64 `json:"plusMinusAvg,omitempty"`
	Ppg          *float64 `json:"ppg,omitempty"`
	Rpg          *float64 `json:"rpg,omitempty"`
	Spg          *float64 `json:"spg,omitempty"`
	ThreePct     *float64 `json:"threePct,omitempty"`
	Topg         *float64 `json:"topg,omitempty"`
}

ComputedAverages defines model for ComputedAverages.

type ConnectErrorCode

type ConnectErrorCode = string

ConnectErrorCode Why the server refused a connection, in `err.data.code` on `connect_error`. More codes can be added.

Not retryable (reconnecting with the same key cannot succeed until something changes on your account): `MISSING_API_KEY`, `INVALID_API_KEY`, `API_KEY_DEACTIVATED`, `TIER_NO_WS`, `SUBSCRIPTION_INACTIVE`, `PAYMENT_OVERDUE`, `TRIAL_EXPIRED`, `TRIAL_UNVERIFIABLE`.

Retryable: `SUBSCRIPTION_CHECK_FAILED`, `INTERNAL`, `IP_BLOCKED`, `CONNECTION_LIMIT` (every connection your plan allows is in use), `CLIENT_CLOSED`.

type ConnectErrorData

type ConnectErrorData struct {
	// Code Why the server refused a connection, in `err.data.code` on `connect_error`. More
	// codes can be added.
	//
	// Not retryable (reconnecting with the same key cannot succeed until something
	// changes on your account): `MISSING_API_KEY`, `INVALID_API_KEY`,
	// `API_KEY_DEACTIVATED`, `TIER_NO_WS`, `SUBSCRIPTION_INACTIVE`, `PAYMENT_OVERDUE`,
	// `TRIAL_EXPIRED`, `TRIAL_UNVERIFIABLE`.
	//
	// Retryable: `SUBSCRIPTION_CHECK_FAILED`, `INTERNAL`, `IP_BLOCKED`,
	// `CONNECTION_LIMIT` (every connection your plan allows is in use), `CLIENT_CLOSED`.
	Code *ConnectErrorCode `json:"code,omitempty"`

	// MaxConnections On `CONNECTION_LIMIT`: how many connections your plan allows.
	MaxConnections *float64 `json:"maxConnections,omitempty"`

	// RetryAfterMs Wait at least this long before trying again.
	RetryAfterMs *float64 `json:"retryAfterMs,omitempty"`

	// Retryable True when reconnecting later with the same key can succeed.
	Retryable *bool `json:"retryable,omitempty"`
}

ConnectErrorData `err.data` on a `connect_error` the server refused. Absent from API versions that predate it.

type CreateWebhookParams added in v0.5.0

type CreateWebhookParams struct {
	// URL must be https on the default port with a public hostname. IP addresses
	// and private or internal hosts are refused.
	URL string `json:"url"`
	// EventTypes are one or more of the WebhookEvent* constants (not
	// WebhookEventTest). A type that is not open yet is refused with a 400
	// (event_type_unavailable).
	EventTypes []string `json:"event_types"`
	// Description is up to 200 characters; empty sends none.
	Description string `json:"description,omitempty"`
	// Filters narrows what is sent; nil takes every default.
	Filters *WebhookFiltersInput `json:"filters,omitempty"`
}

CreateWebhookParams is the body of Client.CreateWebhook.

type DayScheduleBuildWindow added in v0.3.0

type DayScheduleBuildWindow struct {
	From *string `json:"from,omitempty"`
	To   *string `json:"to,omitempty"`
}

DayScheduleBuildWindow The days the schedule is built for: three UTC days back to nine ahead.

type DayScheduleCompetitor added in v0.3.0

type DayScheduleCompetitor struct {
	Code *string `json:"code,omitempty"`
	Name *string `json:"name,omitempty"`

	// Score Null before the game has a score.
	Score *float64 `json:"score,omitempty"`

	// Sets Tennis only: games won in each set, in set order.
	Sets  []float64 `json:"sets,omitempty"`
	Short *string   `json:"short,omitempty"`
	Side  *string   `json:"side,omitempty"`

	// WinPrice Kalshi price to buy this side to win, 0 to 1, so the two sides can add to a
	// little over 1. Null once the market closes, and null when the market is too thin
	// to price.
	WinPrice *float64 `json:"winPrice,omitempty"`

	// Winner Set once the game is final; null before then, and on a level final score.
	Winner *bool `json:"winner,omitempty"`
}

DayScheduleCompetitor One side of a game. Team sports list away then home; tennis and UFC list the first then the second competitor, with `side` null.

type DayScheduleGame added in v0.3.0

type DayScheduleGame struct {
	// Competition Tennis tournament or UFC event name; null elsewhere.
	Competition *string `json:"competition,omitempty"`

	// Competitors Always two entries; see DayScheduleCompetitor for the order.
	Competitors []DayScheduleCompetitor `json:"competitors,omitempty"`

	// DrawPrice Soccer only: Kalshi price for the draw.
	DrawPrice *float64 `json:"drawPrice,omitempty"`

	// EventID The game's eventId on /api/v1/{sport}/odds when it was found there, else null.
	EventID *string `json:"eventId,omitempty"`

	// ID Stable for the life of the game, even when it is rescheduled.
	ID                *string `json:"id,omitempty"`
	KalshiEventTicker *string `json:"kalshiEventTicker,omitempty"`
	League            *string `json:"league,omitempty"`

	// LiveUpdatedAt When this game's live data (status, score, clock) was last read; null before it has any.
	LiveUpdatedAt *string `json:"liveUpdatedAt,omitempty"`

	// Round Tennis round, playoff "Game N", or UFC weight class; null when none.
	Round *string `json:"round,omitempty"`
	Sport *string `json:"sport,omitempty"`

	// StartCheck The game's start time compared with independent sources.
	StartCheck *DayScheduleStartCheck `json:"startCheck,omitempty"`

	// StartTime Scheduled start, ISO 8601 UTC.
	StartTime *string `json:"startTime,omitempty"`

	// Status A game's state on the day schedule. Open: a new value must not break a reader.
	// - `unconfirmed`: the scheduled start passed long ago and no live data confirms the
	//   game started or ended, or live data for a game in progress stopped updating for
	//   more than 10 minutes.
	// - `time_tbd`: the start time is not set yet.
	Status *DayScheduleStatus `json:"status,omitempty"`

	// StatusDetail Clock or result detail: "Q2 8:12", "Top 5th", "45'", "Final/OT", "KO/TKO R1 3:58".
	StatusDetail *string `json:"statusDetail,omitempty"`
	UpdatedAt    *string `json:"updatedAt,omitempty"`

	// Volume Kalshi volume across the game's winner markets, in contracts. Games starting together are ordered by it.
	Volume *float64 `json:"volume,omitempty"`
}

DayScheduleGame One game on the day schedule.

type DayScheduleMeta added in v0.3.0

type DayScheduleMeta struct {
	// AgeSeconds Seconds since `cacheTimestamp`.
	AgeSeconds  *float64                `json:"ageSeconds,omitempty"`
	BuildWindow *DayScheduleBuildWindow `json:"buildWindow,omitempty"`

	// CacheTimestamp Build time of the least recently built day in range; null when none is known.
	CacheTimestamp *string `json:"cacheTimestamp,omitempty"`

	// LiveAgeSeconds Seconds since live data (scores, clocks) was last read in full.
	LiveAgeSeconds *float64 `json:"liveAgeSeconds,omitempty"`

	// Source Where the schedule comes from, currently "kalshi".
	Source    *string `json:"source,omitempty"`
	Status    *string `json:"status,omitempty"`
	Timestamp *string `json:"timestamp,omitempty"`

	// Window A UTC instant range `[start, end)`.
	Window *DayScheduleWindow `json:"window,omitempty"`
}

DayScheduleMeta Freshness of a day-schedule answer. `status`, first match wins:

  • `no-data`: nothing built for the range.
  • `stale`: built more than 3 minutes ago, the schedule not refreshed for 5 minutes, or live scores more than 90 seconds old while a game has started and not finished.
  • `partial`: the range reaches outside `buildWindow`, a day in range was not built, or the list behind the build was cut short.
  • `ok`.

type DayScheduleParams added in v0.3.0

type DayScheduleParams struct {
	// Date is the first calendar day, YYYY-MM-DD in TZ. Default: today in TZ.
	Date string
	// TZ is the IANA time zone that defines the day, such as America/Los_Angeles.
	// Default: America/New_York. An unknown zone is a 400.
	TZ string
	// Days is the number of days from Date, 1 to 10. Default: 1.
	Days int
	// Sports narrows the schedule to these sports: nfl, ncaaf, mlb, nba, wnba,
	// nhl, tennis, soccer, ufc. Default: all. An unknown sport is a 400 that lists
	// the supported ones.
	Sports []string
}

DayScheduleParams selects GetDaySchedule. Every field is optional: the zero value asks for today's games of every sport in America/New_York.

type DayScheduleResponse added in v0.3.0

type DayScheduleResponse struct {
	Count *float64 `json:"count,omitempty"`

	// Date The day asked for (or today in `tz`), YYYY-MM-DD.
	Date  *string           `json:"date,omitempty"`
	Days  *float64          `json:"days,omitempty"`
	Games []DayScheduleGame `json:"games,omitempty"`

	// Meta Freshness of a day-schedule answer. `status`, first match wins:
	// - `no-data`: nothing built for the range.
	// - `stale`: built more than 3 minutes ago, the schedule not refreshed for 5
	//   minutes, or live scores more than 90 seconds old while a game has started and
	//   not finished.
	// - `partial`: the range reaches outside `buildWindow`, a day in range was not
	//   built, or the list behind the build was cut short.
	// - `ok`.
	Meta *DayScheduleMeta `json:"meta,omitempty"`

	// Sports The sports asked for (every sport by default).
	Sports  []string `json:"sports,omitempty"`
	Success *bool    `json:"success,omitempty"`
	Tz      *string  `json:"tz,omitempty"`
}

DayScheduleResponse Response from GET /api/v1/schedule. `games` are in start order (larger volume first among games starting together).

type DayScheduleStartCheck added in v0.3.0

type DayScheduleStartCheck struct {
	// DiffersByMinutes The larger of the two gaps below, in minutes; null when neither differs.
	DiffersByMinutes *float64 `json:"differsByMinutes,omitempty"`

	// Flashscore How an independent source's start time compares with the schedule's.
	Flashscore *DayScheduleStartCheckResult `json:"flashscore,omitempty"`

	// FlashscoreMinutes Minutes between this start and the scores feed's start, when they differ.
	FlashscoreMinutes *float64 `json:"flashscoreMinutes,omitempty"`

	// Sportsbooks How an independent source's start time compares with the schedule's.
	Sportsbooks *DayScheduleStartCheckResult `json:"sportsbooks,omitempty"`

	// SportsbooksMinutes Minutes between this start and the sportsbooks' start, when they differ.
	SportsbooksMinutes *float64 `json:"sportsbooksMinutes,omitempty"`
}

DayScheduleStartCheck The game's start time compared with independent sources.

type DayScheduleStartCheckResult added in v0.3.0

type DayScheduleStartCheckResult = string

DayScheduleStartCheckResult How an independent source's start time compares with the schedule's.

type DayScheduleStatus added in v0.3.0

type DayScheduleStatus = string

DayScheduleStatus A game's state on the day schedule. Open: a new value must not break a reader.

  • `unconfirmed`: the scheduled start passed long ago and no live data confirms the game started or ended, or live data for a game in progress stopped updating for more than 10 minutes.
  • `time_tbd`: the start time is not set yet.

type DayScheduleWindow added in v0.3.0

type DayScheduleWindow struct {
	End   *string `json:"end,omitempty"`
	Start *string `json:"start,omitempty"`
}

DayScheduleWindow A UTC instant range `[start, end)`.

type DialError

type DialError struct {
	StatusCode int
	// Body is the start of the response body (at most 1 KiB).
	Body string
	// RetryAfter is the least time to wait before the next attempt: the
	// Retry-After header, or 120 s for the API's per-IP block ("Too many
	// connection attempts").
	RetryAfter time.Duration
}

DialError is a WebSocket upgrade the server answered with an HTTP status other than 101, before any Socket.IO exchange.

func (*DialError) Error

func (e *DialError) Error() string

type DraftKingsV2BookSubscription

type DraftKingsV2BookSubscription struct {
	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Mma Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mma *V2LeagueFilter `json:"mma,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaab Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaab *V2LeagueFilter `json:"ncaab,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Wnba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Wnba *V2LeagueFilter `json:"wnba,omitempty"`
}

DraftKingsV2BookSubscription Per-book subscription shape for DraftKings v2 events.

Each key is a sport `draftkings-v2-update` carries. Soccer and mma are multi-league (filter by league keys, or `'*'` for all); the others are one league each (pass `'*'`). Soccer, mma and wnba push their markets as they change. nba, nhl, mlb, nfl, ncaab and ncaaf push only the live slice (marketId `live`), unless it is too large to send; their main catalog is REST only. Unknown / unsupported sport keys are rejected at compile time.

type EVBoard added in v0.4.0

type EVBoard struct {
	// AgeMs Age of the board when this response was sent, in milliseconds; null when there is no board.
	AgeMs *float64 `json:"ageMs,omitempty"`

	// BuiltAt When the board was built (ISO 8601); null when there is no board.
	BuiltAt *string `json:"builtAt,omitempty"`

	// Stale True when there is no board or it is too old: no EV is served from it.
	Stale *bool `json:"stale,omitempty"`

	// VenueQuotes Whether the board holds Kalshi or Polymarket quotes.
	VenueQuotes *bool `json:"venueQuotes,omitempty"`

	// Venues Whether this request asked for Kalshi and Polymarket (`venues=true`).
	Venues *bool `json:"venues,omitempty"`
}

EVBoard The board an EV response was read from, and how old it is.

type EVEvent

type EVEvent struct {
	AwayTeam *string `json:"awayTeam,omitempty"`

	// BooksInConsensus Independent price sources behind the event's fair.
	BooksInConsensus *float64 `json:"booksInConsensus,omitempty"`

	// CanonicalEventID Our stable id of the game, `{sport}:{away}@{home}-{date}`: the `eventId` the odds board and the history endpoints use. Null when unknown.
	CanonicalEventID *string `json:"canonicalEventId,omitempty"`
	CommenceTime     *string `json:"commenceTime,omitempty"`

	// EventID The game's `id` on the odds board. The history endpoints take `canonicalEventId` instead.
	EventID *string `json:"eventId,omitempty"`

	// Fair How an event's fair was formed, and the fair per side before the 1-point margin.
	// Each side is de-vigged on its own, conservatively, so the sides can sum to slightly
	// less than 1.
	Fair *EVFair `json:"fair,omitempty"`

	// FairProbability The conservative fair per side (the fair minus 1 percentage point); null for a side with no fair. The fair before the margin is in `fair`.
	FairProbability *struct {
		Away *float64 `json:"away,omitempty"`
		Draw *float64 `json:"draw,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"fairProbability,omitempty"`
	HomeTeam *string `json:"homeTeam,omitempty"`
	Market   *string `json:"market,omitempty"`

	// Opportunities Best EV first.
	Opportunities []EVOpportunity `json:"opportunities,omitempty"`
	Sport         *string         `json:"sport,omitempty"`
}

EVEvent A game with its fair and the +EV opportunities found across books.

type EVFair added in v0.4.0

type EVFair struct {
	// Anchor Which Pinnacle line anchored the fair: the realtime feed or the /odds entry; null when Pinnacle did not.
	Anchor *string `json:"anchor,omitempty"`

	// Away Fair probability of an away win before the margin; null when there is none.
	Away *float64 `json:"away,omitempty"`

	// Draw Fair probability of a draw before the margin (three-way markets); null otherwise.
	Draw *float64 `json:"draw,omitempty"`

	// Home Fair probability of a home win before the margin; null when there is none.
	Home *float64 `json:"home,omitempty"`

	// Method `pinnacle`: Pinnacle's de-vigged line. `consensus`: no Pinnacle line, so the median
	// of at least three independent books; only venues are scored against it, never a
	// sportsbook. Null when there is no fair (`reason` says why).
	Method *string `json:"method,omitempty"`

	// Reason Why there is no fair, in plain text; null when there is one.
	Reason *string `json:"reason,omitempty"`

	// Sources Independent price sources behind the event's fair.
	Sources *float64 `json:"sources,omitempty"`
}

EVFair How an event's fair was formed, and the fair per side before the 1-point margin. Each side is de-vigged on its own, conservatively, so the sides can sum to slightly less than 1.

type EVLineEvent added in v0.9.0

type EVLineEvent struct {
	AwayTeam         *string  `json:"awayTeam,omitempty"`
	BooksInConsensus *float64 `json:"booksInConsensus,omitempty"`
	CanonicalEventID *string  `json:"canonicalEventId,omitempty"`
	CommenceTime     *string  `json:"commenceTime,omitempty"`
	EventID          *string  `json:"eventId,omitempty"`

	// Fair The fair per side before the margin, and how it was formed (always Pinnacle on spreads and totals).
	Fair *struct {
		Anchor  *string  `json:"anchor,omitempty"`
		Away    *float64 `json:"away,omitempty"`
		Home    *float64 `json:"home,omitempty"`
		Method  *string  `json:"method,omitempty"`
		Over    *float64 `json:"over,omitempty"`
		Sources *float64 `json:"sources,omitempty"`
		Under   *float64 `json:"under,omitempty"`
	} `json:"fair,omitempty"`

	// FairProbability The conservative fair per side, keyed home/away (spreads) or over/under (totals).
	FairProbability *struct {
		Away  *float64 `json:"away,omitempty"`
		Home  *float64 `json:"home,omitempty"`
		Over  *float64 `json:"over,omitempty"`
		Under *float64 `json:"under,omitempty"`
	} `json:"fairProbability,omitempty"`
	HomeTeam *string `json:"homeTeam,omitempty"`

	// Market A spread/total market: `spreads` (sides home/away) or `totals` (sides over/under).
	Market *EVLineMarket `json:"market,omitempty"`

	// Opportunities Best EV first.
	Opportunities []EVLineOpportunity `json:"opportunities,omitempty"`

	// Point Pinnacle's main number: the home side's number on spreads, the total on totals.
	Point *float64 `json:"point,omitempty"`

	// PushPossible A whole number: a push refunds the stake. The fair is each side's chance given no push.
	PushPossible *bool   `json:"pushPossible,omitempty"`
	Sport        *string `json:"sport,omitempty"`
}

EVLineEvent One game and market with its +EV prices.

type EVLineMarket added in v0.9.0

type EVLineMarket = string

EVLineMarket A spread/total market: `spreads` (sides home/away) or `totals` (sides over/under).

type EVLineOpportunity added in v0.9.0

type EVLineOpportunity struct {
	Book        *string  `json:"book,omitempty"`
	BookImplied *float64 `json:"bookImplied,omitempty"`
	BookPrice   *float64 `json:"bookPrice,omitempty"`

	// BooksInConsensus Independent sources behind the fair this price was scored against (1 = Pinnacle alone).
	BooksInConsensus *float64 `json:"booksInConsensus,omitempty"`
	EdgePp           *float64 `json:"edgePp,omitempty"`

	// EvPct On a whole number, the EV of a bet that is not refunded by a push.
	EvPct     *float64 `json:"evPct,omitempty"`
	FairPrice *float64 `json:"fairPrice,omitempty"`

	// FairProbability The conservative fair (the fair minus 1 percentage point); evPct, edgePp, kellyFraction and fairPrice are computed at it.
	FairProbability *float64 `json:"fairProbability,omitempty"`
	FairUnadjusted  *float64 `json:"fairUnadjusted,omitempty"`
	KellyFraction   *float64 `json:"kellyFraction,omitempty"`
	Kind            *string  `json:"kind,omitempty"`

	// Market A spread/total market: `spreads` (sides home/away) or `totals` (sides over/under).
	Market *EVLineMarket `json:"market,omitempty"`

	// Point This side's own number: -3.5 on the favourite, +3.5 on the underdog; the total on totals.
	Point      *float64    `json:"point,omitempty"`
	QuoteAgeMs *float64    `json:"quoteAgeMs,omitempty"`
	Reason     *string     `json:"reason,omitempty"`
	Roi        *float64    `json:"roi,omitempty"`
	Side       *EVLineSide `json:"side,omitempty"`

	// Team Team name on spreads; "Over" or "Under" on totals.
	Team      *string `json:"team,omitempty"`
	Tradeable *bool   `json:"tradeable,omitempty"`
}

EVLineOpportunity One +EV price on a spread or total, scored at Pinnacle's main number. Same math as EVOpportunity.

type EVLineSide added in v0.9.0

type EVLineSide = string

EVLineSide defines model for EVLineSide.

type EVLinesParams added in v0.9.0

type EVLinesParams struct {
	// Markets is "spreads", "totals" or both, sent as one comma list under
	// market. Nil or empty asks for both. Any other value is refused before a
	// request (see GetEVLines).
	Markets []string
	// MinEV is the least EV% to include (0 means every positive EV; the API caps
	// it at meta.max_ev).
	MinEV float64
	// Book restricts the result to one sportsbook key.
	Book string
}

EVLinesParams filters GetEVLines.

type EVLinesResponse added in v0.9.0

type EVLinesResponse struct {
	Data []EVLineEvent `json:"data,omitempty"`
	Meta *struct {
		Board *struct {
			AgeMs   *float64 `json:"ageMs,omitempty"`
			BuiltAt *string  `json:"builtAt,omitempty"`
			Stale   *bool    `json:"stale,omitempty"`
		} `json:"board,omitempty"`
		Book       *string  `json:"book,omitempty"`
		Events     *float64 `json:"events,omitempty"`
		FairMethod *string  `json:"fair_method,omitempty"`
		Freshness  *struct {
			AgeSeconds *float64 `json:"ageSeconds,omitempty"`
			Stale      *bool    `json:"stale,omitempty"`
		} `json:"freshness,omitempty"`

		// Line Every price is compared at Pinnacle's main number only.
		Line *string `json:"line,omitempty"`

		// Market The markets asked for, e.g. "spreads" or "spreads,totals".
		Market        *string  `json:"market,omitempty"`
		MaxEv         *float64 `json:"max_ev,omitempty"`
		MinEv         *float64 `json:"min_ev,omitempty"`
		OddsFormat    *string  `json:"odds_format,omitempty"`
		Opportunities *float64 `json:"opportunities,omitempty"`
		PregameOnly   *bool    `json:"pregame_only,omitempty"`
		Sport         *string  `json:"sport,omitempty"`
		Timestamp     *string  `json:"timestamp,omitempty"`

		// Unsupported Set when the sport's books do not agree on what a handicap counts (tennis, esports, MMA): no line is scored.
		Unsupported *string `json:"unsupported,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

EVLinesResponse Response for `GET /api/v1/{sport}/ev?market=spreads|totals` (MVP+). Pre-game only.

type EVOpportunity

type EVOpportunity struct {
	// Book Sportsbook or venue key. `kalshi` and `polymarket` are listed only with `venues: true`.
	Book *string `json:"book,omitempty"`

	// BookImplied Implied probability of `bookPrice` (a sportsbook's includes its vig; a venue's is its ask).
	BookImplied *float64 `json:"bookImplied,omitempty"`

	// BookPrice American odds of the price: a sportsbook's price, or a venue's ask.
	BookPrice *float64 `json:"bookPrice,omitempty"`

	// BooksInConsensus Independent price sources behind the fair this price was scored against. A
	// sportsbook's own feed never counts toward its own fair, so 1 means Pinnacle alone.
	BooksInConsensus *float64 `json:"booksInConsensus,omitempty"`

	// EdgePp (fairProbability - bookImplied) * 100, in percentage points.
	EdgePp *float64 `json:"edgePp,omitempty"`

	// EvPct Expected value %, at `fairProbability`. Sportsbook: `(fairProbability * decimalOdds - 1) * 100`.
	// Venue, its fee included: `(fairProbability - ask - fee) / (ask + fee) * 100`.
	EvPct *float64 `json:"evPct,omitempty"`

	// FairPrice American odds of `fairProbability` (the break-even price).
	FairPrice *float64 `json:"fairPrice,omitempty"`

	// FairProbability The conservative fair probability of this outcome: the fair minus 1 percentage point. `fairPrice`, `evPct`, `edgePp` and `kellyFraction` are computed at it.
	FairProbability *float64 `json:"fairProbability,omitempty"`

	// FairUnadjusted The fair probability before the 1-point margin: `fairProbability` is this minus 0.01.
	FairUnadjusted *float64 `json:"fairUnadjusted,omitempty"`

	// Fee The venue's trading fee per contract, in dollars; 0 for a sportsbook.
	Fee *float64 `json:"fee,omitempty"`

	// KellyFraction Full-Kelly bankroll fraction at `fairProbability`.
	KellyFraction *float64 `json:"kellyFraction,omitempty"`

	// Kind `book` for a sportsbook, `venue` for an exchange or prediction market (Novig, Kalshi, Polymarket).
	Kind *string `json:"kind,omitempty"`

	// Link The market's page at the venue; null for a sportsbook.
	Link   *string `json:"link,omitempty"`
	Market *string `json:"market,omitempty"`

	// QuoteAgeMs Age of the price when this answer was served, in milliseconds, by its own timestamp; null when unknown.
	QuoteAgeMs *float64 `json:"quoteAgeMs,omitempty"`

	// Reason Why it is not tradeable, in plain text (such as `EV under 2%`); null when it is.
	Reason *string `json:"reason,omitempty"`

	// Roi `evPct / 100`, not rounded: EV per dollar staked (sportsbook) or per dollar paid, fee included (venue).
	Roi *float64 `json:"roi,omitempty"`

	// Route Kalshi and Polymarket: the contract to buy, `yes`, or `no:<CODE>` (NO on the opponent's market). Null otherwise.
	Route *string `json:"route,omitempty"`
	Side  *string `json:"side,omitempty"`

	// Size Dollars available at the ask; null when unknown (Polymarket, sportsbooks).
	Size *float64 `json:"size,omitempty"`
	Team *string  `json:"team,omitempty"`

	// Tradeable Whether the opportunity clears the markets page trade bar. Sportsbook: EV of at
	// least 2%. Venue: EV of at least 4%, a bid, a spread of 4 cents or less and at least
	// $50 at the ask.
	Tradeable *bool `json:"tradeable,omitempty"`
}

EVOpportunity One +EV opportunity: a sportsbook price, or a venue's ask (an exchange such as Novig, and with `venues: true` the prediction markets Kalshi and Polymarket), that beats the fair.

The fair is Pinnacle's de-vigged line: the other books do not move it, they only veto a side they disagree with (when Pinnacle has no line, a venue can be scored against a consensus of books instead; see `EVFair.method`). `fairProbability` is the CONSERVATIVE fair, the fair minus 1 percentage point, and `fairPrice`, `evPct`, `edgePp` and `kellyFraction` are all computed at it. The fair before that margin is `fairUnadjusted`.

type EVParams

type EVParams struct {
	// MinEV is the least EV% to include (0 means every positive EV).
	MinEV float64
	// Book restricts the result to one sportsbook key, or to kalshi or polymarket
	// when Venues is set.
	Book string
	// Venues also lists Kalshi and Polymarket, their trading fee included in the EV
	// (sportsbooks and Novig are always listed). It is sent as venues=true only when
	// set. The first request for a sport starts their prices, which appear within
	// about 15 seconds and stay on while you keep asking.
	Venues bool
}

EVParams filters GetEV.

type EVResponse

type EVResponse struct {
	Data []EVEvent `json:"data,omitempty"`
	Meta *struct {
		// Board The board an EV response was read from, and how old it is.
		Board  *EVBoard `json:"board,omitempty"`
		Book   *string  `json:"book,omitempty"`
		Events *float64 `json:"events,omitempty"`

		// FairMethod How the fair is computed: `fair_v1` (Pinnacle's de-vigged line, the other books only veto, a 1-point margin). `consensus_devig_median` is the method it replaced.
		FairMethod *string `json:"fair_method,omitempty"`

		// Freshness Age of the odds snapshot behind the answer. `ageSeconds` is always a number; `stale` is true when the snapshot is over 90 seconds old or there is no data.
		Freshness *struct {
			AgeSeconds *float64 `json:"ageSeconds,omitempty"`
			Stale      *bool    `json:"stale,omitempty"`
		} `json:"freshness,omitempty"`
		Market        *string  `json:"market,omitempty"`
		MaxEv         *float64 `json:"max_ev,omitempty"`
		MinEv         *float64 `json:"min_ev,omitempty"`
		OddsFormat    *string  `json:"odds_format,omitempty"`
		Opportunities *float64 `json:"opportunities,omitempty"`
		PregameOnly   *bool    `json:"pregame_only,omitempty"`
		Sport         *string  `json:"sport,omitempty"`

		// Timestamp When the odds snapshot behind the answer was written (ISO 8601); `1970-01-01T00:00:00.000Z` when there is no data at all.
		Timestamp *string `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

EVResponse Response for `GET /api/v1/{sport}/ev` (MVP+). Pre-game value bets only.

No opportunity is listed while the odds snapshot behind the answer is over 90 seconds old, or for a game that has started. Within that, a sportsbook price must be under 3 minutes old and an exchange quote (Novig by default, Kalshi and Polymarket with `venues: true`) under 2 minutes, each by its own timestamp.

The optional fields (`kind`, `fairUnadjusted`, `roi`, `fee`, `tradeable`, `reason`, `route`, `size`, `link` and `quoteAgeMs` on an opportunity, `canonicalEventId` and `fair` on an event, `board` in `meta`) come with `fair_method: "fair_v1"`.

type EsportsRealtimeFilter

type EsportsRealtimeFilter struct {
	// League is a substring of the Pinnacle league name, for example "BLAST".
	League string `json:"league"`
}

EsportsRealtimeFilter is the league filter of one esport's realtime stream.

type EsportsRealtimeMeta

type EsportsRealtimeMeta struct {
	Available *bool    `json:"available,omitempty"`
	Events    *float64 `json:"events,omitempty"`
	Freshness *struct {
		AgeSeconds *float64 `json:"ageSeconds,omitempty"`
		Stale      *bool    `json:"stale,omitempty"`
	} `json:"freshness,omitempty"`
	Game      *EsportsSport `json:"game,omitempty"`
	League    *string       `json:"league,omitempty"`
	Source    *string       `json:"source,omitempty"`
	Timestamp *string       `json:"timestamp,omitempty"`
}

EsportsRealtimeMeta Per-event meta on the esports realtime REST response.

type EsportsRealtimePayload

type EsportsRealtimePayload struct {
	Events    []json.RawMessage `json:"events,omitempty"`
	Game      *EsportsSport     `json:"game,omitempty"`
	Source    *string           `json:"source,omitempty"`
	Timestamp *string           `json:"timestamp,omitempty"`
}

EsportsRealtimePayload Pinnacle esports realtime payload for a single game. Delivered via OwlsInsightEvents as `cs2-realtime`, `lol-realtime`, `valorant-realtime`, or `dota2-realtime`.

**Pinnacle wire format** - no team-name normalization, prices in American odds. The `events` array carries Pinnacle matchups directly (`participants`, `markets[].prices[].designation/price/points`, `markets[].limits[]`).

type EsportsRealtimeResponse

type EsportsRealtimeResponse struct {
	Data []json.RawMessage `json:"data,omitempty"`

	// Meta Per-event meta on the esports realtime REST response.
	Meta    *EsportsRealtimeMeta `json:"meta,omitempty"`
	Success *bool                `json:"success,omitempty"`
}

EsportsRealtimeResponse Response shape for `GET /api/v1/{cs2|lol|valorant|dota2}/realtime?league=...`.

**Distinct from `RealtimeResponse`** - the traditional `getRealtime(sport)` call returns normalized `OddsEvent[]` for nba/nhl/etc. The esports realtime endpoints return the Pinnacle wire format (matchups with `participants`, `markets`, `prices`, `limits` - no team normalization, prices in American odds). Each item in `data` is shaped like one `EsportsRealtimePayload.events[i]`.

type EsportsRealtimeSubscription

type EsportsRealtimeSubscription struct {
	Cs2 *struct {
		League *string `json:"league,omitempty"`
	} `json:"cs2,omitempty"`
	Dota2 *struct {
		League *string `json:"league,omitempty"`
	} `json:"dota2,omitempty"`
	Lol *struct {
		League *string `json:"league,omitempty"`
	} `json:"lol,omitempty"`
	Valorant *struct {
		League *string `json:"league,omitempty"`
	} `json:"valorant,omitempty"`
}

EsportsRealtimeSubscription Per-game subscription filter for the Pinnacle esports realtime WebSocket events (OwlsInsightEvents).

Each entry MUST include a `league` substring - the server never broadcasts the firehose unfiltered. Mirrors the REST `/api/v1/{game}/realtime?league=` required query param.

type EsportsSport

type EsportsSport = string

EsportsSport defines model for EsportsSport.

type EventFreshness

type EventFreshness struct {
	// AgeSeconds Seconds since the price last moved - tradeability age.
	AgeSeconds *float64 `json:"ageSeconds,omitempty"`

	// LastUpdate Epoch ms this event's price last MOVED (drives `stale`/`ageSeconds`), or null.
	LastUpdate *float64 `json:"lastUpdate,omitempty"`

	// RefreshedAgeSeconds Seconds since the feed last confirmed this row.
	RefreshedAgeSeconds *float64 `json:"refreshedAgeSeconds,omitempty"`

	// RefreshedAt Epoch ms the feed last CONFIRMED this row (moved or not) - liveness, not movement.
	RefreshedAt *float64 `json:"refreshedAt,omitempty"`

	// Stale Live row whose price has not moved within the stale window - not tradeable.
	Stale *bool `json:"stale,omitempty"`
}

EventFreshness Per-event freshness on the realtime streams. Keyed off PRICE MOVEMENT (`lastUpdate`), not feed re-confirmation (`refreshedAt`), so a frozen-but-still- served live line reads `stale: true` - the market-maker pull signal.

type EventPhase

type EventPhase = string

EventPhase OddsEvent.phase. More values can be added: treat an unknown one like a missing field.

type EventSummary

type EventSummary struct {
	AwayTeam *string `json:"away_team,omitempty"`

	// AwayTeamID Stable roster id for `away_team`; same rules.
	AwayTeamID   *string `json:"away_team_id,omitempty"`
	CommenceTime *string `json:"commence_time,omitempty"`
	CountryCode  *string `json:"country_code,omitempty"`

	// EventID Formatted event ID for use with history endpoints.
	EventID  *string `json:"eventId,omitempty"`
	HomeTeam *string `json:"home_team,omitempty"`

	// HomeTeamID Stable roster id for `home_team`, absent when the name is not on a roster
	// we carry. Same values as OddsEvent.home_team_id.
	HomeTeamID *string     `json:"home_team_id,omitempty"`
	ID         *string     `json:"id,omitempty"`
	League     *string     `json:"league,omitempty"`
	SportKey   *string     `json:"sport_key,omitempty"`
	SportTitle *string     `json:"sport_title,omitempty"`
	Status     *GameStatus `json:"status,omitempty"`
}

EventSummary defines model for EventSummary.

type EventsMeta

type EventsMeta struct {
	// AvailableLeagues Every league value present for this sport, before any `league` filter.
	//
	// Use it to discover valid `league` values instead of guessing them, and to
	// tell a filter that matched nothing from a genuinely empty slate: a
	// non-empty `available_leagues` with `event_count: 0` means the filter missed.
	AvailableLeagues []string       `json:"available_leagues,omitempty"`
	EventCount       *float64       `json:"event_count,omitempty"`
	Freshness        *OddsFreshness `json:"freshness,omitempty"`

	// League The `league` filter that was applied, echoed back.
	League    *string `json:"league,omitempty"`
	Sport     *string `json:"sport,omitempty"`
	SportKey  *string `json:"sport_key,omitempty"`
	Timestamp *string `json:"timestamp,omitempty"`
}

EventsMeta defines model for EventsMeta.

type EventsResponse

type EventsResponse struct {
	Data    []EventSummary `json:"data,omitempty"`
	Meta    *EventsMeta    `json:"meta,omitempty"`
	Success *bool          `json:"success,omitempty"`
}

EventsResponse defines model for EventsResponse.

type FairEvent added in v0.9.0

type FairEvent struct {
	AwayTeam         *string `json:"awayTeam,omitempty"`
	CanonicalEventID *string `json:"canonicalEventId,omitempty"`
	CommenceTime     *string `json:"commenceTime,omitempty"`
	EventID          *string `json:"eventId,omitempty"`
	HomeTeam         *string `json:"homeTeam,omitempty"`
	League           *string `json:"league,omitempty"`
	Moneyline        *struct {
		Away *FairPrice `json:"away,omitempty"`
		Draw *FairPrice `json:"draw,omitempty"`
		Home *FairPrice `json:"home,omitempty"`

		// Method `pinnacle`, or `consensus` (at least three independent books) when Pinnacle has no line.
		Method  *string  `json:"method,omitempty"`
		Sources *float64 `json:"sources,omitempty"`
	} `json:"moneyline,omitempty"`
	Sport  *string `json:"sport,omitempty"`
	Spread *struct {
		Away         *FairLineSide `json:"away,omitempty"`
		Home         *FairLineSide `json:"home,omitempty"`
		Point        *float64      `json:"point,omitempty"`
		PushPossible *bool         `json:"pushPossible,omitempty"`
		Sources      *float64      `json:"sources,omitempty"`
	} `json:"spread,omitempty"`
	Total *struct {
		Over         *FairLineSide `json:"over,omitempty"`
		Point        *float64      `json:"point,omitempty"`
		PushPossible *bool         `json:"pushPossible,omitempty"`
		Sources      *float64      `json:"sources,omitempty"`
		Under        *FairLineSide `json:"under,omitempty"`
	} `json:"total,omitempty"`
}

FairEvent A game's fair line. A market is null when there is no current fair for it.

type FairLineSide added in v0.9.0

type FairLineSide struct {
	// American Its American odds: the break-even price.
	American *float64 `json:"american,omitempty"`

	// BooksDisagree The other books' median fair chance differs from Pinnacle's by more than 1.5 percentage points on this side.
	BooksDisagree *bool `json:"booksDisagree,omitempty"`

	// Point This side's own number.
	Point *float64 `json:"point,omitempty"`

	// Probability The fair (no-vig) chance of this side.
	Probability *float64 `json:"probability,omitempty"`
}

FairLineSide defines model for FairLineSide.

type FairPrice added in v0.9.0

type FairPrice struct {
	// American Its American odds: the break-even price.
	American *float64 `json:"american,omitempty"`

	// BooksDisagree The other books' median fair chance differs from Pinnacle's by more than 1.5 percentage points on this side.
	BooksDisagree *bool `json:"booksDisagree,omitempty"`

	// Probability The fair (no-vig) chance of this side.
	Probability *float64 `json:"probability,omitempty"`
}

FairPrice One side of a fair price.

type FairResponse added in v0.9.0

type FairResponse struct {
	Data []FairEvent `json:"data,omitempty"`
	Meta *struct {
		Anchor *string `json:"anchor,omitempty"`
		Boards *struct {
			Lines *struct {
				AgeMs   *float64 `json:"ageMs,omitempty"`
				BuiltAt *string  `json:"builtAt,omitempty"`
				Stale   *bool    `json:"stale,omitempty"`
			} `json:"lines,omitempty"`
			Moneyline *struct {
				AgeMs   *float64 `json:"ageMs,omitempty"`
				BuiltAt *string  `json:"builtAt,omitempty"`
				Stale   *bool    `json:"stale,omitempty"`
			} `json:"moneyline,omitempty"`
		} `json:"boards,omitempty"`
		Events     *float64 `json:"events,omitempty"`
		FairMethod *string  `json:"fair_method,omitempty"`

		// Markets Which markets this sport is priced on.
		Markets *struct {
			Moneyline *bool `json:"moneyline,omitempty"`
			Spreads   *bool `json:"spreads,omitempty"`
			Totals    *bool `json:"totals,omitempty"`
		} `json:"markets,omitempty"`
		Notes       []string `json:"notes,omitempty"`
		OddsFormat  *string  `json:"odds_format,omitempty"`
		PregameOnly *bool    `json:"pregame_only,omitempty"`
		Sport       *string  `json:"sport,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

FairResponse Response for `GET /api/v1/{sport}/fair` (MVP+): the Owls fair line.

type FanDuelStateMarketIdsMeta added in v0.9.0

type FanDuelStateMarketIdsMeta struct {
	// Mapped marketIds (props and alternate-line rungs) rewritten to the state's id.
	Mapped *float64 `json:"mapped,omitempty"`
	State  *string  `json:"state,omitempty"`

	// Timestamp When the state map was last refreshed.
	Timestamp *string `json:"timestamp,omitempty"`

	// Unmapped FanDuel marketIds with no id for the state, omitted rather than served as New Jersey's.
	Unmapped *float64 `json:"unmapped,omitempty"`
}

FanDuelStateMarketIdsMeta meta.fanduelMarketIds (getProps) / meta.marketIds (getBookProps fanduel) when `state` was sent.

type FanDuelStateOddsMeta added in v0.9.0

type FanDuelStateOddsMeta struct {
	// Available false: no FanDuel ids for this sport and state, every FanDuel marketId omitted.
	Available *bool `json:"available,omitempty"`

	// Mapped FanDuel outcomes whose marketId is the state's.
	Mapped *float64 `json:"mapped,omitempty"`
	State  *string  `json:"state,omitempty"`

	// Timestamp When the state's ids were last refreshed; null when `available` is false.
	Timestamp *string `json:"timestamp,omitempty"`

	// Unmapped FanDuel outcomes with no id for the state, marketId omitted.
	Unmapped *float64 `json:"unmapped,omitempty"`
}

FanDuelStateOddsMeta meta.fanduelMarketIds on getOdds (and the per-market routes) with `state`.

type FanDuelV2BookSubscription

type FanDuelV2BookSubscription struct {
	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Mma Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mma *V2LeagueFilter `json:"mma,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`

	// Wnba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Wnba *V2LeagueFilter `json:"wnba,omitempty"`
}

FanDuelV2BookSubscription Per-book subscription shape for FanDuel v2 events.

Each key is a sport FanDuel v2 currently supports. Soccer, tennis and mma are multi-league: filter by specific league keys or `'*'` for all.

type FanaticsMarketsV2BookSubscription

type FanaticsMarketsV2BookSubscription struct {
	// Books Also receive the live order books of the sports above on
	// `fanaticsmarkets-books-v2-update` (within about a second of a move).
	// Off by default: `fanaticsmarkets-v2-update` carries the events alone.
	Books *bool `json:"books,omitempty"`

	// Boxing Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Boxing *V2LeagueFilter `json:"boxing,omitempty"`

	// Cs2 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Cs2 *V2LeagueFilter `json:"cs2,omitempty"`

	// Dota2 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Dota2 *V2LeagueFilter `json:"dota2,omitempty"`

	// Golf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Golf *V2LeagueFilter `json:"golf,omitempty"`

	// Lol Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Lol *V2LeagueFilter `json:"lol,omitempty"`

	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Mma Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mma *V2LeagueFilter `json:"mma,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaab Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaab *V2LeagueFilter `json:"ncaab,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`

	// Valorant Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Valorant *V2LeagueFilter `json:"valorant,omitempty"`

	// Wnba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Wnba *V2LeagueFilter `json:"wnba,omitempty"`
}

FanaticsMarketsV2BookSubscription Per-book subscription shape for Fanatics Markets v2 events. Use `'*'` for a whole sport or exact series keys (e.g. `['mls']`) from OwlsInsightRest.getFanaticsMarketsV2Leagues.

type FootballPlayDetail added in v0.9.0

type FootballPlayDetail struct {
	Distance            *float64 `json:"distance,omitempty"`
	Down                *float64 `json:"down,omitempty"`
	DownDistanceText    *string  `json:"downDistanceText,omitempty"`
	Drive               *string  `json:"drive,omitempty"`
	EndDistance         *float64 `json:"endDistance,omitempty"`
	EndDown             *float64 `json:"endDown,omitempty"`
	EndDownDistanceText *string  `json:"endDownDistanceText,omitempty"`
	EndYardLine         *float64 `json:"endYardLine,omitempty"`
	EndYardsToEndzone   *float64 `json:"endYardsToEndzone,omitempty"`
	IsPenalty           *bool    `json:"isPenalty,omitempty"`
	IsTurnover          *bool    `json:"isTurnover,omitempty"`
	PossessionText      *string  `json:"possessionText,omitempty"`
	ScoreValue          *float64 `json:"scoreValue,omitempty"`
	YardLine            *float64 `json:"yardLine,omitempty"`
	Yards               *float64 `json:"yards,omitempty"`
	YardsToEndzone      *float64 `json:"yardsToEndzone,omitempty"`
}

FootballPlayDetail NFL and NCAAF: the down and distance before (`down`…) and after (`endDown`…) the play.

type FourcastersV2BookSubscription

type FourcastersV2BookSubscription struct {
	Cfl     *json.RawMessage `json:"cfl,omitempty"`
	Esports *json.RawMessage `json:"esports,omitempty"`
	Mlb     *json.RawMessage `json:"mlb,omitempty"`
	Mma     *json.RawMessage `json:"mma,omitempty"`
	Nba     *json.RawMessage `json:"nba,omitempty"`
	Ncaab   *json.RawMessage `json:"ncaab,omitempty"`
	Ncaaf   *json.RawMessage `json:"ncaaf,omitempty"`
	Nfl     *json.RawMessage `json:"nfl,omitempty"`
	Nhl     *json.RawMessage `json:"nhl,omitempty"`
	Soccer  *json.RawMessage `json:"soccer,omitempty"`
	Tennis  *json.RawMessage `json:"tennis,omitempty"`
	Wnba    *json.RawMessage `json:"wnba,omitempty"`
}

FourcastersV2BookSubscription Per-sport league filter for `v2: { '4casters': {...} }`.

type GameLogEntry

type GameLogEntry struct {
	GameDate *string `json:"gameDate,omitempty"`
	HomeAway *string `json:"homeAway,omitempty"`
	Opponent *string `json:"opponent,omitempty"`
	Stats    *struct {
		Assists *float64 `json:"assists,omitempty"`
		Blocks  *float64 `json:"blocks,omitempty"`
		Fg      *struct {
			Attempted *float64 `json:"attempted,omitempty"`
			Made      *float64 `json:"made,omitempty"`
		} `json:"fg,omitempty"`
		Fouls     *float64 `json:"fouls,omitempty"`
		FreeThrow *struct {
			Attempted *float64 `json:"attempted,omitempty"`
			Made      *float64 `json:"made,omitempty"`
		} `json:"freeThrow,omitempty"`
		Minutes    *string  `json:"minutes,omitempty"`
		PlusMinus  *float64 `json:"plusMinus,omitempty"`
		Points     *float64 `json:"points,omitempty"`
		Rebounds   *float64 `json:"rebounds,omitempty"`
		Steals     *float64 `json:"steals,omitempty"`
		ThreePoint *struct {
			Attempted *float64 `json:"attempted,omitempty"`
			Made      *float64 `json:"made,omitempty"`
		} `json:"threePoint,omitempty"`
		Turnovers *float64 `json:"turnovers,omitempty"`
	} `json:"stats,omitempty"`
	TeamScore *float64 `json:"teamScore,omitempty"`
}

GameLogEntry defines model for GameLogEntry.

type GameProps

type GameProps struct {
	AwayTeam     *string     `json:"awayTeam,omitempty"`
	Books        []BookProps `json:"books,omitempty"`
	CommenceTime *string     `json:"commenceTime,omitempty"`
	GameID       *string     `json:"gameId,omitempty"`
	HomeTeam     *string     `json:"homeTeam,omitempty"`
	IsLive       *bool       `json:"isLive,omitempty"`
	Sport        *string     `json:"sport,omitempty"`
}

GameProps defines model for GameProps.

type GameStatsDetailData

type GameStatsDetailData struct {
	AwayTeam   *string         `json:"awayTeam,omitempty"`
	EventID    *string         `json:"eventId,omitempty"`
	GameDate   *string         `json:"gameDate,omitempty"`
	H2H        *H2HData        `json:"h2h,omitempty"`
	HomeTeam   *string         `json:"homeTeam,omitempty"`
	Incidents  []MatchIncident `json:"incidents,omitempty"`
	Lineups    *LineupsData    `json:"lineups,omitempty"`
	RecordedAt *string         `json:"recordedAt,omitempty"`
	Sport      *string         `json:"sport,omitempty"`
	TeamStats  map[string]struct {
		Away *json.RawMessage `json:"away,omitempty"`
		Home *json.RawMessage `json:"home,omitempty"`
	} `json:"teamStats,omitempty"`
}

GameStatsDetailData defines model for GameStatsDetailData.

type GameStatsDetailResponse

type GameStatsDetailResponse struct {
	Data    *GameStatsDetailData `json:"data,omitempty"`
	Success *bool                `json:"success,omitempty"`
}

GameStatsDetailResponse defines model for GameStatsDetailResponse.

type GameStatus

type GameStatus = string

GameStatus defines model for GameStatus.

type GetPropResultsResponse

type GetPropResultsResponse struct {
	// Data Differs between the response shapes; untyped.
	Data *json.RawMessage `json:"data,omitempty"`

	// Meta Differs between the response shapes; untyped.
	Meta    *json.RawMessage `json:"meta,omitempty"`
	Success *bool            `json:"success,omitempty"`
}

GetPropResultsResponse Any of: PropResultsDayResponse, PropResultsResponse.

type H2HData

type H2HData struct {
	AwayCountry   *string      `json:"awayCountry,omitempty"`
	AwayRanking   *float64     `json:"awayRanking,omitempty"`
	AwaySlug      *string      `json:"awaySlug,omitempty"`
	HeadToHead    []RecentGame `json:"headToHead,omitempty"`
	HomeCountry   *string      `json:"homeCountry,omitempty"`
	HomeRanking   *float64     `json:"homeRanking,omitempty"`
	HomeSlug      *string      `json:"homeSlug,omitempty"`
	LastGamesAway []RecentGame `json:"lastGamesAway,omitempty"`
	LastGamesHome []RecentGame `json:"lastGamesHome,omitempty"`
	SurfaceFilter *string      `json:"surfaceFilter,omitempty"`
}

H2HData defines model for H2HData.

type H2HResponse

type H2HResponse struct {
	Data *struct {
		EventID *string  `json:"eventId,omitempty"`
		H2H     *H2HData `json:"h2h,omitempty"`
		Sport   *string  `json:"sport,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

H2HResponse defines model for H2HResponse.

type HardRockEvent

type HardRockEvent struct {
	EventTime        *string                `json:"eventTime,omitempty"`
	ID               *string                `json:"id,omitempty"`
	Inplay           *bool                  `json:"inplay,omitempty"`
	Markets          []HardRockMarket       `json:"markets,omitempty"`
	Name             *string                `json:"name,omitempty"`
	Participants     []HardRockParticipant  `json:"participants,omitempty"`
	SimpleMatchState map[string]interface{} `json:"simpleMatchState,omitempty"`
	Sport            *string                `json:"sport,omitempty"`
}

HardRockEvent defines model for HardRockEvent.

type HardRockEventsResponse

type HardRockEventsResponse struct {
	CategoryName *string         `json:"categoryName,omitempty"`
	CompID       *string         `json:"compId,omitempty"`
	CompName     *string         `json:"compName,omitempty"`
	Count        *float64        `json:"count,omitempty"`
	Data         []HardRockEvent `json:"data,omitempty"`
	Etag         *string         `json:"etag,omitempty"`
	League       *string         `json:"league,omitempty"`
	Meta         *HardRockMeta   `json:"meta,omitempty"`
	Slug         *string         `json:"slug,omitempty"`
	Sport        *HardRockSport  `json:"sport,omitempty"`
	State        *HardRockState  `json:"state,omitempty"`
	Success      *bool           `json:"success,omitempty"`
}

HardRockEventsResponse defines model for HardRockEventsResponse.

type HardRockLadderEntry

type HardRockLadderEntry struct {
	// AmericanOdds Decoded American odds for this rootIdx.
	AmericanOdds *float64 `json:"americanOdds,omitempty"`

	// RootIdx Hard Rock's price-ladder index, as it appears on each selection.
	RootIdx *float64 `json:"rootIdx,omitempty"`
}

HardRockLadderEntry One rung of the Hard Rock price ladder.

type HardRockLadderResponse

type HardRockLadderResponse struct {
	Data *struct {
		// Even rootIdx for EVEN money (+100). Below = favorites, above = underdogs.
		Even *float64 `json:"even,omitempty"`

		// Ladder Verified rootIdx → American-odds anchors, ascending by rootIdx.
		Ladder []HardRockLadderEntry `json:"ladder,omitempty"`
	} `json:"data,omitempty"`
	Meta *struct {
		Count *float64 `json:"count,omitempty"`

		// Decoding Human-readable description of the exact-lookup + interpolation rule.
		Decoding  *string `json:"decoding,omitempty"`
		Source    *string `json:"source,omitempty"`
		Timestamp *string `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

HardRockLadderResponse Response from `GET /api/v2/hardrock/ladder` (`getHardRockLadder()`).

type HardRockLeague

type HardRockLeague struct {
	CategoryName *string  `json:"categoryName,omitempty"`
	CompID       *string  `json:"compId,omitempty"`
	CompName     *string  `json:"compName,omitempty"`
	EventCount   *float64 `json:"eventCount,omitempty"`
	Slug         *string  `json:"slug,omitempty"`
}

HardRockLeague defines model for HardRockLeague.

type HardRockLeaguesResponse

type HardRockLeaguesResponse struct {
	Leagues []HardRockLeague `json:"leagues,omitempty"`
	Meta    *HardRockMeta    `json:"meta,omitempty"`
	Sport   *HardRockSport   `json:"sport,omitempty"`
	State   *HardRockState   `json:"state,omitempty"`
	Success *bool            `json:"success,omitempty"`
}

HardRockLeaguesResponse defines model for HardRockLeaguesResponse.

type HardRockMarket

type HardRockMarket struct {
	ID         *string             `json:"id,omitempty"`
	Name       *string             `json:"name,omitempty"`
	Selections []HardRockSelection `json:"selections,omitempty"`
	State      *string             `json:"state,omitempty"`
	Type       *string             `json:"type,omitempty"`
}

HardRockMarket defines model for HardRockMarket.

type HardRockMeta

type HardRockMeta struct {
	AgeSeconds     *float64 `json:"ageSeconds,omitempty"`
	CacheTimestamp *string  `json:"cacheTimestamp,omitempty"`
	Cached         *bool    `json:"cached,omitempty"`
	Source         *string  `json:"source,omitempty"`
	Status         *string  `json:"status,omitempty"`
	Timestamp      *string  `json:"timestamp,omitempty"`
}

HardRockMeta defines model for HardRockMeta.

type HardRockParticipant

type HardRockParticipant struct {
	ID       *string  `json:"id,omitempty"`
	Name     *string  `json:"name,omitempty"`
	Position *float64 `json:"position,omitempty"`
}

HardRockParticipant defines model for HardRockParticipant.

type HardRockSelection

type HardRockSelection struct {
	ID      *string  `json:"id,omitempty"`
	Name    *string  `json:"name,omitempty"`
	RootIdx *float64 `json:"rootIdx,omitempty"`
	State   *string  `json:"state,omitempty"`
	Type    *string  `json:"type,omitempty"`
}

HardRockSelection defines model for HardRockSelection.

type HardRockSport

type HardRockSport = string

HardRockSport defines model for HardRockSport.

type HardRockState

type HardRockState = string

HardRockState defines model for HardRockState.

type HistoricalPlayerPropRow

type HistoricalPlayerPropRow struct {
	Book    *string `json:"book,omitempty"`
	Closing *struct {
		Line       *float64 `json:"line,omitempty"`
		OverPrice  *float64 `json:"overPrice,omitempty"`
		UnderPrice *float64 `json:"underPrice,omitempty"`
	} `json:"closing,omitempty"`
	EventID  *string `json:"eventId,omitempty"`
	GameDate *string `json:"gameDate,omitempty"`
	Opening  *struct {
		Line       *float64 `json:"line,omitempty"`
		OverPrice  *float64 `json:"overPrice,omitempty"`
		UnderPrice *float64 `json:"underPrice,omitempty"`
	} `json:"opening,omitempty"`
	PlayerName *string `json:"playerName,omitempty"`
	PropType   *string `json:"propType,omitempty"`
	Sport      *string `json:"sport,omitempty"`
}

HistoricalPlayerPropRow defines model for HistoricalPlayerPropRow.

type HistoricalPlayerPropsParams

type HistoricalPlayerPropsParams struct {
	EventID  string
	Sport    string
	Player   string
	PropType string
	Book     string
	// IsMain filters to main lines (true) or alternate lines (false); nil sends nothing.
	IsMain    *bool
	StartDate string
	EndDate   string
	Limit     int
	Offset    int
}

HistoricalPlayerPropsParams filters GetHistoricalPlayerProps.

type HistoricalPlayerPropsResponse

type HistoricalPlayerPropsResponse struct {
	Data *struct {
		Pagination *struct {
			HasMore *bool    `json:"hasMore,omitempty"`
			Limit   *float64 `json:"limit,omitempty"`
			Offset  *float64 `json:"offset,omitempty"`
			Total   *float64 `json:"total,omitempty"`
		} `json:"pagination,omitempty"`
		Props []HistoricalPlayerPropRow `json:"props,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

HistoricalPlayerPropsResponse defines model for HistoricalPlayerPropsResponse.

type HistoryCoverageDataset

type HistoryCoverageDataset struct {
	Description *string `json:"description,omitempty"`

	// Sports Keyed by sport. A sport that is absent usually has no rows in this dataset,
	// which is a distinct case from an empty range, but it is also absent when its
	// lookup failed; such a partial answer is not cached, so ask again later before
	// concluding a sport is not covered.
	Sports map[string]HistoryCoverageRange `json:"sports,omitempty"`
}

HistoryCoverageDataset defines model for HistoryCoverageDataset.

type HistoryCoverageRange

type HistoryCoverageRange struct {
	Earliest *string `json:"earliest,omitempty"`
	Latest   *string `json:"latest,omitempty"`
}

HistoryCoverageRange First and last date actually held for one sport in one historical dataset.

type HistoryCoverageResponse

type HistoryCoverageResponse struct {
	Data *struct {
		// Datasets Keyed by dataset, currently `games` and `playerProps`.
		Datasets    map[string]HistoryCoverageDataset `json:"datasets,omitempty"`
		GeneratedAt *string                           `json:"generatedAt,omitempty"`
		Notes       []string                          `json:"notes,omitempty"`
	} `json:"data,omitempty"`
	Meta *struct {
		CacheTTLSeconds *float64 `json:"cacheTtlSeconds,omitempty"`
		Cached          *bool    `json:"cached,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

HistoryCoverageResponse defines model for HistoryCoverageResponse.

type HistoryGamesParams

type HistoryGamesParams struct {
	Sport     string
	Season    string
	Team      string
	GameType  string
	StartDate string
	EndDate   string
	Limit     int
	Offset    int
}

HistoryGamesParams filters GetHistoryGames.

type HistoryGamesResponse

type HistoryGamesResponse struct {
	Data *struct {
		Games      []ArchivedGame `json:"games,omitempty"`
		Pagination *struct {
			HasMore *bool    `json:"hasMore,omitempty"`
			Limit   *float64 `json:"limit,omitempty"`
			Offset  *float64 `json:"offset,omitempty"`
			Total   *float64 `json:"total,omitempty"`
		} `json:"pagination,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

HistoryGamesResponse defines model for HistoryGamesResponse.

type HistoryOddsDataQuality

type HistoryOddsDataQuality struct {
	MainLineNote *string `json:"mainLineNote,omitempty"`

	// MainLineOnly Each row is one book's main line for that market and side.
	MainLineOnly *bool   `json:"mainLineOnly,omitempty"`
	Note         *string `json:"note,omitempty"`

	// SuspectPricesExcluded Unusable rows (a destroyed price, or no market label) are excluded.
	SuspectPricesExcluded *bool `json:"suspectPricesExcluded,omitempty"`
}

HistoryOddsDataQuality What `/history/odds` says about the rows it just returned. Both flags are always `true`; they exist so the accompanying notes have somewhere to hang.

type HistoryOddsParams

type HistoryOddsParams struct {
	EventID   string // required
	Book      string
	Market    string
	Side      string
	StartTime string
	EndTime   string
	Opening   bool
	Limit     int
	Offset    int
}

HistoryOddsParams selects the odds snapshots of one archived game (GetHistoryOdds, IterHistoryOdds; the pager sets Limit and Offset itself).

type HistoryOddsResponse

type HistoryOddsResponse struct {
	Data *struct {
		Count *float64 `json:"count,omitempty"`

		// DataQuality What `/history/odds` says about the rows it just returned. Both flags are
		// always `true`; they exist so the accompanying notes have somewhere to hang.
		DataQuality *HistoryOddsDataQuality `json:"dataQuality,omitempty"`
		EventID     *string                 `json:"eventId,omitempty"`
		Limit       *float64                `json:"limit,omitempty"`
		Offset      *float64                `json:"offset,omitempty"`
		Opening     *bool                   `json:"opening,omitempty"`
		Snapshots   []OddsSnapshot          `json:"snapshots,omitempty"`
		TimeRange   *struct {
			End   *string `json:"end,omitempty"`
			Start *string `json:"start,omitempty"`
		} `json:"timeRange,omitempty"`
	} `json:"data,omitempty"`

	// Meta `archive` when a completed game was served from the history archive store,
	// `live` when read from the database. The payload shape is identical either
	// way. Note this one object mixes casings: `source` and `archiveBuiltAt` are
	// camelCase, the ignored report is snake_case, and that is the wire.
	Meta *struct {
		ArchiveBuiltAt *string `json:"archiveBuiltAt,omitempty"`

		// IgnoredParams Query parameters the endpoint did not honour, sorted. Absent when none were.
		IgnoredParams []string `json:"ignored_params,omitempty"`

		// IgnoredReasons Why, keyed by parameter name; only for entries that have a reason.
		IgnoredReasons map[string]string `json:"ignored_reasons,omitempty"`
		Source         *string           `json:"source,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

HistoryOddsResponse defines model for HistoryOddsResponse.

type HistoryPlayerStat

type HistoryPlayerStat struct {
	EspnGameID *string `json:"espnGameId,omitempty"`
	EventID    *string `json:"eventId,omitempty"`
	GameDate   *string `json:"gameDate,omitempty"`
	Player     *struct {
		DidNotPlay *bool   `json:"didNotPlay,omitempty"`
		EspnID     *string `json:"espnId,omitempty"`
		Jersey     *string `json:"jersey,omitempty"`
		Name       *string `json:"name,omitempty"`
		Position   *string `json:"position,omitempty"`
		Starter    *bool   `json:"starter,omitempty"`
	} `json:"player,omitempty"`
	RecordedAt *string `json:"recordedAt,omitempty"`
	Sport      *string `json:"sport,omitempty"`

	// StatType Stat type discriminator. null = BasketballStats, "batter"|"team_batting" = BattingStats, "pitcher"|"team_pitching" = PitchingStats.
	StatType *string          `json:"statType,omitempty"`
	Stats    *json.RawMessage `json:"stats,omitempty"`
	Team     *struct {
		Abbreviation *string  `json:"abbreviation,omitempty"`
		HomeAway     *string  `json:"homeAway,omitempty"`
		Name         *string  `json:"name,omitempty"`
		Score        *float64 `json:"score,omitempty"`
	} `json:"team,omitempty"`
}

HistoryPlayerStat defines model for HistoryPlayerStat.

type HistoryPoint

type HistoryPoint struct {
	// Point The stored line, which may arrive as a decimal string such as `"-1.50"`.
	Point     *json.RawMessage `json:"point,omitempty"`
	Price     *float64         `json:"price,omitempty"`
	Timestamp *string          `json:"timestamp,omitempty"`

	// TimestampUtc `timestamp` in UTC, or `null` when its clock cannot be known; `timestamp` is the stored time (US Eastern for many rows stored before 2026-09-16). Added Oct 2026.
	TimestampUtc *string `json:"timestampUtc,omitempty"`
}

HistoryPoint defines model for HistoryPoint.

type HistoryPropsDataQuality added in v0.9.0

type HistoryPropsDataQuality struct {
	FlaggedRows *float64 `json:"flaggedRows,omitempty"`
	Flags       map[string]struct {
		Meaning *string  `json:"meaning,omitempty"`
		Rows    *float64 `json:"rows,omitempty"`
	} `json:"flags,omitempty"`
	Note         *string  `json:"note,omitempty"`
	RowsExcluded *float64 `json:"rowsExcluded,omitempty"`
}

HistoryPropsDataQuality Present on /history/props when any row on the page is flagged: counts per flag and what each means.

type HistoryPropsParams

type HistoryPropsParams struct {
	EventID    string // required
	PlayerName string
	PropType   string
	Book       string
	StartTime  string
	EndTime    string
	Opening    bool
	Limit      int
	Offset     int
}

HistoryPropsParams selects the prop snapshots of one archived game (GetHistoryProps, IterHistoryProps; the pager sets Limit and Offset itself).

type HistoryPropsResponse

type HistoryPropsResponse struct {
	Data *struct {
		Count *float64 `json:"count,omitempty"`

		// DataQuality Present on /history/props when any row on the page is flagged: counts per flag and what each means.
		DataQuality *HistoryPropsDataQuality `json:"dataQuality,omitempty"`
		EventID     *string                  `json:"eventId,omitempty"`
		Limit       *float64                 `json:"limit,omitempty"`
		Offset      *float64                 `json:"offset,omitempty"`
		Opening     *bool                    `json:"opening,omitempty"`
		Snapshots   []PropsSnapshot          `json:"snapshots,omitempty"`
		TimeRange   *struct {
			End   *string `json:"end,omitempty"`
			Start *string `json:"start,omitempty"`
		} `json:"timeRange,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

HistoryPropsResponse defines model for HistoryPropsResponse.

type HistorySplitsParams added in v0.2.0

type HistorySplitsParams struct {
	// EventID is the game's eventId on GetOdds (the EventID of a SplitsGame).
	EventID string
	// Sport is required without EventID: nfl, ncaaf, mlb, nba, nhl, ncaab or wnba.
	Sport string
	// Book is a book key as on the live splits: dk, circa or betmgm.
	Book string
	// StartDate (YYYY-MM-DD, UTC, on the time each reading was recorded) is
	// required with Sport.
	StartDate string
	// EndDate (YYYY-MM-DD, UTC) is inclusive. With Sport the window is at most 7
	// days and defaults to 7 days from StartDate.
	EndDate string
	// Limit is the rows per page: default 100, at most 500.
	Limit  int
	Offset int
}

HistorySplitsParams selects GetHistorySplits: one game by EventID, or a sport's games by Sport and StartDate over a window of at most 7 days.

type HistorySplitsResponse added in v0.2.0

type HistorySplitsResponse struct {
	Data *struct {
		// EventID Echoed when the request named an eventId.
		EventID    *string `json:"eventId,omitempty"`
		Pagination *struct {
			HasMore *bool    `json:"hasMore,omitempty"`
			Limit   *float64 `json:"limit,omitempty"`
			Offset  *float64 `json:"offset,omitempty"`
			Total   *float64 `json:"total,omitempty"`
		} `json:"pagination,omitempty"`
		Splits []SplitsHistoryRow `json:"splits,omitempty"`

		// Sport Echoed when the request named a sport.
		Sport *string `json:"sport,omitempty"`

		// Window The UTC date window served (both ends inclusive), on a query by sport only.
		Window *struct {
			End   *string `json:"end,omitempty"`
			Start *string `json:"start,omitempty"`
		} `json:"window,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

HistorySplitsResponse Response for GET /api/v1/history/splits. Rows are oldest first.

type HistoryStatsParams

type HistoryStatsParams struct {
	EventID    string
	PlayerName string
	Sport      string
	Position   string
	StartDate  string
	EndDate    string
	Limit      int
	Offset     int
}

HistoryStatsParams filters GetHistoryStats.

type HistoryStatsResponse

type HistoryStatsResponse struct {
	Data *struct {
		Count      *float64            `json:"count,omitempty"`
		EventID    *string             `json:"eventId,omitempty"`
		Limit      *float64            `json:"limit,omitempty"`
		Offset     *float64            `json:"offset,omitempty"`
		PlayerName *string             `json:"playerName,omitempty"`
		Stats      []HistoryPlayerStat `json:"stats,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

HistoryStatsResponse defines model for HistoryStatsResponse.

type HockeyPlayDetail added in v0.9.0

type HockeyPlayDetail struct {
	AwayShotsOnGoal *float64 `json:"awayShotsOnGoal,omitempty"`

	// HomeShotsOnGoal On shots: the shots on goal after this one.
	HomeShotsOnGoal *float64 `json:"homeShotsOnGoal,omitempty"`
	Penalty         *struct {
		DescKey  *string  `json:"descKey,omitempty"`
		Duration *float64 `json:"duration,omitempty"`
		TypeCode *string  `json:"typeCode,omitempty"`
	} `json:"penalty,omitempty"`
	Players  []HockeyPlayer `json:"players,omitempty"`
	Reason   *string        `json:"reason,omitempty"`
	ShotType *string        `json:"shotType,omitempty"`

	// SituationCode The league's skaters-and-goalies code for the situation on the ice.
	SituationCode *string  `json:"situationCode,omitempty"`
	X             *float64 `json:"x,omitempty"`
	Y             *float64 `json:"y,omitempty"`
	Zone          *string  `json:"zone,omitempty"`
}

HockeyPlayDetail NHL: the players involved by role, the ice position, the shot or penalty.

type HockeyPlayer added in v0.9.0

type HockeyPlayer struct {
	ID *float64 `json:"id,omitempty"`

	// Name Null when the league's roster for the game does not list the id.
	Name    *string  `json:"name,omitempty"`
	Role    *string  `json:"role,omitempty"`
	Sweater *float64 `json:"sweater,omitempty"`
}

HockeyPlayer One player on an NHL play: `role` is scorer, assist, shooter, goalie, winner, loser, hitter, hittee, blocker, player, committedBy, drawnBy or servedBy.

type HomeAway

type HomeAway struct {
	Away *float64 `json:"away,omitempty"`
	Home *float64 `json:"home,omitempty"`
}

HomeAway defines model for HomeAway.

type InjuriesParams

type InjuriesParams struct {
	// Source limits the result to one source by role.
	Source string
	// Status keeps only these designations; it overrides the default view.
	Status []string
	// Team keeps only these teams (names or abbreviations).
	Team []string
	// IncludeActive includes players every source considers available.
	IncludeActive bool
}

InjuriesParams filters GetInjuries.

type InjuriesResponse

type InjuriesResponse struct {
	Count *float64 `json:"count,omitempty"`
	Meta  *struct {
		// Conflicts Players both sources cover but disagree about. Counted over what is SERVED.
		Conflicts *float64 `json:"conflicts,omitempty"`

		// ListDaysConflicts Players both sources call injured but disagree about the LENGTH of.
		ListDaysConflicts *float64 `json:"listDaysConflicts,omitempty"`

		// ReportAgeSummary Freshness of THIS response. The first four partition the players returned
		// and sum to `count`; the last three describe how much of the whole response
		// could be dated and are not scoped to those buckets.
		ReportAgeSummary *struct {
			DatedRecords             *float64 `json:"datedRecords,omitempty"`
			FreshConsensus           *float64 `json:"freshConsensus,omitempty"`
			NoConsensus              *float64 `json:"noConsensus,omitempty"`
			NoVerdict                *float64 `json:"noVerdict,omitempty"`
			PlayersWithNoDatedRecord *float64 `json:"playersWithNoDatedRecord,omitempty"`
			StaleConsensus           *float64 `json:"staleConsensus,omitempty"`
			UndatedRecords           *float64 `json:"undatedRecords,omitempty"`
		} `json:"reportAgeSummary,omitempty"`
		Sources            map[string]InjurySourceMeta `json:"sources,omitempty"`
		SuffixJoins        []InjurySuffixJoin          `json:"suffixJoins,omitempty"`
		Timestamp          *string                     `json:"timestamp,omitempty"`
		UnresolvedIdentity []InjuryUnresolvedIdentity  `json:"unresolvedIdentity,omitempty"`
	} `json:"meta,omitempty"`
	Players []InjuryPlayer `json:"players,omitempty"`
	Sport   *string        `json:"sport,omitempty"`
	Success *bool          `json:"success,omitempty"`
}

InjuriesResponse defines model for InjuriesResponse.

type InjuryAgreement

type InjuryAgreement = string

InjuryAgreement Whether the sources corroborate one another for this player.

type InjuryConsensus

type InjuryConsensus struct {
	// Agreement Whether the sources corroborate one another for this player.
	Agreement *InjuryAgreement `json:"agreement,omitempty"`

	// IDVerified True only when a shared player id corroborates the name-and-team join.
	IDVerified *bool `json:"idVerified,omitempty"`

	// InjuryStatus Null on a conflict: an indeterminate answer is its own verdict, never a pick.
	InjuryStatus *InjuryStatus `json:"injuryStatus,omitempty"`

	// ListDaysConflict True when both sources agree a player is on an injured list but disagree
	// about its length, for example 10 days against 60. `agreement` still reads
	// "agrees", so check this when the length matters.
	ListDaysConflict *bool               `json:"listDaysConflict,omitempty"`
	Sources          []InjurySourceLabel `json:"sources,omitempty"`

	// Stale Whether the designation is still current, judged against `staleAfterSeconds`.
	//
	// True when every dated record behind it is older than the window, false when
	// at least one is inside it, and null when no verdict is possible. Null is
	// never a substitute for false.
	Stale *bool `json:"stale,omitempty"`

	// StaleAfterSeconds The window the designation was judged against, in seconds, chosen by what the
	// designation IS rather than by sport alone: a weekly football designation
	// lapses far sooner than a baseball injured-list stint, which is given its own
	// length plus grace.
	//
	// Read it together with `stale` to tell the two nulls apart. A number beside a
	// null `stale` means the rule exists but nothing carried a timestamp. A null
	// here means no rule applies at all.
	StaleAfterSeconds *float64 `json:"staleAfterSeconds,omitempty"`

	// SuffixJoin True when the join bridged a name suffix. Disclosed rather than hidden.
	SuffixJoin *bool `json:"suffixJoin,omitempty"`
}

InjuryConsensus Derived agreement between sources. Present ONLY when the player's identity is certain; absent players are listed in `meta.unresolvedIdentity`.

type InjuryPlayer

type InjuryPlayer struct {
	// Consensus Derived agreement between sources. Present ONLY when the player's identity is
	// certain; absent players are listed in `meta.unresolvedIdentity`.
	Consensus  *InjuryConsensus `json:"consensus,omitempty"`
	PlayerKey  *string          `json:"playerKey,omitempty"`
	PlayerName *string          `json:"playerName,omitempty"`
	Position   *string          `json:"position,omitempty"`

	// Records Every source's own view, always present, never collapsed.
	Records []InjurySourceRecord `json:"records,omitempty"`

	// ReportAge How much of a player's evidence carries a timestamp, and how old it is.
	ReportAge        *InjuryReportAge `json:"reportAge,omitempty"`
	TeamAbbreviation *string          `json:"teamAbbreviation,omitempty"`
	TeamName         *string          `json:"teamName,omitempty"`
}

InjuryPlayer defines model for InjuryPlayer.

type InjuryReportAge

type InjuryReportAge struct {
	DatedRecords *float64 `json:"datedRecords,omitempty"`

	// NewestSeconds Age of the most recently dated record. Null when nothing is dated.
	NewestSeconds *float64 `json:"newestSeconds,omitempty"`

	// OldestSeconds Age of the least recently dated record. Null when nothing is dated.
	OldestSeconds  *float64 `json:"oldestSeconds,omitempty"`
	UndatedRecords *float64 `json:"undatedRecords,omitempty"`
}

InjuryReportAge How much of a player's evidence carries a timestamp, and how old it is.

type InjurySourceLabel

type InjurySourceLabel = string

InjurySourceLabel Which of the sport's sources a record came from.

Deliberately a ROLE, not a vendor name: "primary" is the source the API leads with for that sport. The label stays correct if a source is swapped underneath it, and it is stable across sports, where the primary differs.

type InjurySourceMeta

type InjurySourceMeta struct {
	// AgeSeconds Seconds since this source was last stored, or null when unknown. Never 0 as a
	// stand-in for unknown.
	AgeSeconds *float64 `json:"ageSeconds,omitempty"`

	// Checks The validations that ran on THIS source's data. Never pooled across sources.
	Checks       []string `json:"checks,omitempty"`
	InjuredCount *float64 `json:"injuredCount,omitempty"`

	// LastDataAgeSeconds How long since this source last produced a non-empty result.
	LastDataAgeSeconds *float64 `json:"lastDataAgeSeconds,omitempty"`

	// LastFetchError The reason CODE of the last refused write. Free-text detail is not served.
	LastFetchError *struct {
		At     *string `json:"at,omitempty"`
		Reason *string `json:"reason,omitempty"`
	} `json:"lastFetchError,omitempty"`

	// MetaMismatch Present when the stored metadata did not describe the stored data, so its age
	// and counts were not trusted and `ageSeconds` is null. Surfaced rather than
	// quietly corrected, because the divergence is itself the signal.
	MetaMismatch *bool `json:"metaMismatch,omitempty"`
	Partial      *struct {
		UnitsExpected *float64 `json:"unitsExpected,omitempty"`
		UnitsFetched  *float64 `json:"unitsFetched,omitempty"`
	} `json:"partial,omitempty"`
	RecordCount *float64 `json:"recordCount,omitempty"`
	Stale       *bool    `json:"stale,omitempty"`
}

InjurySourceMeta Per-source freshness and provenance.

type InjurySourceRecord

type InjurySourceRecord struct {
	// BodyPart A factual descriptor such as "Ankle", not editorial copy.
	BodyPart        *string  `json:"bodyPart,omitempty"`
	DepthChartOrder *float64 `json:"depthChartOrder,omitempty"`

	// InjuryStatus Normalised availability. `unmapped` is a first-class value, not an error: an
	// unrecognised upstream token surfaces as unknown rather than defaulting to
	// healthy, because "healthy" is an affirmative claim you would act on.
	InjuryStatus *InjuryStatus `json:"injuryStatus,omitempty"`

	// ListDays Injured-list length in days, where the source encodes one.
	ListDays *float64 `json:"listDays,omitempty"`

	// PlayerName This source's own spelling of the name, which is why it can differ per record.
	PlayerName            *string `json:"playerName,omitempty"`
	Position              *string `json:"position,omitempty"`
	PracticeDescription   *string `json:"practiceDescription,omitempty"`
	PracticeParticipation *string `json:"practiceParticipation,omitempty"`

	// ReportAgeSeconds Seconds since this source published the state, derived from `reportedAt`.
	//
	// Null when the source publishes no timestamp, and never 0 as a stand-in for
	// unknown. Distinct from `meta.sources[source].ageSeconds`, which is how long
	// ago the feed was fetched: a designation weeks past its weekly reissue can sit
	// behind a fetch that happened seconds ago.
	ReportAgeSeconds *float64 `json:"reportAgeSeconds,omitempty"`

	// ReportedAt The SOURCE's timestamp for this state, not when it was fetched. Null when the
	// source publishes none.
	ReportedAt   *string `json:"reportedAt,omitempty"`
	SeasonEnding *bool   `json:"seasonEnding,omitempty"`

	// Source Which of the sport's sources a record came from.
	//
	// Deliberately a ROLE, not a vendor name: "primary" is the source the API leads
	// with for that sport. The label stays correct if a source is swapped underneath
	// it, and it is stable across sports, where the primary differs.
	Source *InjurySourceLabel `json:"source,omitempty"`

	// StatusLabel The source's own display string, verbatim. Shown, never compared.
	StatusLabel      *string `json:"statusLabel,omitempty"`
	TeamAbbreviation *string `json:"teamAbbreviation,omitempty"`

	// TeamID Canonical team id, stable across sources and sports.
	TeamID   *string `json:"teamId,omitempty"`
	TeamName *string `json:"teamName,omitempty"`
}

InjurySourceRecord One player's status according to ONE source. Never merged with another source's view.

type InjuryStatus

type InjuryStatus = string

InjuryStatus Normalised availability. `unmapped` is a first-class value, not an error: an unrecognised upstream token surfaces as unknown rather than defaulting to healthy, because "healthy" is an affirmative claim you would act on.

type InjurySuffixJoin

type InjurySuffixJoin struct {
	PlayerKey *string  `json:"playerKey,omitempty"`
	Spellings []string `json:"spellings,omitempty"`
	TeamName  *string  `json:"teamName,omitempty"`
}

InjurySuffixJoin defines model for InjurySuffixJoin.

type InjuryUnresolvedIdentity

type InjuryUnresolvedIdentity struct {
	PlayerKey *string  `json:"playerKey,omitempty"`
	Reason    *string  `json:"reason,omitempty"`
	Spellings []string `json:"spellings,omitempty"`
	TeamName  *string  `json:"teamName,omitempty"`
}

InjuryUnresolvedIdentity Why a player could not be given a consensus.

type KalshiV2BookSubscription

type KalshiV2BookSubscription struct {
	// Cs2 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Cs2 *V2LeagueFilter `json:"cs2,omitempty"`

	// Dota2 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Dota2 *V2LeagueFilter `json:"dota2,omitempty"`

	// Lol Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Lol *V2LeagueFilter `json:"lol,omitempty"`

	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Overwatch Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Overwatch *V2LeagueFilter `json:"overwatch,omitempty"`

	// Pubg Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Pubg *V2LeagueFilter `json:"pubg,omitempty"`

	// Rainbow6 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Rainbow6 *V2LeagueFilter `json:"rainbow6,omitempty"`

	// Rocketleague Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Rocketleague *V2LeagueFilter `json:"rocketleague,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`

	// Valorant Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Valorant *V2LeagueFilter `json:"valorant,omitempty"`

	// Wnba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Wnba *V2LeagueFilter `json:"wnba,omitempty"`
}

KalshiV2BookSubscription Per-book subscription shape for Kalshi v2 events.

Each Kalshi sport carries one or more series (game lines, props, futures). Pass `'*'` to receive every series for that sport, or an array of Kalshi series tickers / league slugs to filter.

type LineHistoryParams

type LineHistoryParams struct {
	EventID string // required
	Book    string // required
	Side    string // required: "home", "away" ("draw" for moneyline), or "over" or "under" for totals
	// StartTime is a Unix time in milliseconds to start the window at.
	StartTime int64
	// Hours looks back this many hours (1 to 168); ignored when StartTime is set.
	Hours int
}

LineHistoryParams selects GetMoneylineHistory, GetSpreadHistory and GetTotalsHistory: OddsHistoryParams without the market, which each method sets.

type LineupsData

type LineupsData struct {
	Away []PlayerLineup `json:"away,omitempty"`
	Home []PlayerLineup `json:"home,omitempty"`
}

LineupsData defines model for LineupsData.

type LiveMatchStats

type LiveMatchStats struct {
	Corners         *HomeAway `json:"corners,omitempty"`
	Fouls           *HomeAway `json:"fouls,omitempty"`
	FreeKicks       *HomeAway `json:"freeKicks,omitempty"`
	GoalkeeperSaves *HomeAway `json:"goalkeeperSaves,omitempty"`
	Offsides        *HomeAway `json:"offsides,omitempty"`
	Passes          *HomeAway `json:"passes,omitempty"`
	Possession      *HomeAway `json:"possession,omitempty"`
	RedCards        *HomeAway `json:"redCards,omitempty"`
	ShotsOnTarget   *HomeAway `json:"shotsOnTarget,omitempty"`
	ShotsTotal      *HomeAway `json:"shotsTotal,omitempty"`
	Tackles         *HomeAway `json:"tackles,omitempty"`
	ThrowIns        *HomeAway `json:"throwIns,omitempty"`
	YellowCards     *HomeAway `json:"yellowCards,omitempty"`
}

LiveMatchStats defines model for LiveMatchStats.

type LowvigV2BookSubscription

type LowvigV2BookSubscription struct {
	// Boxing Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Boxing *V2LeagueFilter `json:"boxing,omitempty"`

	// Cricket Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Cricket *V2LeagueFilter `json:"cricket,omitempty"`

	// Darts Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Darts *V2LeagueFilter `json:"darts,omitempty"`

	// Golf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Golf *V2LeagueFilter `json:"golf,omitempty"`

	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Mma Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mma *V2LeagueFilter `json:"mma,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaab Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaab *V2LeagueFilter `json:"ncaab,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// TableTennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	TableTennis *V2LeagueFilter `json:"table-tennis,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`

	// Wnba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Wnba *V2LeagueFilter `json:"wnba,omitempty"`
}

LowvigV2BookSubscription defines model for LowvigV2BookSubscription.

type Market

type Market struct {
	// Category Source's own market category (e.g. "Team Props", "Game Props").
	Category *string `json:"category,omitempty"`

	// Description Source's own market description, where the book provides one.
	Description *string `json:"description,omitempty"`

	// IsAlternate True when this entry is an alternate line rather than the main line.
	IsAlternate *bool `json:"isAlternate,omitempty"`

	// Key Market identifier.
	//
	// On the Pinnacle `/realtime` pipeline, markets folded from a sub-matchup are
	// keyed `special_<matchupId>_<type>_p<period>`. ALTERNATE ladders additionally
	// carry their LINE, because a key that resolves to several markets does not
	// identify one:
	//   `special_1634192207_total_p0_alt_10.5`
	//   `special_1634192207_spread_p0_alt_-3`   <-- CAN BE NEGATIVE
	// A spread's line is stated from the home side, so `_alt_` may be followed by
	// a minus sign; do not assume that segment parses as an unsigned number.
	// Non-alternate keys (`special_<id>_total_p0`) and team-total keys
	// (`special_<id>_team_total_p0_home_1.5`) are unchanged.
	//
	// A rung whose line MOVES changes key - a different line is a different rung.
	// If you map markets by key, key each rung separately; keying only on the
	// family collapses a ladder onto whichever rung you saw last.
	Key *string `json:"key,omitempty"`

	// LastSeen When the book last CONFIRMED this market, as an ISO timestamp, whether or not
	// the price moved. Pinnacle realtime markets only.
	//
	// Use this, not `last_update`, to judge whether a quote is still live: a quiet
	// line the book keeps re-confirming has a fresh `last_seen` and an old
	// `last_update`, so `last_update` alone cannot tell a quiet line from a pulled one.
	//
	// Compare a line against ITS OWN history, not against other lines on the same
	// game. Lines on one match are confirmed at very different rates (the main line
	// far more often than alternates or period lines), so a line that trails its
	// siblings is normal. A line whose `last_seen` stops advancing when it normally
	// keeps moving has stopped being quoted.
	LastSeen *string `json:"last_seen,omitempty"`

	// LastUpdate When this market's price last CHANGED. It does not move when the book re-confirms an unchanged price.
	LastUpdate *string `json:"last_update,omitempty"`
	Limits     []struct {
		Amount *float64 `json:"amount,omitempty"`
		Type   *string  `json:"type,omitempty"`
	} `json:"limits,omitempty"`
	Outcomes []Outcome `json:"outcomes,omitempty"`

	// Side The source's own market-level side designation, passed through unmodified.
	//
	// Set on markets that price ONE competitor rather than both. The case it
	// exists for is a team total, whose outcomes are named only `over` / `under`,
	// so nothing inside the market says whose total is priced.
	//
	// Join it yourself: `side === 'home'` means the event's `home_team`,
	// `side === 'away'` means its `away_team`. The API deliberately does not
	// resolve the name, because the resolved string came from a different naming
	// layer than `home_team` / `away_team` and failed to join.
	//
	// Set today only by the Pinnacle `/realtime` pipeline, on `special_*` markets.
	Side *string `json:"side,omitempty"`

	// Suspended True when betting on this market is closed: the book closed it, and on the
	// Pinnacle realtime feed it can also be set once the market's period is closed or
	// its betting cutoff has passed. Absent otherwise, never `false`.
	//
	// On the Pinnacle realtime feed it is also set on a main market (moneyline,
	// spread, total or team total) that Pinnacle withdraws while the rest of the match
	// stays open, and cleared when Pinnacle offers it again or its period ends. On a
	// spread or total only the main line is flagged, never its `alternateLines`.
	//
	// Its absence does NOT mean every line is being quoted: a withdrawn alternate rung,
	// or a special or prop market, can arrive with no `suspended` at all. Use
	// `last_seen` (on the market, and on each rung) to tell whether a quote is live.
	Suspended *bool `json:"suspended,omitempty"`

	// Units The source's own unit-of-measure for the sub-matchup this market came from,
	// passed through unmodified.
	//
	// NOT AN ENUM - match it exactly and treat unknown values as unknown. Values
	// seen in one soccer sample: `Regular`, `Corners`, `1st Goal`, `2nd Goal`,
	// `CombinedResult`, `Place 1st`. Tennis team totals report `Games`. It varies
	// by sport AND by market, so do not hard-code a whitelist you expect to be
	// exhaustive.
	//
	// Matters most on `special_*` team totals: one soccer event can carry a goals
	// team total AND a corners team total folded on from different sub-matchups,
	// and `units` is what separates them. Absent means the source stated no unit;
	// do NOT read absence as "goals".
	Units *string `json:"units,omitempty"`
}

Market defines model for Market.

type MarketStructure

type MarketStructure = string

MarketStructure Market structure as published by the upstream sportsbook. Tells the consumer how to interpret `line`, `overPrice`, and `underPrice` together. Populated from upstream signals - not inferred client-side.

  • `over_under`: Both Over and Under selections published with a continuous line (e.g. strikeouts 5.5). Standard interpretation.
  • `milestone`: Upstream provides an explicit threshold count (e.g. DK's `milestoneValue: 1` = "1+ of category"). `line` is the threshold value verbatim. `underPrice` is always null.
  • `single_sided`: Only one side published (typically Over for rare-event props). The missing side is `null`, not inferred.
  • `pick_one`: Multi-selection market where each selection is one participant's Yes price (e.g. anytime touchdown scorer).

ABSENT DOES NOT MEAN `over_under`. Treat a missing value as UNKNOWN; see PlayerProp.marketStructure for why defaulting it is a settlement bug.

type MatchIncident

type MatchIncident struct {
	AssistPlayerName *string  `json:"assistPlayerName,omitempty"`
	Minute           *float64 `json:"minute,omitempty"`
	PlayerName       *string  `json:"playerName,omitempty"`
	PlayerOut        *string  `json:"playerOut,omitempty"`
	TeamSide         *string  `json:"teamSide,omitempty"`
	Type             *string  `json:"type,omitempty"`
}

MatchIncident defines model for MatchIncident.

type MatchStatsResponse

type MatchStatsResponse struct {
	Data *struct {
		EventID   *string         `json:"eventId,omitempty"`
		Incidents []MatchIncident `json:"incidents,omitempty"`

		// LastUpdated Last time the score or state changed.
		LastUpdated *string      `json:"lastUpdated,omitempty"`
		Lineups     *LineupsData `json:"lineups,omitempty"`
		Name        *string      `json:"name,omitempty"`

		// Score Null for a scheduled match (never a 0-0). Soccer: excludes shootout goals.
		Score *struct {
			Away *float64 `json:"away,omitempty"`
			Home *float64 `json:"home,omitempty"`
		} `json:"score,omitempty"`

		// SoccerDetail Soccer result breakdown. `home.score`/`away.score` (and `score` on the stats
		// endpoints) are the score of the match itself, after extra time when it was
		// played, and NEVER include shootout goals; the shootout is only in `penalties`.
		SoccerDetail *SoccerScoreDetail `json:"soccerDetail,omitempty"`
		Sport        *string            `json:"sport,omitempty"`
		StartTime    *string            `json:"startTime,omitempty"`
		Status       *ScoreStatus       `json:"status,omitempty"`
		TeamStats    map[string]struct {
			Away *json.RawMessage `json:"away,omitempty"`
			Home *json.RawMessage `json:"home,omitempty"`
		} `json:"teamStats,omitempty"`
		TennisStats *TennisStatsPayload `json:"tennisStats,omitempty"`
	} `json:"data,omitempty"`
	Meta *struct {
		Sport     *string `json:"sport,omitempty"`
		Timestamp *string `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

MatchStatsResponse defines model for MatchStatsResponse.

type MyBookieV2BookSubscription

type MyBookieV2BookSubscription struct {
	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`
}

MyBookieV2BookSubscription Per-book subscription shape for MyBookie v2 events.

MyBookie is single-league per sport (mlb, nba) - pass `'*'`. Arrays are accepted and coerced to `'*'` server-side (V2_SINGLE_LEAGUE_PAIRS).

type NormalizeBatchResponse

type NormalizeBatchResponse struct {
	Results []NormalizeResult `json:"results,omitempty"`
	Sport   *string           `json:"sport,omitempty"`
}

NormalizeBatchResponse defines model for NormalizeBatchResponse.

type NormalizeResponse

type NormalizeResponse struct {
	Canonical  *string `json:"canonical,omitempty"`
	Input      *string `json:"input,omitempty"`
	Simplified *string `json:"simplified,omitempty"`
	Sport      *string `json:"sport,omitempty"`
}

NormalizeResponse defines model for NormalizeResponse.

type NormalizeResult

type NormalizeResult struct {
	Canonical *string `json:"canonical,omitempty"`
	Input     *string `json:"input,omitempty"`

	// Simplified Simplified form (noise words stripped, lowercased). Only for soccer/tennis.
	Simplified *string `json:"simplified,omitempty"`
}

NormalizeResult defines model for NormalizeResult.

type OddsEvent

type OddsEvent struct {
	AwayTeam *string `json:"away_team,omitempty"`

	// AwayTeamID Stable identifier for the team behind `away_team`; same rules as `home_team_id`.
	AwayTeamID   *string     `json:"away_team_id,omitempty"`
	Bookmakers   []Bookmaker `json:"bookmakers,omitempty"`
	CommenceTime *string     `json:"commence_time,omitempty"`
	CountryCode  *string     `json:"country_code,omitempty"`
	EventID      *string     `json:"eventId,omitempty"`

	// Freshness Per-event freshness on the realtime streams. Keyed off PRICE MOVEMENT
	// (`lastUpdate`), not feed re-confirmation (`refreshedAt`), so a frozen-but-still-
	// served live line reads `stale: true` - the market-maker pull signal.
	Freshness *EventFreshness `json:"freshness,omitempty"`
	HomeTeam  *string         `json:"home_team,omitempty"`

	// HomeTeamID Stable identifier for the team behind `home_team`, e.g. `"la-lakers"`.
	// The same team carries the same id across every book and every spelling of
	// the name, so you can join on it instead of on the string.
	//
	// The key is ABSENT (never `null`) when the name is not on a roster we carry:
	// every tennis competitor, soccer and college names outside our rosters, and
	// outright events, which never carry ids at all.
	HomeTeamID *string `json:"home_team_id,omitempty"`
	ID         *string `json:"id,omitempty"`

	// IsLive In-play flag on the realtime feeds.
	//
	// When `phase` is present: true while a live line for the match is started and
	// not ended, meaning in-play betting on it is running.
	//
	// Without `phase`: the book's own flag for its live-betting offering, which does
	// not mean "in play" and can be false on a match in progress, so check `status`
	// as well.
	//
	// `freshness.stale` applies to an event that reads as in play.
	IsLive *bool `json:"isLive,omitempty"`

	// LastUpdate Epoch ms of the newest price change on this event (realtime only).
	LastUpdate *float64 `json:"lastUpdate,omitempty"`
	League     *string  `json:"league,omitempty"`

	// MarketType `'outright'` for aggregated futures/progression markets (e.g. Pinnacle
	// "To Reach The Final") that are NOT team-vs-team games. For these, the single
	// `bookmakers[].markets[].outcomes` list holds the competitors (each with a
	// price), `home_team` carries the market label, and `away_team` is empty.
	// Absent for real games. Currently surfaced on the realtime (`/realtime`,
	// `pinnacle-realtime`) soccer feed.
	MarketType *string `json:"market_type,omitempty"`

	// Phase OddsEvent.phase. More values can be added: treat an unknown one like a
	// missing field.
	Phase *EventPhase `json:"phase,omitempty"`

	// SeenAt Epoch ms the feed last CONFIRMED a price on this event, changed or not (realtime only).
	SeenAt     *float64    `json:"seenAt,omitempty"`
	Sport      *string     `json:"sport,omitempty"`
	SportKey   *string     `json:"sport_key,omitempty"`
	SportTitle *string     `json:"sport_title,omitempty"`
	Status     *GameStatus `json:"status,omitempty"`
}

OddsEvent defines model for OddsEvent.

func RealtimeSportEvents

func RealtimeSportEvents(frame json.RawMessage, sport string) ([]OddsEvent, error)

RealtimeSportEvents returns the events a pinnacle-realtime or ps3838-realtime frame carries for sport, or nil when it carries none for it.

A frame is keyed by sport ({"timestamp": ..., "<sport>": [...], ...}) and holds one sport; Stream.Sport names it when the frame has a stream position. RealtimeOddsPayload names the common sports as fields; this reads any sport, including one the API adds later. The events decode like Decode. Only a frame that is not a JSON object is an error.

s.On(owls.EventPinnacleRealtime, func(raw json.RawMessage) {
	events, err := owls.RealtimeSportEvents(raw, "soccer")
	...
})

type OddsFreshness

type OddsFreshness struct {
	AgeSeconds *float64 `json:"ageSeconds,omitempty"`

	// DroppedAsFinished Events dropped because the match was confirmed finished (realtime only).
	DroppedAsFinished *float64 `json:"droppedAsFinished,omitempty"`

	// DroppedAsStale Events dropped because their scheduled start is older than the sport's
	// max plausible live duration - a source-independent staleness guard
	// (realtime only).
	DroppedAsStale *float64 `json:"droppedAsStale,omitempty"`
	Stale          *bool    `json:"stale,omitempty"`
}

OddsFreshness defines model for OddsFreshness.

type OddsHistoryParams

type OddsHistoryParams struct {
	EventID string // required
	Book    string // required
	Market  string // required
	Side    string // required
	// StartTime is a Unix time in milliseconds to start the window at.
	StartTime int64
	// Hours looks back this many hours (1 to 168); ignored when StartTime is set.
	Hours int
}

OddsHistoryParams selects GetOddsHistory: the line movement of one book, market and side of a live or recent event.

type OddsHistoryResponse

type OddsHistoryResponse struct {
	Data *struct {
		Book       *string  `json:"book,omitempty"`
		DataPoints *float64 `json:"dataPoints,omitempty"`

		// DataQuality Which snapshots were withheld as unusable. `allPricesSuppressed` (with its own
		// `note`) means the market has recorded history but every snapshot in it was withheld.
		DataQuality *struct {
			AllPricesSuppressed *bool   `json:"allPricesSuppressed,omitempty"`
			MainLineNote        *string `json:"mainLineNote,omitempty"`

			// MainLineOnly Each row is one book's main line for that market and side.
			MainLineOnly *bool   `json:"mainLineOnly,omitempty"`
			Note         *string `json:"note,omitempty"`

			// SuspectPricesExcluded Unusable rows (a destroyed price, or no market label) are excluded.
			SuspectPricesExcluded *bool `json:"suspectPricesExcluded,omitempty"`
		} `json:"dataQuality,omitempty"`
		EventID     *string        `json:"eventId,omitempty"`
		History     []HistoryPoint `json:"history,omitempty"`
		Market      *string        `json:"market,omitempty"`
		OpeningLine *struct {
			// Point Same column, same query and same type as `history[].point`: the stored
			// line, which may arrive as a decimal string such as `"-1.50"`.
			Point     *json.RawMessage `json:"point,omitempty"`
			Price     *float64         `json:"price,omitempty"`
			Timestamp *string          `json:"timestamp,omitempty"`

			// TimestampUtc `timestamp` in UTC, or `null` when its clock cannot be known; `timestamp` is the stored time (US Eastern for many rows stored before 2026-09-16). Added Oct 2026.
			TimestampUtc *string `json:"timestampUtc,omitempty"`
		} `json:"openingLine,omitempty"`
		Side      *string `json:"side,omitempty"`
		TimeRange *struct {
			DurationHours *float64 `json:"durationHours,omitempty"`
			End           *string  `json:"end,omitempty"`
			Mode          *string  `json:"mode,omitempty"`
			Start         *string  `json:"start,omitempty"`
		} `json:"timeRange,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

OddsHistoryResponse defines model for OddsHistoryResponse.

type OddsMeta

type OddsMeta struct {
	Alternates     *bool    `json:"alternates,omitempty"`
	AvailableBooks []string `json:"availableBooks,omitempty"`

	// AvailableLeagues Every league key on the sport's board, so a `league` value can be checked.
	AvailableLeagues []string `json:"available_leagues,omitempty"`
	BooksReturned    []string `json:"booksReturned,omitempty"`

	// ExchangeBooks The prediction-market exchanges this board can carry (kalshi, polymarket, novig).
	ExchangeBooks []string `json:"exchangeBooks,omitempty"`

	// ExcludeExchanges Echo of `excludeExchanges`: true when the exchanges were left out.
	ExcludeExchanges *bool `json:"excludeExchanges,omitempty"`

	// FanduelMarketIds meta.fanduelMarketIds on getOdds (and the per-market routes) with `state`.
	FanduelMarketIds *FanDuelStateOddsMeta `json:"fanduelMarketIds,omitempty"`
	Freshness        *OddsFreshness        `json:"freshness,omitempty"`

	// IgnoredParams Query parameters the endpoint did NOT honour, sorted. Present only when
	// something was ignored, so a clean request carries no key at all (never an
	// empty array). A name we do not read, or a filter whose value did not
	// resolve, lands here instead of silently returning the full board.
	IgnoredParams []string `json:"ignored_params,omitempty"`

	// IgnoredReasons Why a name in `ignored_params` was ignored, keyed by that name. Present
	// only for the entries that have a reason: an unknown parameter name has
	// none, so it appears in `ignored_params` alone.
	IgnoredReasons map[string]string `json:"ignored_reasons,omitempty"`
	LastOddsChange *string           `json:"last_odds_change,omitempty"`
	League         *string           `json:"league,omitempty"`
	Market         *string           `json:"market,omitempty"`
	RequestedBooks []string          `json:"requestedBooks,omitempty"`
	Sport          *string           `json:"sport,omitempty"`
	SportKey       *string           `json:"sport_key,omitempty"`
	Timestamp      *string           `json:"timestamp,omitempty"`
}

OddsMeta defines model for OddsMeta.

type OddsParams

type OddsParams struct {
	// Books narrows the board to these books.
	Books []string
	// Market narrows the board to one market. Only GetOdds reads it; the
	// per-market routes report a differing value in meta.ignored_params.
	Market string
	// ExcludeExchanges leaves the prediction-market exchanges (kalshi, polymarket
	// and novig) out of the board, keeping only sportsbooks. It is sent as
	// exclude_exchanges=true only when set, and Meta.ExcludeExchanges echoes it
	// (Meta.ExchangeBooks lists the exchanges the board can carry). If it removes
	// every book named in Books, the API answers 400.
	ExcludeExchanges bool
	// Alternates includes alternate lines.
	Alternates bool
	League     string
	// State is a two-letter FanDuel state code (for example "co") whose marketIds
	// to serve on the FanDuel outcomes. FanDuel issues a different marketId in every
	// state while the selectionId is the same everywhere, so a bet-slip link is
	// built with the state's marketId on that state's FanDuel site. FanDuel deepLink
	// fields are left out of a state response (they point at the New Jersey site),
	// and an outcome with no id for the state has its marketId omitted.
	// Meta.FanduelMarketIds reports what was mapped; Available false means the sport
	// has no ids for that state and every FanDuel marketId is omitted (the other
	// books are served as usual). A value that is not two letters is named in
	// meta.ignored_params. Default (empty): New Jersey, the feed's own ids. REST
	// only: odds-update carries the New Jersey ids.
	State string
}

OddsParams filters GetOdds, GetMoneyline, GetSpreads and GetTotals.

type OddsResponse

type OddsResponse struct {
	// Data Keyed by sportsbook name (e.g. "pinnacle", "fanduel").
	Data    map[string][]OddsEvent `json:"data,omitempty"`
	Meta    *OddsMeta              `json:"meta,omitempty"`
	Success *bool                  `json:"success,omitempty"`
}

OddsResponse defines model for OddsResponse.

type OddsSnapshot

type OddsSnapshot struct {
	Book *string `json:"book,omitempty"`

	// InPlay Whether the game was in play when the row was captured.
	//
	// Three states, and the third is an ABSENT KEY: `true` in play, `false`
	// pre-game, absent when the phase was not recorded. Absent covers every row
	// written before 2026-09-20, every esports row, and rows from sources that
	// do not derive a phase. Do NOT read a missing key as `false`: that files
	// unknown-phase rows as pre-game.
	InPlay *bool   `json:"inPlay,omitempty"`
	Market *string `json:"market,omitempty"`

	// Point The line (spread or total) as stored: a decimal string such as "-1.50",
	// because the archive keeps two-decimal precision as text. Prefer
	// `pointValue`, which is the same line already parsed. null for moneylines.
	Point *json.RawMessage `json:"point,omitempty"`

	// PointValue `point` as a number, always present: `-1.5` beside `"-1.50"`. `null` when
	// the line is null or could not be parsed, and never `0` for either, so
	// `null` and a genuine zero line stay distinguishable.
	PointValue *float64 `json:"pointValue,omitempty"`

	// Price American odds (integer). For decimal-native books (e.g. 1xBet) this is the
	// American conversion; see `priceDecimal` for the exact upstream value.
	Price *float64 `json:"price,omitempty"`

	// PriceDecimal Exact upstream decimal (European) odds at full precision, e.g. 1xBet 2.387.
	// Populated for decimal-native books; null for American-native books.
	// Available on snapshots archived after the price-precision fix (2026-07).
	PriceDecimal *float64 `json:"priceDecimal,omitempty"`
	RecordedAt   *string  `json:"recordedAt,omitempty"`

	// RecordedAtUtc The capture time in UTC (ISO, ends in Z), or `null` when its clock cannot be known (rows stored
	// before 2026-03-28, and book `pinnacle` before 2026-04-10). `recordedAt` is the time as stored: most
	// rows stored before 2026-09-16 hold US Eastern wall time even though the value ends in Z. Use this
	// one for time arithmetic. Added Oct 2026: absent on an API older than that.
	RecordedAtUtc *string `json:"recordedAtUtc,omitempty"`
	Side          *string `json:"side,omitempty"`
}

OddsSnapshot defines model for OddsSnapshot.

type OddsUpdatePayload

type OddsUpdatePayload struct {
	// BookStatus Per-book data freshness status.
	BookStatus     map[string]BookStatusData `json:"bookStatus,omitempty"`
	LastOddsChange *string                   `json:"last_odds_change,omitempty"`

	// Leagues Available leagues per sport.
	Leagues map[string][]string `json:"leagues,omitempty"`

	// OpeningLines Opening lines keyed by "eventId:book:market:side".
	OpeningLines map[string]OpeningLineData `json:"openingLines,omitempty"`

	// Sports Odds keyed by sport (e.g. "nba", "nhl"). Each sport contains OddsEvent[] with bookmakers nested inside; merge them by `id`.
	Sports    map[string][]OddsEvent `json:"sports,omitempty"`
	Timestamp *string                `json:"timestamp,omitempty"`
}

OddsUpdatePayload Payload for `odds-update` WebSocket events.

Frames can be partial. After a subscribe the first frames are a snapshot of the sports and books you asked for; later frames carry only the games that changed. Merge each event into your own state by `id` instead of replacing your state with a frame.

type OneXBetSoccerResponse

type OneXBetSoccerResponse struct {
	Book  *string     `json:"book,omitempty"`
	Count *float64    `json:"count,omitempty"`
	Data  []OddsEvent `json:"data,omitempty"`
	Meta  *struct {
		AgeMs     *float64 `json:"ageMs,omitempty"`
		Fresh     *bool    `json:"fresh,omitempty"`
		Message   *string  `json:"message,omitempty"`
		Source    *string  `json:"source,omitempty"`
		UpdatedAt *string  `json:"updatedAt,omitempty"`
	} `json:"meta,omitempty"`
	Sport   *string `json:"sport,omitempty"`
	Success *bool   `json:"success,omitempty"`
}

OneXBetSoccerResponse Response for `GET /api/v1/1xbet/soccer` - the isolated 1xBet SOCCER feed. 1xBet soccer is intentionally NOT in `/odds` (it's a scrape-based, best-effort fresh feed). 1xBet *esports* is unaffected and remains in the normal streams.

type OneXBetSoccerUpdate

type OneXBetSoccerUpdate struct {
	Book      *string     `json:"book,omitempty"`
	Events    []OddsEvent `json:"events,omitempty"`
	Sport     *string     `json:"sport,omitempty"`
	UpdatedAt *float64    `json:"updatedAt,omitempty"`
}

OneXBetSoccerUpdate Payload for the WebSocket `1xbet-update` event (isolated 1xBet soccer).

type OpeningLineData

type OpeningLineData struct {
	Point         *float64 `json:"point,omitempty"`
	PriceAmerican *float64 `json:"priceAmerican,omitempty"`
	Timestamp     *string  `json:"timestamp,omitempty"`
}

OpeningLineData Opening line snapshot sent with odds-update payloads.

type Option

type Option func(*config)

Option configures a Client.

func WithBaseURL

func WithBaseURL(u string) Option

WithBaseURL sets the REST base URL (default https://api.owlsinsight.com). The WebSocket URL follows it unless WithWSURL is also given.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets the http.Client used for REST calls and the WebSocket upgrade. Leave its Timeout at zero: timeouts come from WithTimeout, per attempt.

func WithHistoryConcurrency

func WithHistoryConcurrency(n int) Option

WithHistoryConcurrency sets how many /api/v1/history/* and /api/odds/history requests the client keeps in flight at once (default 3, the per-key allowance of the MVP plan). Requests beyond it wait for a slot instead of being refused by the API. 0 disables the gate.

func WithRetry

func WithRetry(p RetryPolicy) Option

WithRetry enables retries of 429 and 503 responses on GET requests. Zero fields take their defaults.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets how long one REST attempt may take, response body included (default 30 s). It is applied through the request context; 0 disables it. BatchSgp and CompareSgpCombo raise a shorter value to 45 s for their own calls.

func WithWSURL

func WithWSURL(u string) Option

WithWSURL sets the WebSocket base URL, without the /socket.io/ path.

type Outcome

type Outcome struct {
	AlternateLines []AlternateLine `json:"alternateLines,omitempty"`

	// DeepLink Ready-made URL that opens this outcome on the sportsbook, pre-built with a
	// host that resolves the ids. Optional and absent for most books - read it
	// defensively rather than assuming every outcome has one.
	//
	// What the link does differs by book. Read this before putting one behind a
	// button labelled "add to betslip":
	//
	// - **FanDuel** - `nj.sportsbook.fanduel.com/addToBetslip`, adds the selection
	//   to the existing slip. The NJ host is required, not cosmetic: the
	//   underlying `marketId` namespace only resolves on that subdomain. For
	//   another state, request `state` (getOdds, getProps): `marketId` is then
	//   that state's and `deepLink` is left out; the `selectionId` is the same
	//   in every state.
	// - **DraftKings** - `sportsbook.draftkings.com/event/{eventId}?outcomes=...`,
	//   which opens the event with the outcome selected. Not region-locked at the
	//   ID level; DraftKings applies its own state gating once the slip opens.
	// - **Hard Rock** - `share.hardrock.bet/Pt0T/landing?deep_link_value=…`, Hard
	//   Rock's AppsFlyer share host. On a phone with the app installed it opens
	//   the app on the selection; without the app it goes to the app store, and
	//   there is no mobile web fallback. On desktop the `af_web_dp` parameter is
	//   reported to send the visitor to `app.hardrock.bet` (inherited claim, not
	//   verified by us).
	//   **This one REPLACES the betslip.** Hard Rock's handler clears the existing
	//   slip before it resolves and adds the selection, so anything the customer
	//   had already built is discarded. Label it accordingly. A logged-out visitor
	//   is prompted to sign in and the selection lands after they do.
	// - **Novig** - `novig.onelink.me/JHQQ?…&deep_link_value=…`, an AppsFlyer
	//   OneLink that PREFILLS a slip with this one side (no stake prescribed).
	//   Hands off to the Novig app when installed; there is no web autofill
	//   fallback. Novig is an exchange, so the price quoted is a peer's resting
	//   order - treat the click as an order to place, not a guaranteed fill.
	//   Only the props form of this link has been click-tested; game-line links
	//   come off the same builder but have not been.
	//
	// Books not listed here do not publish a link today.
	DeepLink    *string `json:"deepLink,omitempty"`
	Description *string `json:"description,omitempty"`

	// Liquidity Order-book depth, on exchanges that expose a book (Novig today).
	//
	// ⚠️ `size` is in CENTS, the exchange's native unit - NOT dollars and not
	// contracts. `size: 47691` is **$476.91**. Divide by 100 before showing it
	// to anyone or sizing a bet against it; reading it as dollars overstates
	// available depth by 100x.
	//
	// ⚠️ `price` here is AMERICAN ODDS, converted from Novig's native
	// probability so this surface matches every other book in `/odds`. The
	// player-props feed does NOT do this - `PlayerProp.liquidity` levels carry
	// `price` as a raw probability (0–1) and size as `qty`/`qtyDollars`. Do not
	// share a parsing path between the two.
	//
	// Levels are best price first, up to a 100-level safety bound (server-side,
	// `NOVIG_LIQUIDITY_DEPTH`). Deepest observed to date is 57, so nothing is
	// clipped today, but there is no flag telling you a ladder WAS clipped.
	// Main markets were capped at 5 levels until 2026-08-21; player props never
	// were, which is the asymmetry a customer reported.
	//
	// `asks` is empty in practice on Novig - its ladders are bids-only (measured
	// across 2,659 outcomes), so an empty `asks` is the venue, not missing data.
	//
	// Attached to the PRIMARY line only, not to alternate lines. A deep ladder's
	// tail can hold real but far-out-of-the-money resting orders (observed up to
	// +99900 at ~0.1% implied) - read the tail as depth, not as "the price".
	//
	// Absent for traditional sportsbooks and for exchange outcomes with no ladder.
	Liquidity *struct {
		// Asks Always present, but empty on Novig - its ladders are bids-only.
		Asks []struct {
			Price *float64 `json:"price,omitempty"`
			Size  *float64 `json:"size,omitempty"`
		} `json:"asks,omitempty"`

		// Bids Best bid first. `price` = American odds, `size` = CENTS.
		Bids []struct {
			Price *float64 `json:"price,omitempty"`
			Size  *float64 `json:"size,omitempty"`
		} `json:"bids,omitempty"`
	} `json:"liquidity,omitempty"`

	// MarketID The sportsbook's own market identifier for the parent market, passed through
	// verbatim. Use it to correlate an outcome with the book's public API, not to
	// hand-build a link - `deepLink` is the supported way to link out.
	//
	// Present only when the book publishes one. Do not assume a book that carries
	// `deepLink` also carries a usable `marketId`: Hard Rock's link format resolves
	// a selection on its own and never consults a market id.
	MarketID *string  `json:"marketId,omitempty"`
	Name     *string  `json:"name,omitempty"`
	Point    *float64 `json:"point,omitempty"`
	Price    *float64 `json:"price,omitempty"`

	// SelectionID The sportsbook's own identifier for this specific outcome, passed through
	// verbatim.
	//
	// The type is book-dependent - FanDuel publishes a number (`29172`),
	// DraftKings a string (`'0ML85877697_3'`). Compare loosely or coerce; do not
	// assume one or the other.
	//
	// A `selectionId` with no `deepLink` beside it means the id did not match a
	// shape we could safely put into that book's URL, so no link was emitted.
	// Treat the id as a correlation key in that case, not as link material.
	SelectionID *json.RawMessage `json:"selectionId,omitempty"`
}

Outcome defines model for Outcome.

type PageOptions

type PageOptions struct {
	// PageSize is the rows per request: default 1000, at most the API's 5000.
	PageSize int
	// MaxPages stops after this many pages; 0 reads until a short page.
	MaxPages int
	// MaxRetries is the retries per page on 429 and 503, honouring Retry-After.
	// 0 means the default of 3; a negative value disables retries.
	MaxRetries int
}

PageOptions controls IterHistoryOdds and IterHistoryProps.

type PinnacleRealtimeSyncPayload

type PinnacleRealtimeSyncPayload struct {
	// Feed The sport's upstream feed state on every `pinnacle-realtime` and
	// `pinnacle-realtime-sync` frame: the same fields as REST `meta.feed`
	// (RealtimeFeedState). `stalled: true` means prices can be out of date while
	// frames keep arriving. Each field is `null` when unknown.
	Feed *RealtimeFrameFeed `json:"feed,omitempty"`

	// Removed Event ids to delete from your board, keyed by sport.
	Removed map[string][]string `json:"removed,omitempty"`
	Sport   *string             `json:"sport,omitempty"`

	// Stream The stream position a `pinnacle-realtime` or `pinnacle-realtime-sync` frame
	// carries for its one sport.
	Stream    *RealtimeClientStreamPosition `json:"stream,omitempty"`
	Timestamp *string                       `json:"timestamp,omitempty"`
}

PinnacleRealtimeSyncPayload Payload of the `pinnacle-realtime-sync` event: a stream position, removals, or both, for a sport that has no events to send. It never carries events. Sent on the same connections and for the same sports as `pinnacle-realtime`; track `stream` and delete the `removed` ids exactly as you do there. See RealtimeOddsPayload.

type PinnacleV2BookSubscription

type PinnacleV2BookSubscription struct {
	// Esports Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Esports *V2LeagueFilter `json:"esports,omitempty"`

	// Handball Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Handball *V2LeagueFilter `json:"handball,omitempty"`

	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Mma Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mma *V2LeagueFilter `json:"mma,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaab Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaab *V2LeagueFilter `json:"ncaab,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`

	// Wnba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Wnba *V2LeagueFilter `json:"wnba,omitempty"`
}

PinnacleV2BookSubscription Per-book subscription shape for Pinnacle v2 events.

Single-league sports (wnba, nba, mlb, nhl, ncaab, ncaaf, nfl) map 1:1 to a sport key - pass `'*'` to receive every matchup, or an array containing the same sport key (filters are tolerated for forward compatibility).

Multi-league sports (tennis, soccer, esports, handball, mma) use slugified Pinnacle league names as filter values (e.g. `'atp-singles'`).

type PitchingStats

type PitchingStats struct {
	EarnedRuns      *float64 `json:"earnedRuns,omitempty"`
	Era             *string  `json:"era,omitempty"`
	HitsAllowed     *float64 `json:"hitsAllowed,omitempty"`
	HomeRunsAllowed *float64 `json:"homeRunsAllowed,omitempty"`
	InningsPitched  *string  `json:"inningsPitched,omitempty"`
	PitchCount      *float64 `json:"pitchCount,omitempty"`
	PitchStrikes    *float64 `json:"pitchStrikes,omitempty"`
	RunsAllowed     *float64 `json:"runsAllowed,omitempty"`
	Strikeouts      *float64 `json:"strikeouts,omitempty"`
	WalksAllowed    *float64 `json:"walksAllowed,omitempty"`
}

PitchingStats defines model for PitchingStats.

type Play added in v0.9.0

type Play struct {
	AwayScore *float64 `json:"awayScore,omitempty"`

	// Clock Time left in the period as the venue shows it ("5:32"), or for soccer the match minute ("67", "45+2"); null when the source has none.
	Clock  *string          `json:"clock,omitempty"`
	Detail *json.RawMessage `json:"detail,omitempty"`

	// FirstSeenAt When the play was first read under this id; never moves. For the NHL and soccer (no wallclock) the nearest thing to an event time.
	FirstSeenAt *string `json:"firstSeenAt,omitempty"`

	// HomeScore Score after the play.
	HomeScore *float64 `json:"homeScore,omitempty"`

	// ID The source's play id, unique within the game.
	ID     *string  `json:"id,omitempty"`
	Period *float64 `json:"period,omitempty"`

	// Replaces The id of the play this one replaced on the same read (the NHL's shot that became this goal under a new id).
	Replaces    *string `json:"replaces,omitempty"`
	ScoringPlay *bool   `json:"scoringPlay,omitempty"`

	// SecondsRemaining Null for soccer, whose clock counts up.
	SecondsRemaining *float64 `json:"secondsRemaining,omitempty"`

	// Seq The source's own order within the game: ascending, not contiguous.
	Seq *float64 `json:"seq,omitempty"`

	// Team The side the play belongs to (the offense, the shooter's team, the team that took the penalty, the side credited with a goal); null for a neutral play.
	Team *string `json:"team,omitempty"`
	Text *string `json:"text,omitempty"`

	// Type The source's own type key, unchanged: "Rush", "Pass Reception"; "shot-on-goal", "faceoff"; "2pt/Jump Shot", "rebound/defensive"; soccer "Goal", "Yellow Card", "Substitution".
	Type *string `json:"type,omitempty"`

	// UpdatedAt When this version of the play was first seen: an amendment after first publication moves it.
	UpdatedAt *string `json:"updatedAt,omitempty"`

	// Wallclock When the play happened according to the source; null for the NHL and soccer, which publish none.
	Wallclock *string `json:"wallclock,omitempty"`

	// Withdrawn The source no longer lists the play (a goal overturned on review, a shot the NHL replaced with a goal): sent once, kept 10 minutes, then dropped.
	Withdrawn *bool `json:"withdrawn,omitempty"`
}

Play One play of an NBA, NHL, NFL, NCAAF or soccer game, as GET /{sport}/plays and the `plays-update` event carry it. Merge plays by `id`: a play can be amended after it is first published (a review, an assist added). For soccer a play is a match incident (goal, card, substitution), not every pass.

type PlayerAveragesParams

type PlayerAveragesParams struct {
	PlayerName string // required
	Opponent   string
}

PlayerAveragesParams selects GetPlayerAverages.

type PlayerAveragesResponse

type PlayerAveragesResponse struct {
	Data *struct {
		// Averages L5/L10/L20 rolling windows (computed by Owls Insight)
		Averages *struct {
			Last10 *ComputedAverages `json:"last10,omitempty"`
			Last20 *ComputedAverages `json:"last20,omitempty"`
			Last5  *ComputedAverages `json:"last5,omitempty"`
		} `json:"averages,omitempty"`
		GameLogs []GameLogEntry `json:"gameLogs,omitempty"`

		// Monthly Per-month averages from ESPN
		Monthly []struct {
			Averages map[string]string `json:"averages,omitempty"`
			Month    *string           `json:"month,omitempty"`
		} `json:"monthly,omitempty"`
		Opponent *string `json:"opponent,omitempty"`
		Player   *struct {
			Name     *string `json:"name,omitempty"`
			Position *string `json:"position,omitempty"`
			Team     *string `json:"team,omitempty"`
		} `json:"player,omitempty"`

		// SeasonAverages ESPN's pre-computed season averages (label → value, e.g. "PTS" → "17.6")
		SeasonAverages map[string]string `json:"seasonAverages,omitempty"`

		// SeasonTotals ESPN's pre-computed season totals (label → value, e.g. "PTS" → "704")
		SeasonTotals map[string]string `json:"seasonTotals,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PlayerAveragesResponse defines model for PlayerAveragesResponse.

type PlayerLineup

type PlayerLineup struct {
	Country   *string  `json:"country,omitempty"`
	Jersey    *float64 `json:"jersey,omitempty"`
	Name      *string  `json:"name,omitempty"`
	PlayerID  *string  `json:"playerId,omitempty"`
	PlayerURL *string  `json:"playerUrl,omitempty"`
	Rating    *float64 `json:"rating,omitempty"`
	Starter   *bool    `json:"starter,omitempty"`
}

PlayerLineup defines model for PlayerLineup.

type PlayerProp

type PlayerProp struct {
	AlternateLines []PropAlternateLine `json:"alternateLines,omitempty"`
	Category       *string             `json:"category,omitempty"`

	// CategoryRaw The raw upstream stat/unit string, present ONLY when `category` is `'unknown'`.
	// Lets you identify a market we have not mapped yet without re-deriving it from
	// the sportsbook, and tell an unmapped market apart from a genuinely
	// uncategorisable one. Added Jul 2026.
	CategoryRaw *string `json:"categoryRaw,omitempty"`

	// ComboStat Multi-player markets: the stat, e.g. `shots_on_goal`, `points`, `receiving_yards`.
	ComboStat *string `json:"comboStat,omitempty"`

	// ComboType Multi-player markets (`category: 'combos'`, Oct 2026): the market kind. `combined` prices the
	// players' total, `either` prices "either player reaches the line"; `other` for anything else.
	// The market itself is named in `playerName` as `"<players> - <market>"` (e.g.
	// `"Charlie McAvoy & JJ Peterka - Either Player Shots on Goal"`), so two markets for one pair never
	// share a name. A same-name twin in one game carries a stable `" (market <id>)"` suffix.
	ComboType  *string `json:"comboType,omitempty"`
	LastUpdate *string `json:"lastUpdate,omitempty"`

	// Line Threshold value as published by the upstream sportsbook. Interpretation
	// depends on `marketStructure`. As of the DraftKings mapper passthrough
	// change (May 2026), DK's `milestoneValue` is no longer rebased to the
	// "industry-standard 0.5" - it is passed through verbatim. Consumers
	// that previously assumed `line === 0.5` for "1+" markets should read
	// `marketStructure` and treat `milestone` markets as "Nth+ where N = line".
	Line *float64 `json:"line,omitempty"`

	// Liquidity Exchange liquidity. A UNION across books - which fields are present depends
	// on which book produced the prop:
	//
	// * Kalshi - `volume`, `volume24h`, `openInterest`, `bidSize`, `askSize`
	//   (contracts).
	// * Polymarket - `amount`, `volume` (USD).
	// * Novig - `volume` plus the two order books below.
	//
	// ⚠️ Novig's `overBook`/`underBook` levels are NOT shaped like
	// `Outcome.liquidity` on the main-markets feed. Here `price` is a raw
	// implied probability (0–1) and size is `qty` (CENTS) with a `qtyDollars`
	// convenience already computed. There, `price` is American odds and size is
	// `size` (also cents, no companion field). Same venue, two surfaces, two
	// shapes - the props side is deliberate raw pass-through.
	Liquidity *struct {
		Amount       *float64 `json:"amount,omitempty"`
		AskSize      *float64 `json:"askSize,omitempty"`
		BidSize      *float64 `json:"bidSize,omitempty"`
		OpenInterest *float64 `json:"openInterest,omitempty"`

		// OverBook One side of a Novig player-prop order book.
		OverBook *PropOrderBook `json:"overBook,omitempty"`

		// UnderBook One side of a Novig player-prop order book.
		UnderBook *PropOrderBook `json:"underBook,omitempty"`
		Volume    *float64       `json:"volume,omitempty"`
		Volume24H *float64       `json:"volume24h,omitempty"`
	} `json:"liquidity,omitempty"`

	// MarketStructure Market structure as published by the upstream sportsbook. Tells the consumer
	// how to interpret `line`, `overPrice`, and `underPrice` together. Populated
	// from upstream signals - not inferred client-side.
	//
	// - `over_under`: Both Over and Under selections published with a continuous
	//   line (e.g. strikeouts 5.5). Standard interpretation.
	// - `milestone`: Upstream provides an explicit threshold count (e.g. DK's
	//   `milestoneValue: 1` = "1+ of category"). `line` is the threshold value
	//   verbatim. `underPrice` is always null.
	// - `single_sided`: Only one side published (typically Over for rare-event
	//   props). The missing side is `null`, not inferred.
	// - `pick_one`: Multi-selection market where each selection is one
	//   participant's Yes price (e.g. anytime touchdown scorer).
	//
	// ABSENT DOES NOT MEAN `over_under`. Treat a missing value as UNKNOWN; see
	// PlayerProp.marketStructure for why defaulting it is a settlement bug.
	MarketStructure *MarketStructure `json:"marketStructure,omitempty"`

	// OverPrice American odds for the over.
	//
	// On EXCHANGE books (currently `kalshi`) this is the venue's own ASK - the price a
	// buyer actually pays - as of Sep 2026. It previously carried the bid/ask MIDPOINT,
	// which nobody can trade at: measured across 40 live kalshi markets the published
	// number beat the takeable one on 40 of 40, median 3.5c and worst case 48c quoted
	// against 94c payable, so cross-book comparisons showed edges that had no
	// counterparty. Read overPriceBasis / underPriceBasis to see which
	// side each price came from.
	OverPrice *float64 `json:"overPrice,omitempty"`

	// OverPriceBasis Which side of the book overPrice came from. Set only by exchange books,
	// where the distinction is real:
	//
	// * `takeable` - the venue's ask: transactable now.
	// * `midpoint` - no ask was resting, so this is the bid/ask midpoint. An estimate of
	//   fair value, NOT a quote.
	//
	// Absent on traditional sportsbooks, whose posted price is takeable by construction.
	OverPriceBasis *string `json:"overPriceBasis,omitempty"`

	// OverPriceMid Bid/ask MIDPOINT as American odds, for books publishing a two-sided quote. The
	// better input to a no-vig fair line; overPrice is what you transact at.
	// Equal to `overPrice` exactly when overPriceBasis is `midpoint`.
	OverPriceMid *float64 `json:"overPriceMid,omitempty"`
	PlayerName   *string  `json:"playerName,omitempty"`

	// Players Multi-player markets: the players the market prices, in the book's order.
	Players []string `json:"players,omitempty"`
	PropID  *string  `json:"propId,omitempty"`

	// RawMarketName Multi-player markets: the book's own market name, verbatim.
	RawMarketName *string  `json:"rawMarketName,omitempty"`
	Team          *string  `json:"team,omitempty"`
	UnderPrice    *float64 `json:"underPrice,omitempty"`

	// UnderPriceBasis Under-side counterpart of overPriceBasis. SEPARATE on purpose: a book can
	// hold a resting ask on one side and not the other, so a single flag derived from
	// the over side would label a midpoint under-price `takeable` - a midpoint passed
	// off as a quote, under the one field meant to prevent that.
	UnderPriceBasis *string `json:"underPriceBasis,omitempty"`

	// UnderPriceMid Under-side counterpart of overPriceMid.
	UnderPriceMid *float64 `json:"underPriceMid,omitempty"`
}

PlayerProp defines model for PlayerProp.

type Plays added in v0.9.0

type Plays struct {
	// Game is set when GetPlays was called with an event id.
	Game *PlaysResponse
	// Games is set when GetPlays was called with an empty event id.
	Games *PlaysGamesResponse
}

Plays is the result of GetPlays: Game for one game's play-by-play, Games for the list of games that have it.

type PlaysGame added in v0.9.0

type PlaysGame struct {
	AsOf      *string  `json:"asOf,omitempty"`
	Away      *string  `json:"away,omitempty"`
	EventID   *string  `json:"eventId,omitempty"`
	Home      *string  `json:"home,omitempty"`
	PlayCount *float64 `json:"playCount,omitempty"`
	State     *string  `json:"state,omitempty"`
}

PlaysGame One game in the list GET /{sport}/plays returns without an eventId.

type PlaysGamesResponse added in v0.9.0

type PlaysGamesResponse struct {
	Count *float64    `json:"count,omitempty"`
	Games []PlaysGame `json:"games,omitempty"`
	Meta  *struct {
		Timestamp *string `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`

	// Sport Sports served by GET /{sport}/plays and the `plays-update` event.
	Sport   *PlaysSport `json:"sport,omitempty"`
	Success *bool       `json:"success,omitempty"`
}

PlaysGamesResponse Response from GET /{sport}/plays without an eventId: the games that have play-by-play.

type PlaysParams added in v0.9.0

type PlaysParams struct {
	// Since returns the plays whose Seq is greater than this, plus every play
	// amended in the last 2 minutes whatever its Seq: pass the LastSeq of the
	// previous response (Since: res.Game.LastSeq). Nil sends nothing (every play);
	// 0 is sent. The API takes a non-negative integer and answers 400 to anything
	// else.
	Since *float64
	// Limit returns only the last Limit plays (1 to 5000), after Since. 0 sends
	// nothing.
	Limit int
}

PlaysParams narrows GetPlays for one game. Without an event id GetPlays refuses a Since or Limit.

type PlaysResponse added in v0.9.0

type PlaysResponse struct {
	Away *string `json:"away,omitempty"`

	// Count Plays in the game (before `since` and `limit`).
	Count *float64 `json:"count,omitempty"`

	// EventID The event id as `getScores()` names the game.
	EventID *string `json:"eventId,omitempty"`
	Home    *string `json:"home,omitempty"`

	// LastSeq The last play's seq; pass it as `since` to poll for new plays. Null when the game has no plays yet.
	LastSeq *float64 `json:"lastSeq,omitempty"`
	Meta    *struct {
		AgeSeconds *float64 `json:"ageSeconds,omitempty"`

		// AmendedWindowSeconds With `since`: plays amended within this many seconds are included whatever their seq.
		AmendedWindowSeconds *float64 `json:"amendedWindowSeconds,omitempty"`

		// AsOf When the source was read.
		AsOf     *string  `json:"asOf,omitempty"`
		Returned *float64 `json:"returned,omitempty"`
		Since    *float64 `json:"since,omitempty"`
		Source   *string  `json:"source,omitempty"`

		// SourceAgeSeconds The source's own edge-cache age at that read (NHL about 19 s, ESPN about 7 s); null when it sends none (NBA, soccer).
		SourceAgeSeconds *float64 `json:"sourceAgeSeconds,omitempty"`
		Timestamp        *string  `json:"timestamp,omitempty"`
		Truncated        *bool    `json:"truncated,omitempty"`
	} `json:"meta,omitempty"`
	Plays []Play `json:"plays,omitempty"`

	// Sport Sports served by GET /{sport}/plays and the `plays-update` event.
	Sport *PlaysSport `json:"sport,omitempty"`
	State *string     `json:"state,omitempty"`

	// Status `stale` when a game in play has not been read for 30 seconds (soccer: 60).
	Status  *string `json:"status,omitempty"`
	Success *bool   `json:"success,omitempty"`
}

PlaysResponse Response from GET /{sport}/plays?eventId=.

type PlaysSport added in v0.9.0

type PlaysSport = string

PlaysSport Sports served by GET /{sport}/plays and the `plays-update` event.

type PlaysUpdate added in v0.9.0

type PlaysUpdate struct {
	Events []PlaysUpdateEvent `json:"events,omitempty"`

	// Sport Sports served by GET /{sport}/plays and the `plays-update` event.
	Sport *PlaysSport `json:"sport,omitempty"`

	// Timestamp ISO 8601 time the message was built.
	Timestamp *string `json:"timestamp,omitempty"`
	Type      *string `json:"type,omitempty"`
}

PlaysUpdate Payload of the `plays-update` WebSocket event: play-by-play for ONE sport.

Opt in with `subscribe({ ..., plays: true })` (nba, nhl, nfl, ncaaf and soccer) or `plays: ['nfl', 'nba', 'soccer']`; no `plays` field, no messages. Same access as `scores-update`. There is no full message: call `getPlays(sport, eventId)` once to start, then apply each delta by merging `plays` by `id`.

type PlaysUpdateEvent added in v0.9.0

type PlaysUpdateEvent struct {
	Away *string `json:"away,omitempty"`

	// Count Plays in the game after this message.
	Count   *float64 `json:"count,omitempty"`
	EventID *string  `json:"eventId,omitempty"`
	Home    *string  `json:"home,omitempty"`
	Plays   []Play   `json:"plays,omitempty"`

	// State `post` once more when the game ends.
	State *string `json:"state,omitempty"`
}

PlaysUpdateEvent One game on the `plays-update` event: the plays new or amended since the previous message.

type PolymarketV2BookSubscription

type PolymarketV2BookSubscription struct {
	// Cs2 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Cs2 *V2LeagueFilter `json:"cs2,omitempty"`

	// Dota2 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Dota2 *V2LeagueFilter `json:"dota2,omitempty"`

	// EventTypes Which upstream message types to receive. Omit it for the default set: `book`,
	// `best_bid_ask`, `last_trade_price` and `tick_size_change`. List types to narrow
	// or extend it (`market_resolved` is only sent when you ask for it, by name or with
	// `'*'`), or pass `'*'` for every type available to your key. Omitted and `'*'` also
	// deliver upstream message types not named in PolymarketV2EventType; a list
	// delivers exactly the types it names. The `subscribed` acknowledgement reports
	// dropped values and any type this connection will not receive in
	// `v2OptionsAdvisory`.
	//
	// These options apply to the polymarket sports listed next to them.
	// `updateSubscription` replaces the whole `v2` object, so send the sport keys again
	// together with the options.
	EventTypes *json.RawMessage `json:"eventTypes,omitempty"`

	// Lol Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Lol *V2LeagueFilter `json:"lol,omitempty"`

	// MarketTypes Only markets of these types (the `sportsMarketType` values on the REST
	// `data[*].markets[]`, for example `moneyline`). `'*'` or omitted means every type.
	// Values are not checked at subscribe time: one that no market carries never
	// matches, and the acknowledgement says so in `v2OptionsAdvisory`.
	MarketTypes *json.RawMessage `json:"marketTypes,omitempty"`

	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Nba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nba *V2LeagueFilter `json:"nba,omitempty"`

	// Ncaaf Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Ncaaf *V2LeagueFilter `json:"ncaaf,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Overwatch Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Overwatch *V2LeagueFilter `json:"overwatch,omitempty"`

	// Pubg Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Pubg *V2LeagueFilter `json:"pubg,omitempty"`

	// Rainbow6 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Rainbow6 *V2LeagueFilter `json:"rainbow6,omitempty"`

	// Rocketleague Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Rocketleague *V2LeagueFilter `json:"rocketleague,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`

	// Valorant Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Valorant *V2LeagueFilter `json:"valorant,omitempty"`

	// Wnba Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Wnba *V2LeagueFilter `json:"wnba,omitempty"`
}

PolymarketV2BookSubscription Per-book subscription shape for Polymarket v2 events.

Each Polymarket sport is segmented by Polymarket tag slug (e.g. `epl`, `ucl`, `nba`, `mlb`). Pass `'*'` for every tag, or an array of tag slugs to filter.

type PropAlternateLine

type PropAlternateLine struct {
	Line *float64 `json:"line,omitempty"`
	Odds *float64 `json:"odds,omitempty"`
}

PropAlternateLine defines model for PropAlternateLine.

type PropOrderBook

type PropOrderBook struct {
	// Asks Present but empty in practice - Novig ladders are bids-only.
	Asks []PropOrderBookLevel `json:"asks,omitempty"`

	// Bids Best bid first.
	Bids []PropOrderBookLevel `json:"bids,omitempty"`
}

PropOrderBook One side of a Novig player-prop order book.

type PropOrderBookLevel

type PropOrderBookLevel struct {
	Currency *string `json:"currency,omitempty"`
	ID       *string `json:"id,omitempty"`
	Inverted *bool   `json:"inverted,omitempty"`
	IsBid    *bool   `json:"isBid,omitempty"`
	MarketID *string `json:"marketId,omitempty"`

	// OriginalQty Originally posted size, before partial fills. Native unit (cents).
	OriginalQty *float64 `json:"originalQty,omitempty"`

	// OriginalQtyDollars Convenience: `originalQty / 100`.
	OriginalQtyDollars *float64 `json:"originalQtyDollars,omitempty"`
	OutcomeID          *string  `json:"outcomeId,omitempty"`

	// Price Implied probability of the outcome at this level (0–1).
	Price *float64 `json:"price,omitempty"`

	// Qty Size in the exchange's native unit - Novig posts CENTS.
	Qty *float64 `json:"qty,omitempty"`

	// QtyDollars Convenience: `qty / 100`, rounded to 2 decimals.
	QtyDollars *float64 `json:"qtyDollars,omitempty"`
	Status     *string  `json:"status,omitempty"`

	// Tif Time-in-force, e.g. `GTC`, `GTT`.
	Tif       *string `json:"tif,omitempty"`
	Timestamp *string `json:"timestamp,omitempty"`
}

PropOrderBookLevel One resting order in a Novig player-prop book, passed through verbatim.

Unlike `Outcome.liquidity` levels on the main-markets feed, `price` is an implied PROBABILITY (0–1), not American odds.

type PropResultEntry

type PropResultEntry struct {
	// Actual The measured stat. Null unless `status` is `ok`.
	Actual   *float64 `json:"actual,omitempty"`
	Category *string  `json:"category,omitempty"`

	// Final Box scores are revised for a short window after a game. `false` means the row is
	// still provisional and may change.
	Final    *bool            `json:"final,omitempty"`
	GradedAt *string          `json:"gradedAt,omitempty"`
	Lines    []PropResultLine `json:"lines,omitempty"`

	// PlayerID Stable provider player id. Two players CAN share a display name.
	PlayerID   *string `json:"playerId,omitempty"`
	PlayerName *string `json:"playerName,omitempty"`
	SourceRef  *string `json:"sourceRef,omitempty"`
	StatSource *string `json:"statSource,omitempty"`
	Status     *string `json:"status,omitempty"`

	// UngradableReason Why this entry could not be graded at all, independent of any book line.
	UngradableReason *PropUngradableReason `json:"ungradableReason,omitempty"`
}

PropResultEntry defines model for PropResultEntry.

type PropResultLine

type PropResultLine struct {
	Book *string `json:"book,omitempty"`

	// CapturePhase `commence` - captured on the pre-game to in-play transition. The real closing
	// line, and the only phase valid for closing-line value.
	//
	// `archive` - captured at game end because the transition was missed. For a game
	// that ran live this is the LAST IN-PLAY price, a different thing entirely: an
	// in-play total in the eighth inning is nearly settled. Never pool the two.
	CapturePhase *string  `json:"capturePhase,omitempty"`
	Line         *float64 `json:"line,omitempty"`

	// MarketStructure How the book structured this market, when it published one. NULL means UNKNOWN.
	MarketStructure  *MarketStructure      `json:"marketStructure,omitempty"`
	OverPrice        *float64              `json:"overPrice,omitempty"`
	UnderPrice       *float64              `json:"underPrice,omitempty"`
	UngradableReason *PropUngradableReason `json:"ungradableReason,omitempty"`
	Verdict          *PropVerdict          `json:"verdict,omitempty"`
}

PropResultLine defines model for PropResultLine.

type PropResults

type PropResults struct {
	// Game is set when the request named a GameID.
	Game *PropResultsResponse
	// Day is set when the request named only a Date.
	Day *PropResultsDayResponse
}

PropResults is the result of GetPropResults: Game for one game's graded board, Day for the list of a day's graded games.

type PropResultsDayGame

type PropResultsDayGame struct {
	Entries       *float64 `json:"entries,omitempty"`
	FinalEntries  *float64 `json:"finalEntries,omitempty"`
	FirstGradedAt *string  `json:"firstGradedAt,omitempty"`
	GameID        *string  `json:"gameId,omitempty"`
	LastGradedAt  *string  `json:"lastGradedAt,omitempty"`
	Players       *float64 `json:"players,omitempty"`
}

PropResultsDayGame One graded game listed by `getPropResults({ date })`.

type PropResultsDayResponse

type PropResultsDayResponse struct {
	Data []PropResultsDayGame `json:"data,omitempty"`
	Meta *struct {
		Date          *string  `json:"date,omitempty"`
		GamesReturned *float64 `json:"gamesReturned,omitempty"`

		// GamesTruncated True when the day's list hit its cap (NCAAB Saturdays can).
		GamesTruncated *bool   `json:"gamesTruncated,omitempty"`
		Mode           *string `json:"mode,omitempty"`
		Note           *string `json:"note,omitempty"`
		Sport          *string `json:"sport,omitempty"`
		Timestamp      *string `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PropResultsDayResponse defines model for PropResultsDayResponse.

type PropResultsParams

type PropResultsParams struct {
	GameID        string
	Date          string
	Player        string
	Category      string
	ConfirmedOnly bool
}

PropResultsParams selects GetPropResults: one game (GameID) or, with only Date (YYYY-MM-DD), the graded games of a day.

type PropResultsResponse

type PropResultsResponse struct {
	Data []PropResultEntry `json:"data,omitempty"`
	Meta *struct {
		// AbsentCategoryGroups Line groups for a player who IS in the box score but has no row for that
		// category. Expected for football - a quoted receiver with zero catches is in no
		// receiving group, so nothing is graded (never a 0, never a void). Kept apart from
		// `unmatchedLineGroups` so a real join failure is not buried in this benign count.
		AbsentCategoryGroups *float64 `json:"absentCategoryGroups,omitempty"`
		ActualsTruncated     *bool    `json:"actualsTruncated,omitempty"`

		// AmbiguousPlayers Players refused as `ambiguous_player`, with every spelling seen and which side
		// of the join proved it. `from: "lines"` with spellings differing only by a suffix
		// is the disclosed trade-off: from names alone that could be two people.
		AmbiguousPlayers []struct {
			From      *string  `json:"from,omitempty"`
			Key       *string  `json:"key,omitempty"`
			Spellings []string `json:"spellings,omitempty"`
		} `json:"ambiguousPlayers,omitempty"`
		CapturePhases []string `json:"capturePhases,omitempty"`

		// Checks What was actually evaluated to produce this response.
		Checks          []string `json:"checks,omitempty"`
		ConfirmedOnly   *bool    `json:"confirmedOnly,omitempty"`
		EntriesReturned *float64 `json:"entriesReturned,omitempty"`

		// EntryStatuses Status counts, ONE PER PLAYER-CATEGORY ENTRY - including entries no book quoted,
		// which contribute nothing to `lineOutcomes`. Use these for "how many players did
		// not play", never the line counts.
		EntryStatuses map[string]float64 `json:"entryStatuses,omitempty"`
		GameID        *string            `json:"gameId,omitempty"`

		// LineOutcomes Verdict counts, ONE PER BOOK LINE. `over + under + push` is a LINE count, so a
		// rate built from it is weighted by how many books quoted each player.
		LineOutcomes   map[string]float64 `json:"lineOutcomes,omitempty"`
		LinesGraded    *float64           `json:"linesGraded,omitempty"`
		LinesTruncated *bool              `json:"linesTruncated,omitempty"`
		Message        *string            `json:"message,omitempty"`
		Mode           *string            `json:"mode,omitempty"`

		// NameVariantJoins Line groups joined to a box-score player written with another form of his first
		// name ("Alexandre Sarr" in the books, "Alex Sarr" in the box score): joined, and
		// disclosed like `suffixGaps`. NBA only; [] elsewhere.
		NameVariantJoins []struct {
			Actual *string  `json:"actual,omitempty"`
			Lines  []string `json:"lines,omitempty"`
		} `json:"nameVariantJoins,omitempty"`
		PlayersReturned *float64 `json:"playersReturned,omitempty"`

		// Provisional Entries returned that are still provisional. OVERLAPS `entryStatuses`.
		Provisional *float64 `json:"provisional,omitempty"`
		Sport       *string  `json:"sport,omitempty"`

		// SuffixGaps Joins that bridged a suffix - box score says "X Jr.", books say "X". Joined as
		// one player, usually rightly; disclosed because it is also the shape of the one
		// collision no name rule can see.
		SuffixGaps []struct {
			Actual *string  `json:"actual,omitempty"`
			Key    *string  `json:"key,omitempty"`
			Lines  []string `json:"lines,omitempty"`
		} `json:"suffixGaps,omitempty"`
		Timestamp *string `json:"timestamp,omitempty"`

		// UnmatchedLineExamples A few of those, named, so the mismatch can be chased.
		UnmatchedLineExamples []string `json:"unmatchedLineExamples,omitempty"`

		// UnmatchedLineGroups Closing-line groups no actual matched - a book quoted a player the box score did
		// not resolve under that spelling. Non-zero means the name join is losing data,
		// which otherwise looks exactly like "no book quoted this player".
		UnmatchedLineGroups *float64 `json:"unmatchedLineGroups,omitempty"`

		// UnsupportedCategoryExamples A few of those, named ("<player or roster> <category>"). NBA only; [] elsewhere.
		UnsupportedCategoryExamples []string `json:"unsupportedCategoryExamples,omitempty"`

		// UnsupportedCategoryGroups Line groups in a category no box score grades (multi-player `combos`, award and
		// team markets): never a join failure, so not counted in `unmatchedLineGroups`.
		// Filled for NBA only; 0 for other sports, where those groups stay in the counters
		// above. Absent from responses of API versions before it was added.
		UnsupportedCategoryGroups *float64 `json:"unsupportedCategoryGroups,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PropResultsResponse defines model for PropResultsResponse.

type PropTrendGame

type PropTrendGame struct {
	Actual *float64 `json:"actual,omitempty"`

	// Closing Present with `line: "closing"`: every rung the book had captured at game start
	// (commence captures only - an archive row is an in-play line, never a close). The
	// game-level `verdict` comes from the rung with `isMain: true`; alternates are the
	// ladder and never decide. With no main rung the game is `no_closing_line`; with
	// more than one (rare) it is `multiple_closing_lines`.
	Closing []struct {
		IsMain           *bool                 `json:"isMain,omitempty"`
		Line             *float64              `json:"line,omitempty"`
		UngradableReason *PropUngradableReason `json:"ungradableReason,omitempty"`
		Verdict          *PropVerdict          `json:"verdict,omitempty"`
	} `json:"closing,omitempty"`
	EventID          *string               `json:"eventId,omitempty"`
	Final            *bool                 `json:"final,omitempty"`
	GameDate         *string               `json:"gameDate,omitempty"`
	Status           *string               `json:"status,omitempty"`
	UngradableReason *PropUngradableReason `json:"ungradableReason,omitempty"`
	Verdict          *PropVerdict          `json:"verdict,omitempty"`
}

PropTrendGame defines model for PropTrendGame.

type PropTrendsParams

type PropTrendsParams struct {
	Player   string // required
	Category string // required
	// Line is a threshold such as "0.5", or "closing" to grade each game against
	// the line Book had captured at its start. Without it no hit rate is computed.
	Line string
	// Book is required with Line "closing".
	Book string
	// Window is the number of games, 1-50 (default 10).
	Window        int
	ConfirmedOnly bool
}

PropTrendsParams selects GetPropTrends.

type PropTrendsResponse

type PropTrendsResponse struct {
	Data *struct {
		Average  *float64 `json:"average,omitempty"`
		Book     *string  `json:"book,omitempty"`
		Category *string  `json:"category,omitempty"`

		// Decided The hit-rate denominator. Excludes pushes AND unresolved games.
		Decided       *float64         `json:"decided,omitempty"`
		Games         []PropTrendGame  `json:"games,omitempty"`
		GamesReturned *float64         `json:"gamesReturned,omitempty"`
		Line          *json.RawMessage `json:"line,omitempty"`

		// OverRate overs / decided. NULL when nothing resolved - zero decided games is not a 0% hit
		// rate, so do not render a null as "0%".
		OverRate *float64 `json:"overRate,omitempty"`
		Overs    *float64 `json:"overs,omitempty"`
		Player   *string  `json:"player,omitempty"`
		Pushes   *float64 `json:"pushes,omitempty"`

		// Streak Consecutive run from the most recent game. Stops at the first unresolved game.
		Streak *struct {
			Length *float64 `json:"length,omitempty"`
			Side   *string  `json:"side,omitempty"`
		} `json:"streak,omitempty"`
		Unders *float64 `json:"unders,omitempty"`
	} `json:"data,omitempty"`
	Meta *struct {
		Checks []string `json:"checks,omitempty"`

		// ClosingPhase With `line: "closing"`: always `"commence"` - archive captures are excluded.
		ClosingPhase *string `json:"closingPhase,omitempty"`

		// ClosingTruncated With `line: "closing"`: true if the rung fetch hit its cap; games past the cut read as no_closing_line, so re-query with a smaller window.
		ClosingTruncated *bool `json:"closingTruncated,omitempty"`
		ConfirmedOnly    *bool `json:"confirmedOnly,omitempty"`

		// GamesAvailable Rows the lookback could see. A FLOOR when `gamesAvailableCapped` is true.
		GamesAvailable       *float64 `json:"gamesAvailable,omitempty"`
		GamesAvailableCapped *bool    `json:"gamesAvailableCapped,omitempty"`

		// MatchedNames The stored spelling(s) the returned games came from. MORE THAN ONE means the name
		// matched two distinct spellings that normalize identically - check them before
		// trusting the aggregate, because it may pool two different people.
		MatchedNames []string `json:"matchedNames,omitempty"`
		Note         *string  `json:"note,omitempty"`

		// Provisional Games in the lookback still marked provisional.
		Provisional *float64 `json:"provisional,omitempty"`
		Sport       *string  `json:"sport,omitempty"`
		Timestamp   *string  `json:"timestamp,omitempty"`
		Ungradable  *float64 `json:"ungradable,omitempty"`
		Window      *float64 `json:"window,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PropTrendsResponse defines model for PropTrendsResponse.

type PropUngradableReason

type PropUngradableReason = string

PropUngradableReason Why a prop could not be graded. Never a verdict - always a stated reason.

type PropVerdict

type PropVerdict = string

PropVerdict defines model for PropVerdict.

type PropbuilderV2BookSubscription added in v0.8.0

type PropbuilderV2BookSubscription struct {
	Mlb   *json.RawMessage `json:"mlb,omitempty"`
	Nba   *json.RawMessage `json:"nba,omitempty"`
	Ncaaf *json.RawMessage `json:"ncaaf,omitempty"`
	Nfl   *json.RawMessage `json:"nfl,omitempty"`
	Nhl   *json.RawMessage `json:"nhl,omitempty"`
	Wnba  *json.RawMessage `json:"wnba,omitempty"`
}

PropbuilderV2BookSubscription Per-sport filter for `v2: { propbuilder: {...} }`. Every sport is one league: pass `'*'`.

type ProphetXCompetitor

type ProphetXCompetitor struct {
	Abbreviation *string  `json:"abbreviation,omitempty"`
	DisplayName  *string  `json:"displayName,omitempty"`
	ID           *float64 `json:"id,omitempty"`
	Name         *string  `json:"name,omitempty"`

	// Seq 0 = home, 1 = away
	Seq    *float64 `json:"seq,omitempty"`
	Winner *bool    `json:"winner,omitempty"`
}

ProphetXCompetitor Competitor inside a ProphetX event. Stable fields - the server-side publisher always emits these five keys for every competitor.

`seq` indicates home/away: 0 = home, 1 = away.

type ProphetXDeltaPayload

type ProphetXDeltaPayload struct {
	Events []struct {
		Competitors []ProphetXCompetitor `json:"competitors,omitempty"`
		EventID     *float64             `json:"eventId,omitempty"`

		// Markets Changed markets only, keyed by `"{market_id}:{line}"`. See ProphetXMarket.
		Markets   map[string]ProphetXMarket `json:"markets,omitempty"`
		Name      *string                   `json:"name,omitempty"`
		Scheduled *string                   `json:"scheduled,omitempty"`
		Sport     *string                   `json:"sport,omitempty"`
		SportID   *float64                  `json:"sportId,omitempty"`

		// Status ProphetX status string (e.g. `not_started`, `live`, `ended`, `closed`).
		Status     *string `json:"status,omitempty"`
		Tournament *string `json:"tournament,omitempty"`
	} `json:"events,omitempty"`

	// Kind `game` = main game lines (moneyline/spread/total). `prop` = player/team props.
	Kind *ProphetXKind `json:"kind,omitempty"`

	// Sport Raw ProphetX sport name - e.g. `Basketball`, `Ice Hockey`, `Golf`.
	Sport *string `json:"sport,omitempty"`

	// SportSlug Valid ProphetX sport slugs accepted by REST filter and WS subscription.
	SportSlug *ProphetXSportSlug `json:"sportSlug,omitempty"`
	Timestamp *string            `json:"timestamp,omitempty"`
	Type      *string            `json:"type,omitempty"`
}

ProphetXDeltaPayload Payload for the WebSocket `prophetx-update` event.

Each delta contains ONLY the events (and within them, only the markets) that changed since the previous publish. Clients should accumulate deltas into their own state to maintain a full snapshot - or re-fetch the REST endpoint for the current full state.

type ProphetXEventData

type ProphetXEventData struct {
	// Event Raw ProphetX event metadata. The stable top-level fields are typed
	// (id, name, sport, tournament, competitors, etc.) while ProphetX's other
	// extension fields pass through via the index signature. Matches the shape
	// of `ProphetXEvent` in the nba-odds-app source of truth.
	Event *ProphetXRawEvent `json:"event,omitempty"`

	// LastUpdate ISO timestamp of the last ingest for this event.
	LastUpdate *string `json:"lastUpdate,omitempty"`

	// Markets Keyed by `"{market_id}:{line}"`. See ProphetXMarket for the shape.
	Markets map[string]ProphetXMarket `json:"markets,omitempty"`
}

ProphetXEventData One event inside a ProphetX REST response.

The `event` field uses the stable `ProphetXRawEvent` shape (id, name, sport, tournament, competitors, scheduled, status) with an index signature for any extra ProphetX fields. The `markets` field is a keyed map of raw ProphetX market payloads, typed as ProphetXMarket: the fields every entry carries are required, the ones that depend on which envelope ProphetX sent are optional, and everything else passes through the index signature.

type ProphetXKind

type ProphetXKind = string

ProphetXKind `game` = main game lines (moneyline/spread/total). `prop` = player/team props.

type ProphetXMarket

type ProphetXMarket struct {
	CategoryID *float64 `json:"categoryID,omitempty"`

	// CategoryName e.g. "Game Lines", "Runs".
	CategoryName *string `json:"categoryName,omitempty"`

	// FavouriteMarketLine ProphetX's market-favourite line. FREQUENTLY NOT THIS KEY'S LINE: measured
	// 2026-09-02, 531 of 1,321 entries carrying it quoted a different line than
	// the key (same id, different `line` and prices). Never read prices from it;
	// the book for this key is `selections` / `marketLines[0]` only.
	FavouriteMarketLine *ProphetXMarketLine `json:"favouriteMarketLine,omitempty"`
	HighRisk            *bool               `json:"highRisk,omitempty"`
	ID                  *float64            `json:"id,omitempty"`

	// Line The line this entry is keyed on; 0 for moneylines.
	Line        *json.RawMessage     `json:"line,omitempty"`
	MarketLines []ProphetXMarketLine `json:"marketLines,omitempty"`
	MarketID    *float64             `json:"market_id,omitempty"`

	// Name e.g. "Run Line", "Cam Smith Total Runs".
	Name *string `json:"name,omitempty"`

	// Outcomes Absent on delta-shaped entries, where the labels sit under
	// `marketLines[0].outcomes` instead - prophetxOutcomes resolves it.
	Outcomes []ProphetXOutcome `json:"outcomes,omitempty"`
	RefIds   []string          `json:"ref_ids,omitempty"`

	// Selections Sides, each levels-best-first or null. NULL AT THE TOP LEVEL on
	// delta-shaped spread/total entries - read `marketLines[0].selections`
	// then, or call prophetxBook.
	Selections      []*[]ProphetXSelectionLevel `json:"selections,omitempty"`
	SportEventID    *float64                    `json:"sportEventId,omitempty"`
	SportEventIDAlt *float64                    `json:"sport_event_id,omitempty"`
	Status          *string                     `json:"status,omitempty"`

	// SubType e.g. `spread`, `total`, `player_total_runs`.
	SubType *string `json:"subType,omitempty"`

	// TotalStake ProphetX market-level aggregate (catalog-shaped entries). Not executable liquidity.
	TotalStake *float64 `json:"totalStake,omitempty"`

	// TradedValue ProphetX market-level aggregate (delta-shaped entries). Not executable liquidity.
	TradedValue *float64 `json:"traded_value,omitempty"`

	// Type `moneyline` | `spread` | `total`, plus ProphetX's own extra values.
	Type *string `json:"type,omitempty"`
}

ProphetXMarket One `markets` entry: ProphetX's own market payload, verbatim.

type ProphetXMarketLine

type ProphetXMarketLine struct {
	Favourite *bool    `json:"favourite,omitempty"`
	HighRisk  *bool    `json:"highRisk,omitempty"`
	ID        *float64 `json:"id,omitempty"`

	// Name e.g. "Fixed home -1.5", "Fixed total 8.5".
	Name     *string           `json:"name,omitempty"`
	Outcomes []ProphetXOutcome `json:"outcomes,omitempty"`

	// Selections Sides, each an array of levels (best first) or null when empty.
	Selections []*[]ProphetXSelectionLevel `json:"selections,omitempty"`
	Type       *string                     `json:"type,omitempty"`
}

ProphetXMarketLine One line of a spread/total market as ProphetX groups it.

type ProphetXOddsMeta

type ProphetXOddsMeta struct {
	Available *bool   `json:"available,omitempty"`
	Bookmaker *string `json:"bookmaker,omitempty"`

	// Exchange Always `true` for ProphetX - indicates this is a betting exchange, not a traditional book.
	Exchange  *bool `json:"exchange,omitempty"`
	Freshness *struct {
		AgeSeconds *float64 `json:"ageSeconds,omitempty"`
		Stale      *bool    `json:"stale,omitempty"`
	} `json:"freshness,omitempty"`

	// Kind `game` = main game lines (moneyline/spread/total). `prop` = player/team props.
	Kind *ProphetXKind `json:"kind,omitempty"`

	// RequestedSports Echo of the sports that matched the filter (slug form).
	RequestedSports []ProphetXSportSlug `json:"requestedSports,omitempty"`
}

ProphetXOddsMeta defines model for ProphetXOddsMeta.

type ProphetXOddsParams

type ProphetXOddsParams struct {
	// Sports is one or more ProphetX sport slugs (required).
	Sports []string
	// Kind is "game" or "prop".
	Kind string
}

ProphetXOddsParams selects GetProphetxOdds.

type ProphetXOddsResponse

type ProphetXOddsResponse struct {
	Data    *ProphetXSnapshot `json:"data,omitempty"`
	Meta    *ProphetXOddsMeta `json:"meta,omitempty"`
	Success *bool             `json:"success,omitempty"`
}

ProphetXOddsResponse defines model for ProphetXOddsResponse.

type ProphetXOutcome

type ProphetXOutcome struct {
	AbbreviatedName *string  `json:"abbreviatedName,omitempty"`
	CompetitorID    *float64 `json:"competitorId,omitempty"`
	DisplayLine     *string  `json:"displayLine,omitempty"`
	DisplayName     *string  `json:"displayName,omitempty"`
	ID              *float64 `json:"id,omitempty"`
	Line            *float64 `json:"line,omitempty"`
	LineID          *string  `json:"lineID,omitempty"`
	Name            *string  `json:"name,omitempty"`

	// OutcomeLink Bet-slip deep link, synthesized by the API when ProphetX gives it the ids.
	OutcomeLink *string `json:"outcome_link,omitempty"`
}

ProphetXOutcome One outcome of a ProphetX market. `outcomes[i]` pairs with `selections[i]`.

type ProphetXRawEvent

type ProphetXRawEvent struct {
	Competitors []ProphetXCompetitor `json:"competitors,omitempty"`
	DisplayName *string              `json:"displayName,omitempty"`
	ID          *float64             `json:"id,omitempty"`
	Name        *string              `json:"name,omitempty"`

	// Scheduled ISO 8601 timestamp - event start time.
	Scheduled *string `json:"scheduled,omitempty"`
	Sport     *struct {
		ID   *float64 `json:"id,omitempty"`
		Name *string  `json:"name,omitempty"`
	} `json:"sport,omitempty"`

	// Status ProphetX status string (e.g. `not_started`, `live`, `ended`, `closed`).
	Status     *string `json:"status,omitempty"`
	Tournament *struct {
		ID   *float64 `json:"id,omitempty"`
		Name *string  `json:"name,omitempty"`
	} `json:"tournament,omitempty"`
}

ProphetXRawEvent Raw ProphetX event metadata. The stable top-level fields are typed (id, name, sport, tournament, competitors, etc.) while ProphetX's other extension fields pass through via the index signature. Matches the shape of `ProphetXEvent` in the nba-odds-app source of truth.

type ProphetXSelectionLevel

type ProphetXSelectionLevel struct {
	AbbreviatedName *string `json:"abbreviatedName,omitempty"`

	// CompetitorID Present on team markets.
	CompetitorID *float64 `json:"competitorId,omitempty"`
	DisplayLine  *string  `json:"displayLine,omitempty"`
	DisplayName  *string  `json:"displayName,omitempty"`
	DisplayOdds  *string  `json:"displayOdds,omitempty"`

	// ID Outcome id - equals `outcomes[i].id` for the side this level sits in.
	ID *float64 `json:"id,omitempty"`

	// Line Spread or total for this level; 0 on moneylines.
	Line *float64 `json:"line,omitempty"`

	// LineID ProphetX line id; also used by `outcome_link`.
	LineID *string `json:"lineID,omitempty"`
	Name   *string `json:"name,omitempty"`

	// Odds American odds, integer (e.g. -162, 142).
	Odds *float64 `json:"odds,omitempty"`

	// Stake The resting order's stake as ProphetX reports it. Equals what `value`
	// wins on an untouched order, but stays at the original figure after a
	// partial fill - derive payout from `value` and `odds`, not from this.
	Stake *float64 `json:"stake,omitempty"`

	// Value Amount available for YOU to bet at this price right now, in dollars.
	Value *float64 `json:"value,omitempty"`
}

ProphetXSelectionLevel One resting price level on one side of a ProphetX market.

type ProphetXSnapshot

type ProphetXSnapshot struct {
	// Sports Keyed by raw ProphetX sport name - e.g. `Basketball`, `Ice Hockey`, `Golf`.
	Sports map[string][]ProphetXEventData `json:"sports,omitempty"`

	// Timestamp ISO timestamp of the snapshot.
	Timestamp *string `json:"timestamp,omitempty"`

	// TotalEvents Total number of events across all sports in the response.
	TotalEvents *float64 `json:"totalEvents,omitempty"`
}

ProphetXSnapshot defines model for ProphetXSnapshot.

type ProphetXSportSlug

type ProphetXSportSlug = string

ProphetXSportSlug Valid ProphetX sport slugs accepted by REST filter and WS subscription.

type ProphetXSubscription

type ProphetXSubscription = json.RawMessage

ProphetXSubscription ProphetX WebSocket subscription filter.

  • omitted / false → do NOT receive prophetx-update events (default)
  • true → receive every ProphetX delta (all sports + kinds)
  • object with filters → receive only matching (sport, kind) deltas

The object form is optional in both dimensions:

  • `sports: ['basketball']` alone → all kinds for basketball
  • `kinds: ['prop']` alone → all sports' props
  • both specified → intersection

Invalid filter values (unknown sport / kind) are silently dropped by the server AFTER being received - the server never widens the subscription to "all". See the server's validateSubscription() for exact semantics.

type PropsEVOpportunity added in v0.9.0

type PropsEVOpportunity struct {
	Book             *string  `json:"book,omitempty"`
	BookImplied      *float64 `json:"bookImplied,omitempty"`
	BookPrice        *float64 `json:"bookPrice,omitempty"`
	BooksInConsensus *float64 `json:"booksInConsensus,omitempty"`
	EdgePp           *float64 `json:"edgePp,omitempty"`
	EvPct            *float64 `json:"evPct,omitempty"`
	FairPrice        *float64 `json:"fairPrice,omitempty"`

	// FairProbability The conservative fair (the fair minus 1 point).
	FairProbability *float64 `json:"fairProbability,omitempty"`
	FairUnadjusted  *float64 `json:"fairUnadjusted,omitempty"`
	KellyFraction   *float64 `json:"kellyFraction,omitempty"`

	// Kind `venue` for an exchange (Novig): never tradeable here, its depth is not checked on props.
	Kind       *string  `json:"kind,omitempty"`
	QuoteAgeMs *float64 `json:"quoteAgeMs,omitempty"`
	Reason     *string  `json:"reason,omitempty"`
	Roi        *float64 `json:"roi,omitempty"`
	Side       *string  `json:"side,omitempty"`
	Tradeable  *bool    `json:"tradeable,omitempty"`
}

PropsEVOpportunity One +EV price on a player prop (same math as EVOpportunity).

type PropsEVParams added in v0.9.0

type PropsEVParams struct {
	// MinEV is the least EV% of an opportunity (0 means every positive EV; at
	// most meta.max_ev).
	MinEV float64
	// Book restricts the opportunities to one sportsbook key.
	Book string
	// Player is a partial match on the player name, as on GetProps.
	Player string
	// Category is one prop category (the GetProps category names and aliases).
	Category string
	// All also lists the props with no positive-EV price, each with its fair
	// line. It is sent as all=true only when set.
	All bool
}

PropsEVParams filters GetPropsEV.

type PropsEVResponse added in v0.9.0

type PropsEVResponse struct {
	Data []PropsEVRow `json:"data,omitempty"`
	Meta *struct {
		All      *bool   `json:"all,omitempty"`
		Anchor   *string `json:"anchor,omitempty"`
		Book     *string `json:"book,omitempty"`
		Coverage *struct {
			Games         *float64 `json:"games,omitempty"`
			PinnacleProps *float64 `json:"pinnacleProps,omitempty"`
			Positive      *float64 `json:"positive,omitempty"`
			ScoredPrices  *float64 `json:"scoredPrices,omitempty"`
		} `json:"coverage,omitempty"`
		FairMethod    *string  `json:"fair_method,omitempty"`
		MaxEv         *float64 `json:"max_ev,omitempty"`
		MinEv         *float64 `json:"min_ev,omitempty"`
		OddsFormat    *string  `json:"odds_format,omitempty"`
		Opportunities *float64 `json:"opportunities,omitempty"`
		PregameOnly   *bool    `json:"pregame_only,omitempty"`
		Rows          *float64 `json:"rows,omitempty"`
		Sport         *string  `json:"sport,omitempty"`
		Timestamp     *string  `json:"timestamp,omitempty"`
		Truncated     *bool    `json:"truncated,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PropsEVResponse Response for `GET /api/v1/{sport}/props/ev` (MVP+). Pre-game only.

type PropsEVRow added in v0.9.0

type PropsEVRow struct {
	AwayTeam     *string `json:"awayTeam,omitempty"`
	Category     *string `json:"category,omitempty"`
	CommenceTime *string `json:"commenceTime,omitempty"`
	Fair         *struct {
		Method  *string    `json:"method,omitempty"`
		Over    *FairPrice `json:"over,omitempty"`
		Sources *float64   `json:"sources,omitempty"`
		Under   *FairPrice `json:"under,omitempty"`
	} `json:"fair,omitempty"`
	GameID   *string  `json:"gameId,omitempty"`
	HomeTeam *string  `json:"homeTeam,omitempty"`
	Line     *float64 `json:"line,omitempty"`

	// Opportunities Best EV first; empty on a row listed only because `all: true`.
	Opportunities []PropsEVOpportunity `json:"opportunities,omitempty"`
	Player        *string              `json:"player,omitempty"`
	Sport         *string              `json:"sport,omitempty"`
}

PropsEVRow A Pinnacle-priced player prop with its fair line and the prices that beat it.

type PropsFilter

type PropsFilter struct {
	Sports     []string `json:"sports,omitempty"`
	Games      []string `json:"games,omitempty"`
	Categories []string `json:"categories,omitempty"`
}

PropsFilter narrows a props subscription. Empty fields mean everything.

type PropsHistoryParams

type PropsHistoryParams struct {
	GameID   string // required
	Player   string // required
	Category string // required
	Book     string
	Hours    int
}

PropsHistoryParams selects the line history of one prop (GetPropsHistory).

type PropsHistoryPoint

type PropsHistoryPoint struct {
	AlternateLines []PropAlternateLine `json:"alternateLines,omitempty"`
	Line           *float64            `json:"line,omitempty"`
	OverPrice      *float64            `json:"overPrice,omitempty"`
	Timestamp      *string             `json:"timestamp,omitempty"`

	// TimestampUtc `timestamp` in UTC, or `null` when its clock cannot be known; `timestamp` is the stored time (US Eastern for many rows stored before 2026-09-16). Added Oct 2026.
	TimestampUtc *string  `json:"timestampUtc,omitempty"`
	UnderPrice   *float64 `json:"underPrice,omitempty"`
}

PropsHistoryPoint defines model for PropsHistoryPoint.

type PropsHistoryResponse

type PropsHistoryResponse struct {
	Data *struct {
		Book        *string             `json:"book,omitempty"`
		Category    *string             `json:"category,omitempty"`
		DataPoints  *float64            `json:"dataPoints,omitempty"`
		GameID      *string             `json:"gameId,omitempty"`
		History     []PropsHistoryPoint `json:"history,omitempty"`
		OpeningLine *struct {
			Line      *float64 `json:"line,omitempty"`
			OverPrice *float64 `json:"overPrice,omitempty"`
			Timestamp *string  `json:"timestamp,omitempty"`

			// TimestampUtc `timestamp` in UTC, or `null` when its clock cannot be known. Added Oct 2026.
			TimestampUtc *string  `json:"timestampUtc,omitempty"`
			UnderPrice   *float64 `json:"underPrice,omitempty"`
		} `json:"openingLine,omitempty"`
		Player    *string `json:"player,omitempty"`
		Sport     *string `json:"sport,omitempty"`
		TimeRange *struct {
			DurationHours *float64 `json:"durationHours,omitempty"`
			End           *string  `json:"end,omitempty"`
			Mode          *string  `json:"mode,omitempty"`
			Start         *string  `json:"start,omitempty"`
		} `json:"timeRange,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PropsHistoryResponse defines model for PropsHistoryResponse.

type PropsParams

type PropsParams struct {
	GameID   string
	Player   string
	Category string
	Books    []string
	// State is a two-letter FanDuel state code (for example "ks") whose marketIds
	// to serve on the FanDuel props and alternate-line rungs, on GetProps and
	// GetBookProps(ctx, sport, "fanduel", ...). FanDuel issues a different marketId
	// in every state while the selectionId is the same everywhere, so a bet-slip
	// link built with another state's marketId fails. A market with no id for the
	// state has its marketId omitted rather than served as New Jersey's;
	// Meta.FanduelMarketIds (GetProps) or Meta.MarketIds (GetBookProps for fanduel)
	// reports what was mapped. A state the API does not serve is a 404, a code that
	// is not two letters a 400. Default (empty): New Jersey, the feed's own ids. REST
	// only: the props WebSocket events carry the New Jersey ids.
	State string
}

PropsParams filters GetProps and GetBookProps (Books is read by GetProps only).

type PropsPayload

type PropsPayload struct {
	LastOddsChange *string                `json:"last_odds_change,omitempty"`
	Sports         map[string][]GameProps `json:"sports,omitempty"`
	Timestamp      *string                `json:"timestamp,omitempty"`
}

PropsPayload defines model for PropsPayload.

type PropsResponse

type PropsResponse struct {
	Data []GameProps `json:"data,omitempty"`
	Meta *struct {
		AvailableBooks []string `json:"availableBooks,omitempty"`
		Book           *string  `json:"book,omitempty"`

		// Books Per-requested-book feeder liveness. Added Sep 2026.
		//
		// `ageSeconds` is how long since that book last PUBLISHED, not how old the price
		// is: pollers rewrite their cache on a cadence even when no line moved. There is
		// deliberately no aggregate status field - a book whose feeder dies drops off the
		// board rather than ageing, so a roll-up would report healthy for a dead book.
		Books map[string]struct {
			AgeSeconds *float64 `json:"ageSeconds,omitempty"`
			LastUpdate *string  `json:"lastUpdate,omitempty"`
			Status     *string  `json:"status,omitempty"`
		} `json:"books,omitempty"`

		// BooksReturned Books that actually contributed props to THIS response. Added Sep 2026.
		BooksReturned []string `json:"booksReturned,omitempty"`

		// FanduelMarketIds meta.fanduelMarketIds (getProps) / meta.marketIds (getBookProps fanduel) when `state` was sent.
		FanduelMarketIds *FanDuelStateMarketIdsMeta `json:"fanduelMarketIds,omitempty"`
		Filters          *string                    `json:"filters,omitempty"`
		GamesReturned    *float64                   `json:"gamesReturned,omitempty"`

		// MarketIds meta.fanduelMarketIds (getProps) / meta.marketIds (getBookProps fanduel) when `state` was sent.
		MarketIds      *FanDuelStateMarketIdsMeta `json:"marketIds,omitempty"`
		Message        *string                    `json:"message,omitempty"`
		PropsReturned  *float64                   `json:"propsReturned,omitempty"`
		RequestedBooks []string                   `json:"requestedBooks,omitempty"`

		// RetiredBooks Books named in `books` whose props are no longer on this board (bet365, polymarket),
		// each with where they are served now. Present only when one was named.
		RetiredBooks map[string]string `json:"retiredBooks,omitempty"`
		Sport        *string           `json:"sport,omitempty"`
		Timestamp    *string           `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PropsResponse defines model for PropsResponse.

type PropsSnapshot

type PropsSnapshot struct {
	AlternateLines []PropAlternateLine `json:"alternateLines,omitempty"`

	// AmericanPrice American price for a row whose side the book did not label (some alternate-line rows); null otherwise.
	AmericanPrice *float64 `json:"americanPrice,omitempty"`
	Book          *string  `json:"book,omitempty"`

	// Line The line as stored, which may be a decimal string such as `"1.50"`.
	Line *json.RawMessage `json:"line,omitempty"`

	// LineValue `line` as a number, always present; `null` when it is null or unparseable.
	LineValue *float64 `json:"lineValue,omitempty"`

	// OverPrice `null` when the book offered no over side.
	OverPrice  *float64 `json:"overPrice,omitempty"`
	PlayerName *string  `json:"playerName,omitempty"`
	PropType   *string  `json:"propType,omitempty"`

	// QualityFlags Known issues of this row (Oct 2026); an empty list when none applies. Some NHL rows carry
	// `prop_other_market_under_label`, `prop_two_markets_alternate`, `prop_two_markets_in_play` or
	// `prop_market_partly_wrong`: their prices are another market than `propType` and `line` say, or one of
	// two markets stored alternately under one key. Flagged rows are still returned; leave them out of
	// models and grading. `prop_two_markets_in_play` affects only rows recorded after the game started.
	QualityFlags []string `json:"qualityFlags,omitempty"`
	RecordedAt   *string  `json:"recordedAt,omitempty"`

	// RecordedAtUtc The capture time in UTC (ISO, ends in Z), or `null` when its clock cannot be known (rows stored
	// before 2026-03-28, and draftkings rows from 2026-03-28 04:00 to 2026-04-10). `recordedAt` is the
	// time as stored: fanduel and caesars rows before 2026-09-16, and draftkings before 2026-03-28 04:00,
	// hold US Eastern wall time even though the value ends in Z. Added Oct 2026: absent on an older API.
	RecordedAtUtc *string  `json:"recordedAtUtc,omitempty"`
	UnderPrice    *float64 `json:"underPrice,omitempty"`
}

PropsSnapshot defines model for PropsSnapshot.

type PropsStatsResponse

type PropsStatsResponse struct {
	Data *struct {
		AgeMs     *float64                   `json:"ageMs,omitempty"`
		BySport   map[string]SportPropsCount `json:"bySport,omitempty"`
		Timestamp *string                    `json:"timestamp,omitempty"`
		Total     *float64                   `json:"total,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PropsStatsResponse defines model for PropsStatsResponse.

type PropsSubscription

type PropsSubscription struct {
	Categories []string `json:"categories,omitempty"`
	Games      []string `json:"games,omitempty"`
	Sports     []Sport  `json:"sports,omitempty"`
}

PropsSubscription defines model for PropsSubscription.

type PublicBettingParams

type PublicBettingParams struct {
	EventID   string
	Sport     string
	StartDate string
	EndDate   string
	Limit     int
	Offset    int
}

PublicBettingParams filters GetPublicBetting.

type PublicBettingResponse

type PublicBettingResponse struct {
	Data *struct {
		Betting    []PublicBettingRow `json:"betting,omitempty"`
		Pagination *struct {
			HasMore *bool    `json:"hasMore,omitempty"`
			Limit   *float64 `json:"limit,omitempty"`
			Offset  *float64 `json:"offset,omitempty"`
			Total   *float64 `json:"total,omitempty"`
		} `json:"pagination,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

PublicBettingResponse defines model for PublicBettingResponse.

type PublicBettingRow

type PublicBettingRow struct {
	AwayTeam *string `json:"awayTeam,omitempty"`
	EventID  *string `json:"eventId,omitempty"`
	GameDate *string `json:"gameDate,omitempty"`
	HomeTeam *string `json:"homeTeam,omitempty"`
	Sport    *string `json:"sport,omitempty"`
	Spread   *struct {
		AwayPct *float64 `json:"awayPct,omitempty"`
		HomePct *float64 `json:"homePct,omitempty"`
	} `json:"spread,omitempty"`
	Total *struct {
		OverPct  *float64 `json:"overPct,omitempty"`
		UnderPct *float64 `json:"underPct,omitempty"`
	} `json:"total,omitempty"`
}

PublicBettingRow defines model for PublicBettingRow.

type RawJSON

type RawJSON = json.RawMessage

RawJSON The book's own JSON, served verbatim. Decode without strict typing.

type RealtimeClientStreamPosition

type RealtimeClientStreamPosition struct {
	// Checkpoint Set on the periodic checkpoint frame. `GET /{sport}/realtime` answers with a
	// `meta.stream` you align to these; see RealtimeOddsPayload.
	Checkpoint *bool `json:"checkpoint,omitempty"`

	// Epoch Opaque id of one run of the stream. A new epoch means the stream restarted and
	// its `seq` numbering started again: reload the board.
	Epoch *string `json:"epoch,omitempty"`

	// Seq Goes up by 1 per frame for the sport within one epoch.
	Seq   *float64 `json:"seq,omitempty"`
	Sport *string  `json:"sport,omitempty"`
}

RealtimeClientStreamPosition The stream position a `pinnacle-realtime` or `pinnacle-realtime-sync` frame carries for its one sport.

type RealtimeFeedState

type RealtimeFeedState struct {
	// LastDataAgeSeconds Seconds since this sport last produced ANY data, or `null` if it has not been
	// observed producing data. `null` does NOT mean healthy, and does not mean zero.
	LastDataAgeSeconds *float64 `json:"lastDataAgeSeconds,omitempty"`

	// Resubscribing True while the feed is subscribing again after a reconnect, when some leagues
	// can be silent. `null` means unknown, by the same rules as `upstreamAgeSeconds`.
	// Absent from API versions that predate it.
	Resubscribing *bool `json:"resubscribing,omitempty"`

	// Stalled True when the board has an event in play (`phase: "live"`) and Pinnacle has sent
	// nothing for this sport for longer than its normal quiet spells during play: prices
	// can be out of date even though the board was rewritten seconds ago. False on a board
	// with nothing in play. `null` when `upstreamAgeSeconds` is null or the events carry no
	// `phase`. It does not change `freshness.stale`. Absent from API versions that predate it.
	Stalled *bool   `json:"stalled,omitempty"`
	State   *string `json:"state,omitempty"`

	// UpstreamAgeSeconds Seconds since Pinnacle last sent anything for this sport, as of this response.
	// `lastDataAgeSeconds` stays low while Pinnacle is quiet, because the board is
	// rewritten on a schedule either way; this keeps growing, so it is the field that
	// shows a feed that is running but hearing nothing.
	//
	// `null` means unknown: always when `meta.source` is not `"pinnacle"`, and before
	// the feed has received anything since it started. Absent from API versions that
	// predate it.
	UpstreamAgeSeconds *float64 `json:"upstreamAgeSeconds,omitempty"`
}

RealtimeFeedState Why this response is empty, when it is.

`live` - events were served. `unknown` - we cannot tell whether the feed is idle or broken.

Deliberately only two states. Earlier drafts also had `idle` and `down`; both were removed after measurement. `down` was unreachable. `idle` keyed on the upstream reporting "connected, zero matchups", which it also reports when a live feed has frozen - so it could have claimed "nothing on" during an outage. Judge with `lastDataAgeSeconds`: a value in the hundreds of thousands means the feed has produced nothing for days.

type RealtimeFeeds added in v0.8.0

type RealtimeFeeds = RealtimeFeedsSubscription

RealtimeFeeds is Subscription.Realtime, the same type the subscribed acknowledgement carries in ClientSubscription.Realtime. PS3838 set to false stops ps3838-realtime for this connection; the server honours only a literal false, so a nil or true field leaves the event on.

type RealtimeFeedsSubscription added in v0.8.0

type RealtimeFeedsSubscription struct {
	PS3838 *bool `json:"ps3838,omitempty"`
}

RealtimeFeedsSubscription See SubscribeOptions.realtime.

type RealtimeFrameFeed added in v0.8.0

type RealtimeFrameFeed struct {
	Resubscribing      *bool    `json:"resubscribing,omitempty"`
	Stalled            *bool    `json:"stalled,omitempty"`
	UpstreamAgeSeconds *float64 `json:"upstreamAgeSeconds,omitempty"`
}

RealtimeFrameFeed The sport's upstream feed state on every `pinnacle-realtime` and `pinnacle-realtime-sync` frame: the same fields as REST `meta.feed` (RealtimeFeedState). `stalled: true` means prices can be out of date while frames keep arriving. Each field is `null` when unknown.

type RealtimeFreshness

type RealtimeFreshness struct {
	// AgeSeconds Seconds since this data was produced, or `null` when that is unknowable.
	AgeSeconds *float64 `json:"ageSeconds,omitempty"`

	// DroppedAsFinished Events dropped because the match was confirmed finished (realtime only).
	DroppedAsFinished *float64 `json:"droppedAsFinished,omitempty"`

	// DroppedAsStale Events dropped because their scheduled start is older than the sport's
	// max plausible live duration - a source-independent staleness guard
	// (realtime only).
	DroppedAsStale *float64 `json:"droppedAsStale,omitempty"`
	Stale          *bool    `json:"stale,omitempty"`
}

RealtimeFreshness Freshness for the realtime endpoints, where `ageSeconds` can legitimately be unknown.

Deliberately NOT a widening of OddsFreshness: `/odds` and `/events` share that type and customers do arithmetic directly on their `ageSeconds`, so loosening it there would force null-checks on consumers who do not need them today.

`ageSeconds: null` means the age is genuinely unmeasurable - no source produced data and we have never observed this sport produce any. It replaced a reported `0`, which claimed the freshest possible data on a feed that had served nothing for four days. When null, `stale` is always `true`.

type RealtimeMeta

type RealtimeMeta struct {
	Available *bool    `json:"available,omitempty"`
	Events    *float64 `json:"events,omitempty"`

	// Feed Why this response is empty, when it is.
	//
	// `live`    - events were served.
	// `unknown` - we cannot tell whether the feed is idle or broken.
	//
	// Deliberately only two states. Earlier drafts also had `idle` and `down`; both were
	// removed after measurement. `down` was unreachable. `idle` keyed on the upstream
	// reporting "connected, zero matchups", which it also reports when a live feed has
	// frozen - so it could have claimed "nothing on" during an outage. Judge with
	// `lastDataAgeSeconds`: a value in the hundreds of thousands means the feed has
	// produced nothing for days.
	Feed *RealtimeFeedState `json:"feed,omitempty"`

	// Freshness Freshness for the realtime endpoints, where `ageSeconds` can legitimately be
	// unknown.
	//
	// Deliberately NOT a widening of OddsFreshness: `/odds` and `/events` share
	// that type and customers do arithmetic directly on their `ageSeconds`, so loosening
	// it there would force null-checks on consumers who do not need them today.
	//
	// `ageSeconds: null` means the age is genuinely unmeasurable - no source produced
	// data and we have never observed this sport produce any. It replaced a reported
	// `0`, which claimed the freshest possible data on a feed that had served nothing
	// for four days. When null, `stale` is always `true`.
	Freshness *RealtimeFreshness `json:"freshness,omitempty"`
	League    *string            `json:"league,omitempty"`
	Source    *string            `json:"source,omitempty"`
	Sport     *string            `json:"sport,omitempty"`

	// Stream The `pinnacle-realtime` stream position this response matches, or `null` when
	// the response cannot be aligned with the stream (for example when it was served
	// from PS3838). Absent from API versions that predate it.
	//
	// When it is set, `data` is the board at that position after your `league`
	// filter, so an empty `data` means no event matches, not a missing feed.
	// `pinnacle-realtime` frames are never filtered by league: if you load with
	// `league`, apply the same filter to every frame, or load without it. To follow
	// `pinnacle-realtime` without gaps, see the loading procedure on
	// RealtimeOddsPayload.
	Stream *RealtimeStreamPosition `json:"stream,omitempty"`

	// Timestamp Absent only on an empty response for a sport we have never seen produce data.
	Timestamp *string `json:"timestamp,omitempty"`
}

RealtimeMeta defines model for RealtimeMeta.

type RealtimeOddsPayload

type RealtimeOddsPayload struct {
	Basketball []OddsEvent `json:"basketball,omitempty"`
	Cricket    []OddsEvent `json:"cricket,omitempty"`
	Darts      []OddsEvent `json:"darts,omitempty"`

	// Delta The frame carries only changed events: merge them into your board by `id`. Set on
	// every `pinnacle-realtime` frame, and on `ps3838-realtime` delta frames.
	Delta *bool `json:"delta,omitempty"`

	// Feed The sport's upstream feed state on every `pinnacle-realtime` and
	// `pinnacle-realtime-sync` frame: the same fields as REST `meta.feed`
	// (RealtimeFeedState). `stalled: true` means prices can be out of date while
	// frames keep arriving. Each field is `null` when unknown.
	Feed     *RealtimeFrameFeed `json:"feed,omitempty"`
	Handball []OddsEvent        `json:"handball,omitempty"`
	Mlb      []OddsEvent        `json:"mlb,omitempty"`
	Nba      []OddsEvent        `json:"nba,omitempty"`
	Ncaab    []OddsEvent        `json:"ncaab,omitempty"`
	Ncaaf    []OddsEvent        `json:"ncaaf,omitempty"`
	Nfl      []OddsEvent        `json:"nfl,omitempty"`
	Nhl      []OddsEvent        `json:"nhl,omitempty"`

	// OddsFormat Odds format hint from the upstream source. Prices on the realtime feeds are American odds (value: "american").
	OddsFormat *string `json:"odds_format,omitempty"`

	// Removed `pinnacle-realtime`: event ids to delete from your board, keyed by sport.
	Removed map[string][]string `json:"removed,omitempty"`

	// Resync `pinnacle-realtime`: this frame comes from the periodic resend and can repeat unchanged events.
	Resync *bool `json:"resync,omitempty"`

	// Snapshot `ps3838-realtime` only: the sport's full board. Replace your board for that sport.
	Snapshot *bool       `json:"snapshot,omitempty"`
	Soccer   []OddsEvent `json:"soccer,omitempty"`

	// Stream The stream position a `pinnacle-realtime` or `pinnacle-realtime-sync` frame
	// carries for its one sport.
	Stream    *RealtimeClientStreamPosition `json:"stream,omitempty"`
	Tennis    []OddsEvent                   `json:"tennis,omitempty"`
	Timestamp *string                       `json:"timestamp,omitempty"`
	Wnba      []OddsEvent                   `json:"wnba,omitempty"`
}

RealtimeOddsPayload Payload of the `pinnacle-realtime` and `ps3838-realtime` WebSocket events.

**Not the REST shape.** `GET /{sport}/realtime` returns `{ data: OddsEvent[], meta }`; a frame is keyed by sport: `{ timestamp, <sport>: OddsEvent[], ... }`. Each frame carries exactly one sport with at least one event. Read it with realtimeSportEvents; `stream?.sport`, or the one key whose value is an array, tells you which sport it is.

Both events go to connections on Hall of Fame, or on a plan with the WebSocket add-on, for the sports in your subscription (every sport if you never subscribe). `books` does not narrow them, and neither is filtered by league. A `ps3838-realtime` frame can be several MB, so allow a client message size of at least 32 MB.

## `pinnacle-realtime`: every frame is partial (`delta: true`)

  • Merge each event into your board by `id`, replacing the whole event, then delete the ids in `removed[<sport>]`.
  • `resync: true` marks a frame from the periodic resend, which also carries events that have not changed.
  • When present, `stream` is `{ sport, epoch, seq, checkpoint? }`. Within one epoch, `seq` goes up by 1 per frame for that sport. A frame whose seq is at or below the last one you applied is a duplicate: ignore it. A skipped seq, a new epoch, or a frame without `stream` after frames that had one means you must load the board again; keep your current board until the new one is ready.
  • A stream position or removals for a sport with no events to send arrive on `pinnacle-realtime-sync` (PinnacleRealtimeSyncPayload). Handle both events.

**Loading a board.** On every connect and reconnect, and whenever the rules above say so:

  1. Buffer the sport's messages from both `pinnacle-realtime` and `pinnacle-realtime-sync`.
  2. Wait up to 25 seconds for one with `stream.checkpoint` set. If the sport's frames carry no `stream`, go on at once.
  3. Call `client.rest.getRealtime(sport)` and read `meta.stream`: - `null`: replace your board with `data`, merge every frame with no seq check, and load again after 10, 20, 40 seconds and so on, up to every 5 minutes. - its `epoch` differs from the checkpoint's, or its `seq` is lower than the checkpoint's: load again at the next checkpoint. - otherwise: replace your board with `data`, discard buffered messages with a seq at or below `meta.stream.seq`, and apply the rest in order.

Frames are not filtered by league. If you load with `league` (REST keeps events whose `league` contains that text, ignoring case), apply the same filter to every frame; otherwise load without it.

`removed` does not always list every event that leaves the board, and a sport whose last event ends can stop sending. So also load a sport's board again when it has sent no message for about 30 seconds, and every 5 minutes or so; each load drops events that ended without being listed.

Each load counts against your request quota.

## `ps3838-realtime`: the frame says how to apply it

  • `delta: true`: only the events that changed. Merge them by `id`.
  • `snapshot: true`: the sport's full board. Replace your board for that sport. Sent about every 20 seconds, and sooner after an event leaves the board.
  • Neither marker: also the sport's full board. Replace it.

A sport with no events sends nothing, so when a sport's last event ends no frame says so. On every connect and reconnect, and whenever a sport has sent nothing for about a minute, load its board from `client.rest.getPS3838Realtime(sport)` and apply frames from there.

`ps3838-realtime` handlers share this type, so `stream`, `removed` and `resync` are typed on them too; those fields are only sent on `pinnacle-realtime`.

type RealtimeResponse

type RealtimeResponse struct {
	Data    []OddsEvent   `json:"data,omitempty"`
	Meta    *RealtimeMeta `json:"meta,omitempty"`
	Success *bool         `json:"success,omitempty"`
}

RealtimeResponse defines model for RealtimeResponse.

type RealtimeStreamPosition

type RealtimeStreamPosition struct {
	// Epoch Opaque id of one run of the stream. A new epoch means the stream restarted and
	// its `seq` numbering started again: reload the board.
	Epoch *string `json:"epoch,omitempty"`

	// Seq Goes up by 1 per frame for the sport within one epoch.
	Seq *float64 `json:"seq,omitempty"`
}

RealtimeStreamPosition A position on a realtime stream: `seq` counts frames for one sport within one `epoch`.

type RecentGame

type RecentGame struct {
	AwayTeam   *string  `json:"awayTeam,omitempty"`
	Date       *float64 `json:"date,omitempty"`
	HomeSide   *bool    `json:"homeSide,omitempty"`
	HomeTeam   *string  `json:"homeTeam,omitempty"`
	MatchID    *string  `json:"matchId,omitempty"`
	Result     *string  `json:"result,omitempty"`
	Score      *string  `json:"score,omitempty"`
	Surface    *string  `json:"surface,omitempty"`
	Tournament *string  `json:"tournament,omitempty"`
}

RecentGame defines model for RecentGame.

type RefusalError

type RefusalError struct {
	// Message is the server's message, unchanged.
	Message string
	// Code is the reason, one of the Refusal* constants (for example
	// RefusalConnectionLimit). More codes can be added. An older server sends
	// none: then it is RefusalConnectionLimit when the message says so, else empty.
	Code string
	// Retryable is true when waiting alone can let the same key connect. The
	// non-retryable account codes (RefusalTierNoWS, RefusalSubscriptionInactive,
	// RefusalPaymentOverdue, RefusalTrialExpired, RefusalTrialUnverifiable) clear
	// once the account is fixed (see [Stream.Run]).
	Retryable bool
	// RetryAfter is the least time to wait before trying again, when the server said.
	RetryAfter time.Duration
	// MaxConnections is how many connections the plan allows (CONNECTION_LIMIT only).
	MaxConnections int
	// contains filtered or unexported fields
}

RefusalError is a WebSocket connection the server refused (a Socket.IO connect_error).

func (*RefusalError) Error

func (e *RefusalError) Error() string

type ResultsResponse

type ResultsResponse struct {
	Count     *float64     `json:"count,omitempty"`
	Events    []ScoreEvent `json:"events,omitempty"`
	Sport     *string      `json:"sport,omitempty"`
	Success   *bool        `json:"success,omitempty"`
	Timestamp *string      `json:"timestamp,omitempty"`
}

ResultsResponse Response from GET /{sport}/results (completed games).

type RetryPolicy

type RetryPolicy struct {
	// MaxRetries is the number of attempts after the first. Default 0: no retry.
	MaxRetries int
	// BaseDelay is the backoff base when the server sent no Retry-After; it
	// doubles per attempt with full jitter. Default 2 s.
	BaseDelay time.Duration
	// MaxDelay caps the computed backoff. Default 30 s.
	MaxDelay time.Duration
	// MaxRetryAfter is the longest Retry-After the client waits for; a longer one
	// returns the error instead. Default 120 s.
	MaxRetryAfter time.Duration
}

RetryPolicy says which failed GET requests are sent again. Only 429 and 503 responses are retried, a 429 is never retried once the monthly quota is used up (X-RateLimit-Remaining-Month: 0), and a POST is never retried.

type RotateWebhookSecretParams added in v0.5.0

type RotateWebhookSecretParams struct {
	// ExpirePreviousAfterHours is how long the previous secret keeps signing beside
	// the new one: 0 to 168 hours. nil takes the default, 24; Ptr(0.0) retires it
	// at once.
	ExpirePreviousAfterHours *float64 `json:"expire_previous_after_hours,omitempty"`
}

RotateWebhookSecretParams is the optional body of Client.RotateWebhookSecret.

type ScheduleResponse

type ScheduleResponse struct {
	Count     *float64     `json:"count,omitempty"`
	Events    []ScoreEvent `json:"events,omitempty"`
	Sport     *string      `json:"sport,omitempty"`
	Success   *bool        `json:"success,omitempty"`
	Timestamp *string      `json:"timestamp,omitempty"`
}

ScheduleResponse Response from GET /{sport}/schedule (upcoming games).

type ScoreClock added in v0.9.0

type ScoreClock struct {
	// AsOf The instant the clock was true at the venue (the read, less the league feed's own edge-cache age): tick secondsRemaining down from it while running.
	AsOf *string `json:"asOf,omitempty"`

	// Intermission Halftime or between periods.
	Intermission *bool `json:"intermission,omitempty"`

	// Period The period the clock belongs to, in the source's numbering; status.period and status.detail follow it while the clock is present.
	Period *float64 `json:"period,omitempty"`

	// Running True while the clock runs, false in a stoppage or intermission, null when the feed cannot say (NFL and NCAAF between plays; any game before its first movement).
	Running *bool `json:"running,omitempty"`

	// SecondsRemaining Seconds left in the period; null when the source shows no clock (a shootout, for one).
	SecondsRemaining *float64 `json:"secondsRemaining,omitempty"`
}

ScoreClock The game clock of an in-play NBA, NHL, NFL or NCAAF game. Absent on every other sport and whenever no clock was read for the game.

type ScoreEvent

type ScoreEvent struct {
	Away *ScoreTeam `json:"away,omitempty"`

	// BaseballDetail Baseball-specific live detail.
	//
	// `inning`/`half` are present for live games only. `hits`/`errors` complete the
	// R-H-E line - runs are `home.score`/`away.score`, and per-inning runs are
	// `home.linescores`/`away.linescores`.
	//
	// Outs and runners on base are NOT available: the upstream source does not
	// carry them.
	BaseballDetail *BaseballScoreDetail `json:"baseballDetail,omitempty"`

	// GenericTeamStats Sport-specific aggregate team stats as a key-value map. For baseball this
	// carries the box score (hits, errors, homeRuns, runsBattedIn, leftOnBase,
	// walks, strikeouts, stolenBases, atBat, battingAverage, …).
	GenericTeamStats map[string]struct {
		Away *json.RawMessage `json:"away,omitempty"`
		Home *json.RawMessage `json:"home,omitempty"`
	} `json:"genericTeamStats,omitempty"`
	H2H  *H2HData   `json:"h2h,omitempty"`
	Home *ScoreTeam `json:"home,omitempty"`
	ID   *string    `json:"id,omitempty"`

	// Incidents Live match incidents - goals, cards, subs (soccer).
	Incidents []MatchIncident `json:"incidents,omitempty"`

	// LastUpdated Last time this event's CONTENT changed (score, status, linescores, …), not the poll time.
	LastUpdated *string         `json:"lastUpdated,omitempty"`
	League      *string         `json:"league,omitempty"`
	Lineups     *LineupsData    `json:"lineups,omitempty"`
	MatchStats  *LiveMatchStats `json:"matchStats,omitempty"`
	Name        *string         `json:"name,omitempty"`

	// SoccerDetail Soccer result breakdown. `home.score`/`away.score` (and `score` on the stats
	// endpoints) are the score of the match itself, after extra time when it was
	// played, and NEVER include shootout goals; the shootout is only in `penalties`.
	SoccerDetail *SoccerScoreDetail `json:"soccerDetail,omitempty"`

	// SourceMatchID Upstream match id, for correlating with the stats endpoints.
	SourceMatchID *string `json:"sourceMatchId,omitempty"`
	Sport         *string `json:"sport,omitempty"`

	// StartTime Null when the source did not supply a start time.
	StartTime *string      `json:"startTime,omitempty"`
	Status    *ScoreStatus `json:"status,omitempty"`

	// TennisDetail Tennis-specific live detail (games within sets, current game points, serving).
	TennisDetail *TennisScoreDetail `json:"tennisDetail,omitempty"`
}

ScoreEvent defines model for ScoreEvent.

type ScoreStatus

type ScoreStatus struct {
	// Clock The game clock of an in-play NBA, NHL, NFL or NCAAF game. Absent on every other
	// sport and whenever no clock was read for the game.
	Clock  *ScoreClock `json:"clock,omitempty"`
	Detail *string     `json:"detail,omitempty"`

	// DisplayClock Time left in the period for NBA/NHL/NFL/NCAAF games in play (5:32, or 48.3 under a minute); the soccer minute; null otherwise.
	DisplayClock *string  `json:"displayClock,omitempty"`
	Period       *float64 `json:"period,omitempty"`
	State        *string  `json:"state,omitempty"`
}

ScoreStatus defines model for ScoreStatus.

type ScoreTeam

type ScoreTeam struct {
	HomeAway *string `json:"homeAway,omitempty"`

	// Linescores Per-period scores. Quarters, sets, maps, or - for baseball - INNINGS.
	// A side that never batted in a half-inning (walk-off, or the home team
	// leading after the top of the 9th) simply has no entry for that period,
	// so the two sides' arrays can differ in length.
	Linescores []struct {
		Period *float64 `json:"period,omitempty"`
		Score  *float64 `json:"score,omitempty"`
	} `json:"linescores,omitempty"`
	Score *float64 `json:"score,omitempty"`
	Team  *struct {
		Abbreviation *string `json:"abbreviation,omitempty"`
		DisplayName  *string `json:"displayName,omitempty"`
		LogoURL      *string `json:"logoUrl,omitempty"`
	} `json:"team,omitempty"`
}

ScoreTeam defines model for ScoreTeam.

type ScoreView added in v0.7.0

type ScoreView struct {
	Away *struct {
		Score *float64 `json:"score,omitempty"`
		Team  *struct {
			Abbreviation *string `json:"abbreviation,omitempty"`
			DisplayName  *string `json:"displayName,omitempty"`
		} `json:"team,omitempty"`
	} `json:"away,omitempty"`
	Home *struct {
		Score *float64 `json:"score,omitempty"`
		Team  *struct {
			Abbreviation *string `json:"abbreviation,omitempty"`
			DisplayName  *string `json:"displayName,omitempty"`
		} `json:"team,omitempty"`
	} `json:"home,omitempty"`

	// ID Same id as `ScoreEvent.id` on `getScores()`.
	ID *string `json:"id,omitempty"`

	// LastUpdated In a `delta`, when this change happened. In a `full`, the game's own last
	// content change, as on `getScores()`.
	LastUpdated *string `json:"lastUpdated,omitempty"`
	Name        *string `json:"name,omitempty"`
	Sport       *string `json:"sport,omitempty"`

	// StartTime Null when the source did not supply a start time.
	StartTime *string      `json:"startTime,omitempty"`
	Status    *ScoreStatus `json:"status,omitempty"`
}

ScoreView One game on the `scores-update` WebSocket event: a lean view of a ScoreEvent with the score, status and teams only. Linescores, stats, lineups, incidents and sport-specific detail stay on `getScores()`.

type Scores

type Scores struct {
	// All is set when GetScores was called with an empty sport.
	All *ScoresResponse
	// Sport is set when GetScores was called with a sport.
	Sport *SportScoresResponse
}

Scores is the result of GetScores: All for every sport, Sport for one.

type ScoresResponse

type ScoresResponse struct {
	Cached *bool `json:"cached,omitempty"`
	Data   *struct {
		Sports map[string][]ScoreEvent `json:"sports,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

ScoresResponse Response from GET /scores/live (all sports).

type ScoresUpdate added in v0.7.0

type ScoresUpdate struct {
	Events []ScoreView `json:"events,omitempty"`

	// Removed `delta` only: ids of games that left the live board without a known final
	// state. Treat them as no longer live; `getScores()` has how they ended.
	Removed []string `json:"removed,omitempty"`
	Sport   *string  `json:"sport,omitempty"`

	// Timestamp ISO 8601 time the message was built.
	Timestamp *string `json:"timestamp,omitempty"`
	Type      *string `json:"type,omitempty"`
}

ScoresUpdate Payload of the `scores-update` WebSocket event: live scores for ONE sport.

Opt in with `subscribe({ ..., scores: true })` (every sport) or `scores: ['nfl', 'soccer']`; no `scores` field, no messages. Same access as the v2 events: Hall of Fame, or a paid plan with the WebSocket add-on.

  • `full`: every game in play for `sport`, about every 30 seconds. Replace your live board for that sport. A sport with no game in play sends no `full`.
  • `delta`: only what changed since the previous message: games in play whose score or state changed, games that just finished (`status.state === "post"` with the final score), and `removed`. Merge `events` by `id`, then drop the `removed` ids.

A score change goes out within about 1 second where the source pushes scores, and within about 3 seconds otherwise. `getScores()` is unchanged.

type ServerHeartbeat

type ServerHeartbeat struct {
	Seq *float64 `json:"seq,omitempty"`

	// TS Server time, epoch ms.
	TS *float64 `json:"ts,omitempty"`
}

ServerHeartbeat `server-heartbeat`: sent every 10 seconds on every connection, independent of odds traffic, so silence is detectable. `seq` is counted by the server, not per connection: it goes up by 1 every 10 seconds and can jump ahead or start again from 1 after a reconnect. Compare it only within one connection, where a gap means messages were missed.

type ServerNotice

type ServerNotice struct {
	// At ISO time the connection was closed.
	At *string `json:"at,omitempty"`

	// Code Why the server closed a connection, in ServerNotice.code. More codes can be
	// added.
	//
	// - `BACKLOG_CAP`: more data was waiting to be sent to the connection than the
	//   per-connection limit allows.
	// - `AGGREGATE_SHED`: the connection was among those furthest behind while the
	//   server was shedding load.
	// - `GHOST`: data sent to the connection had not been read for over a minute.
	// - `SLOT_EVICTED`: closed to admit a new connection with the same API key, because it
	//   had stopped responding.
	// - `CLIENT_FRAME_LIMIT`: your client closed it because a message was larger than its
	//   maximum message size (close code 1009).
	// - `SERVER_RESTART`: sent to open connections just before a restart.
	Code *ServerNoticeCode `json:"code,omitempty"`

	// Details Measurements behind the decision, when there are any.
	Details *struct {
		AgeSeconds *float64 `json:"ageSeconds,omitempty"`
		CapMB      *float64 `json:"capMB,omitempty"`
		CloseCode  *float64 `json:"closeCode,omitempty"`
		DeflateMB  *float64 `json:"deflateMB,omitempty"`
		EngineMB   *float64 `json:"engineMB,omitempty"`
		QueuedMB   *float64 `json:"queuedMB,omitempty"`
		TCPMB      *float64 `json:"tcpMB,omitempty"`
	} `json:"details,omitempty"`

	// Hint What to change on your side.
	Hint *string `json:"hint,omitempty"`

	// Message What happened, in words.
	Message *string `json:"message,omitempty"`

	// SocketID Id of the connection that was closed, not the one receiving the notice.
	SocketID *string `json:"socketId,omitempty"`
}

ServerNotice `server-notice`: why the server closed your previous connection, sent once on the next connect. `SERVER_RESTART` is the exception: it is sent to open connections just before a restart, after which you reconnect (the SDK does) and send your subscription again (the SDK does that too).

type ServerNoticeCode

type ServerNoticeCode = string

ServerNoticeCode Why the server closed a connection, in ServerNotice.code. More codes can be added.

  • `BACKLOG_CAP`: more data was waiting to be sent to the connection than the per-connection limit allows.
  • `AGGREGATE_SHED`: the connection was among those furthest behind while the server was shedding load.
  • `GHOST`: data sent to the connection had not been read for over a minute.
  • `SLOT_EVICTED`: closed to admit a new connection with the same API key, because it had stopped responding.
  • `CLIENT_FRAME_LIMIT`: your client closed it because a message was larger than its maximum message size (close code 1009).
  • `SERVER_RESTART`: sent to open connections just before a restart.

type SgpBatchMeta added in v0.10.0

type SgpBatchMeta struct {
	// DeadlineMs The per-parlay deadline used, in ms.
	DeadlineMs *float64 `json:"deadlineMs,omitempty"`

	// Parlays Parlays sent.
	Parlays *float64 `json:"parlays,omitempty"`

	// QuotesCached Quotes answered from the cache (a repeated parlay counts once).
	QuotesCached *float64 `json:"quotesCached,omitempty"`

	// QuotesMade Quotes that reached a book's price request (each counted one build).
	QuotesMade *float64 `json:"quotesMade,omitempty"`

	// QuotesPriced Quotes that priced (a repeated parlay counts once).
	QuotesPriced *float64 `json:"quotesPriced,omitempty"`
	Timestamp    *string  `json:"timestamp,omitempty"`
}

SgpBatchMeta `meta` of a SgpBatchResponse.

type SgpBatchMode added in v0.10.0

type SgpBatchMode = string

SgpBatchMode `"book"`: every parlay on the one `book`. `"compare"`: every parlay on every book (or `books`), as `compareSgp` prices one.

type SgpBatchParams added in v0.10.0

type SgpBatchParams struct {
	// Mode is "book" (every parlay on the one Book) or "compare" (every parlay on
	// every book, or on Books, as CompareSgp prices one).
	Mode string `json:"mode"`
	// Book is the one book every parlay is priced on; required in mode "book".
	Book string `json:"book,omitempty"`
	// Books are the books to ask in mode "compare". Empty means every book.
	Books []string `json:"books,omitempty"`
	// Parlays holds at most 10 parlays in mode "book", 4 in mode "compare". A
	// parlay repeated in one batch (same EventID, same legs in the same order) is
	// priced once and reported for each copy.
	Parlays []SgpBatchParlay `json:"parlays"`
	// DeadlineMs is how long each parlay waits for its books, 1000 to 15000 ms.
	// 0 sends nothing: the server's default is 6000.
	DeadlineMs int `json:"deadlineMs,omitempty"`
	// FanDuel is "cold" (the server's default) or "skip", as on CompareSgp.
	FanDuel string `json:"fanduel,omitempty"`
	// Fair adds our fair-price estimate to each parlay's result (SgpFairBlock).
	Fair bool `json:"fair,omitempty"`
	// Fresh never uses a cached quote.
	Fresh bool `json:"fresh,omitempty"`
}

SgpBatchParams is the body of BatchSgp.

type SgpBatchParlay added in v0.10.0

type SgpBatchParlay struct {
	// Ref is your label for the parlay (up to 100 characters), returned with its
	// result.
	Ref string `json:"ref,omitempty"`
	// EventID is OUR event id for the game, as GetOdds gives it (as on CompareSgp).
	EventID string `json:"eventId"`
	// Legs holds the legs BuildSgp takes, 1 to 12.
	Legs []SgpLeg `json:"legs"`
}

SgpBatchParlay is one parlay of a BatchSgp call.

type SgpBatchResponse added in v0.10.0

type SgpBatchResponse struct {
	// Meta `meta` of a SgpBatchResponse.
	Meta *SgpBatchMeta `json:"meta,omitempty"`
	Mode *string       `json:"mode,omitempty"`

	// Results One per parlay, in request order.
	Results []SgpBatchResult `json:"results,omitempty"`
	Sport   *string          `json:"sport,omitempty"`
	Success *bool            `json:"success,omitempty"`
}

SgpBatchResponse Response from `POST /api/v1/{sport}/sgp/batch`.

type SgpBatchResult added in v0.10.0

type SgpBatchResult struct {
	// Best Mode `"compare"`: the highest decimal price among the books that priced,
	// or `null`.
	Best *struct {
		American *float64 `json:"american,omitempty"`
		Book     *string  `json:"book,omitempty"`
		Decimal  *float64 `json:"decimal,omitempty"`
	} `json:"best,omitempty"`

	// Book Mode `"book"`: the book priced.
	Book *string `json:"book,omitempty"`

	// BookEventID Mode `"book"`: the book's own event id for the game, once it was mapped.
	BookEventID *string `json:"bookEventId,omitempty"`

	// BookStatus What happened on one book in a comparison:
	// - `priced`: `result` is the book's 200 body (`sgp` is its price).
	// - `refused`: the book refused the parlay; `result` is its 422 body (`reasonCode`).
	// - `unresolved`: a leg did not resolve on that book (for example a different
	//   main line); `result` says which and why.
	// - `busy`, `no-price`: that book produced no usable answer; retry later.
	// - `timeout`: the book had not answered when `deadlineMs` ran out, or was not
	//   reached in time.
	// - `event-not-listed`: the book does not list this game for same game parlays.
	// - `ambiguous-event`: two games match (a doubleheader without `-G2`), or the
	//   book lists this matchup at a different start, so it is a different game.
	// - `unsupported`: the book does not price this sport or one of the legs (`reason`).
	// - `disabled`: that book's pricing is paused.
	// - `skipped`: `fanduel: "skip"`.
	//
	// New statuses can appear: handle the ones you know and treat the rest as "no price".
	BookStatus *SgpCompareBookStatus `json:"bookStatus,omitempty"`

	// Books Mode `"compare"`: every book asked, each with its `status`.
	Books []SgpCompareBook `json:"books,omitempty"`

	// Cached Mode `"book"`: `true` when `result` is a cached quote (no build counted).
	Cached  *bool   `json:"cached,omitempty"`
	EventID *string `json:"eventId,omitempty"`

	// Fair With `fair: true`: our estimate for this parlay, or `null` when nothing priced.
	Fair *SgpFairBlock `json:"fair,omitempty"`

	// HTTPStatus Mode `"book"`: the status `/sgp/build` would have answered with.
	HTTPStatus *float64 `json:"httpStatus,omitempty"`

	// Index The parlay's index in your `parlays` array.
	Index *float64 `json:"index,omitempty"`

	// LegsHash A hash of the sport and legs (as `meta.legsHash` on `compareSgp`), 16 hex
	// characters: the `legsHash` RestClient.getSgpHistory files this
	// parlay's quotes under.
	LegsHash *string `json:"legsHash,omitempty"`

	// Reason Mode `"book"`: why the book was left out or not priced, when there is no `result`.
	Reason *string `json:"reason,omitempty"`

	// Ref Your `ref`, when you sent one.
	Ref *string `json:"ref,omitempty"`

	// Result A book's `result` in a comparison: exactly the body `/sgp/build` returned for
	// that book. Which fields are set depends on the book's `status`: a priced 200
	// (`sgp`, `legs`, `meta`), a refused or unresolved 422 (`reasonCode`, `legs`,
	// ...), a no-price or busy 503 (`code`, `legs`, `meta`), or a short
	// `{ error, message }` (an unknown game, or too few legs resolved). Every field is
	// therefore optional here: branch on the book's `status` first.
	Result *SgpCompareBookResult `json:"result,omitempty"`

	// Status `"done"`: priced or answered. `"not-started"`: this parlay's pricing
	// would have begun more than about 20 seconds after the call began, so it was
	// not priced (except by any book answered from a cached quote); its builds are
	// given back while the minute they were counted in is still running. Send it
	// again.
	Status *string `json:"status,omitempty"`
}

SgpBatchResult One parlay's answer in a SgpBatchResponse, in request order. The fields after `status` depend on the batch's `mode`:

  • mode `"book"`: `book`, `bookStatus` and, as on a SgpCompareBook, `bookEventId`, `httpStatus`, `result` (the `/sgp/build` body), `cached`, `reason`;
  • mode `"compare"`: `best` and `books` (exactly what `compareSgp` returns).

With `fair: true`, `fair` is on every `done` result (`null` when nothing priced). A `not-started` parlay carries no fair block: `fair` is `null` in mode `"book"` and absent in mode `"compare"`.

type SgpBookSelection added in v0.9.0

type SgpBookSelection struct {
	MarketID    *string          `json:"marketId,omitempty"`
	SelectionID *json.RawMessage `json:"selectionId,omitempty"`
}

SgpBookSelection The book's own ids for a resolved SGP leg on DraftKings, Hard Rock, BetRivers, Bally Bet and BetMGM. Hard Rock's, BetRivers' and Bally Bet's are strings of digits; BetMGM's `marketId` reads `v1:<fixture>:<market>` or `v2:<fixture>:<market>`, and on NHL `sc:<fixture>:<builderId>` with the option string BetMGM's bet builder lists as `selectionId`.

type SgpBuildErrorBody

type SgpBuildErrorBody struct {
	// Code Repeats `reasonCode`.
	Code  *string `json:"code,omitempty"`
	Error *string `json:"error,omitempty"`

	// FailureCodes Only when FanDuel rejected the parlay with its own failure codes.
	FailureCodes []string `json:"failureCodes,omitempty"`

	// LegIndex Only for the `line-*` codes: the leg's index in your `legs` array.
	LegIndex *float64         `json:"legIndex,omitempty"`
	Legs     []SgpResolvedLeg `json:"legs,omitempty"`

	// LineEchoed Only for the `line-*` codes: the line FanDuel priced, exactly as it sent
	// it, or `null` when it sent none.
	LineEchoed *float64 `json:"lineEchoed,omitempty"`
	Message    *string  `json:"message,omitempty"`

	// Meta `meta` of a `buildSgp` response (200, the leg-resolution 422 and the refused
	// 422), and of the no-price 503, `SGP_BUSY` 503 and legacy 502 error bodies.
	//
	// The fields marked "API v2.348.0" are absent from older API versions.
	Meta       *SgpBuildMeta `json:"meta,omitempty"`
	ReasonCode *string       `json:"reasonCode,omitempty"`
	Success    *bool         `json:"success,omitempty"`
}

SgpBuildErrorBody The body of a thrown `buildSgp` failure that reached pricing, found on the error as `err.details`; `err.code` is `reasonCode`:

  • the no-price 503 (`ServiceBusyError`, `code` `no-price`): no usable answer came back; retry after `err.retryAfterMs`;
  • the 502 older API versions sent for every refusal (`OwlsInsightError`, status 502). Current API versions return a refused parlay as a 422 on SgpBuildResponse instead, with the same fields.

Match on `reasonCode`, `legIndex`, `lineEchoed` and `failureCodes`, never on `message`, which is prose.

type SgpBuildMeta

type SgpBuildMeta struct {
	// Book The book the legs were resolved and priced against (`"fanduel"`, `"draftkings"`, ...).
	Book *string `json:"book,omitempty"`

	// CatalogAgeMs The MINIMUM age, in ms, the catalog data already had at `resolvedAt`; the
	// true age can be higher. Omitted when the age is unknown. API v2.348.0.
	CatalogAgeMs *float64 `json:"catalogAgeMs,omitempty"`
	CommenceTime *string  `json:"commence_time,omitempty"`

	// EventID The event id as the API echoes it: a number on FanDuel, a string on every
	// other book (digits, or BetMGM NFL's `"6:43680"`).
	EventID *json.RawMessage `json:"eventId,omitempty"`

	// LegsHash On a priced 200 only (from `buildSgp`, and each priced book's `result` on
	// `compareSgp` and `batchSgp`): a hash of the sport and the legs as sent, 16
	// hex characters. The same parlay sent the same way always has the same hash,
	// so passing it as `legsHash` to RestClient.getSgpHistory narrows the
	// price history to this parlay. Absent on a refusal, a leg-resolution 422 and
	// the no-price and busy 503s. API v2.522.0.
	LegsHash *string `json:"legsHash,omitempty"`

	// LineUnverified `true` on a 200 that priced at least one leg whose line the book's
	// response does not confirm (FanDuel's alternate N-or-more strikeout rungs;
	// every BetMGM NHL leg, whose price answer names no leg). Absent when every
	// line was confirmed. API v2.348.0.
	LineUnverified *bool `json:"lineUnverified,omitempty"`

	// LineUnverifiedLegs With `lineUnverified`: the indexes of those legs in your `legs` array.
	// API v2.348.0.
	LineUnverifiedLegs []float64 `json:"lineUnverifiedLegs,omitempty"`
	Matchup            *string   `json:"matchup,omitempty"`

	// PriceRequestedAt When the price request went out, on the API server's clock. Present on
	// every 200, and on a refused 422, a no-price 503 or a legacy 502 only when
	// FanDuel returned a response. Absent on a leg-resolution 422 (nothing was
	// priced). API v2.348.0.
	PriceRequestedAt *string `json:"priceRequestedAt,omitempty"`

	// PricedAt When FanDuel's answer came back, on the API server's clock. Never earlier
	// than `priceRequestedAt`; this is the quote time to use. Same presence as
	// `priceRequestedAt`. API v2.348.0.
	PricedAt *string `json:"pricedAt,omitempty"`

	// ResolvedAt When the legs were resolved against FanDuel's catalog, before pricing.
	// API v2.348.0.
	ResolvedAt *string `json:"resolvedAt,omitempty"`

	// Timestamp The same instant as `resolvedAt`: when the legs were resolved, BEFORE
	// pricing. It is not the quote time; use `pricedAt` for that.
	Timestamp *string `json:"timestamp,omitempty"`
}

SgpBuildMeta `meta` of a `buildSgp` response (200, the leg-resolution 422 and the refused 422), and of the no-price 503, `SGP_BUSY` 503 and legacy 502 error bodies.

The fields marked "API v2.348.0" are absent from older API versions.

type SgpBuildParams

type SgpBuildParams struct {
	// EventID is the book's event id from GetSgpEvents, as a string. SgpEvent.EventID
	// is raw JSON: a number on FanDuel (send its digits) and a JSON string on every
	// other book, such as BetMGM NFL's "6:43680" (send it unquoted).
	EventID string `json:"eventId"`
	// Book is "fanduel", "draftkings", "hardrock", "betrivers", "ballybet" or
	// "betmgm". Any other value is a 400 with code SGP_BOOK_UNSUPPORTED.
	Book string `json:"book"`
	// Legs holds 2 to 12 legs.
	Legs []SgpLeg `json:"legs"`
	// Fair adds our fair-price estimate as Fair on a priced 200 (SgpFairBlock).
	// MVP and Hall of Fame plans: on the Rookie plan the build is a 403 with Code
	// SGP_TIER_REQUIRED, refused before anything is priced and not counted against
	// any limit (below Rookie the builder itself is a 403 FORBIDDEN); a 503
	// SGP_FEATURE_DISABLED means the fair price is not switched on yet.
	Fair bool `json:"fair,omitempty"`
}

SgpBuildParams is the body of BuildSgp.

type SgpBuildResponse

type SgpBuildResponse struct {
	// Code Repeats `reasonCode`.
	Code *string `json:"code,omitempty"`

	// Error Present when `success: false`.
	Error *string `json:"error,omitempty"`

	// FailureCodes Refused parlay only, when FanDuel rejected it with its own failure codes
	// (e.g. `SELECTION_NOT_FOUND`).
	FailureCodes []string `json:"failureCodes,omitempty"`

	// Fair Our estimate of a same game parlay's price (`fair` on SgpBuildResponse
	// and SgpCompareResponse), built from de-vigged leg probabilities. It is
	// never a sportsbook's price: the book's own quote stays in `sgp`.
	//
	// The legs' probabilities are multiplied and adjusted by how the book itself
	// priced these legs together: `basis.correlationFactor` is the product of the
	// book's single prices divided by its parlay price (on compare, the median across
	// the books that priced). That factor also keeps the book's parlay surcharge over
	// its singles (`basis.marginAdjusted` is `false`), so the estimate removes the
	// singles' vig, not that surcharge.
	//
	// `price` is `null`, with `why`, when any leg has no fair (`coverage.missingLegs`
	// and each leg's `why` say which), when no book returned a usable single price
	// for every leg or the factor fell outside 0.25 to 4, once the game has started,
	// or when the fair could not be worked out in time.
	Fair *SgpFairBlock `json:"fair,omitempty"`

	// LegIndex Refused parlay, `line-*` codes only: the leg's index in your `legs` array.
	LegIndex *float64         `json:"legIndex,omitempty"`
	Legs     []SgpResolvedLeg `json:"legs,omitempty"`

	// LineEchoed Refused parlay, `line-*` codes only: the line FanDuel priced, exactly as it
	// sent it, or `null` when it sent none.
	LineEchoed *float64 `json:"lineEchoed,omitempty"`

	// Message Refused parlay (422) only: human-readable prose. Match on `reasonCode`,
	// `legIndex`, `lineEchoed` and `failureCodes`, never on this.
	Message *string `json:"message,omitempty"`

	// Meta `meta` of a `buildSgp` response (200, the leg-resolution 422 and the refused
	// 422), and of the no-price 503, `SGP_BUSY` 503 and legacy 502 error bodies.
	//
	// The fields marked "API v2.348.0" are absent from older API versions.
	Meta *SgpBuildMeta `json:"meta,omitempty"`

	// Pricing `"<book>-slip"` on a 200 (`"fanduel-slip"`, `"draftkings-slip"`, ...): the
	// price is that book's own bet-slip answer.
	Pricing *string `json:"pricing,omitempty"`

	// ReasonCode Present when `success: false`: `"unresolved"` on a leg-resolution 422,
	// otherwise the SgpReasonCode of a refused parlay (never `no-price`,
	// which is thrown as a 503). Absent from older API versions' leg-resolution
	// 422.
	ReasonCode *string `json:"reasonCode,omitempty"`

	// Sgp The computed parlay price from a successful `buildSgp` call.
	Sgp     *SgpPrice `json:"sgp,omitempty"`
	Success *bool     `json:"success,omitempty"`
}

SgpBuildResponse Response from `POST /api/v1/{sport}/sgp/build`.

Also what a 422 returns. A 422 is NOT thrown, so check `success` before reading `sgp`, then branch on `reasonCode` (the API sends it on every 422 since the refusal-status change; older API versions sent none on a leg-resolution 422 and threw the refusal as a 502):

  • `"unresolved"`: a leg could not be resolved. No `sgp`, and a `reason` on each unresolved leg naming which lines FanDuel does offer or why the category, side or leg type cannot work. Fix the legs.
  • any other SgpReasonCode (`line-mismatch`, `bet-failure`, `not-placeable`, ...): FanDuel judged the slip and refused it, or its answer did not confirm the legs as sent. The request was valid but cannot be priced as specified, so do not resend the same body unchanged. `legIndex`, `lineEchoed` and `failureCodes` are set where they apply; `message` is prose.

Every other failure throws: a `ServiceBusyError` with `code` `no-price` when no usable answer came back (503, retry after `retryAfterMs`), one with an `SGP_*` code (SgpErrorBody in `details`), or, from older API versions, an `OwlsInsightError` with status 502 whose `details` is the SgpBuildErrorBody.

type SgpCombo added in v0.10.0

type SgpCombo struct {
	// Legs Every leg, in the combo's order.
	Legs []SgpComboLeg `json:"legs,omitempty"`

	// Ticker The combo ticker sent (upper case), or `null` when `legs` were sent.
	Ticker *string `json:"ticker,omitempty"`
}

SgpCombo The combo of a SgpComboCompareResponse.

type SgpComboCompareParams added in v0.10.0

type SgpComboCompareParams struct {
	// Venue is the prediction market the combo is on: "kalshi", the only one.
	// Empty sends "kalshi".
	Venue string `json:"venue"`
	// ComboTicker is a Kalshi combo market's ticker (a multivariate market, for
	// example "KXMVECROSSCATEGORY-S2026ZJDATBFMVOSS4HNQ-QRB4CF6A6"): its legs are
	// read from Kalshi. Lower case is accepted.
	ComboTicker string `json:"comboTicker,omitempty"`
	// Legs names the combo's legs one by one: 2 to 12, each a different market.
	Legs []SgpComboLegInput `json:"legs,omitempty"`
	// Books, DeadlineMs, FanDuel, Fair and Fresh are SgpCompareParams': the books
	// to ask (empty: every book), how long to wait for them (1000 to 15000 ms; 0
	// sends nothing, the server's default is 6000), "cold" or "skip" for FanDuel,
	// our fair-price estimate as Compare.Fair, and never a cached quote.
	Books      []string `json:"books,omitempty"`
	DeadlineMs int      `json:"deadlineMs,omitempty"`
	FanDuel    string   `json:"fanduel,omitempty"`
	Fair       bool     `json:"fair,omitempty"`
	Fresh      bool     `json:"fresh,omitempty"`
}

SgpComboCompareParams is the body of CompareSgpCombo. Set ComboTicker OR Legs, never both (the API answers a 400 COMBO_INVALID otherwise).

type SgpComboCompareResponse added in v0.10.0

type SgpComboCompareResponse struct {
	// Code A refusal only: which one. New codes can appear.
	Code *string `json:"code,omitempty"`

	// Combo The combo of a SgpComboCompareResponse.
	Combo *SgpCombo `json:"combo,omitempty"`

	// Compare Response from `POST /api/v1/{sport}/sgp/compare`.
	Compare *SgpCompareResponse `json:"compare,omitempty"`

	// Error A refusal only.
	Error *string `json:"error,omitempty"`

	// EventID OUR event id for the game the legs were matched to.
	EventID *string `json:"eventId,omitempty"`

	// Games `COMBO_MULTI_GAME` only: the legs grouped by game, in first-seen order.
	Games []SgpComboGame `json:"games,omitempty"`

	// Message A refusal only: human-readable prose.
	Message *string `json:"message,omitempty"`
	Success *bool   `json:"success,omitempty"`

	// Venue `"kalshi"`.
	Venue *string `json:"venue,omitempty"`

	// VenuePrice The combo market's own quote on Kalshi when `comboTicker` was sent;
	// `null` when `legs` were sent.
	VenuePrice *SgpComboVenuePrice `json:"venuePrice,omitempty"`
}

SgpComboCompareResponse Response from `POST /api/v1/sgp/combo-compare`: a 200, or a returned 422 refusal. Branch on `success` first.

  • `success: true`: `venue`, `combo`, `venuePrice`, `eventId` and `compare` are set. `compare` is exactly what RestClient.compareSgp returns for the translated legs (`combo.legs[].translated`) on `eventId`.
  • `success: false` (a 422): `code` says which refusal (SgpComboErrorCode), `error` and `message` say it in words. `combo` (and `venuePrice`) come with every refusal but `COMBO_NOT_A_COMBO`, and `games` with `COMBO_MULTI_GAME`. The API sends `COMBO_NOT_A_COMBO` without a `success` field; the SDK sets it to `false` there too.

type SgpComboGame added in v0.10.0

type SgpComboGame struct {
	// EventDateEt The game's US Eastern date, `YYYY-MM-DD`.
	EventDateEt *string `json:"eventDateEt,omitempty"`

	// Legs Indexes into `combo.legs`.
	Legs  []float64 `json:"legs,omitempty"`
	Sport *string   `json:"sport,omitempty"`

	// Teams Kalshi's two clubs, in ticker order (away, home in US sports).
	Teams []string `json:"teams,omitempty"`
}

SgpComboGame One game of a `COMBO_MULTI_GAME` refusal: the legs (by index) that name it.

type SgpComboLeg added in v0.10.0

type SgpComboLeg struct {
	// MarketTicker The leg's market ticker, upper case.
	MarketTicker *string `json:"marketTicker,omitempty"`
	Side         *string `json:"side,omitempty"`

	// Title Kalshi's own wording of the YES outcome (`"Jackson Chourio: 2+"`) when the
	// leg's market was read, otherwise `null`.
	Title *string `json:"title,omitempty"`

	// Translated The same game parlay leg it stands for (what `buildSgp` and `compareSgp`
	// take), or `null` when it has none. A game winner YES is that team's
	// moneyline; a spread YES the team at minus the line (NO: the other team at
	// plus it); a total or team total YES the over (NO the under); a player
	// `"N+"` market YES the over N - 0.5 (NO the under).
	Translated *SgpLeg `json:"translated,omitempty"`

	// Why Why the leg has no translation, or `null`. A leg whose market was not
	// read, because the call stopped on the tickers alone (`COMBO_MULTI_GAME`,
	// `COMBO_EVENT_NOT_FOUND`, or `COMBO_LEGS_UNSUPPORTED` for another leg's
	// series or clubs), has `translated` and `why` both `null`.
	Why *string `json:"why,omitempty"`
}

SgpComboLeg One Kalshi leg of a combo as the API read and translated it.

type SgpComboLegInput added in v0.10.0

type SgpComboLegInput struct {
	// MarketTicker is the leg's Kalshi market ticker, for example
	// "KXNHLGAME-26OCT28PHIWSH-WSH" (lower case is accepted).
	MarketTicker string `json:"marketTicker"`
	// Side is the side of that market the combo takes: "yes" or "no".
	Side string `json:"side"`
}

SgpComboLegInput is one leg of a Kalshi combo, named by its own market in SgpComboCompareParams.Legs.

type SgpComboVenuePrice added in v0.10.0

type SgpComboVenuePrice struct {
	// Status The market's status on Kalshi (`"active"`, ...), or `null`.
	Status *string  `json:"status,omitempty"`
	YesAsk *float64 `json:"yesAsk,omitempty"`
	YesBid *float64 `json:"yesBid,omitempty"`
}

SgpComboVenuePrice The combo market's own public quote on Kalshi, in dollars per contract (0 to 1), as context. Never a sportsbook price. A combo usually has no resting quote, and then both prices are `null`.

type SgpCompareBook added in v0.10.0

type SgpCompareBook struct {
	Book *string `json:"book,omitempty"`

	// BookEventID The book's own event id for the game, once it was mapped.
	BookEventID *string `json:"bookEventId,omitempty"`

	// Cached `true` when `result` is a cached quote (no build was counted for it).
	Cached *bool `json:"cached,omitempty"`

	// HTTPStatus The status `/sgp/build` would have answered with, when the book was priced
	// or refused.
	HTTPStatus *float64 `json:"httpStatus,omitempty"`

	// Label The book's display name (`"FanDuel"`, `"Hard Rock"`, ...).
	Label *string `json:"label,omitempty"`

	// Ms How long this book took, in ms (mapping and pricing).
	Ms *float64 `json:"ms,omitempty"`

	// Reason Why the book was left out or not priced, when there is no `result` to say it.
	Reason *string `json:"reason,omitempty"`

	// Result A book's `result` in a comparison: exactly the body `/sgp/build` returned for
	// that book. Which fields are set depends on the book's `status`: a priced 200
	// (`sgp`, `legs`, `meta`), a refused or unresolved 422 (`reasonCode`, `legs`,
	// ...), a no-price or busy 503 (`code`, `legs`, `meta`), or a short
	// `{ error, message }` (an unknown game, or too few legs resolved). Every field is
	// therefore optional here: branch on the book's `status` first.
	Result *SgpCompareBookResult `json:"result,omitempty"`

	// Status What happened on one book in a comparison:
	// - `priced`: `result` is the book's 200 body (`sgp` is its price).
	// - `refused`: the book refused the parlay; `result` is its 422 body (`reasonCode`).
	// - `unresolved`: a leg did not resolve on that book (for example a different
	//   main line); `result` says which and why.
	// - `busy`, `no-price`: that book produced no usable answer; retry later.
	// - `timeout`: the book had not answered when `deadlineMs` ran out, or was not
	//   reached in time.
	// - `event-not-listed`: the book does not list this game for same game parlays.
	// - `ambiguous-event`: two games match (a doubleheader without `-G2`), or the
	//   book lists this matchup at a different start, so it is a different game.
	// - `unsupported`: the book does not price this sport or one of the legs (`reason`).
	// - `disabled`: that book's pricing is paused.
	// - `skipped`: `fanduel: "skip"`.
	//
	// New statuses can appear: handle the ones you know and treat the rest as "no price".
	Status *SgpCompareBookStatus `json:"status,omitempty"`
}

SgpCompareBook One book's answer in a SgpCompareResponse.

type SgpCompareBookResult added in v0.10.0

type SgpCompareBookResult struct {
	Code         *string          `json:"code,omitempty"`
	Error        *string          `json:"error,omitempty"`
	FailureCodes []string         `json:"failureCodes,omitempty"`
	LegIndex     *float64         `json:"legIndex,omitempty"`
	Legs         []SgpResolvedLeg `json:"legs,omitempty"`
	LineEchoed   *float64         `json:"lineEchoed,omitempty"`
	Message      *string          `json:"message,omitempty"`

	// Meta `meta` of a book's `result` in a comparison: the build's meta, plus the cache
	// label when the answer was a cached quote.
	Meta       *SgpCompareResultMeta `json:"meta,omitempty"`
	Pricing    *string               `json:"pricing,omitempty"`
	ReasonCode *string               `json:"reasonCode,omitempty"`

	// Sgp The computed parlay price from a successful `buildSgp` call.
	Sgp     *SgpPrice `json:"sgp,omitempty"`
	Success *bool     `json:"success,omitempty"`
}

SgpCompareBookResult A book's `result` in a comparison: exactly the body `/sgp/build` returned for that book. Which fields are set depends on the book's `status`: a priced 200 (`sgp`, `legs`, `meta`), a refused or unresolved 422 (`reasonCode`, `legs`, ...), a no-price or busy 503 (`code`, `legs`, `meta`), or a short `{ error, message }` (an unknown game, or too few legs resolved). Every field is therefore optional here: branch on the book's `status` first.

type SgpCompareBookStatus added in v0.10.0

type SgpCompareBookStatus = string

SgpCompareBookStatus What happened on one book in a comparison:

  • `priced`: `result` is the book's 200 body (`sgp` is its price).
  • `refused`: the book refused the parlay; `result` is its 422 body (`reasonCode`).
  • `unresolved`: a leg did not resolve on that book (for example a different main line); `result` says which and why.
  • `busy`, `no-price`: that book produced no usable answer; retry later.
  • `timeout`: the book had not answered when `deadlineMs` ran out, or was not reached in time.
  • `event-not-listed`: the book does not list this game for same game parlays.
  • `ambiguous-event`: two games match (a doubleheader without `-G2`), or the book lists this matchup at a different start, so it is a different game.
  • `unsupported`: the book does not price this sport or one of the legs (`reason`).
  • `disabled`: that book's pricing is paused.
  • `skipped`: `fanduel: "skip"`.

New statuses can appear: handle the ones you know and treat the rest as "no price".

type SgpCompareMeta added in v0.10.0

type SgpCompareMeta struct {
	// BooksAsked Books in `books`.
	BooksAsked *float64 `json:"booksAsked,omitempty"`

	// BooksCached Books answered from a cached quote.
	BooksCached *float64 `json:"booksCached,omitempty"`

	// BooksPriced Books with `status: "priced"`.
	BooksPriced *float64 `json:"booksPriced,omitempty"`

	// BooksQuoted Books that reached a book's price request (each counted one build).
	BooksQuoted *float64 `json:"booksQuoted,omitempty"`

	// DeadlineMs The deadline used, in ms.
	DeadlineMs *float64 `json:"deadlineMs,omitempty"`

	// LegsHash A hash of the sport and legs, 16 hex characters: the same parlay always
	// hashes the same. Each priced book's `result.meta.legsHash` carries the same
	// value; pass it to RestClient.getSgpHistory as `legsHash`.
	LegsHash  *string `json:"legsHash,omitempty"`
	Timestamp *string `json:"timestamp,omitempty"`
}

SgpCompareMeta `meta` of a SgpCompareResponse.

type SgpCompareParams added in v0.10.0

type SgpCompareParams struct {
	// EventID is OUR event id for the game, as GetOdds gives it:
	// {sport}:{Away}@{Home}-{YYYYMMDD}, with -G2 for the second game of a
	// doubleheader. Not a book's id from GetSgpEvents.
	EventID string `json:"eventId"`
	// Legs holds the same legs BuildSgp takes, 1 to 12, sent to every book.
	Legs []SgpLeg `json:"legs"`
	// Books are the books to ask ("fanduel", "draftkings", "hardrock",
	// "betrivers", "ballybet", "betmgm"). Empty means every book.
	Books []string `json:"books,omitempty"`
	// DeadlineMs is how long to wait for the books, 1000 to 15000 ms. 0 sends
	// nothing: the server's default is 6000. Keep the client's timeout above it.
	DeadlineMs int `json:"deadlineMs,omitempty"`
	// FanDuel is "cold" (the server's default: FanDuel prices in a fresh browser
	// session and is given at least 12000 ms) or "skip" (FanDuel is left out,
	// status "skipped").
	FanDuel string `json:"fanduel,omitempty"`
	// Fair adds our fair-price estimate as Fair, built from every book that
	// priced (SgpFairBlock).
	Fair bool `json:"fair,omitempty"`
	// Fresh never uses a cached quote: every book is priced now.
	Fresh bool `json:"fresh,omitempty"`
}

SgpCompareParams is the body of CompareSgp.

type SgpCompareResponse added in v0.10.0

type SgpCompareResponse struct {
	// Best The highest decimal price among the books that priced, or `null` when none did.
	Best *struct {
		American *float64 `json:"american,omitempty"`
		Book     *string  `json:"book,omitempty"`
		Decimal  *float64 `json:"decimal,omitempty"`
	} `json:"best,omitempty"`

	// Books Every book asked, priced or not, each with its `status`.
	Books []SgpCompareBook `json:"books,omitempty"`

	// EventID Our event id, as sent.
	EventID *string `json:"eventId,omitempty"`

	// Fair With `fair: true`: our estimate from every book that priced, or `null` when
	// no book priced. Absent without `fair: true`.
	Fair *SgpFairBlock `json:"fair,omitempty"`

	// Legs The legs, as sent.
	Legs []SgpLeg `json:"legs,omitempty"`

	// Meta `meta` of a SgpCompareResponse.
	Meta    *SgpCompareMeta `json:"meta,omitempty"`
	Sport   *string         `json:"sport,omitempty"`
	Success *bool           `json:"success,omitempty"`
}

SgpCompareResponse Response from `POST /api/v1/{sport}/sgp/compare`.

type SgpCompareResultMeta added in v0.10.0

type SgpCompareResultMeta struct {
	// Book The book the legs were resolved and priced against (`"fanduel"`, `"draftkings"`, ...).
	Book *string `json:"book,omitempty"`

	// CacheAgeMs With `cached`: the quote's age in ms.
	CacheAgeMs *float64 `json:"cacheAgeMs,omitempty"`

	// Cached `true` when this answer is a quote the book priced for exactly these legs
	// and this game in the last 15 seconds (`pricedAt` still says when).
	Cached *bool `json:"cached,omitempty"`

	// CatalogAgeMs The MINIMUM age, in ms, the catalog data already had at `resolvedAt`; the
	// true age can be higher. Omitted when the age is unknown. API v2.348.0.
	CatalogAgeMs *float64 `json:"catalogAgeMs,omitempty"`
	CommenceTime *string  `json:"commence_time,omitempty"`

	// EventID The event id as the API echoes it: a number on FanDuel, a string on every
	// other book (digits, or BetMGM NFL's `"6:43680"`).
	EventID *json.RawMessage `json:"eventId,omitempty"`

	// LegsHash On a priced 200 only (from `buildSgp`, and each priced book's `result` on
	// `compareSgp` and `batchSgp`): a hash of the sport and the legs as sent, 16
	// hex characters. The same parlay sent the same way always has the same hash,
	// so passing it as `legsHash` to RestClient.getSgpHistory narrows the
	// price history to this parlay. Absent on a refusal, a leg-resolution 422 and
	// the no-price and busy 503s. API v2.522.0.
	LegsHash *string `json:"legsHash,omitempty"`

	// LineUnverified `true` on a 200 that priced at least one leg whose line the book's
	// response does not confirm (FanDuel's alternate N-or-more strikeout rungs;
	// every BetMGM NHL leg, whose price answer names no leg). Absent when every
	// line was confirmed. API v2.348.0.
	LineUnverified *bool `json:"lineUnverified,omitempty"`

	// LineUnverifiedLegs With `lineUnverified`: the indexes of those legs in your `legs` array.
	// API v2.348.0.
	LineUnverifiedLegs []float64 `json:"lineUnverifiedLegs,omitempty"`
	Matchup            *string   `json:"matchup,omitempty"`

	// PriceRequestedAt When the price request went out, on the API server's clock. Present on
	// every 200, and on a refused 422, a no-price 503 or a legacy 502 only when
	// FanDuel returned a response. Absent on a leg-resolution 422 (nothing was
	// priced). API v2.348.0.
	PriceRequestedAt *string `json:"priceRequestedAt,omitempty"`

	// PricedAt When FanDuel's answer came back, on the API server's clock. Never earlier
	// than `priceRequestedAt`; this is the quote time to use. Same presence as
	// `priceRequestedAt`. API v2.348.0.
	PricedAt *string `json:"pricedAt,omitempty"`

	// ResolvedAt When the legs were resolved against FanDuel's catalog, before pricing.
	// API v2.348.0.
	ResolvedAt *string `json:"resolvedAt,omitempty"`

	// Timestamp The same instant as `resolvedAt`: when the legs were resolved, BEFORE
	// pricing. It is not the quote time; use `pricedAt` for that.
	Timestamp *string `json:"timestamp,omitempty"`
}

SgpCompareResultMeta `meta` of a book's `result` in a comparison: the build's meta, plus the cache label when the answer was a cached quote.

type SgpEvent

type SgpEvent struct {
	Away *string `json:"away,omitempty"`
	// Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set
	CommenceTime *string `json:"commence_time,omitempty"`

	// Competition FanDuel's competition name; empty when unknown.
	Competition *string `json:"competition,omitempty"`

	// EventID The book's event id: a number on the wire on FanDuel, a string on every
	// other book (digits, or BetMGM NFL's `"6:43680"`). Pass it to `buildSgp` as
	// `String(event.eventId)`.
	EventID *json.RawMessage `json:"eventId,omitempty"`
	Home    *string          `json:"home,omitempty"`

	// Matchup `"Away @ Home"`.
	Matchup *string `json:"matchup,omitempty"`

	// Start Scheduled start (ISO 8601), or `null` when FanDuel lists none.
	Start *string `json:"start,omitempty"`
}

SgpEvent One bookable event returned by `getSgpEvents`.

type SgpEventsResponse

type SgpEventsResponse struct {
	// Book The book the events belong to (`"fanduel"`).
	Book   *string    `json:"book,omitempty"`
	Count  *float64   `json:"count,omitempty"`
	Events []SgpEvent `json:"events,omitempty"`
	Meta   *struct {
		Timestamp *string `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`
	Sport   *string `json:"sport,omitempty"`
	Success *bool   `json:"success,omitempty"`
}

SgpEventsResponse Response from `GET /api/v1/{sport}/sgp/events`.

type SgpFairBlock added in v0.10.0

type SgpFairBlock struct {
	Basis *struct {
		// Correlation `"book-implied"`: read off the books' own quotes.
		Correlation *string `json:"correlation,omitempty"`

		// CorrelationFactor The correlation factor used, or `null` when none was usable.
		CorrelationFactor *float64 `json:"correlationFactor,omitempty"`

		// CorrelationSources Each priced book's own factor (`null` when that book's quote gave none).
		CorrelationSources []struct {
			Book   *string  `json:"book,omitempty"`
			Factor *float64 `json:"factor,omitempty"`
		} `json:"correlationSources,omitempty"`

		// Legs `"fair-v1"`: each leg's `source` says whether Pinnacle anchored it.
		Legs *string `json:"legs,omitempty"`

		// MarginAdjusted `false`: the factor keeps the book's parlay surcharge over its singles.
		MarginAdjusted *bool `json:"marginAdjusted,omitempty"`
	} `json:"basis,omitempty"`

	// BooksDisagree Some leg's fair is one other books' prices disagree with (each leg says which).
	BooksDisagree *bool `json:"booksDisagree,omitempty"`

	// ComputedAt ISO 8601: when the estimate was worked out.
	ComputedAt *string `json:"computedAt,omitempty"`
	Coverage   *struct {
		Legs *float64 `json:"legs,omitempty"`

		// MissingLegs Indexes (in your `legs` array) of the legs with no fair.
		MissingLegs []float64 `json:"missingLegs,omitempty"`
		WithFair    *float64  `json:"withFair,omitempty"`
	} `json:"coverage,omitempty"`

	// Estimate Always `true`: this is an estimate, not a book's price.
	Estimate *bool `json:"estimate,omitempty"`

	// Independent The same legs treated as unrelated (no correlation), or `null`.
	Independent *SgpFairPrice `json:"independent,omitempty"`

	// Label `"Owls Insight estimate, not a sportsbook price"`.
	Label *string      `json:"label,omitempty"`
	Legs  []SgpFairLeg `json:"legs,omitempty"`

	// Price The fair price of the parlay with the books' correlation, or `null` with `why`.
	Price *SgpFairPrice `json:"price,omitempty"`

	// Why Why `price` is `null`, or `null` when there is a price. Known values:
	// `missing-leg-fair`, `no-correlation`, `not-a-probability`, `in-play`. New
	// values can appear.
	Why *string `json:"why,omitempty"`
}

SgpFairBlock Our estimate of a same game parlay's price (`fair` on SgpBuildResponse and SgpCompareResponse), built from de-vigged leg probabilities. It is never a sportsbook's price: the book's own quote stays in `sgp`.

The legs' probabilities are multiplied and adjusted by how the book itself priced these legs together: `basis.correlationFactor` is the product of the book's single prices divided by its parlay price (on compare, the median across the books that priced). That factor also keeps the book's parlay surcharge over its singles (`basis.marginAdjusted` is `false`), so the estimate removes the singles' vig, not that surcharge.

`price` is `null`, with `why`, when any leg has no fair (`coverage.missingLegs` and each leg's `why` say which), when no book returned a usable single price for every leg or the factor fell outside 0.25 to 4, once the game has started, or when the fair could not be worked out in time.

type SgpFairLeg added in v0.10.0

type SgpFairLeg struct {
	// AgeMs Age in ms of the fair board the leg was read from, or `null`.
	AgeMs *float64 `json:"ageMs,omitempty"`

	// BooksDisagree Other books' prices disagree with this leg's fair.
	BooksDisagree *bool `json:"booksDisagree,omitempty"`

	// LegIndex The leg's index in your `legs` array.
	LegIndex *float64 `json:"legIndex,omitempty"`

	// Line The number the fair is at (the spread or total point, the prop line);
	// `null` for a moneyline and without a fair.
	Line *float64 `json:"line,omitempty"`

	// PinnacleLine With `why: "line-not-at-pinnacle-number"`: the number Pinnacle has, for
	// the leg's side.
	PinnacleLine *float64 `json:"pinnacleLine,omitempty"`

	// Probability The leg's fair (no-vig) probability, or `null` with `why` when it has none.
	Probability *float64 `json:"probability,omitempty"`

	// Source How the fair was made: `"pinnacle"` (Pinnacle-anchored) or, for a
	// moneyline, `"consensus"` (three or more books agreed). `null` without a fair.
	Source *string `json:"source,omitempty"`

	// Why Why the leg has no fair, or `null` when it has one. Known values:
	// `fair-board-unavailable` (no board, or one too old to use),
	// `game-not-on-fair-board`, `ambiguous-game`, `team-not-matched`,
	// `side-not-matched`, `no-line`, `whole-number-line`,
	// `line-not-at-pinnacle-number` (see `pinnacleLine`),
	// `category-not-on-fair-board`, `prop-not-on-fair-board`, `ambiguous-player`,
	// `no-fair`, `in-play`, `fair-unavailable` (not worked out in time). New
	// values can appear.
	Why *string `json:"why,omitempty"`
}

SgpFairLeg One leg's fair probability in an SgpFairBlock: our Fair v1 fair for the leg, the one `/api/v1/{sport}/fair` serves for a moneyline, spread or total (a spread or total only at that fair's own number, never a whole number) and the one `/api/v1/{sport}/props/ev` scores a player prop against (x.5 lines).

type SgpFairPrice added in v0.10.0

type SgpFairPrice struct {
	American    *float64 `json:"american,omitempty"`
	Decimal     *float64 `json:"decimal,omitempty"`
	Probability *float64 `json:"probability,omitempty"`
}

SgpFairPrice A probability with its prices: decimal odds, and American odds rounded to a whole number.

type SgpFdSelection

type SgpFdSelection struct {
	MarketID    *string  `json:"marketId,omitempty"`
	SelectionID *float64 `json:"selectionId,omitempty"`
}

SgpFdSelection The FanDuel selection a resolved SGP leg maps to. `marketId` is specific to the FanDuel state that priced it (New Jersey); `selectionId` is the same in every state.

type SgpHistoryFair added in v0.10.0

type SgpHistoryFair struct {
	// CorrelationFactor The book-implied correlation factor (SgpFairBlock `basis`), or `null`.
	CorrelationFactor *float64 `json:"correlationFactor,omitempty"`
	Decimal           *float64 `json:"decimal,omitempty"`
}

SgpHistoryFair Our fair-price estimate stored with a quote: the decimal price and the correlation factor it used. Only a `buildSgp` quote made with `fair: true` can carry one: `compareSgp`, `compareSgpCombo` and `batchSgp` record their quotes before the fair price is worked out, so theirs never do, whatever `fair` was sent.

type SgpHistoryLeg added in v0.10.0

type SgpHistoryLeg struct {
	Category *string `json:"category,omitempty"`

	// Description How the book named the selection it priced.
	Description *string  `json:"description,omitempty"`
	Line        *float64 `json:"line,omitempty"`
	Period      *string  `json:"period,omitempty"`
	Player      *string  `json:"player,omitempty"`
	Point       *float64 `json:"point,omitempty"`
	Selection   *string  `json:"selection,omitempty"`

	// SelectionIds The book's ids for a leg's selection in the price history.
	SelectionIds *SgpHistorySelectionIds `json:"selection_ids,omitempty"`
	Side         *string                 `json:"side,omitempty"`
	Team         *string                 `json:"team,omitempty"`
	Type         *string                 `json:"type,omitempty"`
}

SgpHistoryLeg One leg of a quote in the price history: the leg as sent (`type`, `selection`, `point`, `team`, `period`, `player`, `category`, `side`, `line`, whichever it had), the book's `description` of it, and the book's own ids for the selection.

type SgpHistoryMeta added in v0.10.0

type SgpHistoryMeta struct {
	// Book When the game was named by a book and its event id (lowercased).
	Book *string `json:"book,omitempty"`

	// BookEventID With `book`.
	BookEventID *string `json:"bookEventId,omitempty"`

	// Count Quotes returned.
	Count *float64 `json:"count,omitempty"`

	// EventID When the game was named by our event id.
	EventID *string `json:"eventId,omitempty"`

	// From ISO 8601: the window's start (inclusive).
	From *string `json:"from,omitempty"`

	// LegsHash When the call named one parlay.
	LegsHash *string `json:"legsHash,omitempty"`

	// Limit The limit applied (100 when none was sent).
	Limit *float64 `json:"limit,omitempty"`

	// To ISO 8601: the window's end (exclusive).
	To *string `json:"to,omitempty"`
}

SgpHistoryMeta `meta` of a SgpHistoryResponse: the query as the API applied it.

type SgpHistoryParams added in v0.10.0

type SgpHistoryParams struct {
	// EventID is OUR event id for the game, as GetOdds gives it
	// ({sport}:{Away}@{Home}-{YYYYMMDD}).
	EventID string
	// Book, with BookEventID, names the game by one book's own event id
	// ("fanduel", "draftkings", "hardrock", "betrivers", "ballybet", "betmgm"):
	// that book's quotes for it.
	Book string
	// BookEventID is the book's own event id, as GetSgpEvents lists it and BuildSgp
	// takes it (SgpBuildParams.EventID): FanDuel's numeric id as its digits, every
	// other book's id unquoted. SgpEvent.EventID and SgpBuildMeta.EventID are raw
	// JSON: take a string id with json.Unmarshal into a string and FanDuel's number
	// as string(raw), or reuse the EventID you sent to BuildSgp. string(raw) of a
	// string id keeps its quotes, and a number decoded into any prints as 3.5989831e+07:
	// either matches no quote, so the history comes back empty rather than refused.
	BookEventID string
	// LegsHash narrows the history to one parlay: the 16 hex characters of
	// Meta.LegsHash on a priced BuildSgp answer (or on CompareSgp and BatchSgp).
	LegsHash string
	// From is the ISO 8601 start of the window (inclusive). Empty: 24 hours
	// before To.
	From string
	// To is the ISO 8601 end of the window (exclusive). Empty: now (or 24 hours
	// after From, if that is earlier). The window is at most 7 days.
	To string
	// Limit is at most this many quotes, 1 to 500. 0 sends nothing: the server's
	// default is 100.
	Limit int
}

SgpHistoryParams selects GetSgpHistory. Name the game one way, never both: by OUR event id (EventID), or by a book and that book's own event id (Book and BookEventID). The API checks the fields and answers a 400 (Message says which), for example for the game named both ways or not at all, a window over 7 days, a Limit outside 1 to 500 or a malformed LegsHash.

type SgpHistoryPrice added in v0.10.0

type SgpHistoryPrice struct {
	American *float64 `json:"american,omitempty"`
	Decimal  *float64 `json:"decimal,omitempty"`
}

SgpHistoryPrice A price in the SGP price history: American and decimal odds. Either is `null` only when the stored value is unreadable.

type SgpHistoryQuote added in v0.10.0

type SgpHistoryQuote struct {
	// Book The book that priced it.
	Book *string `json:"book,omitempty"`

	// BookEventID The book's own event id for the game, as a string.
	BookEventID *string `json:"bookEventId,omitempty"`

	// EventID OUR event id for the game, or `null` when it could not be derived.
	EventID *string `json:"eventId,omitempty"`

	// Fair Our estimate, when the quote carried one, otherwise `null`. Only a
	// `buildSgp` quote made with `fair: true` can (and only when the estimate
	// was made): quotes from `compareSgp`, `compareSgpCombo` and `batchSgp`
	// are recorded before the fair price exists, so theirs are always `null`.
	Fair *SgpHistoryFair `json:"fair,omitempty"`
	Legs []SgpHistoryLeg `json:"legs,omitempty"`

	// LegsHash The parlay's legs hash (`meta.legsHash` on the quote).
	LegsHash *string `json:"legsHash,omitempty"`

	// QuotedAt ISO 8601: when the book priced the parlay (`meta.pricedAt` on the quote).
	QuotedAt *string `json:"quotedAt,omitempty"`

	// Sgp A price in the SGP price history: American and decimal odds. Either is
	// `null` only when the stored value is unreadable.
	Sgp *SgpHistoryPrice `json:"sgp,omitempty"`

	// Singles Each leg's own price, in `legs` order (`null` for a leg with none), or
	// `null` when none was stored.
	Singles []*SgpHistoryPrice `json:"singles,omitempty"`

	// Via The call that priced it.
	Via *string `json:"via,omitempty"`
}

SgpHistoryQuote One priced quote in the price history.

type SgpHistoryResponse added in v0.10.0

type SgpHistoryResponse struct {
	// Meta `meta` of a SgpHistoryResponse: the query as the API applied it.
	Meta *SgpHistoryMeta `json:"meta,omitempty"`

	// Quotes Newest first. Only the calling account's own priced quotes.
	Quotes  []SgpHistoryQuote `json:"quotes,omitempty"`
	Sport   *string           `json:"sport,omitempty"`
	Success *bool             `json:"success,omitempty"`
}

SgpHistoryResponse Response from `GET /api/v1/{sport}/sgp/history`.

type SgpHistorySelectionIds added in v0.10.0

type SgpHistorySelectionIds struct {
	MarketID *string `json:"marketId,omitempty"`

	// SelectionID A number on FanDuel, a string on most other books.
	SelectionID *json.RawMessage `json:"selectionId,omitempty"`
}

SgpHistorySelectionIds The book's ids for a leg's selection in the price history.

type SgpLeg

type SgpLeg struct {
	Type      string   `json:"type"`
	Team      string   `json:"team,omitempty"`
	Selection string   `json:"selection,omitempty"`
	Point     *float64 `json:"point,omitempty"`
	Period    string   `json:"period,omitempty"`
	Player    string   `json:"player,omitempty"`
	Category  string   `json:"category,omitempty"`
	Side      string   `json:"side,omitempty"`
	Line      *float64 `json:"line,omitempty"`
}

SgpLeg is one leg of a same game parlay. Type is one of:

  • "moneyline": Selection, the team. With a Period, the period's 3-way result: a team or "Tie".
  • "spread": Selection and Point.
  • "total": Selection "over" or "under", and Point.
  • "team_total": one team's own full-game total (Hard Rock NHL): Team, Selection "over" or "under", and Point, a line Hard Rock lists for that team.
  • "player_prop": Player, Category, Side and Line.

Period is "P1", "P2" or "P3" on an NHL moneyline, spread or total leg, and prices that period instead of the full game. Only Hard Rock prices period and team_total legs. Every other book refuses them rather than pricing the full game: BuildSgp answers a 422 (ReasonCode "unresolved", the leg's reason says why), and CompareSgp and BatchSgp report that book with status "unsupported".

type SgpPeriod added in v0.10.0

type SgpPeriod = string

SgpPeriod One period of an NHL game: the 1st, 2nd or 3rd period. A moneyline, spread or total leg with a `period` prices that period instead of the full game; only Hard Rock prices period legs: on RestClient.buildSgp every other book refuses them (422), and RestClient.compareSgp and RestClient.batchSgp report such a book as `unsupported`. A period on a sport other than NHL is a 400.

type SgpPrice

type SgpPrice struct {
	American *float64 `json:"american,omitempty"`
	Decimal  *float64 `json:"decimal,omitempty"`

	// IsSGM Always `true` on a 200: the price is the book's correlated same-game price
	// for exactly the legs sent.
	IsSGM *bool    `json:"isSGM,omitempty"`
	Legs  *float64 `json:"legs,omitempty"`
}

SgpPrice The computed parlay price from a successful `buildSgp` call.

type SgpResolvedLeg

type SgpResolvedLeg struct {
	// BookSelection The book's own ids for a resolved SGP leg on DraftKings, Hard Rock, BetRivers,
	// Bally Bet and BetMGM. Hard Rock's, BetRivers' and Bally Bet's are strings of
	// digits; BetMGM's `marketId` reads `v1:<fixture>:<market>` or
	// `v2:<fixture>:<market>`, and on NHL `sc:<fixture>:<builderId>` with the
	// option string BetMGM's bet builder lists as `selectionId`.
	BookSelection *SgpBookSelection `json:"bookSelection,omitempty"`

	// Description Present when `resolved: true`: the FanDuel selection it resolved to.
	Description *string `json:"description,omitempty"`

	// FdSelection The FanDuel selection a resolved SGP leg maps to. `marketId` is specific to
	// the FanDuel state that priced it (New Jersey); `selectionId` is the same in
	// every state.
	FdSelection *SgpFdSelection `json:"fdSelection,omitempty"`

	// Reason Present when `resolved: false`: why the leg could not be used.
	Reason   *string `json:"reason,omitempty"`
	Resolved *bool   `json:"resolved,omitempty"`

	// Single On a 200: the leg's stand-alone price, or `null` when none was matched.
	Single *struct {
		American *float64 `json:"american,omitempty"`
		Decimal  *float64 `json:"decimal,omitempty"`
	} `json:"single,omitempty"`
}

SgpResolvedLeg A single leg echoed back in a `buildSgp` response, with its resolution.

type ShootingSplit

type ShootingSplit struct {
	Attempted  *float64 `json:"attempted,omitempty"`
	Made       *float64 `json:"made,omitempty"`
	Percentage *float64 `json:"percentage,omitempty"`
}

ShootingSplit defines model for ShootingSplit.

type SoccerPlayDetail added in v0.10.0

type SoccerPlayDetail struct {
	// AddedMinutes Stoppage-time minutes (2 for "45+2"); null when none.
	AddedMinutes *float64      `json:"addedMinutes,omitempty"`
	Assist       *SoccerPlayer `json:"assist,omitempty"`
	Card         *string       `json:"card,omitempty"`

	// Code The source's own numeric incident code.
	Code   *float64 `json:"code,omitempty"`
	Minute *float64 `json:"minute,omitempty"`

	// Note The source's note on the incident when it carries one.
	Note      *string       `json:"note,omitempty"`
	Player    *SoccerPlayer `json:"player,omitempty"`
	PlayerOut *SoccerPlayer `json:"playerOut,omitempty"`
}

SoccerPlayDetail Soccer: a play is a match incident (type `Goal`, `Own goal`, `Penalty`, `Yellow Card`, `Red Card`, `Substitution`), with the match minute, the player and, per type, the assist (goal), the player going off (substitution: the play's `player` is the one coming on) or the card. `clock` on the play is the minute as shown ("45+2"); `secondsRemaining` and `wallclock` are null; `period` is the half (1, 2; 3 and 4 extra time; 5 penalties).

type SoccerPlayer added in v0.10.0

type SoccerPlayer struct {
	ID   *string `json:"id,omitempty"`
	Name *string `json:"name,omitempty"`
}

SoccerPlayer One player on a soccer play; `id` is null when the source names the player without an id.

type SoccerScoreDetail

type SoccerScoreDetail struct {
	// DecidedBy How a finished match was decided. Absent while in play.
	DecidedBy *string `json:"decidedBy,omitempty"`

	// ExtraTime Score at the end of extra time. Only when extra time was played.
	ExtraTime *struct {
		Away *float64 `json:"away,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"extraTime,omitempty"`

	// Penalties Penalty shootout score. Only when a shootout was played.
	Penalties *struct {
		Away *float64 `json:"away,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"penalties,omitempty"`

	// Regulation Score at the end of normal time.
	Regulation *struct {
		Away *float64 `json:"away,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"regulation,omitempty"`
}

SoccerScoreDetail Soccer result breakdown. `home.score`/`away.score` (and `score` on the stats endpoints) are the score of the match itself, after extra time when it was played, and NEVER include shootout goals; the shootout is only in `penalties`.

type SplitsBookEntry

type SplitsBookEntry struct {
	// AsOf ISO 8601 time this book's figures were read. The books are read on different
	// schedules, so two books on one game can show different times. DraftKings figures
	// run until kickoff; the last pre-game DraftKings figures stay on the board for up to
	// 4 hours after kickoff, with the `as_of` of that last read.
	// Absent on responses from before 2026-09-27.
	AsOf *string `json:"as_of,omitempty"`

	// Book Book key: 'dk' (DraftKings), 'betmgm' (BetMGM, tickets only, since 2026-09-29) or
	// 'circa' (Circa Sports). Other keys may appear as books are added.
	Book      *string `json:"book,omitempty"`
	Moneyline *struct {
		AwayBetsPct   *float64 `json:"away_bets_pct,omitempty"`
		AwayHandlePct *float64 `json:"away_handle_pct,omitempty"`
		AwayPrice     *float64 `json:"away_price,omitempty"`
		HomeBetsPct   *float64 `json:"home_bets_pct,omitempty"`
		HomeHandlePct *float64 `json:"home_handle_pct,omitempty"`
		HomePrice     *float64 `json:"home_price,omitempty"`
	} `json:"moneyline,omitempty"`
	Spread *struct {
		AwayBetsPct   *float64 `json:"away_bets_pct,omitempty"`
		AwayHandlePct *float64 `json:"away_handle_pct,omitempty"`
		AwayLine      *float64 `json:"away_line,omitempty"`
		HomeBetsPct   *float64 `json:"home_bets_pct,omitempty"`
		HomeHandlePct *float64 `json:"home_handle_pct,omitempty"`
		HomeLine      *float64 `json:"home_line,omitempty"`
	} `json:"spread,omitempty"`

	// Title Display name: 'DraftKings', 'BetMGM' or 'Circa Sports'.
	Title *string `json:"title,omitempty"`
	Total *struct {
		Line           *float64 `json:"line,omitempty"`
		OverBetsPct    *float64 `json:"over_bets_pct,omitempty"`
		OverHandlePct  *float64 `json:"over_handle_pct,omitempty"`
		UnderBetsPct   *float64 `json:"under_bets_pct,omitempty"`
		UnderHandlePct *float64 `json:"under_handle_pct,omitempty"`
	} `json:"total,omitempty"`
}

SplitsBookEntry One book's figures for a game. A market the book has not published for the game is absent, not zero, and so is any percentage a book does not publish: BetMGM publishes ticket percentages only, so a `betmgm` entry carries the `*_bets_pct` fields and no `*_handle_pct` fields. Treat every field below `book`/`title` as possibly absent.

type SplitsGame

type SplitsGame struct {
	// AwayTeam The /odds team name (same string as OddsEvent.away_team).
	AwayTeam *string `json:"away_team,omitempty"`

	// EventID Our event id: the OddsEvent.eventId that `/api/v1/{sport}/odds` gives the
	// same game, `{sport}:{Away}@{Home}-{YYYYMMDD}` (e.g.
	// "nfl:Carolina Panthers@Cleveland Browns-20260927"), so splits join to odds on it.
	// The date is the game's UTC start date, except MLB games starting from
	// 2026-10-11T10:00Z, which carry the US Eastern date (as on /odds). A game not on
	// /odds gets an id built the same way from its scheduled start. Both games of a
	// doubleheader share one `event_id`, as on /odds.
	//
	// Before 2026-09-27 this was an upstream game code (e.g. "20260622MLB00030") that did
	// not match /odds.
	EventID *string `json:"event_id,omitempty"`

	// HomeTeam The /odds team name (same string as OddsEvent.home_team).
	HomeTeam *string           `json:"home_team,omitempty"`
	Splits   []SplitsBookEntry `json:"splits,omitempty"`
}

SplitsGame defines model for SplitsGame.

type SplitsHistoryRow added in v0.2.0

type SplitsHistoryRow struct {
	// Book Book key: 'dk', 'circa' or 'betmgm'.
	Book *string `json:"book,omitempty"`

	// EventID The game's /odds eventId, the same as SplitsGame.event_id.
	EventID   *string `json:"event_id,omitempty"`
	Moneyline *struct {
		AwayBetsPct   *float64 `json:"away_bets_pct,omitempty"`
		AwayHandlePct *float64 `json:"away_handle_pct,omitempty"`
		AwayPrice     *float64 `json:"away_price,omitempty"`
		HomeBetsPct   *float64 `json:"home_bets_pct,omitempty"`
		HomeHandlePct *float64 `json:"home_handle_pct,omitempty"`
		HomePrice     *float64 `json:"home_price,omitempty"`
	} `json:"moneyline,omitempty"`

	// RecordedAt ISO 8601 time this reading was recorded.
	RecordedAt *string `json:"recorded_at,omitempty"`
	Sport      *string `json:"sport,omitempty"`
	Spread     *struct {
		AwayBetsPct   *float64 `json:"away_bets_pct,omitempty"`
		AwayHandlePct *float64 `json:"away_handle_pct,omitempty"`
		AwayLine      *float64 `json:"away_line,omitempty"`
		HomeBetsPct   *float64 `json:"home_bets_pct,omitempty"`
		HomeHandlePct *float64 `json:"home_handle_pct,omitempty"`
		HomeLine      *float64 `json:"home_line,omitempty"`
	} `json:"spread,omitempty"`

	// Title Display name: 'DraftKings', 'Circa Sports' or 'BetMGM'.
	Title *string `json:"title,omitempty"`
	Total *struct {
		Line           *float64 `json:"line,omitempty"`
		OverBetsPct    *float64 `json:"over_bets_pct,omitempty"`
		OverHandlePct  *float64 `json:"over_handle_pct,omitempty"`
		UnderBetsPct   *float64 `json:"under_bets_pct,omitempty"`
		UnderHandlePct *float64 `json:"under_handle_pct,omitempty"`
	} `json:"total,omitempty"`
}

SplitsHistoryRow One recorded reading of one book's splits for one game. Same shape as a SplitsBookEntry on the live board, plus the game's `event_id` and `sport`, with `recorded_at` where the live board has `as_of`. A row is written when the book's figures (percentages, line or price) change, and again after 24 hours without a change, so consecutive rows can carry the same figures.

A market absent from a row was not quoted at that read, and a percentage absent from a market is not zero (BetMGM rows carry ticket percentages only).

type SplitsResponse

type SplitsResponse struct {
	Data []SplitsGame `json:"data,omitempty"`
	Meta *struct {
		// AgeSeconds Only when `status` is 'stale': how old the served board is, in seconds.
		AgeSeconds *float64 `json:"age_seconds,omitempty"`

		// AsOf ISO time of the OLDEST of the books' latest reads behind this board, so it never
		// overstates freshness. Each split also carries its own SplitsBookEntry.as_of;
		// a DraftKings split kept after kickoff has an older one than this.
		// Since API v2.362.0.
		AsOf *string `json:"as_of,omitempty"`

		// Books The books on this board: 'draftkings', 'betmgm' and, while it is offered, 'circa'.
		// Can be `['draftkings']` alone. (Each split's own `book` key is 'dk', 'betmgm' or
		// 'circa'.) Read the book set from here, never from a fixed list: books are added.
		Books []string `json:"books,omitempty"`

		// PartialReason Only when `status` is 'partial': 'upstream_limited' = the Circa figures were withheld;
		// 'book_unavailable' = one book has no recent read (that book is missing from `books`);
		// 'upstream_paused' = no board at all is available: no book could be read and no board
		// from the last 12 hours exists (`data` is empty).
		PartialReason *string `json:"partial_reason,omitempty"`

		// Source A fixed legacy value, 'circa_dk', whatever books are on the board. Do not read
		// the book set from it: use `books`.
		Source *string `json:"source,omitempty"`

		// Status 'ok' = every book on offer has a recent read. 'partial' = one does not (see
		// `partial_reason`), so `data` may be a subset. 'stale' = no book has a recent read,
		// and the last full board (at most 12 hours old, see `as_of` / `age_seconds`) is
		// served instead.
		// Absent on API versions before v2.346.0; 'stale' since v2.362.0.
		Status *string `json:"status,omitempty"`

		// TotalGames Number of games in `data`. Present on every response since API v2.346.0 (earlier: only on cache misses).
		TotalGames *float64 `json:"total_games,omitempty"`
	} `json:"meta,omitempty"`
	Sport *string `json:"sport,omitempty"`
}

SplitsResponse defines model for SplitsResponse.

type Sport

type Sport = string

Sport defines model for Sport.

type SportPropsCount

type SportPropsCount struct {
	AgeMs     *float64 `json:"ageMs,omitempty"`
	Games     *float64 `json:"games,omitempty"`
	Props     *float64 `json:"props,omitempty"`
	Timestamp *float64 `json:"timestamp,omitempty"`
}

SportPropsCount defines model for SportPropsCount.

type SportScoresResponse

type SportScoresResponse struct {
	Cached    *bool        `json:"cached,omitempty"`
	Count     *float64     `json:"count,omitempty"`
	Events    []ScoreEvent `json:"events,omitempty"`
	Sport     *string      `json:"sport,omitempty"`
	Success   *bool        `json:"success,omitempty"`
	Timestamp *string      `json:"timestamp,omitempty"`
}

SportScoresResponse Response from GET /{sport}/scores/live (single sport).

type StakeBet

type StakeBet struct {
	Active *bool `json:"active,omitempty"`

	// Amount Stake on the whole slip, in `currency`. null when the bettor hid it.
	Amount            *float64 `json:"amount,omitempty"`
	CashoutMultiplier *float64 `json:"cashoutMultiplier,omitempty"`

	// CreatedAt RFC-1123, passed through from upstream (e.g. 'Tue, 01 Sep 2026 16:10:32 GMT').
	CreatedAt *string `json:"createdAt,omitempty"`

	// Currency The bettor's own currency, fiat or crypto (usdt, inr, sol, ngn, ...). NOT converted.
	Currency *string `json:"currency,omitempty"`
	Feed     *string `json:"feed,omitempty"`
	ID       *string `json:"id,omitempty"`

	// Outcomes More than one leg means a parlay, so `amount` cannot be attributed to one market.
	Outcomes            []StakeBetLeg `json:"outcomes,omitempty"`
	Payout              *float64      `json:"payout,omitempty"`
	PayoutMultiplier    *float64      `json:"payoutMultiplier,omitempty"`
	PotentialMultiplier *float64      `json:"potentialMultiplier,omitempty"`
	Status              *string       `json:"status,omitempty"`
	UpdatedAt           *string       `json:"updatedAt,omitempty"`
}

StakeBet One public sportsbook bet from Stake. Bettor identity is never returned.

type StakeBetLeg

type StakeBetLeg struct {
	// Fixture The fixture of a Stake bet leg. `id` is a UUID in a DIFFERENT id space from the
	// board and joins to nothing there: join a wager to `getStakeV2()` by `slug`, whose
	// numeric prefix is the board fixture id ("46853667-sporting-galatasaray-istanbul"
	// is board fixture 46853667).
	Fixture *StakeBetLegFixture `json:"fixture,omitempty"`
	ID      *string             `json:"id,omitempty"`
	Market  *struct {
		ID   *string `json:"id,omitempty"`
		Name *string `json:"name,omitempty"`
	} `json:"market,omitempty"`

	// Odds Decimal odds AT PLACEMENT for this leg.
	Odds *float64 `json:"odds,omitempty"`

	// Outcome outcome.id and market.id share a namespace with `getStakeV2()`, so a leg joins
	// directly to a board outcome. `fixture.id` does NOT join: the board uses a different
	// identifier for the same fixture. Join on outcome or market id, never fixture id.
	Outcome *struct {
		ID   *string `json:"id,omitempty"`
		Name *string `json:"name,omitempty"`
	} `json:"outcome,omitempty"`
	Status *string `json:"status,omitempty"`
}

StakeBetLeg One leg of a public Stake bet. `amount` is on the SLIP, not the leg.

type StakeBetLegFixture added in v0.9.0

type StakeBetLegFixture struct {
	ID         *string                `json:"id,omitempty"`
	Name       *string                `json:"name,omitempty"`
	Slug       *string                `json:"slug,omitempty"`
	StartTime  *string                `json:"startTime,omitempty"`
	Tournament *StakeBetLegTournament `json:"tournament,omitempty"`
}

StakeBetLegFixture The fixture of a Stake bet leg. `id` is a UUID in a DIFFERENT id space from the board and joins to nothing there: join a wager to `getStakeV2()` by `slug`, whose numeric prefix is the board fixture id ("46853667-sporting-galatasaray-istanbul" is board fixture 46853667).

type StakeBetLegTournament added in v0.9.0

type StakeBetLegTournament struct {
	Category *struct {
		ID   *string `json:"id,omitempty"`
		Name *string `json:"name,omitempty"`
	} `json:"category,omitempty"`
	ID   *string `json:"id,omitempty"`
	Name *string `json:"name,omitempty"`
	Slug *string `json:"slug,omitempty"`
}

StakeBetLegTournament defines model for StakeBetLegTournament.

type StakeBetsMeta

type StakeBetsMeta struct {
	// AgeSeconds Seconds since `timestamp`. null whenever `timestamp` is null; never derive freshness from a null timestamp.
	AgeSeconds *float64 `json:"ageSeconds,omitempty"`
	Cached     *bool    `json:"cached,omitempty"`

	// Hash null on no-data and partial-miss.
	Hash          *string `json:"hash,omitempty"`
	NewestBetTime *string `json:"newestBetTime,omitempty"`

	// Now Response clock, ISO, present on every shape.
	Now *string `json:"now,omitempty"`

	// OldestBetTime ISO-8601, unlike the RFC-1123 `createdAt` inside `data[]`. null on empty shapes.
	OldestBetTime *string `json:"oldestBetTime,omitempty"`

	// Sampling Capture-rate facts. `complete` is always false: upstream caps each query at 50 rows
	// with no cursor and the feed turns over faster than that, so the window is a SAMPLE.
	// Total handle is NOT derivable from it.
	Sampling *StakeBetsSampling `json:"sampling,omitempty"`
	Source   *string            `json:"source,omitempty"`
	Status   *string            `json:"status,omitempty"`

	// Timestamp ISO time of the worker write this response serves. null on no-data and partial-miss: there is no data to date.
	Timestamp *string `json:"timestamp,omitempty"`
}

StakeBetsMeta defines model for StakeBetsMeta.

type StakeBetsResponse

type StakeBetsResponse struct {
	Count   *float64       `json:"count,omitempty"`
	Data    []StakeBet     `json:"data,omitempty"`
	Meta    *StakeBetsMeta `json:"meta,omitempty"`
	Success *bool          `json:"success,omitempty"`
}

StakeBetsResponse defines model for StakeBetsResponse.

type StakeBetsSampling

type StakeBetsSampling struct {
	Complete             *bool    `json:"complete,omitempty"`
	ConfiguredMaxEntries *float64 `json:"configuredMaxEntries,omitempty"`
	ConfiguredWindowMs   *float64 `json:"configuredWindowMs,omitempty"`

	// ObservedNewPerMinute Measured: null on empty shapes, and on a healthy response until a rate has been measured (normal right after a restart).
	ObservedNewPerMinute *float64 `json:"observedNewPerMinute,omitempty"`

	// PollIntervalMs Worker poll interval. Reported on empty shapes too while the feed has run in the last day; null only when even that is unknown.
	PollIntervalMs *float64 `json:"pollIntervalMs,omitempty"`
	SourceLimit    *float64 `json:"sourceLimit,omitempty"`

	// WindowSeconds Measured: null on no-data and partial-miss.
	WindowSeconds *float64 `json:"windowSeconds,omitempty"`
}

StakeBetsSampling Capture-rate facts. `complete` is always false: upstream caps each query at 50 rows with no cursor and the feed turns over faster than that, so the window is a SAMPLE. Total handle is NOT derivable from it.

type StatsGame

type StatsGame struct {
	EventID *string `json:"eventId,omitempty"`

	// LastUpdated Last time the score or state changed.
	LastUpdated *string `json:"lastUpdated,omitempty"`
	Name        *string `json:"name,omitempty"`

	// Score Null for a scheduled match (never a 0-0). Soccer: excludes shootout goals.
	Score *struct {
		Away *float64 `json:"away,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"score,omitempty"`

	// SoccerDetail Soccer result breakdown. `home.score`/`away.score` (and `score` on the stats
	// endpoints) are the score of the match itself, after extra time when it was
	// played, and NEVER include shootout goals; the shootout is only in `penalties`.
	SoccerDetail *SoccerScoreDetail `json:"soccerDetail,omitempty"`
	StartTime    *string            `json:"startTime,omitempty"`
	Status       *ScoreStatus       `json:"status,omitempty"`

	// TeamStats A pair is null (or absent) when the source did not measure it, never a fake 0.
	TeamStats map[string]struct {
		Away *json.RawMessage `json:"away,omitempty"`
		Home *json.RawMessage `json:"home,omitempty"`
	} `json:"teamStats,omitempty"`
}

StatsGame One match from GET /api/v1/{sport}/stats: live, scheduled or finished today.

type StatsResponse

type StatsResponse struct {
	Data []StatsGame `json:"data,omitempty"`
	Meta *struct {
		Date      *string  `json:"date,omitempty"`
		GameCount *float64 `json:"gameCount,omitempty"`
		Notice    *string  `json:"notice,omitempty"`

		// ScheduledAndFinishedAsOf When the scheduled and finished data was refreshed.
		ScheduledAndFinishedAsOf *string `json:"scheduledAndFinishedAsOf,omitempty"`
		Sport                    *string `json:"sport,omitempty"`

		// Timestamp When the live data was refreshed.
		Timestamp *string `json:"timestamp,omitempty"`
	} `json:"meta,omitempty"`
	Success *bool `json:"success,omitempty"`
}

StatsResponse defines model for StatsResponse.

type Stream

type Stream struct {
	// contains filtered or unexported fields
}

Stream is one WebSocket connection to the API, kept open by Stream.Run.

The stream stores the last subscription it sent and the props subscriptions in force, and sends them again on every connect: in the handshake and as subscribe messages, and once more after 5 s if the server has not acknowledged them.

Events are delivered in order, one at a time, from a queue of 64: a slow handler does not stop the stream from answering the server's pings until the queue is full. When it is full the stream stops reading, and the server closes a connection that falls too far behind.

The stream remembers its reconnect delays across calls to Run, so a loop that calls Run again after it returns cannot connect faster than the stream would on its own.

func (*Stream) Connected

func (s *Stream) Connected() bool

Connected reports whether the stream has an open connection.

func (*Stream) On

func (s *Stream) On(name string, fn func(json.RawMessage)) (off func())

On calls fn with the first argument of every event named name (JSON null when the event has none), in the order events arrive. Handlers run on one goroutine; keep them short. Decode turns a payload into one of the model types. It returns a function that removes fn.

func (*Stream) OnOddsUpdate

func (s *Stream) OnOddsUpdate(fn func(*OddsUpdatePayload, error)) (off func())

OnOddsUpdate calls fn with every odds-update, decoded with Decode. Frames can be partial: after a subscribe the first frames are a snapshot, later ones carry only the games that changed, so merge by event id. A frame that cannot be decoded at all (not a JSON object) is passed as a nil payload and the error.

func (*Stream) Run

func (s *Stream) Run(ctx context.Context) error

Run connects and keeps the stream connected until ctx ends, then closes the connection with a Socket.IO disconnect and returns ctx.Err().

If the FIRST connection attempt fails, Run returns that error without retrying: a *RefusalError (read Code, Retryable and RetryAfter), a *DialError, or a network error. After the stream has connected once, Run reconnects on its own:

  • after a dropped connection, with a delay of 1 s that doubles, up to 30 s, each time a connection drops before it has lasted 60 s (a failed attempt counts as such a drop);
  • after a refused reconnect the server marks retryable, on the refusal's own schedule (CONNECTION_LIMIT 5/15/30/60 s, others 2/5/15/30/60 s);
  • after a reconnect refused for the account (TIER_NO_WS, SUBSCRIPTION_INACTIVE, PAYMENT_OVERDUE, TRIAL_EXPIRED, TRIAL_UNVERIFIABLE), after 30 s and then every 60 s, so a reconnect after a network drop or a server restart goes through once the invoice is paid or the plan upgraded;
  • after an upgrade answered "Too many connection attempts", no sooner than 120 s.

Every refusal retry is jittered x0.8-1.2, never sooner than the server's retryAfterMs, and at most 4 refusal retries of any kind fall in a minute. Each refusal is also delivered as an EventConnectError event.

Run returns a *RefusalError when a reconnect is refused with retryable false and anything but an account code: a bad key (MISSING_API_KEY, INVALID_API_KEY, API_KEY_DEACTIVATED: waiting never fixes them, and each counts toward the server's per-IP block), a code this SDK does not know, or no code at all. An expired trial deactivates the key, so its retry meets API_KEY_DEACTIVATED and Run returns; so does a cancelled account until it subscribes again.

Run returns ErrServerDisconnect when the server disconnects the stream. The server does that when its periodic check (every 5 minutes) finds the account no longer allows the connection, and the stream does not reconnect after it: call Run again once the account is fixed.

The delay the stream would have waited before its next attempt is kept: calling Run again waits it out before connecting, so a supervisor loop around Run keeps to the same schedule.

Only one Run may be active at a time. Once ctx ends, events still queued are dropped, and Run returns when the handler running at that moment (if any) has returned.

func (*Stream) Subscribe

func (s *Stream) Subscribe(ctx context.Context, sub Subscription) error

Subscribe REPLACES the subscription: a field left out is cleared. The stream stores it and sends it again on every reconnect. It returns ErrNotConnected, and stores nothing, while the stream is not connected; use WithSubscription for the first connect.

ctx bounds only the wait for a send already in progress; when it ends first, nothing is stored. Once stored, the subscription is written before Subscribe returns, within the stream's 10 s write timeout. A write error means the connection is failing: the subscription stays stored and is sent on the reconnect. The same holds for UpdateSubscription and the props calls.

func (*Stream) SubscribeProps

func (s *Stream) SubscribeProps(ctx context.Context, book string, f PropsFilter) error

SubscribeProps subscribes to a book's player props stream ("" or "pinnacle" for player-props-update, else <book>-props-update). The subscription is kept and sent again on every reconnect until UnsubscribeProps.

func (*Stream) UnsubscribeProps

func (s *Stream) UnsubscribeProps(ctx context.Context, book string) error

UnsubscribeProps ends a book's props subscription.

func (*Stream) UpdateSubscription

func (s *Stream) UpdateSubscription(ctx context.Context, patch Subscription) error

UpdateSubscription MERGES the fields set in patch into the subscription, the way the server does: each top-level field replaces the stored one (a V2 you pass replaces the whole V2). The merged result is sent on every reconnect.

type StreamOption

type StreamOption func(*Stream)

StreamOption configures a Stream.

func WithSubscription

func WithSubscription(sub Subscription) StreamOption

WithSubscription sets the subscription the stream sends on its first connect (in the handshake and as a subscribe message) and on every reconnect.

type Subscription

type Subscription struct {
	// Sports for odds-update and the realtime events. Empty means every sport.
	Sports []string `json:"sports,omitempty"`
	// Books for odds-update and esports-update. Empty means every book; it does
	// not narrow the realtime events.
	Books []string `json:"books,omitempty"`
	// EventIDs limits odds-update to these events (OddsEvent.id).
	EventIDs []string `json:"eventIds,omitempty"`
	// Alternates includes alternate lines.
	Alternates *bool `json:"alternates,omitempty"`
	// ExcludeExchanges drops kalshi, polymarket and novig from Books.
	ExcludeExchanges *bool `json:"exclude_exchanges,omitempty"`
	// Esports is true for every esport, or a list such as []string{"cs2"}.
	Esports any `json:"esports,omitempty"`
	// EsportsRealtime opts in to <game>-realtime per esport, each with a
	// required league substring.
	EsportsRealtime map[string]EsportsRealtimeFilter `json:"esportsRealtime,omitempty"`
	// ProphetX is true for every prophetx-update, false for none, or a filter
	// such as map[string][]string{"sports": {"basketball"}, "kinds": {"prop"}}.
	ProphetX any `json:"prophetx,omitempty"`
	// V2 opts in to the v2 book events (see V2EventName): book, then sport,
	// then "*" or a list of league keys, for example
	// map[string]map[string]any{"fanduel": {"mlb": "*"}}.
	V2 map[string]map[string]any `json:"v2,omitempty"`
	// OneXBetSoccer opts in to 1xbet-update.
	OneXBetSoccer *bool `json:"oneXBetSoccer,omitempty"`
	// Scores opts in to scores-update (live scores; nothing is sent without it):
	// true for every sport, or a list of sport keys such as []string{"nfl"}.
	// Decode the payload with Decode[ScoresUpdate].
	Scores any `json:"scores,omitempty"`
	// Plays opts in to plays-update (play-by-play; nothing is sent without it):
	// true for nba, nhl, nfl, ncaaf and soccer, or a list of those keys such as
	// []string{"nfl", "nba"}. Same access as Scores. Decode the payload with
	// Decode[PlaysUpdate]; GetPlays works either way.
	Plays any `json:"plays,omitempty"`
	// Realtime opts the connection out of a realtime event. Nil (the default)
	// gets both pinnacle-realtime and ps3838-realtime for the subscribed sports;
	// &RealtimeFeeds{PS3838: Ptr(false)} stops ps3838-realtime and keeps
	// pinnacle-realtime. UpdateSubscription replaces the whole object, so
	// &RealtimeFeeds{} there turns ps3838-realtime back on.
	Realtime *RealtimeFeeds `json:"realtime,omitempty"`
}

Subscription is what a Stream subscribes to. The stream keeps the last one it sent and sends it again, unchanged, on every connect.

A nil field is not sent. A list or map that is empty but not nil is sent empty, which is how Stream.UpdateSubscription clears a filter: Subscription{EventIDs: []string{}} sends "eventIds": [] (no event filter) and Subscription{V2: map[string]map[string]any{}} sends "v2": {} (no v2 events).

func (Subscription) MarshalJSON

func (s Subscription) MarshalJSON() ([]byte, error)

MarshalJSON sends a list or map that is empty but not nil as [] or {}, so that an update can clear it; omitempty alone would drop it.

type TennisMatchStats

type TennisMatchStats struct {
	Aces                 *TennisStatLine `json:"aces,omitempty"`
	BreakPointsConverted *TennisStatLine `json:"breakPointsConverted,omitempty"`
	BreakPointsSaved     *TennisStatLine `json:"breakPointsSaved,omitempty"`
	DoubleFaults         *TennisStatLine `json:"doubleFaults,omitempty"`
	FirstServePercent    *TennisStatLine `json:"firstServePercent,omitempty"`
	FirstServePointsWon  *TennisStatLine `json:"firstServePointsWon,omitempty"`
	RecordedAt           *string         `json:"recordedAt,omitempty"`
	Scope                *string         `json:"scope,omitempty"`
	SecondServePointsWon *TennisStatLine `json:"secondServePointsWon,omitempty"`
	TotalPointsWon       *TennisStatLine `json:"totalPointsWon,omitempty"`
	UnforcedErrors       *TennisStatLine `json:"unforcedErrors,omitempty"`
	Winners              *TennisStatLine `json:"winners,omitempty"`
}

TennisMatchStats defines model for TennisMatchStats.

type TennisScoreDetail

type TennisScoreDetail struct {
	// CurrentGameScore Current game points (e.g. "40" vs "A"). Absent between games.
	CurrentGameScore *struct {
		Away *string `json:"away,omitempty"`
		Home *string `json:"home,omitempty"`
	} `json:"currentGameScore,omitempty"`

	// CurrentSet Current set number (1-indexed).
	CurrentSet *float64 `json:"currentSet,omitempty"`

	// Serving Which side is serving.
	Serving *string `json:"serving,omitempty"`

	// Sets Games won by each player in each set, e.g. [{home:6,away:4},{home:3,away:2}].
	Sets []struct {
		Away *float64 `json:"away,omitempty"`
		Home *float64 `json:"home,omitempty"`
	} `json:"sets,omitempty"`
}

TennisScoreDetail Tennis-specific live detail (games within sets, current game points, serving).

type TennisSetStats

type TennisSetStats struct {
	Set   *float64             `json:"set,omitempty"`
	Stats map[string]*HomeAway `json:"stats,omitempty"`
}

TennisSetStats defines model for TennisSetStats.

type TennisStatLine

type TennisStatLine struct {
	Away *float64 `json:"away,omitempty"`
	Home *float64 `json:"home,omitempty"`
}

TennisStatLine defines model for TennisStatLine.

type TennisStatsPayload

type TennisStatsPayload struct {
	Match map[string]*HomeAway `json:"match,omitempty"`
	Sets  []TennisSetStats     `json:"sets,omitempty"`
}

TennisStatsPayload defines model for TennisStatsPayload.

type TennisStatsResponse

type TennisStatsResponse struct {
	Data *struct {
		Count   *float64           `json:"count,omitempty"`
		EventID *string            `json:"eventId,omitempty"`
		Stats   []TennisMatchStats `json:"stats,omitempty"`
	} `json:"data,omitempty"`
	Success *bool `json:"success,omitempty"`
}

TennisStatsResponse defines model for TennisStatsResponse.

type ThunderpickV2BookSubscription

type ThunderpickV2BookSubscription struct {
	// AmericanFootball Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	AmericanFootball *V2LeagueFilter `json:"american-football,omitempty"`

	// Baseball Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Baseball *V2LeagueFilter `json:"baseball,omitempty"`

	// Basketball Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Basketball *V2LeagueFilter `json:"basketball,omitempty"`

	// Cs2 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Cs2 *V2LeagueFilter `json:"cs2,omitempty"`

	// Dota2 Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Dota2 *V2LeagueFilter `json:"dota2,omitempty"`

	// Lol Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Lol *V2LeagueFilter `json:"lol,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`

	// Valorant Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Valorant *V2LeagueFilter `json:"valorant,omitempty"`
}

ThunderpickV2BookSubscription Per-book subscription shape for Thunderpick v2 events.

Each Thunderpick sport is a single stream (no per-league segmentation). Pass `'*'` per sport you want. Arrays are accepted and coerced to `'*'` server-side.

type TippmixproV2BookSubscription

type TippmixproV2BookSubscription struct {
	Boxing   *json.RawMessage `json:"boxing,omitempty"`
	Cs2      *json.RawMessage `json:"cs2,omitempty"`
	Dota2    *json.RawMessage `json:"dota2,omitempty"`
	Lol      *json.RawMessage `json:"lol,omitempty"`
	Mlb      *json.RawMessage `json:"mlb,omitempty"`
	Mma      *json.RawMessage `json:"mma,omitempty"`
	Nba      *json.RawMessage `json:"nba,omitempty"`
	Ncaab    *json.RawMessage `json:"ncaab,omitempty"`
	Ncaaf    *json.RawMessage `json:"ncaaf,omitempty"`
	Nfl      *json.RawMessage `json:"nfl,omitempty"`
	Nhl      *json.RawMessage `json:"nhl,omitempty"`
	Soccer   *json.RawMessage `json:"soccer,omitempty"`
	Tennis   *json.RawMessage `json:"tennis,omitempty"`
	Valorant *json.RawMessage `json:"valorant,omitempty"`
}

TippmixproV2BookSubscription Per-sport league filter for `v2: { tippmixpro: {...} }`.

type UpdateWebhookParams added in v0.5.0

type UpdateWebhookParams struct {
	// URL replaces the endpoint's url; empty leaves it.
	URL string `json:"url,omitempty"`
	// EventTypes replaces the list; nil or empty leaves it.
	EventTypes []string `json:"event_types,omitempty"`
	// Description replaces the description; nil leaves it, and Ptr("") stores an
	// empty one.
	Description *string `json:"description,omitempty"`
	// Filters merge into the stored filters (see [WebhookFiltersInput]); nil
	// leaves them.
	Filters *WebhookFiltersInput `json:"filters,omitempty"`
	// Enabled: Ptr(false) stops deliveries and cancels the undelivered ones;
	// Ptr(true) resumes an endpoint that was disabled or auto-disabled after
	// failing, and resets its failure count. nil leaves it.
	Enabled *bool `json:"enabled,omitempty"`
}

UpdateWebhookParams is the body of Client.UpdateWebhook. Only the fields you set are sent.

type V2BookResponse

type V2BookResponse struct {
	// Data The book's own JSON, served verbatim. Decode without strict typing.
	Data        *RawJSON        `json:"data,omitempty"`
	Etag        *string         `json:"etag,omitempty"`
	League      *string         `json:"league,omitempty"`
	Leagues     []V2LeagueSlice `json:"leagues,omitempty"`
	MarketCount *float64        `json:"marketCount,omitempty"`
	Meta        *V2Meta         `json:"meta,omitempty"`
	Sport       *string         `json:"sport,omitempty"`
	Success     *bool           `json:"success,omitempty"`
}

V2BookResponse Any v2 book's raw board. `data` is keyed by the book's market or event id and holds the book's own JSON. `league` is null on a merged response, which then lists `leagues`.

type V2LeagueFilter

type V2LeagueFilter = json.RawMessage

V2LeagueFilter Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to receive every league for that sport, or an array of league keys to filter to a specific set. League keys come from the corresponding `/leagues` REST endpoint.

type V2LeagueFilterAdvisory

type V2LeagueFilterAdvisory struct {
	// Accepted What the connection is subscribed to for this book and sport: the keys, `['*']`, or `[]`.
	Accepted []string `json:"accepted,omitempty"`
	Book     *string  `json:"book,omitempty"`

	// LeaguesEndpoint Where to list the league keys that carry data, when the book has an index.
	LeaguesEndpoint *string `json:"leaguesEndpoint,omitempty"`
	Message         *string `json:"message,omitempty"`

	// Outcome - `unverified`: the league keys were accepted as written; a key that no book
	//   uses simply never receives anything.
	// - `no-valid-keys`: no usable key, so this sport was not subscribed for the book.
	// - `coerced-to-wildcard`: this book and sport carry no league key, so the
	//   filter was replaced with `'*'`.
	Outcome *string `json:"outcome,omitempty"`

	// RejectedCount How many entries were dropped as invalid league keys.
	RejectedCount *float64 `json:"rejectedCount,omitempty"`

	// RejectedSample Up to 5 of the dropped values, truncated.
	RejectedSample []string `json:"rejectedSample,omitempty"`
	Sport          *string  `json:"sport,omitempty"`
}

V2LeagueFilterAdvisory One entry of ClientSubscription.v2Advisory.

type V2LeagueIndexEntry

type V2LeagueIndexEntry struct {
	LeagueKey   *string  `json:"leagueKey,omitempty"`
	LeagueName  *string  `json:"leagueName,omitempty"`
	MarketCount *float64 `json:"marketCount,omitempty"`
	UpdatedAt   *float64 `json:"updatedAt,omitempty"`
}

V2LeagueIndexEntry defines model for V2LeagueIndexEntry.

type V2LeagueSlice

type V2LeagueSlice struct {
	LeagueKey   *string  `json:"leagueKey,omitempty"`
	LeagueName  *string  `json:"leagueName,omitempty"`
	MarketCount *float64 `json:"marketCount,omitempty"`
	MarketIds   []string `json:"marketIds,omitempty"`
}

V2LeagueSlice One league's share of a merged response: the `data` keys it contributed.

type V2LeaguesResponse

type V2LeaguesResponse struct {
	LeagueCount *float64             `json:"leagueCount,omitempty"`
	Leagues     []V2LeagueIndexEntry `json:"leagues,omitempty"`
	Meta        *V2Meta              `json:"meta,omitempty"`
	Sport       *string              `json:"sport,omitempty"`
	Success     *bool                `json:"success,omitempty"`
}

V2LeaguesResponse defines model for V2LeaguesResponse.

type V2Meta

type V2Meta struct {
	AgeSeconds     *float64 `json:"ageSeconds,omitempty"`
	CacheTimestamp *string  `json:"cacheTimestamp,omitempty"`
	Cached         *bool    `json:"cached,omitempty"`

	// DefaultExcluded On a default request: the entries the book leaves out by default (DraftKings `props-*`, Fanatics Markets `books-*`, Polymarket US `full-*`), with the `marketIds` value that includes them.
	DefaultExcluded *struct {
		Count     *float64 `json:"count,omitempty"`
		Include   *string  `json:"include,omitempty"`
		MarketIds *string  `json:"marketIds,omitempty"`
	} `json:"defaultExcluded,omitempty"`
	DuplicateMarketIdsDropped *float64 `json:"duplicateMarketIdsDropped,omitempty"`
	LeagueCount               *float64 `json:"leagueCount,omitempty"`
	LeaguesOmitted            *float64 `json:"leaguesOmitted,omitempty"`

	// MarketAges Seconds since each returned entry was last confirmed, by entry id, when the book sends it.
	MarketAges map[string]float64 `json:"marketAges,omitempty"`

	// MarketIds Echo of the `marketIds` filter you sent (normalized), when you sent one.
	MarketIds *string `json:"marketIds,omitempty"`

	// MarketsFilteredOut With a `marketIds` filter: how many stored entries it left out.
	MarketsFilteredOut *float64 `json:"marketsFilteredOut,omitempty"`
	MarketsOmitted     *float64 `json:"marketsOmitted,omitempty"`
	Merged             *bool    `json:"merged,omitempty"`
	Source             *string  `json:"source,omitempty"`
	Status             *string  `json:"status,omitempty"`
	Timestamp          *string  `json:"timestamp,omitempty"`
	Truncated          *bool    `json:"truncated,omitempty"`
}

V2Meta defines model for V2Meta.

type V2OptionAdvisory

type V2OptionAdvisory struct {
	// Accepted What the subscription holds for `field` after validation.
	Accepted *json.RawMessage `json:"accepted,omitempty"`
	Book     *string          `json:"book,omitempty"`
	Field    *string          `json:"field,omitempty"`
	Message  *string          `json:"message,omitempty"`

	// Outcome - `unverified`: `marketTypes` were accepted as written; a value no market
	//   carries never matches.
	// - `invalid-values-dropped`: some `eventTypes` were not event types.
	// - `no-valid-values-default-applied`: nothing usable, so the default applies.
	// - `coerced-to-wildcard-room-cap`: the filter was widened to `'*'` to stay under
	//   the per-connection limit. You receive more than you asked for, never less.
	// - `ignored-no-sports`: options without a valid sport subscribe to nothing.
	// - `not-yet-available`: an event type this connection does not receive yet
	//   (`accepted: []`, the type in `rejectedSample`), or that is accepted and
	//   starts arriving once it is enabled (`accepted: [type]`).
	//
	// More outcomes can be added.
	Outcome       *string  `json:"outcome,omitempty"`
	RejectedCount *float64 `json:"rejectedCount,omitempty"`

	// RejectedSample Up to 5 of the dropped values, truncated.
	RejectedSample []string `json:"rejectedSample,omitempty"`

	// Sport Set when the entry is about one sport (a league list widened to `'*'`).
	Sport *string `json:"sport,omitempty"`
}

V2OptionAdvisory One entry of ClientSubscription.v2OptionsAdvisory.

type V2Subscription

type V2Subscription struct {
	// N4Casters Per-sport league filter for `v2: { '4casters': {...} }`.
	N4Casters *FourcastersV2BookSubscription `json:"4casters,omitempty"`
	Ballybet  *BallybetV2BookSubscription    `json:"ballybet,omitempty"`

	// Bet105 Per-sport league filter for `v2: { bet105: {...} }`. Any Bet105 sport.
	Bet105 *Bet105V2BookSubscription `json:"bet105,omitempty"`

	// Bet365 Per-book subscription shape for Bet365 v2 events.
	//
	// Bet365 v2 supports both multi-league sports (soccer, requires
	// specific league keys) and single-league sports (nba, mlb, nhl, nfl, where
	// the entire sport is one stream). For single-league sports pass `'*'`;
	// arrays are accepted and coerced to `'*'` server-side.
	Bet365 *Bet365V2BookSubscription `json:"bet365,omitempty"`

	// Betonline Per-book subscription shape for BetOnline v2 events (`betonline-realtime`).
	//
	// Every sport takes a V2LeagueFilter, and every sport on this book is
	// competition-keyed - there is no single-league sport here. So unlike bet365,
	// mybookie and thunderpick, an array filter is NOT coerced to `'*'`
	// server-side: BetOnline is deliberately excluded from that coercion list,
	// because its rooms really are per-competition and coercing would hand you a
	// blend of competitions you did not ask for. An array is a real filter, and a
	// key that matches no competition simply receives nothing.
	//
	// Use `'*'` to take the whole sport (all competitions), or list exact league
	// keys from OwlsInsightRest.getBetOnlineV2Leagues - passing the sport
	// name as a league key (e.g. `{ mlb: ['mlb'] }`) is not a wildcard and will
	// miss Liga Mexicana, MiLB and anything else the sport carries.
	Betonline *BetOnlineV2BookSubscription `json:"betonline,omitempty"`
	Betrivers *BetriversV2BookSubscription `json:"betrivers,omitempty"`

	// Betus Per-sport league filter for `v2: { betus: {...} }`. Any BetUS sport.
	Betus *BetusV2BookSubscription `json:"betus,omitempty"`

	// Draftkings Per-book subscription shape for DraftKings v2 events.
	//
	// Each key is a sport `draftkings-v2-update` carries. Soccer and mma are multi-league
	// (filter by league keys, or `'*'` for all); the others are one league each (pass
	// `'*'`). Soccer, mma and wnba push their markets as they change. nba, nhl, mlb, nfl,
	// ncaab and ncaaf push only the live slice (marketId `live`), unless it is too large
	// to send; their main catalog is REST only. Unknown / unsupported sport keys are
	// rejected at compile time.
	Draftkings *DraftKingsV2BookSubscription `json:"draftkings,omitempty"`

	// Fanaticsmarkets Per-book subscription shape for Fanatics Markets v2 events.
	// Use `'*'` for a whole sport or exact series keys (e.g. `['mls']`) from
	// OwlsInsightRest.getFanaticsMarketsV2Leagues.
	Fanaticsmarkets *FanaticsMarketsV2BookSubscription `json:"fanaticsmarkets,omitempty"`

	// Fanduel Per-book subscription shape for FanDuel v2 events.
	//
	// Each key is a sport FanDuel v2 currently supports. Soccer, tennis and mma
	// are multi-league: filter by specific league keys or `'*'` for all.
	Fanduel *FanDuelV2BookSubscription `json:"fanduel,omitempty"`

	// Kalshi Per-book subscription shape for Kalshi v2 events.
	//
	// Each Kalshi sport carries one or more series (game lines, props,
	// futures). Pass `'*'` to receive every series for that sport, or an
	// array of Kalshi series tickers / league slugs to filter.
	Kalshi *KalshiV2BookSubscription `json:"kalshi,omitempty"`
	Lowvig *LowvigV2BookSubscription `json:"lowvig,omitempty"`

	// Mybookie Per-book subscription shape for MyBookie v2 events.
	//
	// MyBookie is single-league per sport (mlb, nba) - pass `'*'`. Arrays
	// are accepted and coerced to `'*'` server-side (V2_SINGLE_LEAGUE_PAIRS).
	Mybookie *MyBookieV2BookSubscription `json:"mybookie,omitempty"`

	// Pinnacle Per-book subscription shape for Pinnacle v2 events.
	//
	// Single-league sports (wnba, nba, mlb, nhl, ncaab, ncaaf, nfl) map 1:1
	// to a sport key - pass `'*'` to receive every matchup, or an array
	// containing the same sport key (filters are tolerated for forward
	// compatibility).
	//
	// Multi-league sports (tennis, soccer, esports, handball, mma) use slugified
	// Pinnacle league names as filter values (e.g. `'atp-singles'`).
	Pinnacle *PinnacleV2BookSubscription `json:"pinnacle,omitempty"`

	// Polymarket Per-book subscription shape for Polymarket v2 events.
	//
	// Each Polymarket sport is segmented by Polymarket tag slug (e.g.
	// `epl`, `ucl`, `nba`, `mlb`). Pass `'*'` for every tag, or an array
	// of tag slugs to filter.
	Polymarket *PolymarketV2BookSubscription `json:"polymarket,omitempty"`

	// Propbuilder Per-sport filter for `v2: { propbuilder: {...} }`. Every sport is one league: pass `'*'`.
	Propbuilder *PropbuilderV2BookSubscription `json:"propbuilder,omitempty"`

	// Thunderpick Per-book subscription shape for Thunderpick v2 events.
	//
	// Each Thunderpick sport is a single stream (no per-league
	// segmentation). Pass `'*'` per sport you want. Arrays are accepted
	// and coerced to `'*'` server-side.
	Thunderpick *ThunderpickV2BookSubscription `json:"thunderpick,omitempty"`

	// Tippmixpro Per-sport league filter for `v2: { tippmixpro: {...} }`.
	Tippmixpro *TippmixproV2BookSubscription `json:"tippmixpro,omitempty"`

	// Versus Per-book subscription shape for Versus (versus.es) v2 events.
	//
	// Every versus sport is multi-league - filter by specific league keys
	// (from OwlsInsightRest.getVersusV2Leagues) or `'*'` for all leagues.
	// Only the sports listed here push `versus-v2-update` deltas; the remaining
	// REST-only versus sports are omitted because they never broadcast.
	Versus *VersusV2BookSubscription `json:"versus,omitempty"`
}

V2Subscription Opt-in subscription for raw pass-through Source API (v2) WebSocket events. Pass on `subscribe()` to start receiving the corresponding `<book>-v2-update` events. Omit (or set the book to `undefined`) to receive nothing for that book - these events are opt-in only.

type V2Update

type V2Update struct {
	Count *json.RawMessage `json:"count,omitempty"`

	// Data The book's own JSON, served verbatim. Decode without strict typing.
	Data   *RawJSON         `json:"data,omitempty"`
	Etag   *string          `json:"etag,omitempty"`
	Events *json.RawMessage `json:"events,omitempty"`
	Hash   *string          `json:"hash,omitempty"`

	// Heartbeat Pinnacle: a heartbeat frame; with `matchupIds` it restates the league's full set.
	Heartbeat  *json.RawMessage `json:"heartbeat,omitempty"`
	Kind       *string          `json:"kind,omitempty"`
	League     *string          `json:"league,omitempty"`
	LeagueName *string          `json:"leagueName,omitempty"`
	Leagues    *json.RawMessage `json:"leagues,omitempty"`
	MarketID   *string          `json:"marketId,omitempty"`
	Markets    *json.RawMessage `json:"markets,omitempty"`

	// MatchupIds Pinnacle: the league's current matchup ids. Drop any you hold that are not listed.
	MatchupIds *json.RawMessage `json:"matchupIds,omitempty"`

	// Raw The book's own JSON, served verbatim. Decode without strict typing.
	Raw *RawJSON `json:"raw,omitempty"`

	// RemovedGhids MyBookie: games that left the league: delete them.
	RemovedGhids *json.RawMessage `json:"removedGhids,omitempty"`

	// RemovedIds Ids of entries that left the league: delete them.
	RemovedIds *json.RawMessage `json:"removedIds,omitempty"`

	// RemovedMatchupIds Pinnacle: matchups that left the league: delete them.
	RemovedMatchupIds *json.RawMessage `json:"removedMatchupIds,omitempty"`
	Sport             *string          `json:"sport,omitempty"`
	Timestamp         *json.RawMessage `json:"timestamp,omitempty"`
	TotalCount        *json.RawMessage `json:"totalCount,omitempty"`
}

V2Update Payload of a `{book}-v2-update` WebSocket event. Two shapes exist: most books send a batch (`markets` is an array of `{ marketId, hash, kind, data }`), while fanduel, draftkings and versus send one market per event (`marketId`, `hash`, `data`, with `markets` a count). `timestamp` is epoch milliseconds on most books and an ISO string on some. Fields that differ between books are left untyped; decode `markets`, `timestamp`, `data` and `raw` accordingly.

type VersusV2BookSubscription

type VersusV2BookSubscription struct {
	// Cricket Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Cricket *V2LeagueFilter `json:"cricket,omitempty"`

	// Darts Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Darts *V2LeagueFilter `json:"darts,omitempty"`

	// Handball Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Handball *V2LeagueFilter `json:"handball,omitempty"`

	// Mlb Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Mlb *V2LeagueFilter `json:"mlb,omitempty"`

	// Nfl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nfl *V2LeagueFilter `json:"nfl,omitempty"`

	// Nhl Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Nhl *V2LeagueFilter `json:"nhl,omitempty"`

	// Soccer Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Soccer *V2LeagueFilter `json:"soccer,omitempty"`

	// Tennis Per-sport league filter for v2 WebSocket subscriptions. Use `'*'` to
	// receive every league for that sport, or an array of league keys to
	// filter to a specific set. League keys come from the corresponding
	// `/leagues` REST endpoint.
	Tennis *V2LeagueFilter `json:"tennis,omitempty"`
}

VersusV2BookSubscription Per-book subscription shape for Versus (versus.es) v2 events.

Every versus sport is multi-league - filter by specific league keys (from OwlsInsightRest.getVersusV2Leagues) or `'*'` for all leagues. Only the sports listed here push `versus-v2-update` deltas; the remaining REST-only versus sports are omitted because they never broadcast.

type WSError

type WSError struct {
	Code    *string `json:"code,omitempty"`
	Message *string `json:"message,omitempty"`
}

WSError defines model for WSError.

type WebhookCreateResponse added in v0.5.0

type WebhookCreateResponse struct {
	// Data What create returns: the endpoint and its signing secret.
	Data    *WebhookEndpointWithSecret `json:"data,omitempty"`
	Success *bool                      `json:"success,omitempty"`
}

WebhookCreateResponse defines model for WebhookCreateResponse.

type WebhookDeleteResponse added in v0.5.0

type WebhookDeleteResponse struct {
	Data    *WebhookDeleted `json:"data,omitempty"`
	Success *bool           `json:"success,omitempty"`
}

WebhookDeleteResponse defines model for WebhookDeleteResponse.

type WebhookDeleted added in v0.5.0

type WebhookDeleted struct {
	Deleted *bool   `json:"deleted,omitempty"`
	ID      *string `json:"id,omitempty"`
}

WebhookDeleted defines model for WebhookDeleted.

type WebhookDeliveriesParams added in v0.5.0

type WebhookDeliveriesParams struct {
	// Status is pending, delivered, failed, expired, cancelled or shadow.
	Status string
	// Type is one event type, WebhookEventTest included.
	Type string
	// Limit is the page size, 1 to 50 (default 20).
	Limit int
	// StartingAfter is an event id (evt_...) from the previous page: the
	// deliveries older than it are returned.
	StartingAfter string
}

WebhookDeliveriesParams filters Client.ListWebhookDeliveries. Its zero fields are not sent.

type WebhookDeliveriesResponse added in v0.5.0

type WebhookDeliveriesResponse struct {
	Data []WebhookDelivery `json:"data,omitempty"`

	// HasMore True when older deliveries remain: ask for the next page starting after the last `id`.
	HasMore *bool `json:"has_more,omitempty"`
	Success *bool `json:"success,omitempty"`
}

WebhookDeliveriesResponse defines model for WebhookDeliveriesResponse.

type WebhookDelivery added in v0.5.0

type WebhookDelivery struct {
	Attempts    *float64 `json:"attempts,omitempty"`
	CreatedAt   *string  `json:"created_at,omitempty"`
	DeliveredAt *string  `json:"delivered_at,omitempty"`
	ExpiresAt   *string  `json:"expires_at,omitempty"`

	// ID The event id (`Owls-Webhook-Id`).
	ID            *string  `json:"id,omitempty"`
	LastError     *string  `json:"last_error,omitempty"`
	LastLatencyMs *float64 `json:"last_latency_ms,omitempty"`

	// LastStatusCode Your receiver's last HTTP status, or null.
	LastStatusCode *float64 `json:"last_status_code,omitempty"`

	// NextAttemptAt When the next attempt is due, while `pending`; otherwise null.
	NextAttemptAt *string `json:"next_attempt_at,omitempty"`
	OccurredAt    *string `json:"occurred_at,omitempty"`

	// Payload The exact body sent, or null.
	Payload *WebhookDeliveryPayload `json:"payload,omitempty"`

	// ResponseExcerpt Up to the first 512 bytes of your receiver's last answer.
	ResponseExcerpt *string `json:"response_excerpt,omitempty"`

	// Status `pending` (queued or between retries), `delivered`, `failed` (retries exhausted),
	// `expired` (past `expires_at`), `cancelled` (the endpoint was disabled or deleted) or
	// `shadow` (recorded while the event type was held back, never sent).
	Status *WebhookDeliveryStatus `json:"status,omitempty"`
	Type   *string                `json:"type,omitempty"`
}

WebhookDelivery One delivery to an endpoint, as the deliveries listing returns it.

type WebhookDeliveryPayload added in v0.5.0

type WebhookDeliveryPayload struct {
	// APIVersion The payload version.
	APIVersion *string `json:"api_version,omitempty"`

	// Created When the event was created (ISO 8601).
	Created *string          `json:"created,omitempty"`
	Data    *json.RawMessage `json:"data,omitempty"`

	// EndpointID The endpoint it was sent to, `whk_...`.
	EndpointID *string `json:"endpoint_id,omitempty"`

	// ExpiresAt No delivery is attempted after this (ISO 8601).
	ExpiresAt *string `json:"expires_at,omitempty"`

	// ID The event id, `evt_...`: identical on every retry and in the `Owls-Webhook-Id` header. Dedupe on it.
	ID *string `json:"id,omitempty"`

	// OccurredAt When what it reports happened, as far as is known (ISO 8601).
	OccurredAt *string `json:"occurred_at,omitempty"`

	// Type The event type; a type added later arrives as a string this version does not list.
	Type *string `json:"type,omitempty"`
}

WebhookDeliveryPayload A delivery body as stored, with `data` untyped: its shape depends on `type`.

func (*WebhookDeliveryPayload) EventID added in v0.5.0

func (e *WebhookDeliveryPayload) EventID() string

EventID is the event id ("" when absent).

func (*WebhookDeliveryPayload) EventType added in v0.5.0

func (e *WebhookDeliveryPayload) EventType() string

EventType is the event type ("" when absent).

type WebhookDeliveryStatus added in v0.5.0

type WebhookDeliveryStatus = string

WebhookDeliveryStatus `pending` (queued or between retries), `delivered`, `failed` (retries exhausted), `expired` (past `expires_at`), `cancelled` (the endpoint was disabled or deleted) or `shadow` (recorded while the event type was held back, never sent).

type WebhookEndpoint added in v0.5.0

type WebhookEndpoint struct {
	// APIVersion The payload version this endpoint's deliveries carry.
	APIVersion *string `json:"api_version,omitempty"`

	// ConsecutiveFailures Failed deliveries in a row.
	ConsecutiveFailures *float64                       `json:"consecutive_failures,omitempty"`
	CreatedAt           *string                        `json:"created_at,omitempty"`
	Description         *string                        `json:"description,omitempty"`
	EventTypes          []WebhookSubscribableEventType `json:"event_types,omitempty"`

	// FailingSince When the current run of failures began (ISO 8601), or null.
	FailingSince *string `json:"failing_since,omitempty"`

	// Filters An endpoint's filters as stored: the API fills in every default.
	Filters *WebhookFilters `json:"filters,omitempty"`

	// ID The endpoint id, `whk_...`.
	ID            *string `json:"id,omitempty"`
	LastSuccessAt *string `json:"last_success_at,omitempty"`

	// PreviousSecretExpiresAt While set, deliveries also carry a v1 signature made with the previous secret, until this time (ISO 8601).
	PreviousSecretExpiresAt *string `json:"previous_secret_expires_at,omitempty"`

	// SecretHint The last 4 characters of the current signing secret, to tell secrets apart.
	SecretHint *string `json:"secret_hint,omitempty"`

	// Status `enabled`; `disabled` (by you, or after your receiver answered 410 Gone); or
	// `auto_disabled` (after 50 failed deliveries in a row and 3 days without a success). An
	// update with `enabled` set to true resumes a disabled or auto-disabled endpoint.
	Status *WebhookEndpointStatus `json:"status,omitempty"`

	// StatusReason Why the endpoint is not enabled, or null.
	StatusReason *string `json:"status_reason,omitempty"`
	UpdatedAt    *string `json:"updated_at,omitempty"`
	URL          *string `json:"url,omitempty"`
}

WebhookEndpoint A webhook endpoint. It never carries the signing secret: create and rotate-secret return it once.

type WebhookEndpointStatus added in v0.5.0

type WebhookEndpointStatus = string

WebhookEndpointStatus `enabled`; `disabled` (by you, or after your receiver answered 410 Gone); or `auto_disabled` (after 50 failed deliveries in a row and 3 days without a success). An update with `enabled` set to true resumes a disabled or auto-disabled endpoint.

type WebhookEndpointWithSecret added in v0.5.0

type WebhookEndpointWithSecret struct {
	// APIVersion The payload version this endpoint's deliveries carry.
	APIVersion *string `json:"api_version,omitempty"`

	// ConsecutiveFailures Failed deliveries in a row.
	ConsecutiveFailures *float64                       `json:"consecutive_failures,omitempty"`
	CreatedAt           *string                        `json:"created_at,omitempty"`
	Description         *string                        `json:"description,omitempty"`
	EventTypes          []WebhookSubscribableEventType `json:"event_types,omitempty"`

	// FailingSince When the current run of failures began (ISO 8601), or null.
	FailingSince *string `json:"failing_since,omitempty"`

	// Filters An endpoint's filters as stored: the API fills in every default.
	Filters *WebhookFilters `json:"filters,omitempty"`

	// ID The endpoint id, `whk_...`.
	ID            *string `json:"id,omitempty"`
	LastSuccessAt *string `json:"last_success_at,omitempty"`

	// PreviousSecretExpiresAt While set, deliveries also carry a v1 signature made with the previous secret, until this time (ISO 8601).
	PreviousSecretExpiresAt *string `json:"previous_secret_expires_at,omitempty"`

	// Secret The signing secret, `whsec_...`. Returned once: store it now.
	Secret *string `json:"secret,omitempty"`

	// SecretHint The last 4 characters of the current signing secret, to tell secrets apart.
	SecretHint *string `json:"secret_hint,omitempty"`

	// Status `enabled`; `disabled` (by you, or after your receiver answered 410 Gone); or
	// `auto_disabled` (after 50 failed deliveries in a row and 3 days without a success). An
	// update with `enabled` set to true resumes a disabled or auto-disabled endpoint.
	Status *WebhookEndpointStatus `json:"status,omitempty"`

	// StatusReason Why the endpoint is not enabled, or null.
	StatusReason *string `json:"status_reason,omitempty"`
	UpdatedAt    *string `json:"updated_at,omitempty"`
	URL          *string `json:"url,omitempty"`
}

WebhookEndpointWithSecret What create returns: the endpoint and its signing secret.

type WebhookEvFoundData added in v0.5.0

type WebhookEvFoundData struct {
	AwayTeam *string `json:"away_team,omitempty"`

	// BoardAgeMs The EV board's age in milliseconds when the event was made, or null.
	BoardAgeMs *float64 `json:"board_age_ms,omitempty"`

	// CommenceTime The scheduled start (ISO 8601).
	CommenceTime *string `json:"commence_time,omitempty"`

	// EventID Our stable id of the game (`{sport}:{away}@{home}-{date}`), the `eventId` the odds board and the history endpoints use; empty when unknown.
	EventID  *string `json:"event_id,omitempty"`
	HomeTeam *string `json:"home_team,omitempty"`
	League   *string `json:"league,omitempty"`

	// MinEv The endpoint's `min_ev` the price reached.
	MinEv *float64 `json:"min_ev,omitempty"`

	// OddsEventID The game's `id` on the odds board: what the EV endpoint returns as `eventId`.
	OddsEventID *string `json:"odds_event_id,omitempty"`

	// Signal The price behind an `ev.found` event, scored like the EV endpoint scores it.
	Signal *WebhookEvSignal `json:"signal,omitempty"`
	Sport  *string          `json:"sport,omitempty"`
}

WebhookEvFoundData `ev.found` data: a price on the EV board became tradeable at or above your `min_ev`.

type WebhookEvFoundEvent added in v0.5.0

type WebhookEvFoundEvent struct {
	// APIVersion The payload version.
	APIVersion *string `json:"api_version,omitempty"`

	// Created When the event was created (ISO 8601).
	Created *string `json:"created,omitempty"`

	// Data `ev.found` data: a price on the EV board became tradeable at or above your `min_ev`.
	Data *WebhookEvFoundData `json:"data,omitempty"`

	// EndpointID The endpoint it was sent to, `whk_...`.
	EndpointID *string `json:"endpoint_id,omitempty"`

	// ExpiresAt No delivery is attempted after this (ISO 8601).
	ExpiresAt *string `json:"expires_at,omitempty"`

	// ID The event id, `evt_...`: identical on every retry and in the `Owls-Webhook-Id` header. Dedupe on it.
	ID *string `json:"id,omitempty"`

	// OccurredAt When what it reports happened, as far as is known (ISO 8601).
	OccurredAt *string `json:"occurred_at,omitempty"`

	// Type The event type; a type added later arrives as a string this version does not list.
	Type *string `json:"type,omitempty"`
}

WebhookEvFoundEvent An `ev.found` delivery.

func (*WebhookEvFoundEvent) EventID added in v0.5.0

func (e *WebhookEvFoundEvent) EventID() string

EventID is the event id ("" when absent).

func (*WebhookEvFoundEvent) EventType added in v0.5.0

func (e *WebhookEvFoundEvent) EventType() string

EventType is the event type ("" when absent).

type WebhookEvFoundFilters added in v0.5.0

type WebhookEvFoundFilters struct {
	// Kinds The price kinds that fire (default both).
	Kinds []WebhookEvKind `json:"kinds,omitempty"`

	// MinEv The least EV that fires, in percent: 1, 2, 3, 5 or 8 (default 3).
	MinEv *float64 `json:"min_ev,omitempty"`

	// Venues Sportsbook or venue keys; empty (the default) means every one the EV board scores.
	Venues []string `json:"venues,omitempty"`
}

WebhookEvFoundFilters `ev.found` filters as stored: every field is set.

type WebhookEvFoundFiltersInput added in v0.5.0

type WebhookEvFoundFiltersInput struct {
	// MinEv is the least EV that fires, in percent: 1, 2, 3, 5 or 8 (default 3).
	// 0 is not sent.
	MinEv float64 `json:"min_ev,omitempty"`
	// Kinds are book (sportsbooks) and venue (exchanges and prediction markets);
	// nil or empty is not sent (default both).
	Kinds []string `json:"kinds,omitempty"`
	// Venues are sportsbook or venue keys the EV board scores. nil sends nothing;
	// a non-nil empty slice sends [], which means every one.
	Venues []string `json:"venues,omitempty"`
}

WebhookEvFoundFiltersInput is the ev.found section of WebhookFiltersInput.

func (WebhookEvFoundFiltersInput) MarshalJSON added in v0.5.0

func (f WebhookEvFoundFiltersInput) MarshalJSON() ([]byte, error)

MarshalJSON leaves out the zero fields, but sends Venues when it is non-nil (an empty list included).

type WebhookEvKind added in v0.5.0

type WebhookEvKind = string

WebhookEvKind `ev.found` price kinds: `book` (a sportsbook) or `venue` (an exchange or prediction market).

type WebhookEvSignal added in v0.5.0

type WebhookEvSignal struct {
	// Ev The EV in dollars: per $1 staked at a sportsbook, per contract at a venue. Null when it is not scored.
	Ev *float64 `json:"ev,omitempty"`

	// EvPercent The EV in percent of the amount at risk, rounded to 2 decimals.
	EvPercent *float64 `json:"ev_percent,omitempty"`

	// FairLow The conservative fair (the fair less 1 percentage point) the EV is scored against, or null.
	FairLow *float64 `json:"fair_low,omitempty"`

	// FairProbability The fair probability of this side, or null.
	FairProbability *float64 `json:"fair_probability,omitempty"`

	// Fee A venue's taker fee per contract, in dollars; 0 for a sportsbook.
	Fee *float64 `json:"fee,omitempty"`

	// ImpliedProbability The implied probability of `price`.
	ImpliedProbability *float64 `json:"implied_probability,omitempty"`

	// Kind `ev.found` price kinds: `book` (a sportsbook) or `venue` (an exchange or prediction market).
	Kind *WebhookEvKind `json:"kind,omitempty"`

	// Link The venue's page for the market, or null.
	Link *string `json:"link,omitempty"`

	// Price American odds of the price you pay (a venue's ask).
	Price *float64 `json:"price,omitempty"`

	// QuoteAgeMs The price's age in milliseconds when the EV board was built, or null.
	QuoteAgeMs *float64 `json:"quote_age_ms,omitempty"`

	// Route A venue's route: `yes`, or `no:<CODE>` (buy NO on the opponent's market); null for a sportsbook.
	Route *string `json:"route,omitempty"`
	Side  *string `json:"side,omitempty"`

	// Size Dollars available at `price`, or null when unknown.
	Size *float64 `json:"size,omitempty"`
	Team *string  `json:"team,omitempty"`

	// Venue The sportsbook or venue key.
	Venue *string `json:"venue,omitempty"`
}

WebhookEvSignal The price behind an `ev.found` event, scored like the EV endpoint scores it.

type WebhookEvent added in v0.5.0

type WebhookEvent interface {
	// EventID is the event id (evt_...), identical on every retry: dedupe on it.
	EventID() string
	// EventType is the event type, such as WebhookEventLineMoved.
	EventType() string
}

WebhookEvent is a delivery body as ParseWebhookEvent returns it: *WebhookLineMovedEvent, *WebhookEvFoundEvent, *WebhookGameStartedEvent, *WebhookGameFinalEvent, *WebhookPropsGradedEvent, *WebhookTestEvent, or *WebhookDeliveryPayload for an event type this version does not know.

func ParseWebhookEvent added in v0.5.0

func ParseWebhookEvent(payload []byte) (WebhookEvent, error)

ParseWebhookEvent decodes a delivery body, after VerifyWebhookSignature accepted it, into the model of its type (see WebhookEvent). Decoding is lenient, like Decode. It returns an error when the body is not a JSON object with a type.

ev, err := owls.ParseWebhookEvent(body)
if err != nil {
	return err
}
if seen(ev.EventID()) {
	return nil // a retry of an event already handled
}
switch ev := ev.(type) {
case *owls.WebhookLineMovedEvent:
	fmt.Println(*ev.Data.Market, ev.Data.Current.Home)
case *owls.WebhookEvFoundEvent:
	fmt.Println(*ev.Data.Signal.Venue, *ev.Data.Signal.EvPercent)
}

type WebhookEventType added in v0.5.0

type WebhookEventType = string

WebhookEventType Every event type a delivery can carry: the subscribable ones and `webhook.test` (sent only on request, by the test call).

type WebhookFilters added in v0.5.0

type WebhookFilters struct {
	// EvFound `ev.found` filters as stored: every field is set.
	EvFound *WebhookEvFoundFilters `json:"ev_found,omitempty"`

	// LineMoved `line.moved` filters as stored: every field is set.
	LineMoved *WebhookLineMovedFilters `json:"line_moved,omitempty"`

	// Sports Sport keys; empty (the default) means every sport each subscribed event type covers.
	Sports []string `json:"sports,omitempty"`
}

WebhookFilters An endpoint's filters as stored: the API fills in every default.

type WebhookFiltersInput added in v0.5.0

type WebhookFiltersInput struct {
	// Sports are sport keys. nil sends nothing; a non-nil empty slice sends [],
	// which means every sport the endpoint's event types cover.
	Sports    []string                      `json:"sports,omitempty"`
	LineMoved *WebhookLineMovedFiltersInput `json:"line_moved,omitempty"`
	EvFound   *WebhookEvFoundFiltersInput   `json:"ev_found,omitempty"`
}

WebhookFiltersInput is an endpoint's filters as sent. On create an omitted field takes its default. On update the filters merge into the stored ones: a section you send (LineMoved, EvFound) changes only the fields you set, and Sports replaces the list. A sport that one of the endpoint's event types does not cover is refused with a 400 (sport_not_covered).

func (WebhookFiltersInput) MarshalJSON added in v0.5.0

func (f WebhookFiltersInput) MarshalJSON() ([]byte, error)

MarshalJSON sends only the sections that are set, and Sports when it is non-nil (an empty list included).

type WebhookGameData added in v0.5.0

type WebhookGameData struct {
	AwayTeam *string `json:"away_team,omitempty"`

	// EventID The game's id in the live scores.
	EventID  *string `json:"event_id,omitempty"`
	HomeTeam *string `json:"home_team,omitempty"`
	League   *string `json:"league,omitempty"`

	// Score Null when the scores show none.
	Score *WebhookGameScore `json:"score,omitempty"`
	Sport *string           `json:"sport,omitempty"`

	// StartTime The start time (ISO 8601), or null.
	StartTime *string `json:"start_time,omitempty"`

	// Status A game's status as the live scores show it.
	Status *WebhookGameStatus `json:"status,omitempty"`
}

WebhookGameData `game.started` and `game.final` data.

type WebhookGameFinalEvent added in v0.5.0

type WebhookGameFinalEvent struct {
	// APIVersion The payload version.
	APIVersion *string `json:"api_version,omitempty"`

	// Created When the event was created (ISO 8601).
	Created *string `json:"created,omitempty"`

	// Data `game.started` and `game.final` data.
	Data *WebhookGameData `json:"data,omitempty"`

	// EndpointID The endpoint it was sent to, `whk_...`.
	EndpointID *string `json:"endpoint_id,omitempty"`

	// ExpiresAt No delivery is attempted after this (ISO 8601).
	ExpiresAt *string `json:"expires_at,omitempty"`

	// ID The event id, `evt_...`: identical on every retry and in the `Owls-Webhook-Id` header. Dedupe on it.
	ID *string `json:"id,omitempty"`

	// OccurredAt When what it reports happened, as far as is known (ISO 8601).
	OccurredAt *string `json:"occurred_at,omitempty"`

	// Type The event type; a type added later arrives as a string this version does not list.
	Type *string `json:"type,omitempty"`
}

WebhookGameFinalEvent A `game.final` delivery.

func (*WebhookGameFinalEvent) EventID added in v0.5.0

func (e *WebhookGameFinalEvent) EventID() string

EventID is the event id ("" when absent).

func (*WebhookGameFinalEvent) EventType added in v0.5.0

func (e *WebhookGameFinalEvent) EventType() string

EventType is the event type ("" when absent).

type WebhookGameScore added in v0.5.0

type WebhookGameScore struct {
	Away *float64 `json:"away,omitempty"`
	Home *float64 `json:"home,omitempty"`
}

WebhookGameScore A game's score. For tennis, sets won.

type WebhookGameStartedEvent added in v0.5.0

type WebhookGameStartedEvent struct {
	// APIVersion The payload version.
	APIVersion *string `json:"api_version,omitempty"`

	// Created When the event was created (ISO 8601).
	Created *string `json:"created,omitempty"`

	// Data `game.started` and `game.final` data.
	Data *WebhookGameData `json:"data,omitempty"`

	// EndpointID The endpoint it was sent to, `whk_...`.
	EndpointID *string `json:"endpoint_id,omitempty"`

	// ExpiresAt No delivery is attempted after this (ISO 8601).
	ExpiresAt *string `json:"expires_at,omitempty"`

	// ID The event id, `evt_...`: identical on every retry and in the `Owls-Webhook-Id` header. Dedupe on it.
	ID *string `json:"id,omitempty"`

	// OccurredAt When what it reports happened, as far as is known (ISO 8601).
	OccurredAt *string `json:"occurred_at,omitempty"`

	// Type The event type; a type added later arrives as a string this version does not list.
	Type *string `json:"type,omitempty"`
}

WebhookGameStartedEvent A `game.started` delivery.

func (*WebhookGameStartedEvent) EventID added in v0.5.0

func (e *WebhookGameStartedEvent) EventID() string

EventID is the event id ("" when absent).

func (*WebhookGameStartedEvent) EventType added in v0.5.0

func (e *WebhookGameStartedEvent) EventType() string

EventType is the event type ("" when absent).

type WebhookGameStatus added in v0.5.0

type WebhookGameStatus struct {
	// Detail Its detail text, such as the clock or the result.
	Detail *string `json:"detail,omitempty"`

	// State The scores feed's state, such as `in` or `post`.
	State *string `json:"state,omitempty"`
}

WebhookGameStatus A game's status as the live scores show it.

type WebhookLineMarket added in v0.5.0

type WebhookLineMarket = string

WebhookLineMarket The markets `line.moved` watches.

type WebhookLineMovedData added in v0.5.0

type WebhookLineMovedData struct {
	// AwayTeam Empty when unknown.
	AwayTeam *string `json:"away_team,omitempty"`
	Book     *string `json:"book,omitempty"`

	// CommenceTime The scheduled start (ISO 8601); empty when unknown.
	CommenceTime *string `json:"commence_time,omitempty"`

	// Current The sides of a `line.moved` market: `home`, `away` and, on a three-way market, `draw`
	// (moneyline); `home` and `away` (spread); `over` and `under` (total).
	Current *WebhookLineSides `json:"current,omitempty"`

	// EventID Our stable id of the game (`{sport}:{away}@{home}-{date}`), the `eventId` the odds board and the history endpoints use. Empty when unknown.
	EventID *string `json:"event_id,omitempty"`

	// HomeTeam Empty when unknown.
	HomeTeam *string `json:"home_team,omitempty"`
	League   *string `json:"league,omitempty"`

	// Market The markets `line.moved` watches.
	Market *WebhookLineMarket `json:"market,omitempty"`

	// MaxProbabilityChangePp The largest change of a side's implied probability from `previous`, in percentage points.
	MaxProbabilityChangePp *float64 `json:"max_probability_change_pp,omitempty"`

	// PointChange The change of the line (the home spread, or the total) from `previous`; 0 on a moneyline.
	PointChange *float64 `json:"point_change,omitempty"`

	// Previous The sides of a `line.moved` market: `home`, `away` and, on a three-way market, `draw`
	// (moneyline); `home` and `away` (spread); `over` and `under` (total).
	Previous *WebhookLineSides `json:"previous,omitempty"`

	// Reason `price`: a side's implied probability moved by at least `price_step_pp` at the same line. `point`: the line moved by at least `point_step`.
	Reason *string `json:"reason,omitempty"`

	// SourceEventID The book's own id for the event.
	SourceEventID *string `json:"source_event_id,omitempty"`
	Sport         *string `json:"sport,omitempty"`

	// Thresholds The endpoint's `line.moved` steps the move cleared.
	Thresholds *WebhookLineMovedThresholds `json:"thresholds,omitempty"`
}

WebhookLineMovedData `line.moved` data: Pinnacle's main moneyline, spread or total for a game that has not started moved by at least your step since the last `line.moved` you were sent for that market, so small moves add up.

type WebhookLineMovedEvent added in v0.5.0

type WebhookLineMovedEvent struct {
	// APIVersion The payload version.
	APIVersion *string `json:"api_version,omitempty"`

	// Created When the event was created (ISO 8601).
	Created *string `json:"created,omitempty"`

	// Data `line.moved` data: Pinnacle's main moneyline, spread or total for a game that has not
	// started moved by at least your step since the last `line.moved` you were sent for that
	// market, so small moves add up.
	Data *WebhookLineMovedData `json:"data,omitempty"`

	// EndpointID The endpoint it was sent to, `whk_...`.
	EndpointID *string `json:"endpoint_id,omitempty"`

	// ExpiresAt No delivery is attempted after this (ISO 8601).
	ExpiresAt *string `json:"expires_at,omitempty"`

	// ID The event id, `evt_...`: identical on every retry and in the `Owls-Webhook-Id` header. Dedupe on it.
	ID *string `json:"id,omitempty"`

	// OccurredAt When what it reports happened, as far as is known (ISO 8601).
	OccurredAt *string `json:"occurred_at,omitempty"`

	// Type The event type; a type added later arrives as a string this version does not list.
	Type *string `json:"type,omitempty"`
}

WebhookLineMovedEvent A `line.moved` delivery.

func (*WebhookLineMovedEvent) EventID added in v0.5.0

func (e *WebhookLineMovedEvent) EventID() string

EventID is the event id ("" when absent).

func (*WebhookLineMovedEvent) EventType added in v0.5.0

func (e *WebhookLineMovedEvent) EventType() string

EventType is the event type ("" when absent).

type WebhookLineMovedFilters added in v0.5.0

type WebhookLineMovedFilters struct {
	// Markets The markets watched (default all three).
	Markets []WebhookLineMarket `json:"markets,omitempty"`

	// PointStep The spread or total line move that fires: 0.5, 1, 1.5, 2 or 3 (default 1).
	PointStep *float64 `json:"point_step,omitempty"`

	// PriceStepPp The implied-probability move that fires, in percentage points: 0.5, 1, 2, 3 or 5 (default 2).
	PriceStepPp *float64 `json:"price_step_pp,omitempty"`
}

WebhookLineMovedFilters `line.moved` filters as stored: every field is set.

type WebhookLineMovedFiltersInput added in v0.5.0

type WebhookLineMovedFiltersInput struct {
	// PriceStepPp is the implied-probability move that fires, in percentage
	// points: 0.5, 1, 2, 3 or 5 (default 2).
	PriceStepPp float64 `json:"price_step_pp,omitempty"`
	// PointStep is the spread or total line move that fires: 0.5, 1, 1.5, 2 or 3
	// (default 1).
	PointStep float64 `json:"point_step,omitempty"`
	// Markets are moneyline, spread and total (default all three).
	Markets []string `json:"markets,omitempty"`
}

WebhookLineMovedFiltersInput is the line.moved section of WebhookFiltersInput. Its zero fields are not sent.

type WebhookLineMovedThresholds added in v0.5.0

type WebhookLineMovedThresholds struct {
	PointStep   *float64 `json:"point_step,omitempty"`
	PriceStepPp *float64 `json:"price_step_pp,omitempty"`
}

WebhookLineMovedThresholds The endpoint's `line.moved` steps the move cleared.

type WebhookLineSide added in v0.5.0

type WebhookLineSide struct {
	// Point The line: this side's spread, or the total. Absent on a moneyline.
	Point *float64 `json:"point,omitempty"`

	// Price American odds.
	Price *float64 `json:"price,omitempty"`
}

WebhookLineSide One side of a `line.moved` market.

type WebhookLineSides added in v0.5.0

type WebhookLineSides struct {
	// Away One side of a `line.moved` market.
	Away *WebhookLineSide `json:"away,omitempty"`

	// Draw One side of a `line.moved` market.
	Draw *WebhookLineSide `json:"draw,omitempty"`

	// Home One side of a `line.moved` market.
	Home *WebhookLineSide `json:"home,omitempty"`

	// Over One side of a `line.moved` market.
	Over *WebhookLineSide `json:"over,omitempty"`

	// Under One side of a `line.moved` market.
	Under *WebhookLineSide `json:"under,omitempty"`
}

WebhookLineSides The sides of a `line.moved` market: `home`, `away` and, on a three-way market, `draw` (moneyline); `home` and `away` (spread); `over` and `under` (total).

type WebhookListMeta added in v0.5.0

type WebhookListMeta struct {
	Count *float64 `json:"count,omitempty"`

	// Limit How many endpoints your plan allows.
	Limit *float64 `json:"limit,omitempty"`
}

WebhookListMeta defines model for WebhookListMeta.

type WebhookListResponse added in v0.5.0

type WebhookListResponse struct {
	Data    []WebhookEndpoint `json:"data,omitempty"`
	Meta    *WebhookListMeta  `json:"meta,omitempty"`
	Success *bool             `json:"success,omitempty"`
}

WebhookListResponse defines model for WebhookListResponse.

type WebhookPropsGradedData added in v0.5.0

type WebhookPropsGradedData struct {
	// Complete True when every result of the game is confirmed.
	Complete       *bool    `json:"complete,omitempty"`
	ConfirmedCount *float64 `json:"confirmed_count,omitempty"`
	EventID        *string  `json:"event_id,omitempty"`

	// GameDate The game's date (YYYY-MM-DD), or null.
	GameDate     *string  `json:"game_date,omitempty"`
	PendingCount *float64 `json:"pending_count,omitempty"`

	// ResultsPath The path of the graded results (a GET with your key, Rookie and above), such as `/api/v1/mlb/props/results?game_id=...`.
	ResultsPath *string `json:"results_path,omitempty"`
	Sport       *string `json:"sport,omitempty"`
}

WebhookPropsGradedData `props.graded` data: the first player prop results of a game are confirmed (`complete: false`), or all of them are (`complete: true`).

type WebhookPropsGradedEvent added in v0.5.0

type WebhookPropsGradedEvent struct {
	// APIVersion The payload version.
	APIVersion *string `json:"api_version,omitempty"`

	// Created When the event was created (ISO 8601).
	Created *string `json:"created,omitempty"`

	// Data `props.graded` data: the first player prop results of a game are confirmed (`complete: false`), or all of them are (`complete: true`).
	Data *WebhookPropsGradedData `json:"data,omitempty"`

	// EndpointID The endpoint it was sent to, `whk_...`.
	EndpointID *string `json:"endpoint_id,omitempty"`

	// ExpiresAt No delivery is attempted after this (ISO 8601).
	ExpiresAt *string `json:"expires_at,omitempty"`

	// ID The event id, `evt_...`: identical on every retry and in the `Owls-Webhook-Id` header. Dedupe on it.
	ID *string `json:"id,omitempty"`

	// OccurredAt When what it reports happened, as far as is known (ISO 8601).
	OccurredAt *string `json:"occurred_at,omitempty"`

	// Type The event type; a type added later arrives as a string this version does not list.
	Type *string `json:"type,omitempty"`
}

WebhookPropsGradedEvent A `props.graded` delivery.

func (*WebhookPropsGradedEvent) EventID added in v0.5.0

func (e *WebhookPropsGradedEvent) EventID() string

EventID is the event id ("" when absent).

func (*WebhookPropsGradedEvent) EventType added in v0.5.0

func (e *WebhookPropsGradedEvent) EventType() string

EventType is the event type ("" when absent).

type WebhookResponse added in v0.5.0

type WebhookResponse struct {
	// Data A webhook endpoint. It never carries the signing secret: create and rotate-secret return it once.
	Data    *WebhookEndpoint `json:"data,omitempty"`
	Success *bool            `json:"success,omitempty"`
}

WebhookResponse defines model for WebhookResponse.

type WebhookRotateSecretResponse added in v0.5.0

type WebhookRotateSecretResponse struct {
	Data    *WebhookRotatedSecret `json:"data,omitempty"`
	Success *bool                 `json:"success,omitempty"`
}

WebhookRotateSecretResponse defines model for WebhookRotateSecretResponse.

type WebhookRotatedSecret added in v0.5.0

type WebhookRotatedSecret struct {
	ID *string `json:"id,omitempty"`

	// PreviousSecretExpiresAt Until when the previous secret keeps signing (ISO 8601), or null when it was retired at once.
	PreviousSecretExpiresAt *string `json:"previous_secret_expires_at,omitempty"`

	// Secret The new signing secret, `whsec_...`. Returned once: store it now.
	Secret     *string `json:"secret,omitempty"`
	SecretHint *string `json:"secret_hint,omitempty"`
}

WebhookRotatedSecret defines model for WebhookRotatedSecret.

type WebhookSignature added in v0.5.0

type WebhookSignature struct {
	// Timestamp is the t value, unix seconds; meaningful only when HasTimestamp.
	Timestamp    int64
	HasTimestamp bool
	// V1 holds every well-formed v1 value, lowercase, in header order.
	V1 []string
}

WebhookSignature is a parsed Owls-Signature header.

func ParseWebhookSignature added in v0.5.0

func ParseWebhookSignature(header string) WebhookSignature

ParseWebhookSignature reads t=<unix seconds>,v1=<hex>[,v1=<hex>]. Whitespace around a part is ignored, and so are parts it does not know (another scheme such as a future v2) and malformed values. The last valid t wins; a t has 1 to 12 digits.

type WebhookSubscribableEventType added in v0.5.0

type WebhookSubscribableEventType = string

WebhookSubscribableEventType Event types an endpoint can subscribe to.

type WebhookTestData added in v0.5.0

type WebhookTestData struct {
	Message *string `json:"message,omitempty"`
}

WebhookTestData `webhook.test` data.

type WebhookTestEvent added in v0.5.0

type WebhookTestEvent struct {
	// APIVersion The payload version.
	APIVersion *string `json:"api_version,omitempty"`

	// Created When the event was created (ISO 8601).
	Created *string `json:"created,omitempty"`

	// Data `webhook.test` data.
	Data *WebhookTestData `json:"data,omitempty"`

	// EndpointID The endpoint it was sent to, `whk_...`.
	EndpointID *string `json:"endpoint_id,omitempty"`

	// ExpiresAt No delivery is attempted after this (ISO 8601).
	ExpiresAt *string `json:"expires_at,omitempty"`

	// ID The event id, `evt_...`: identical on every retry and in the `Owls-Webhook-Id` header. Dedupe on it.
	ID *string `json:"id,omitempty"`

	// OccurredAt When what it reports happened, as far as is known (ISO 8601).
	OccurredAt *string `json:"occurred_at,omitempty"`

	// Type The event type; a type added later arrives as a string this version does not list.
	Type *string `json:"type,omitempty"`
}

WebhookTestEvent A `webhook.test` delivery (sent on request, by the test call).

func (*WebhookTestEvent) EventID added in v0.5.0

func (e *WebhookTestEvent) EventID() string

EventID is the event id ("" when absent).

func (*WebhookTestEvent) EventType added in v0.5.0

func (e *WebhookTestEvent) EventType() string

EventType is the event type ("" when absent).

type WebhookTestQueued added in v0.5.0

type WebhookTestQueued struct {
	// ID The test event's id: find it in the endpoint's deliveries.
	ID     *string `json:"id,omitempty"`
	Status *string `json:"status,omitempty"`
	Type   *string `json:"type,omitempty"`
}

WebhookTestQueued defines model for WebhookTestQueued.

type WebhookTestResponse added in v0.5.0

type WebhookTestResponse struct {
	Data    *WebhookTestQueued `json:"data,omitempty"`
	Success *bool              `json:"success,omitempty"`
}

WebhookTestResponse defines model for WebhookTestResponse.

Directories

Path Synopsis
internal
cmd/doctidy command
Command doctidy rewrites the doc comments oapi-codegen copies from the spec into Go doc style: {@link X} (JSDoc) becomes X, and an em dash becomes a hyphen.
Command doctidy rewrites the doc comments oapi-codegen copies from the spec into Go doc style: {@link X} (JSDoc) becomes X, and an em dash becomes a hyphen.
lenient
Package lenient decodes API responses without failing on a value whose JSON type does not fit its Go field.
Package lenient decodes API responses without failing on a value whose JSON type does not fit its Go field.
sio
Package sio is the subset of Engine.IO v4 and Socket.IO v5 the Owls Insight server uses: the websocket transport only, JSON text frames, the "/" namespace, no acknowledgements and no binary packets.
Package sio is the subset of Engine.IO v4 and Socket.IO v5 the Owls Insight server uses: the websocket transport only, JSON text frames, the "/" namespace, no acknowledgements and no binary packets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL