propline 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PropLine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,396 @@
1
+ # PropLine Node.js / TypeScript SDK
2
+
3
+ Official Node and TypeScript client for the [PropLine](https://prop-line.com) player props API — real-time betting odds from Bovada, DraftKings, FanDuel, Pinnacle, Unibet, and PrizePicks across MLB, NBA, NHL, soccer, UFC, and more.
4
+
5
+ Zero runtime dependencies — uses the built-in `fetch`. Requires Node 18+.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install propline
11
+ # or
12
+ pnpm add propline
13
+ # or
14
+ yarn add propline
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import { PropLine } from "propline";
21
+
22
+ const client = new PropLine("your_api_key");
23
+
24
+ // List available sports
25
+ const sports = await client.getSports();
26
+ // [{ key: "baseball_mlb", title: "MLB", active: true }, ...]
27
+
28
+ // Get today's NBA games
29
+ const events = await client.getEvents("basketball_nba");
30
+ for (const event of events) {
31
+ console.log(`${event.away_team} @ ${event.home_team}`);
32
+ }
33
+
34
+ // Get player props for a game
35
+ const odds = await client.getOdds("basketball_nba", {
36
+ eventId: events[0].id,
37
+ markets: ["player_points", "player_rebounds", "player_assists"],
38
+ });
39
+
40
+ for (const bookmaker of odds.bookmakers) {
41
+ for (const market of bookmaker.markets) {
42
+ for (const outcome of market.outcomes) {
43
+ console.log(
44
+ `${outcome.description} ${outcome.name} ${outcome.point} @ ${outcome.price}`
45
+ );
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ CommonJS works too:
52
+
53
+ ```js
54
+ const { PropLine } = require("propline");
55
+ ```
56
+
57
+ ## Get your API key
58
+
59
+ 1. Go to [prop-line.com](https://prop-line.com)
60
+ 2. Enter your email
61
+ 3. Get your API key instantly — **500 requests/day, no credit card required**
62
+
63
+ ## Available sports
64
+
65
+ | Key | Sport |
66
+ |-----|-------|
67
+ | `baseball_mlb` | MLB |
68
+ | `basketball_nba` | NBA |
69
+ | `basketball_ncaab` | College Basketball |
70
+ | `football_ncaaf` | College Football |
71
+ | `golf` | Golf |
72
+ | `tennis` | Tennis |
73
+ | `hockey_nhl` | NHL |
74
+ | `football_nfl` | NFL |
75
+ | `soccer_epl` | EPL |
76
+ | `soccer_la_liga` | La Liga |
77
+ | `soccer_serie_a` | Serie A |
78
+ | `soccer_bundesliga` | Bundesliga |
79
+ | `soccer_ligue_1` | Ligue 1 |
80
+ | `soccer_mls` | MLS |
81
+ | `mma_ufc` | UFC |
82
+ | `boxing` | Boxing |
83
+
84
+ ## Bookmakers
85
+
86
+ Every odds response returns a `bookmakers` array so you can compare lines across books in a single request — iterate the array to line-shop.
87
+
88
+ | Key | Book | Coverage |
89
+ |-----|------|----------|
90
+ | `bovada` | Bovada | All 19 sports — game lines + full player props |
91
+ | `draftkings` | DraftKings | MLB, NBA, NHL, 6 soccer leagues — game lines + player props |
92
+ | `fanduel` | FanDuel | MLB, NBA, NHL, 6 soccer leagues — game lines + player props |
93
+ | `pinnacle` | Pinnacle | MLB (game lines + props), NBA/NHL/soccer (game lines, goalie saves) |
94
+ | `unibet` | Unibet | MLB/NBA/NHL + 6 soccer leagues — game lines; player props on NBA, NHL, soccer |
95
+ | `prizepicks` | PrizePicks (DFS) | MLB, NBA, NHL, 9 soccer leagues — player props only; synthetic +100/+100 even-money pricing since DFS payouts scale with parlay correct-count, not per-pick odds |
96
+
97
+ ```ts
98
+ import { PropLine, Bookmakers } from "propline";
99
+
100
+ const client = new PropLine("your_api_key");
101
+
102
+ const odds = await client.getOdds("baseball_mlb", {
103
+ eventId: 51,
104
+ markets: ["pitcher_strikeouts"],
105
+ });
106
+
107
+ // Filter to a specific book
108
+ for (const bk of odds.bookmakers) {
109
+ if (bk.key === Bookmakers.DRAFTKINGS) {
110
+ // ...
111
+ }
112
+ }
113
+
114
+ // Or iterate all books
115
+ for (const bk of odds.bookmakers) {
116
+ console.log(`\n${bk.title}`);
117
+ for (const market of bk.markets) {
118
+ for (const o of market.outcomes) {
119
+ console.log(` ${o.description} ${o.name} ${o.point}: ${o.price}`);
120
+ }
121
+ }
122
+ }
123
+ // Bovada
124
+ // Zack Wheeler Over 6.5: -130
125
+ // DraftKings
126
+ // Zack Wheeler Over 6.5: -125
127
+ // FanDuel
128
+ // Zack Wheeler Over 6.5: -135
129
+ ```
130
+
131
+ ## Available markets
132
+
133
+ ### MLB
134
+ `pitcher_strikeouts`, `pitcher_outs`, `pitcher_earned_runs`, `pitcher_hits_allowed`, `batter_hits`, `batter_home_runs`, `batter_rbis`, `batter_total_bases`, `batter_stolen_bases`, `batter_walks`, `batter_singles`, `batter_doubles`, `batter_runs`, `batter_2plus_hits`, `batter_2plus_home_runs`, `batter_2plus_rbis`, `batter_3plus_rbis`
135
+
136
+ ### NBA
137
+ `player_points`, `player_rebounds`, `player_assists`, `player_threes`, `player_steals`, `player_blocks`, `player_turnovers`, `player_points_rebounds`, `player_points_assists`, `player_rebounds_assists`, `player_points_rebounds_assists`, `player_double_double`, `player_triple_double`
138
+
139
+ ### NHL
140
+ `player_goals`, `player_first_goal`, `player_goals_2plus`, `player_goals_3plus`, `player_shots_on_goal`, `player_points_1plus`, `player_points_2plus`, `player_points_3plus`, `goalie_saves`, `player_blocked_shots`
141
+
142
+ ### Soccer (EPL, La Liga, Serie A, Bundesliga, Ligue 1, MLS)
143
+ `anytime_goal_scorer`, `first_goal_scorer`, `2plus_goals`, `goal_or_assist`, `player_assists`, `player_2plus_assists`, `player_cards`, `both_teams_to_score`, `double_chance`, `draw_no_bet`, `correct_score`, `total_corners`, `total_cards`
144
+
145
+ ### UFC / Boxing
146
+ `h2h`, `total_rounds`, `fight_distance`, `round_betting`
147
+
148
+ ### Game lines (all sports)
149
+ `h2h`, `spreads`, `totals` (alt lines and team totals included automatically)
150
+
151
+ ## Examples
152
+
153
+ ### Get MLB pitcher strikeout props
154
+
155
+ ```ts
156
+ import { PropLine } from "propline";
157
+
158
+ const client = new PropLine("your_api_key");
159
+
160
+ const events = await client.getEvents("baseball_mlb");
161
+ for (const event of events) {
162
+ const odds = await client.getOdds("baseball_mlb", {
163
+ eventId: event.id,
164
+ markets: ["pitcher_strikeouts"],
165
+ });
166
+ console.log(`\n${event.away_team} @ ${event.home_team}`);
167
+ for (const bk of odds.bookmakers) {
168
+ for (const mkt of bk.markets) {
169
+ for (const o of mkt.outcomes) {
170
+ if (o.point != null) {
171
+ console.log(` ${o.description} ${o.name} ${o.point}: ${o.price}`);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
179
+ ### Get game scores
180
+
181
+ ```ts
182
+ const scores = await client.getScores("baseball_mlb");
183
+ for (const game of scores) {
184
+ if (game.status === "final") {
185
+ console.log(
186
+ `${game.away_team} ${game.away_score}, ${game.home_team} ${game.home_score}`
187
+ );
188
+ }
189
+ }
190
+ ```
191
+
192
+ ### Get resolved prop outcomes (Pro only)
193
+
194
+ ```ts
195
+ const results = await client.getResults("baseball_mlb", 16, {
196
+ markets: ["pitcher_strikeouts", "batter_hits"],
197
+ });
198
+
199
+ console.log(
200
+ `${results.away_team} ${results.away_score}, ${results.home_team} ${results.home_score}`
201
+ );
202
+
203
+ for (const market of results.markets) {
204
+ for (const outcome of market.outcomes) {
205
+ console.log(
206
+ `${outcome.description} ${outcome.name} ${outcome.point}: ${outcome.resolution} (actual: ${outcome.actual_value})`
207
+ );
208
+ }
209
+ }
210
+ // Output: "Tarik Skubal (DET) Over 6.5: won (actual: 7.0)"
211
+ ```
212
+
213
+ ### Get historical line movement (Pro only)
214
+
215
+ ```ts
216
+ const history = await client.getOddsHistory("baseball_mlb", 16, {
217
+ markets: ["pitcher_strikeouts"],
218
+ });
219
+
220
+ for (const market of history.markets) {
221
+ for (const outcome of market.outcomes) {
222
+ console.log(`\n${outcome.description}:`);
223
+ for (const snap of outcome.snapshots) {
224
+ console.log(` ${snap.recorded_at}: ${snap.price} @ ${snap.point}`);
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ ### Get player prop history (Pro full, Free redacted)
231
+
232
+ ```ts
233
+ // "Did Bryan Woo go over/under his last 10 strikeout props?"
234
+ const hist = await client.getPlayerHistory("baseball_mlb", "Bryan Woo", {
235
+ market: "pitcher_strikeouts",
236
+ limit: 10,
237
+ });
238
+
239
+ for (const e of hist.entries) {
240
+ console.log(
241
+ `${e.commence_time.slice(0, 10)} ${e.bookmaker_title}: ` +
242
+ `line ${e.line}, actual ${e.actual_value} ` +
243
+ `-> Over ${e.over_result}, Under ${e.under_result}`
244
+ );
245
+ }
246
+ // Output: "2026-04-19 DraftKings: line 6.5, actual 6.0 -> Over lost, Under won"
247
+ ```
248
+
249
+ ### Bulk CSV export of resolved props (Pro)
250
+
251
+ ```ts
252
+ // Save every resolved MLB strikeout prop since April 1st to disk.
253
+ await client.exportResolvedProps({
254
+ sport: "baseball_mlb",
255
+ market: "pitcher_strikeouts",
256
+ since: "2026-04-01T00:00:00Z",
257
+ outPath: "./mlb-strikeouts.csv",
258
+ });
259
+
260
+ // Or load into memory.
261
+ const csv = await client.exportResolvedProps({ sport: "baseball_mlb" });
262
+ console.log(`got ${csv.byteLength} bytes of CSV`);
263
+ ```
264
+
265
+ ## Webhooks (Streaming tier)
266
+
267
+ The Streaming tier ($149/mo) pushes `line_movement` and `resolution` events to your URL in real time, with HMAC-SHA256 signing and automatic retries.
268
+
269
+ ### Register a subscription
270
+
271
+ ```ts
272
+ const wh = await client.createWebhook({
273
+ url: "https://example.com/hooks/propline",
274
+ filterSportKey: "baseball_mlb",
275
+ filterMarketKey: "pitcher_strikeouts",
276
+ minPriceChangePct: 2.0, // only fire on shifts of 2%+ (or any point change)
277
+ });
278
+
279
+ // Store wh.secret — this is the ONLY time it's returned.
280
+ const SECRET = wh.secret;
281
+ console.log(`webhook id: ${wh.id}`);
282
+ ```
283
+
284
+ ### Verify incoming deliveries
285
+
286
+ Each POST carries these headers:
287
+
288
+ | Header | Purpose |
289
+ |--------|---------|
290
+ | `X-PropLine-Event` | `line_movement`, `resolution`, or `test` |
291
+ | `X-PropLine-Timestamp` | Unix seconds |
292
+ | `X-PropLine-Signature` | HMAC-SHA256 over `${timestamp}.` + body |
293
+ | `X-PropLine-Delivery` | Stable delivery id (use for idempotency) |
294
+
295
+ ```ts
296
+ import express from "express";
297
+ import { PropLine } from "propline";
298
+
299
+ const app = express();
300
+ app.post(
301
+ "/hooks/propline",
302
+ express.raw({ type: "*/*" }),
303
+ (req, res) => {
304
+ const ok = PropLine.verifySignature({
305
+ secret: process.env.WEBHOOK_SECRET!,
306
+ timestamp: req.header("X-PropLine-Timestamp")!,
307
+ body: req.body, // raw Buffer — make sure to use express.raw, not express.json
308
+ signature: req.header("X-PropLine-Signature")!,
309
+ });
310
+ if (!ok) return res.status(401).end();
311
+ // process payload (parse JSON yourself after verification)
312
+ res.status(200).end();
313
+ }
314
+ );
315
+ ```
316
+
317
+ ### Line-movement payload
318
+
319
+ ```json
320
+ {
321
+ "event_type": "line_movement",
322
+ "sport_key": "baseball_mlb",
323
+ "event": { "id": 5070, "home_team": "Seattle Mariners", "away_team": "Texas Rangers" },
324
+ "market_key": "totals",
325
+ "player_name": null,
326
+ "outcome_name": "Over",
327
+ "previous": { "price_american": -750, "point": 7.0 },
328
+ "current": { "price_american": -300, "point": 7.5 },
329
+ "price_change_pct": 60.0,
330
+ "timestamp": "2026-04-18T03:49:00Z"
331
+ }
332
+ ```
333
+
334
+ ### Resolution payload
335
+
336
+ ```json
337
+ {
338
+ "event_type": "resolution",
339
+ "sport_key": "baseball_mlb",
340
+ "event": { "id": 16, "home_score": 4, "away_score": 2, "status": "final" },
341
+ "market_key": "pitcher_strikeouts",
342
+ "player_name": "Tarik Skubal (DET)",
343
+ "outcome_name": "Over",
344
+ "point": 6.5,
345
+ "resolution": "won",
346
+ "actual_value": 9.0,
347
+ "resolved_at": "2026-04-18T06:14:22Z"
348
+ }
349
+ ```
350
+
351
+ ### Manage subscriptions
352
+
353
+ ```ts
354
+ for (const wh of await client.listWebhooks()) {
355
+ console.log(wh.id, wh.url, wh.active ? "active" : "paused");
356
+ }
357
+
358
+ await client.updateWebhook(whId, { minPriceChangePct: 5.0 });
359
+ await client.testWebhook(whId);
360
+ await client.listWebhookDeliveries(whId, { limit: 50 });
361
+ await client.deleteWebhook(whId);
362
+ ```
363
+
364
+ ## Error handling
365
+
366
+ ```ts
367
+ import { PropLine, AuthError, RateLimitError, PropLineError } from "propline";
368
+
369
+ const client = new PropLine("your_api_key");
370
+
371
+ try {
372
+ const odds = await client.getOdds("baseball_mlb", { eventId: 1 });
373
+ } catch (e) {
374
+ if (e instanceof AuthError) {
375
+ console.error("Invalid API key");
376
+ } else if (e instanceof RateLimitError) {
377
+ console.error("Daily limit exceeded — upgrade at prop-line.com/#pricing");
378
+ } else if (e instanceof PropLineError) {
379
+ console.error(`API error: ${e.statusCode} — ${e.detail}`);
380
+ } else {
381
+ throw e;
382
+ }
383
+ }
384
+ ```
385
+
386
+ ## Links
387
+
388
+ - **Website**: [prop-line.com](https://prop-line.com)
389
+ - **API Docs**: [prop-line.com/docs](https://prop-line.com/docs)
390
+ - **Dashboard**: [prop-line.com/dashboard](https://prop-line.com/dashboard)
391
+ - **API Reference**: [api.prop-line.com/docs](https://api.prop-line.com/docs)
392
+ - **Python SDK**: [`pip install propline`](https://pypi.org/project/propline/)
393
+
394
+ ## License
395
+
396
+ MIT