owls-insight-ts
v0.31.0
Published
Official TypeScript SDK for the Owls Insight real-time sports betting odds API
Downloads
2,080
Maintainers
Readme
Owls Insight SDK
Official TypeScript/JavaScript SDK for the Owls Insight real-time sports betting odds API.
Features
- Full TypeScript types for all endpoints and events
- REST client for odds, scores, props, history, and stats
- WebSocket client for real-time streaming updates
- Works in Node.js, Bun, Deno, and modern browsers
- ESM and CommonJS support
Installation
npm install owls-insight-tsQuick Start
import { OwlsInsight } from "owls-insight-ts";
const client = new OwlsInsight({ apiKey: process.env.OWLS_INSIGHT_API_KEY! });
// Get NBA odds from all books
const odds = await client.rest.getOdds("nba");
// odds.data is keyed by book: { pinnacle: [...], fanduel: [...], ... }
for (const [book, events] of Object.entries(odds.data)) {
console.log(`${book}: ${events.length} events`);
}
// Get odds from specific books
const filtered = await client.rest.getOdds("nba", {
books: ["pinnacle", "fanduel"],
});REST API
Odds
// All markets (moneyline + spreads + totals)
const odds = await client.rest.getOdds("nba");
// Single market
const ml = await client.rest.getMoneyline("nba");
const spreads = await client.rest.getSpreads("nfl");
const totals = await client.rest.getTotals("nhl");
// With filters
const filtered = await client.rest.getOdds("soccer", {
books: ["pinnacle", "bet365"],
league: "England - Premier League",
alternates: true,
});Esports (CS2, Valorant, LoL)
const cs2 = await client.rest.getOdds("cs2");
const valorant = await client.rest.getOdds("valorant");
const lol = await client.rest.getOdds("lol");CS2 odds from 1xBet. Valorant and LoL from both 1xBet and Pinnacle. Esports use a separate WebSocket event (esports-update).
Live Scores
// All sports
const allScores = await client.rest.getScores();
// Specific sport
const nbaScores = await client.rest.getScores("nba");Player Props
// All books
const props = await client.rest.getProps("nba");
// Filtered
const filtered = await client.rest.getProps("nba", {
player: "LeBron",
category: "points",
books: ["pinnacle", "fanduel"],
});
// Single book (typed per-book response)
const fdProps = await client.rest.getBookProps("nba", "fanduel");
const mgmProps = await client.rest.getBookProps("nba", "betmgm"); // BetMGMPropsResponse
const b365Props = await client.rest.getBookProps("nba", "bet365"); // Bet365PropsResponse
// Props line movement history
const propsHistory = await client.rest.getPropsHistory("nba", {
game_id: "game_123",
player: "LeBron James",
category: "points",
book: "pinnacle",
hours: 24,
});
// Props cache statistics
const propsStats = await client.rest.getPropsStats();
const bet365Stats = await client.rest.getBookPropsStats("bet365");Box Scores & Stats
// NBA box scores for today
const stats = await client.rest.getStats("nba");
// Filter by date and player
const filtered = await client.rest.getStats("nba", {
date: "2026-03-05",
player: "LeBron",
});
Real-Time Odds
Sub-second sharp Pinnacle odds via a dedicated realtime stream. Available for all sports.
const realtime = await client.rest.getRealtime("nba");
console.log(realtime.meta.source); // "pinnacle"
console.log(realtime.meta.events); // number of events
console.log(realtime.meta.available); // true if data exists
console.log(realtime.meta.freshness); // { ageSeconds, stale }The freshness object indicates data age. Data is considered stale after 30 seconds.
PS3838 Real-Time
Independent real-time stream from PS3838 (Pinnacle whitelabel). Same shape as /realtime,
but sourced from a separate WebSocket pipeline so customers can compare or fall back.
const ps3838 = await client.rest.getPS3838Realtime("nba");
console.log(ps3838.meta.source); // "ps3838"
console.log(ps3838.data.length); // events with PS3838 marketsEsports Real-Time
Per-game realtime from Pinnacle's esports feed. Available for CS2, LoL, Valorant, Dota2.
A league filter is required — pick the tournament you want (e.g. "BLAST", "LEC", "VCT").
const cs2 = await client.rest.getEsportsRealtime("cs2", "BLAST");
const lec = await client.rest.getEsportsRealtime("lol", "LEC");ProphetX (Exchange)
ProphetX is a betting exchange with order-book liquidity per outcome. The endpoint returns the raw ProphetX JSON structure — no canonical normalization.
const px = await client.rest.getProphetxOdds({
sport: "basketball", // raw ProphetX sport slug (or array)
kind: "game", // optional: "game" | "prop"
});
console.log(px.data.totalEvents); // raw ProphetX event count
for (const [sport, events] of Object.entries(px.data.sports)) {
console.log(`${sport}: ${events.length} events`);
}Odds History
// Line movement history
const history = await client.rest.getOddsHistory({
eventId: "event_123",
book: "pinnacle",
market: "spreads",
side: "home",
hours: 24,
});Historical Archive
// List completed games
const games = await client.rest.getHistoryGames({
sport: "nba",
startDate: "2026-03-01",
endDate: "2026-03-06",
});
// Get archived odds snapshots
const snapshots = await client.rest.getHistoryOdds({
eventId: "event_123",
book: "pinnacle",
market: "spreads",
});
// Get archived props snapshots
const propsSnapshots = await client.rest.getHistoryProps({
eventId: "event_123",
playerName: "LeBron James",
propType: "points",
});
// Get archived player stats
const playerStats = await client.rest.getHistoryStats({
sport: "nba",
playerName: "LeBron James",
startDate: "2026-01-01",
endDate: "2026-03-01",
});
// Get archived tennis match stats
const tennisStats = await client.rest.getHistoryTennisStats("event_123");
// CS2 match history
const cs2Matches = await client.rest.getCS2Matches({
team: "Natus Vincere",
stars: 4,
});
// Full match detail with map scores and player stats
const matchDetail = await client.rest.getCS2Match(2374324);
// Player stats across matches
const cs2Players = await client.rest.getCS2Players({
playerName: "s1mple",
minRating: 1.3,
});
// Closing odds per sportsbook (9 books, 2016-present)
const closing = await client.rest.getClosingOdds({
sport: "nba",
startDate: "2026-03-01",
endDate: "2026-03-10",
});
// Historical player props (4 books, 2022-present)
const histProps = await client.rest.getHistoricalPlayerProps({
sport: "nba",
player: "LeBron James",
propType: "points",
});
// Public betting percentages (2019-present)
const publicBetting = await client.rest.getPublicBetting({
sport: "nba",
limit: 50,
});Kalshi Prediction Markets
const markets = await client.rest.getKalshiMarkets("nba", {
status: "open",
});
// Browse series (e.g., KXNBAGAME, KXNFLSBMVP)
const series = await client.rest.getKalshiSeries();
// Markets within a series
const nbaGame = await client.rest.getKalshiSeriesMarkets("KXNBAGAME");Polymarket
Polymarket odds are also included as a bookmaker in the standard /odds response.
const markets = await client.rest.getPolymarketMarkets("nba");Betting Splits
const splits = await client.rest.getSplits("nba");v2 Raw Pass-Through
The v2 endpoints expose each book's raw market shape without canonical mapping.
Use these when you need the source book's full market depth (alt lines, every
prop type, micro markets) instead of the normalized 3-market view in /odds.
Each v2 source has:
- A REST endpoint for the current snapshot
- A WebSocket event (
{book}-v2-update) for sparse deltas — opt in viaclient.ws.subscribe({ v2: { [book]: { [sport]: '*' } } }) - An optional
/leaguesendpoint to list available league slugs
For books that require a league parameter (Bet365 soccer, DraftKings, FanDuel,
Polymarket, Kalshi), call get{Book}V2Leagues(sport) first — slugs change with
the calendar and hard-coding can leave you with empty 200 responses.
Bet365 v2
const nba = await client.rest.getBet365V2("nba");
// Soccer leagues are geographic buckets (uk, spain, italy, germany,
// france, uefa, americas, europe, australia, top-leagues, rest-of-world).
const ukSoccer = await client.rest.getBet365V2("soccer", { league: "uk" });
const leagues = await client.rest.getBet365V2Leagues("soccer");Sports: nba, mlb, nhl, soccer
DraftKings v2
const dk = await client.rest.getDraftKingsV2("soccer", { league: "epl" });
const leagues = await client.rest.getDraftKingsV2Leagues("soccer");Sports: soccer
FanDuel v2
const fd = await client.rest.getFanDuelV2("nba");
const leagues = await client.rest.getFanDuelV2Leagues("soccer");Sports: soccer, mlb, nba, nhl
MyBookie v2
const mb = await client.rest.getMyBookieV2("nba");Sports: mlb, nba
Thunderpick v2
const tp = await client.rest.getThunderpickV2("basketball");Sports: baseball, basketball, tennis
Kalshi v2
const k = await client.rest.getKalshiV2("nba", { league: "kxnbagame" });
const leagues = await client.rest.getKalshiV2Leagues("nba");
// Esports — discover the active series ticker first, then fetch it.
const cs2Leagues = await client.rest.getKalshiV2Leagues("cs2");
const cs2 = await client.rest.getKalshiV2("cs2", { league: "kxcs2game" });Sports: nba, nhl, mlb, soccer, tennis, plus esports — cs2, dota2, lol, valorant, overwatch, rainbow6, rocketleague, pubg
Esports markets are tournament-driven: a game with no active event returns an empty
leaguesarray (meta.status: "no-data"), not an error. Always callgetKalshiV2Leagues(sport)first to get the live series tickers.
Polymarket v2
const pm = await client.rest.getPolymarketV2("soccer", { league: "epl" });
const leagues = await client.rest.getPolymarketV2Leagues("soccer");
// Esports — each game maps to one canonical Gamma tag slug.
const cs2 = await client.rest.getPolymarketV2("cs2", { league: "counter-strike-2" });
const lol = await client.rest.getPolymarketV2("lol", { league: "league-of-legends" });Sports: nba, nhl, mlb, soccer, tennis, plus esports — cs2, dota2, lol, valorant, overwatch, rainbow6, rocketleague, pubg
Esports league slugs:
cs2→counter-strike-2,dota2→dota-2,lol→league-of-legends,valorant→valorant. Dormant games (e.g.rainbow6/rocketleague/pubgbetween tournaments) return an emptyleaguesarray. Always callgetPolymarketV2Leagues(sport)to confirm.
Hard Rock v2
const leagues = await client.rest.getHardRockLeagues("az", "ICE_HOCKEY");
const nhl = await client.rest.getHardRockEvents("az", "ICE_HOCKEY", "nhl");
// Small sport — no league needed
const tennis = await client.rest.getHardRockEvents("fl", "TENNIS");States: az, fl. Sports use Hard Rock's enum naming (BASKETBALL, BASEBALL,
ICE_HOCKEY, SOCCER, TENNIS, etc.). Big sports require a league slug; list via
getHardRockLeagues.
WebSocket Streaming
Real-time odds updates via WebSocket.
Connect & Subscribe
const client = new OwlsInsight({ apiKey: process.env.OWLS_INSIGHT_API_KEY! });
await client.ws.connect();
// Subscribe to sports and books
client.ws.subscribe({
sports: ["nba", "nfl"],
books: ["pinnacle", "fanduel", "draftkings"],
});
// Listen for odds updates
client.ws.on("odds-update", (data) => {
console.log(`Update at ${data.timestamp}`);
// data.last_odds_change — when prices actually changed (vs heartbeat)
for (const [sport, events] of Object.entries(data.sports)) {
console.log(`${sport}: ${events.length} events`);
for (const event of events) {
console.log(` ${event.away_team} @ ${event.home_team}`);
}
}
});Update Subscription
// Update an existing subscription
client.ws.updateSubscription({ sports: ["nba", "nhl", "soccer"] });Props Streaming
// Subscribe to Pinnacle props for specific sports
client.ws.subscribeProps({ sports: ["nba", "nhl"] });
// Subscribe to a specific book's props
client.ws.subscribeProps({ sports: ["nba"] }, "fanduel");
// Filter by categories
client.ws.subscribeProps({
sports: ["nba"],
categories: ["points", "rebounds", "assists"],
});
// Subscribe to all books
client.ws.subscribeProps({ sports: ["nba"] }); // Pinnacle
client.ws.subscribeProps({ sports: ["nba"] }, "fanduel"); // FanDuel
client.ws.subscribeProps({ sports: ["nba"] }, "draftkings"); // DraftKings
client.ws.subscribeProps({ sports: ["nba"] }, "bet365"); // Bet365
client.ws.subscribeProps({ sports: ["nba"] }, "betmgm"); // BetMGM
client.ws.subscribeProps({ sports: ["nba"] }, "caesars"); // Caesars
client.ws.on("player-props-update", (data) => {
console.log("Pinnacle props:", data.sports);
});
client.ws.on("fanduel-props-update", (data) => {
console.log("FanDuel props:", data.sports);
});
// Confirmation events
client.ws.on("props-subscribed", (sub) => {
console.log("Pinnacle props active:", sub);
});
// Unsubscribe
client.ws.unsubscribeProps(); // Pinnacle
client.ws.unsubscribeProps("fanduel"); // FanDuelReal-Time Pinnacle
Pushed automatically — no subscription needed.
client.ws.on("pinnacle-realtime", (data) => {
// data is keyed by sport: { nba: [...], tennis: [...], timestamp: "..." }
for (const [sport, events] of Object.entries(data)) {
if (sport === "timestamp" || !Array.isArray(events)) continue;
console.log(`${sport}: ${events.length} events`);
for (const event of events) {
console.log(` ${event.away_team} @ ${event.home_team}`);
const pinnacle = event.bookmakers.find((b: any) => b.key === "pinnacle");
for (const market of pinnacle?.markets || []) {
console.log(` ${market.key}:`, market.outcomes);
}
}
}
});Real-Time PS3838
Independent realtime stream from PS3838 (Pinnacle whitelabel). Same payload shape
as pinnacle-realtime but sourced from a separate WS pipeline — use as a
comparison source or fallback.
client.ws.on("ps3838-realtime", (data) => {
// Same shape as pinnacle-realtime
});Per-Sport Esports Realtime
Per-game realtime events from Pinnacle's esports feed. Subscribe via
esportsRealtime with a league filter per game (required — no wildcard).
client.ws.subscribe({
esportsRealtime: {
cs2: { league: "BLAST" },
lol: { league: "LEC" },
valorant: { league: "VCT" },
},
});
client.ws.on("cs2-realtime", (data) => console.log("CS2:", data));
client.ws.on("lol-realtime", (data) => console.log("LoL:", data));
client.ws.on("valorant-realtime", (data) => console.log("Valorant:", data));
client.ws.on("dota2-realtime", (data) => console.log("Dota2:", data));Batched Esports Updates (legacy)
client.ws.subscribe({
sports: ["nba"],
books: ["pinnacle"],
esports: true,
});
client.ws.on("esports-update", (data) => {
console.log(`CS2: ${data.sports.cs2?.length}`);
});v2 Raw Pass-Through Streams
Sparse deltas from each v2 source. Opt in via subscribe({ v2: { ... } }),
listen on {book}-v2-update. Only changed buckets publish — unchanged buckets
do not fire.
client.ws.subscribe({
v2: {
bet365: { soccer: "*", nba: "*" }, // '*' = all leagues
draftkings: { soccer: ["epl", "ucl"] }, // or array of league slugs
fanduel: { soccer: "*" },
kalshi: { nba: "*", tennis: "*", cs2: "*" }, // esports: cs2/dota2/lol/valorant/...
polymarket: { soccer: ["epl"], cs2: "*", lol: "*" },// esports stream the same way
mybookie: { nba: "*", mlb: "*" },
thunderpick: { basketball: "*", tennis: "*" },
pinnacle: { wnba: "*" }, // raw Pinnacle sparse deltas
},
});
client.ws.on("bet365-v2-update", (data) => { /* snapshot or outcomes delta */ });
client.ws.on("draftkings-v2-update", (data) => { /* per-tab bucket delta */ });
client.ws.on("fanduel-v2-update", (data) => { /* per-tab bucket delta */ });
client.ws.on("kalshi-v2-update", (data) => { /* per-league series delta */ });
client.ws.on("polymarket-v2-update", (data) => { /* per-league tag delta */ });
client.ws.on("mybookie-v2-update", (data) => { /* market snapshot */ });
client.ws.on("thunderpick-v2-update", (data) => { /* market snapshot */ });
client.ws.on("pinnacle-v2-update", (data) => { /* sparse Pinnacle delta */ });ProphetX Stream
// All ProphetX sports + kinds
client.ws.subscribe({ prophetx: true });
// Or filter to specific sports / kinds
client.ws.subscribe({
prophetx: { sports: ["basketball"], kinds: ["game"] },
});
client.ws.on("prophetx-update", (delta) => {
// Raw ProphetX order-book delta — passes through unchanged from the exchange
});WebSocket Events Reference
| Event | Trigger | Opt-in via |
|---|---|---|
| odds-update | Any change to normalized /odds data | subscribe({ sports, books }) |
| scores-update | Live score change | subscribe({ sports }) |
| player-props-update | Pinnacle player props change | subscribeProps({ sports }) |
| bet365-props-update | Bet365 player props change | subscribeProps({ sports }, "bet365") |
| fanduel-props-update | FanDuel player props change | subscribeProps({ sports }, "fanduel") |
| draftkings-props-update | DraftKings player props change | subscribeProps({ sports }, "draftkings") |
| betmgm-props-update | BetMGM player props change | subscribeProps({ sports }, "betmgm") |
| caesars-props-update | Caesars player props change | subscribeProps({ sports }, "caesars") |
| pinnacle-realtime | Sub-second Pinnacle realtime odds | MVP+ tier (or WS addon) — auto-delivered when entitled |
| ps3838-realtime | Sub-second PS3838 odds | MVP+ tier (or WS addon) — auto-delivered when entitled |
| cs2-realtime / lol-realtime / valorant-realtime / dota2-realtime | Per-game Pinnacle esports odds | subscribe({ esportsRealtime: { [game]: { league: "<tournament>" } } }) |
| esports-update | Batched esports odds (legacy) | subscribe({ esports: true }) |
| bet365-v2-update | Bet365 raw market snapshot/delta | subscribe({ v2: { bet365: {...} } }) |
| draftkings-v2-update | DraftKings raw bucket delta | subscribe({ v2: { draftkings: {...} } }) |
| fanduel-v2-update | FanDuel raw bucket delta | subscribe({ v2: { fanduel: {...} } }) |
| mybookie-v2-update | MyBookie raw snapshot | subscribe({ v2: { mybookie: {...} } }) |
| thunderpick-v2-update | Thunderpick raw snapshot | subscribe({ v2: { thunderpick: {...} } }) |
| kalshi-v2-update | Kalshi per-league series delta | subscribe({ v2: { kalshi: {...} } }) |
| polymarket-v2-update | Polymarket per-league tag delta | subscribe({ v2: { polymarket: {...} } }) |
| pinnacle-v2-update | Pinnacle sparse market delta | subscribe({ v2: { pinnacle: {...} } }) |
| prophetx-update | ProphetX exchange order-book delta | subscribe({ prophetx: {...} }) |
Promise-Based Waiting
// Wait for the next odds update
const update = await client.ws.waitFor("odds-update", 10_000);
// Wait for subscription confirmation
client.ws.subscribe({ sports: ["nba"], books: ["pinnacle"] });
const sub = await client.ws.waitFor("subscribed");
console.log("Subscribed to:", sub.sports);Error Handling
client.ws.on("error", (err) => {
console.error("WebSocket error:", err.message, err.code);
});
client.ws.on("disconnect", (reason) => {
console.log("Disconnected:", reason);
});Cleanup
// Disconnect WebSocket
client.ws.disconnect();
// Or destroy everything
client.destroy();Error Handling
The SDK throws typed errors for different failure modes:
import {
OwlsInsightError,
AuthenticationError,
ForbiddenError,
RateLimitError,
} from "owls-insight-ts";
try {
const odds = await client.rest.getOdds("nba");
} catch (err) {
if (err instanceof RateLimitError) {
console.log(`Rate limited. Retry in ${err.retryAfterMs}ms`);
} else if (err instanceof AuthenticationError) {
console.log("Invalid API key");
} else if (err instanceof ForbiddenError) {
console.log("Upgrade required for this endpoint");
} else if (err instanceof OwlsInsightError) {
console.log(`API error ${err.status}: ${err.message}`);
}
}The API returns rate limit headers on every response:
X-RateLimit-Remaining-Minute— requests left this minuteX-RateLimit-Remaining-Month— requests left this monthX-RateLimit-Reset-Minute— Unix timestamp (ms) when the minute limit resetsX-RateLimit-Reset-Month— ISO 8601 timestamp when the month limit resets
Configuration
const client = new OwlsInsight({
apiKey: process.env.OWLS_INSIGHT_API_KEY!, // Required
baseUrl: "https://api.owlsinsight.com", // Optional (default)
wsUrl: "https://api.owlsinsight.com", // Optional (default, same as baseUrl)
timeout: 30000, // Optional request timeout in ms (default: 30s)
});Supported Sports
| Sport | Key | Odds | Props | Scores |
|---|---|---|---|---|
| NBA | nba | Yes | Yes | Yes |
| NCAAB | ncaab | Yes | Yes | Yes |
| NFL | nfl | Yes | Yes | Yes |
| NHL | nhl | Yes | Yes | Yes |
| NCAAF | ncaaf | Yes | Yes | Yes |
| WNBA | wnba | Yes (Pinnacle realtime) | — | — |
| MLB | mlb | Yes | Yes | Yes |
| Soccer | soccer | Yes | — | Yes |
| Tennis | tennis | Yes | — | Yes |
| MMA | mma | Yes (BetOnline) | — | — |
| CS2 | cs2 | Yes (1xBet, Pinnacle) | — | Yes |
| Valorant | valorant | Yes (1xBet, Pinnacle) | — | Yes |
| LoL | lol | Yes (1xBet, Pinnacle) | — | Yes |
| Dota 2 | dota2 | Yes (Pinnacle realtime) | — | — |
| Handball | handball | Yes (Pinnacle realtime) | — | — |
Sportsbooks & Coverage
Coverage reflects what the SDK exposes through getOdds and the props endpoints.
v2 raw pass-through (per-book deep markets) is documented separately above.
| Book | Game Odds | Player Props | Notes |
|---|---|---|---|
| Pinnacle | NBA, NCAAB, NFL, NHL, NCAAF, MLB, WNBA, Soccer, Tennis, Esports | NBA, NCAAB, NFL, NHL, NCAAF | Sharp/reference line. Realtime via /realtime + pinnacle-realtime |
| PS3838 | Same as Pinnacle (whitelabel) | — | Realtime only via /ps3838-realtime + ps3838-realtime event |
| FanDuel | NBA, NCAAB, NFL, NHL, NCAAF, MLB, Soccer | NBA, NCAAB, NFL, NHL, NCAAF | v2 raw available: soccer / mlb / nba / nhl |
| DraftKings | NBA, NCAAB, NFL, NHL, NCAAF, MLB, Soccer | NBA, NCAAB, NFL, NHL, NCAAF | v2 raw available: soccer |
| BetMGM | NBA, NCAAB, NFL, NHL, NCAAF, MLB | NBA, NCAAB, NFL, NHL, NCAAF | |
| Bet365 | NBA, NCAAB, NFL, NHL, NCAAF, Soccer | NBA, NCAAB, NFL, NHL, NCAAF | v2 raw available: nba / mlb / nhl / soccer |
| Caesars | NBA, NCAAB, NFL, NHL, NCAAF, MLB | NBA, NCAAB, NFL, NHL, NCAAF | |
| Hard Rock | NBA, MLB, NHL, Soccer (per AZ/FL state) | — | v2 raw only — call getHardRockEvents(state, sport) |
| BetOnline | MMA | — | UFC/MMA only |
| 1xBet | CS2, Valorant, LoL, Soccer, MLB | — | Esports + soccer + MLB |
| ProphetX | Multiple sports (raw exchange) | — | Exchange order-book — call getProphetxOdds() |
| Kalshi | NBA, NCAAB, NFL, NHL, MLB | — | Prediction market. v2 raw available: nba / nhl / mlb / soccer / tennis |
| Polymarket | NBA, NCAAB, NFL, NHL, MLB, Soccer | — | Decentralized exchange. v2 raw available: nba / nhl / mlb / soccer / tennis |
| Novig | NBA, NCAAB, NFL, NHL, NCAAF, MLB, Soccer, Tennis | — | Peer-to-peer exchange with top-5 order-book liquidity |
| MyBookie | MLB, NBA | — | v2 raw only |
| Thunderpick | Baseball, Basketball, Tennis | — | v2 raw only |
License
MIT
