Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions content/en/streaming/_meta.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default {
overview: "Overview",
websocket: "WebSocket",
"single-connection": "One Connection, Many Topics",
}
196 changes: 196 additions & 0 deletions content/en/streaming/single-connection.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
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."
---

import { Callout, Tabs } from 'nextra/components'

# One Connection, Many Topics

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.

<Callout type="info">
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.
</Callout>

## Why one connection is enough

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.

| Filter | Single value | Multiple values |
|--------|--------------|-----------------|
| `sport` | `sport=basketball` | `sport=basketball,football,ice_hockey` |
| `league` | `league=nba` | `league=nba,nfl,mlb,nhl` |
| `sportsbook` | `sportsbook=draftkings` | `sportsbook=draftkings,fanduel,pinnacle` |
| `market` | `market=moneyline` | `market=moneyline,point_spread,total_points` |
| `event` | `event=evt_abc` | `event=evt_abc,evt_def,evt_ghi` |
| `channel` (SSE) | `channel=odds` | `channel=all` (odds + opportunities) |
| `channels` (WS) | `channels=ev` | `channels=ev,odds,arbitrage,middles,low_hold` |

## Patterns

### Multi-sport: NBA + NFL + MLB on one socket

Instead of opening three streams, pass all three leagues to one:

<Tabs items={['JavaScript', 'Python']}>
<Tabs.Tab>
```javascript
const es = new EventSource(
'https://api.sharpapi.io/api/v1/stream' +
'?channel=all' +
'&league=nba,nfl,mlb' +
'&api_key=YOUR_KEY'
);

es.addEventListener('odds:update', (e) => {
const { odds, book } = JSON.parse(e.data);
// odds[].league tells you which league this update is for
for (const o of odds) routeByLeague(o);
});
```
</Tabs.Tab>
<Tabs.Tab>
```python
import sseclient, requests, json

response = requests.get(
'https://api.sharpapi.io/api/v1/stream',
params={'channel': 'all', 'league': 'nba,nfl,mlb'},
headers={'X-API-Key': 'YOUR_KEY'},
stream=True,
)
for event in sseclient.SSEClient(response).events():
if event.event == 'odds:update':
data = json.loads(event.data)
for o in data['odds']:
route_by_league(o)
```
</Tabs.Tab>
</Tabs>

### Odds + every opportunity type on one socket

Set `channel=all` (SSE) or include every opportunity type in `channels=` (WS):

<Tabs items={['SSE', 'WebSocket']}>
<Tabs.Tab>
```javascript
const es = new EventSource(
'https://api.sharpapi.io/api/v1/stream?channel=all&api_key=YOUR_KEY'
);

es.addEventListener('odds:update', handleOddsDelta);
es.addEventListener('ev:detected', handleEV);
es.addEventListener('arb:detected', handleArb);
es.addEventListener('middles:detected', handleMiddle);
es.addEventListener('low_hold:detected', handleLowHold);
```
</Tabs.Tab>
<Tabs.Tab>
```javascript
const ws = new WebSocket(
'wss://ws.sharpapi.io' +
'?api_key=YOUR_KEY' +
'&channels=odds,ev,arbitrage,middles,low_hold'
);
```
</Tabs.Tab>
</Tabs>

The server tags each message with its event type — your handler dispatches by event name, exactly as if you'd opened five separate streams.

### Tracking N specific events

If you want tight updates on a fixed list of events (e.g. ten games tonight), pass them all to `event=`:

```javascript
const eventIds = [
'nba_lal_bos_2026-04-30', 'nba_phx_dal_2026-04-30',
'nfl_kc_buf_2026-05-01', /* ... */
].join(',');

const es = new EventSource(
`https://api.sharpapi.io/api/v1/stream?channel=all&event=${eventIds}&api_key=YOUR_KEY`
);
```

This replaces the pattern of opening one `/stream/events/{eventId}` socket per event.

### Per-book or per-market splits

Same idea for books and markets:

```javascript
// Track Pinnacle sharp moves + DK/FD live prices, moneyline + spreads only:
const es = new EventSource(
'https://api.sharpapi.io/api/v1/stream' +
'?channel=odds' +
'&sportsbook=pinnacle,draftkings,fanduel' +
'&market=moneyline,point_spread' +
'&api_key=YOUR_KEY'
);
```

### Dynamic re-subscription (WebSocket)

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):

```javascript
const ws = new WebSocket('wss://ws.sharpapi.io?api_key=YOUR_KEY');

// Initial subscription
ws.onopen = () => ws.send(JSON.stringify({
type: 'subscribe',
channels: ['odds', 'ev'],
filters: { sport: ['basketball'], league: ['nba'] },
}));

// Later — user adds NFL to their dashboard
function addNFL() {
ws.send(JSON.stringify({
type: 'subscribe',
filters: { sport: ['basketball', 'football'], league: ['nba', 'nfl'] },
}));
}
```

See the [WebSocket API Reference](/en/api-reference/websocket) for the full subscribe-message schema.

## Fleet & multi-process

The one case where a single connection genuinely doesn't work: **separate processes or machines that can't share a socket.** Examples:

- Ten trading bots running on ten boxes, each needing its own live feed.
- Worker pool where N workers each consume a stream independently.
- Backend service + browser dashboard, both authenticated as the same user.

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.

For everything else — even very wide subscriptions — the patterns above let one socket cover what looks like ten separate concerns.

## Server-side cost

There is no efficiency reason to split. The server's hot path:

1. Computes the per-cycle book diff **once**.
2. Pre-serializes the unfiltered changed/removed payloads **once**.
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).

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.

## Migration checklist

If you're moving from a multi-stream architecture:

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.
2. **Use `channel=all`** (SSE) or every needed `channels=` value (WS) instead of one stream per event type.
3. **Dispatch in your handler** — read `odds[].league` / `odds[].sportsbook` / `event.event` to route messages internally exactly as before.
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).
5. **Keep `min_ev` / `min_profit` thresholds** — these still narrow the opportunity stream server-side and reduce bandwidth.

## See also

- [Streaming Overview](/en/streaming/overview) — protocol comparison and quick start
- [WebSocket Streaming Guide](/en/streaming/websocket) — subscribe messages, reconnection
- [SSE API Reference](/en/api-reference/stream) — full filter parameter list
- [WebSocket API Reference](/en/api-reference/websocket) — subscribe schema, close codes