Skip to content

Commit 49a8392

Browse files
authored
docs(streaming): add One Connection, Many Topics guide (#198)
New page at /en/streaming/single-connection covering how to cover multiple sports, leagues, books, markets, events, and odds + every opportunity type on a single SSE or WebSocket connection — instead of opening parallel sockets per filter. Frames the guidance against the new 1-stream-per-key cap (newer-wins displacement on second connect) and points fleet/multi-process users to the per-key `maxStreams` Unkey override for Enterprise. Five concrete patterns: multi-sport via comma-joined leagues, `channel=all` for odds + opportunities, N-event tracking via comma-joined event IDs, per-book/market splits, dynamic re-subscription on WebSocket. Ends with a server-side cost rationale and migration checklist for users moving off a multi-stream architecture. Page was drafted in a parked tdev session (8-day-old branch docs/remove-game-state-from-opportunities, already merged via #195) and reviewed clean — cross-links resolve, Nextra components match the rest of the streaming/ section.
1 parent b7e688e commit 49a8392

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

content/en/streaming/_meta.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export default {
22
overview: "Overview",
33
websocket: "WebSocket",
4+
"single-connection": "One Connection, Many Topics",
45
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
---
2+
description: "How to cover many sports, leagues, books, events, and opportunity types on a single SSE or WebSocket connection. Comma-separated filters and channel multiplexing replace the need for multiple parallel streams."
3+
---
4+
5+
import { Callout, Tabs } from 'nextra/components'
6+
7+
# One Connection, Many Topics
8+
9+
A single SharpAPI stream can cover everything you need — multiple sports, multiple leagues, multiple books, multiple events, and odds + every opportunity type — on one socket. You almost never need to open a stream per filter.
10+
11+
<Callout type="info">
12+
The per-key concurrent stream cap is **1 by default for every paid tier**, with newer-wins displacement: a second connection from the same key kicks the older one. This page shows the patterns that make a single connection sufficient. If you genuinely need parallel sockets (multiple processes / machines), see [Fleet & Multi-Process](#fleet--multi-process) below.
13+
</Callout>
14+
15+
## Why one connection is enough
16+
17+
Every identity filter on the stream endpoint accepts a **comma-separated list**, and the `channel` parameter (`all`) merges odds and opportunity events into the same event stream. The server pre-serializes each cycle once and applies your filters per-connection, so wide filters are cheap — there's no efficiency penalty for subscribing to "everything you care about" on one socket vs. splitting it.
18+
19+
| Filter | Single value | Multiple values |
20+
|--------|--------------|-----------------|
21+
| `sport` | `sport=basketball` | `sport=basketball,football,ice_hockey` |
22+
| `league` | `league=nba` | `league=nba,nfl,mlb,nhl` |
23+
| `sportsbook` | `sportsbook=draftkings` | `sportsbook=draftkings,fanduel,pinnacle` |
24+
| `market` | `market=moneyline` | `market=moneyline,point_spread,total_points` |
25+
| `event` | `event=evt_abc` | `event=evt_abc,evt_def,evt_ghi` |
26+
| `channel` (SSE) | `channel=odds` | `channel=all` (odds + opportunities) |
27+
| `channels` (WS) | `channels=ev` | `channels=ev,odds,arbitrage,middles,low_hold` |
28+
29+
## Patterns
30+
31+
### Multi-sport: NBA + NFL + MLB on one socket
32+
33+
Instead of opening three streams, pass all three leagues to one:
34+
35+
<Tabs items={['JavaScript', 'Python']}>
36+
<Tabs.Tab>
37+
```javascript
38+
const es = new EventSource(
39+
'https://api.sharpapi.io/api/v1/stream' +
40+
'?channel=all' +
41+
'&league=nba,nfl,mlb' +
42+
'&api_key=YOUR_KEY'
43+
);
44+
45+
es.addEventListener('odds:update', (e) => {
46+
const { odds, book } = JSON.parse(e.data);
47+
// odds[].league tells you which league this update is for
48+
for (const o of odds) routeByLeague(o);
49+
});
50+
```
51+
</Tabs.Tab>
52+
<Tabs.Tab>
53+
```python
54+
import sseclient, requests, json
55+
56+
response = requests.get(
57+
'https://api.sharpapi.io/api/v1/stream',
58+
params={'channel': 'all', 'league': 'nba,nfl,mlb'},
59+
headers={'X-API-Key': 'YOUR_KEY'},
60+
stream=True,
61+
)
62+
for event in sseclient.SSEClient(response).events():
63+
if event.event == 'odds:update':
64+
data = json.loads(event.data)
65+
for o in data['odds']:
66+
route_by_league(o)
67+
```
68+
</Tabs.Tab>
69+
</Tabs>
70+
71+
### Odds + every opportunity type on one socket
72+
73+
Set `channel=all` (SSE) or include every opportunity type in `channels=` (WS):
74+
75+
<Tabs items={['SSE', 'WebSocket']}>
76+
<Tabs.Tab>
77+
```javascript
78+
const es = new EventSource(
79+
'https://api.sharpapi.io/api/v1/stream?channel=all&api_key=YOUR_KEY'
80+
);
81+
82+
es.addEventListener('odds:update', handleOddsDelta);
83+
es.addEventListener('ev:detected', handleEV);
84+
es.addEventListener('arb:detected', handleArb);
85+
es.addEventListener('middles:detected', handleMiddle);
86+
es.addEventListener('low_hold:detected', handleLowHold);
87+
```
88+
</Tabs.Tab>
89+
<Tabs.Tab>
90+
```javascript
91+
const ws = new WebSocket(
92+
'wss://ws.sharpapi.io' +
93+
'?api_key=YOUR_KEY' +
94+
'&channels=odds,ev,arbitrage,middles,low_hold'
95+
);
96+
```
97+
</Tabs.Tab>
98+
</Tabs>
99+
100+
The server tags each message with its event type — your handler dispatches by event name, exactly as if you'd opened five separate streams.
101+
102+
### Tracking N specific events
103+
104+
If you want tight updates on a fixed list of events (e.g. ten games tonight), pass them all to `event=`:
105+
106+
```javascript
107+
const eventIds = [
108+
'nba_lal_bos_2026-04-30', 'nba_phx_dal_2026-04-30',
109+
'nfl_kc_buf_2026-05-01', /* ... */
110+
].join(',');
111+
112+
const es = new EventSource(
113+
`https://api.sharpapi.io/api/v1/stream?channel=all&event=${eventIds}&api_key=YOUR_KEY`
114+
);
115+
```
116+
117+
This replaces the pattern of opening one `/stream/events/{eventId}` socket per event.
118+
119+
### Per-book or per-market splits
120+
121+
Same idea for books and markets:
122+
123+
```javascript
124+
// Track Pinnacle sharp moves + DK/FD live prices, moneyline + spreads only:
125+
const es = new EventSource(
126+
'https://api.sharpapi.io/api/v1/stream' +
127+
'?channel=odds' +
128+
'&sportsbook=pinnacle,draftkings,fanduel' +
129+
'&market=moneyline,point_spread' +
130+
'&api_key=YOUR_KEY'
131+
);
132+
```
133+
134+
### Dynamic re-subscription (WebSocket)
135+
136+
The bidirectional control of WebSocket lets you change filters mid-connection without dropping the socket. Use this when your set of "interesting" topics changes over the day (e.g. user opens / closes views in a dashboard):
137+
138+
```javascript
139+
const ws = new WebSocket('wss://ws.sharpapi.io?api_key=YOUR_KEY');
140+
141+
// Initial subscription
142+
ws.onopen = () => ws.send(JSON.stringify({
143+
type: 'subscribe',
144+
channels: ['odds', 'ev'],
145+
filters: { sport: ['basketball'], league: ['nba'] },
146+
}));
147+
148+
// Later — user adds NFL to their dashboard
149+
function addNFL() {
150+
ws.send(JSON.stringify({
151+
type: 'subscribe',
152+
filters: { sport: ['basketball', 'football'], league: ['nba', 'nfl'] },
153+
}));
154+
}
155+
```
156+
157+
See the [WebSocket API Reference](/en/api-reference/websocket) for the full subscribe-message schema.
158+
159+
## Fleet & multi-process
160+
161+
The one case where a single connection genuinely doesn't work: **separate processes or machines that can't share a socket.** Examples:
162+
163+
- Ten trading bots running on ten boxes, each needing its own live feed.
164+
- Worker pool where N workers each consume a stream independently.
165+
- Backend service + browser dashboard, both authenticated as the same user.
166+
167+
The default 1-stream cap means a second connection on the same key will displace the first (close code `4001 displaced by newer session` on WS, writer teardown on SSE). The intended escape hatch is a **per-key `maxStreams` override** in Unkey metadata, sized to the fleet. This is set up on Enterprise plans — contact [hello@sharpapi.io](mailto:hello@sharpapi.io) to provision a higher cap for a specific key.
168+
169+
For everything else — even very wide subscriptions — the patterns above let one socket cover what looks like ten separate concerns.
170+
171+
## Server-side cost
172+
173+
There is no efficiency reason to split. The server's hot path:
174+
175+
1. Computes the per-cycle book diff **once**.
176+
2. Pre-serializes the unfiltered changed/removed payloads **once**.
177+
3. For each connected client, applies their filter and serializes the matching subset (or sends the pre-serialized bytes directly when the client has no content filters).
178+
179+
A client with `league=nba,nfl,mlb,nhl` costs almost the same as four clients each with one league — minus four sockets, four TLS handshakes, four sets of HTTP headers, and four `connected`/`snapshot` cycles. The single-connection pattern is also better for **delta consistency**: you see all updates in the order the server produced them, instead of interleaving across sockets with independent backpressure.
180+
181+
## Migration checklist
182+
183+
If you're moving from a multi-stream architecture:
184+
185+
1. **Combine identity filters** — collect every `sport`, `league`, `sportsbook`, `market`, and `event` value across your old streams and join them with commas on one URL.
186+
2. **Use `channel=all`** (SSE) or every needed `channels=` value (WS) instead of one stream per event type.
187+
3. **Dispatch in your handler** — read `odds[].league` / `odds[].sportsbook` / `event.event` to route messages internally exactly as before.
188+
4. **Drop your reconnect-N-times logic** — one socket means one reconnect path. SSE auto-reconnects; for WS, see the [reconnection pattern](/en/streaming/websocket#reconnection).
189+
5. **Keep `min_ev` / `min_profit` thresholds** — these still narrow the opportunity stream server-side and reduce bandwidth.
190+
191+
## See also
192+
193+
- [Streaming Overview](/en/streaming/overview) — protocol comparison and quick start
194+
- [WebSocket Streaming Guide](/en/streaming/websocket) — subscribe messages, reconnection
195+
- [SSE API Reference](/en/api-reference/stream) — full filter parameter list
196+
- [WebSocket API Reference](/en/api-reference/websocket) — subscribe schema, close codes

0 commit comments

Comments
 (0)