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 +21 -0
- package/README.md +396 -0
- package/dist/index.cjs +401 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +446 -0
- package/dist/index.d.ts +446 -0
- package/dist/index.js +369 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
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
|