Streaming Overview
Real-time odds and opportunity updates via SSE or WebSocket.
SharpAPI offers two streaming protocols: SSE (Server-Sent Events) over HTTP and WebSocket for bidirectional communication. Both deliver the same real-time data — choose based on your use case.
Why Streaming?
REST polling introduces a 30-60 second delay between odds changes and your application seeing them. SSE streaming delivers updates in sub-second latency directly to your application as they happen.
| Approach | Latency | Bandwidth | Use Case |
|---|---|---|---|
| REST polling | 30-60s | High (full payload each poll) | Casual browsing, dashboards |
| SSE streaming | < 1s | Low (only deltas) | Live betting, alerts, automated systems |
| WebSocket | < 1s | Low (only deltas) | Bidirectional comms, dynamic filter updates |
Key Benefits
- Sub-second latency — Odds updates arrive as soon as they are detected
- Lower bandwidth — Only changed data is sent, not full snapshots on every poll
- Two protocols — SSE for simplicity, WebSocket for bidirectional control
- Automatic reconnection — SSE reconnects natively; WebSocket with simple retry logic
How SSE Works
Server-Sent Events is a W3C standard for server-to-client streaming over HTTP:
Client Server
| |
|--- GET /api/v1/stream ------->|
| |
|<-- event: connected ----------| Stream established
|<-- event: snapshot -----------| Full current state
|<-- event: odds:update --------| Delta update
|<-- event: odds:update --------| Delta update
|<-- event: ev:detected --------| Opportunity found
|<-- event: heartbeat ----------| Keep-alive (every 30s)
| ... |SSE automatically reconnects on disconnect. The server uses Last-Event-ID to replay any events you missed.
Requirements
| Tier | Streaming Access |
|---|---|
| Free | Not available |
| Hobby + Add-on ($99/mo) | 1 stream (newer-wins displacement) |
| Pro + Add-on ($99/mo) | 1 stream (newer-wins displacement) |
| Sharp + Add-on ($99/mo) | 1 stream (newer-wins displacement) |
| Enterprise | Included (custom limits) |
Newer-wins displacement: opening a second stream from the same API key closes the first one. One well-managed connection is sufficient for most use cases — see Single-Connection Patterns for techniques. Fleet deployments needing multiple simultaneous streams can request a higher limit via sales.
Quick Start
Browser
// Local odds map — snapshot fills it, deltas merge into it
const oddsMap = new Map();
const eventSource = new EventSource(
'https://api.sharpapi.io/api/v1/stream?channel=all&league=nba&api_key=YOUR_KEY'
);
eventSource.addEventListener('connected', (e) => {
const { stream_id, reconnected } = JSON.parse(e.data);
if (reconnected) oddsMap.clear();
console.log('Stream connected:', stream_id);
});
eventSource.addEventListener('snapshot', (e) => {
const { odds } = JSON.parse(e.data);
for (const odd of odds) oddsMap.set(odd.id, odd);
console.log('Snapshot chunk:', odds.length, 'odds');
});
eventSource.addEventListener('odds:update', (e) => {
const { odds, book } = JSON.parse(e.data);
// Delta only contains dynamic fields — merge into local state by ID
for (const delta of odds) {
const full = oddsMap.get(delta.id);
if (full) Object.assign(full, delta);
}
console.log(`${book}: ${odds.length} odds updated`);
});
eventSource.addEventListener('ev:detected', (e) => {
const opps = JSON.parse(e.data);
opps.forEach(opp => console.log(`+EV: ${opp.selection} at ${opp.ev_percentage}% EV`));
});
eventSource.addEventListener('heartbeat', () => {
console.log('Connection alive');
});
eventSource.onerror = () => {
console.log('Connection lost, auto-reconnecting...');
};Node.js
import EventSource from 'eventsource';
const oddsMap = new Map();
const es = new EventSource(
'https://api.sharpapi.io/api/v1/stream?channel=odds&league=nba',
{ headers: { 'X-API-Key': 'YOUR_KEY' } }
);
es.addEventListener('snapshot', (e) => {
const { odds } = JSON.parse(e.data);
for (const odd of odds) oddsMap.set(odd.id, odd);
console.log(`Received ${odds.length} initial odds`);
});
es.addEventListener('odds:update', (e) => {
const { odds, book } = JSON.parse(e.data);
// Merge compact deltas into local state
for (const delta of odds) {
const full = oddsMap.get(delta.id);
if (full) Object.assign(full, delta);
}
console.log(`${book}: ${odds.length} odds updated`);
});Python
import sseclient
import requests
import json
url = 'https://api.sharpapi.io/api/v1/stream'
params = {'channel': 'all', 'league': 'nba'}
headers = {'X-API-Key': 'YOUR_KEY'}
odds_map: dict[str, dict] = {}
response = requests.get(url, params=params, headers=headers, stream=True)
client = sseclient.SSEClient(response)
for event in client.events():
data = json.loads(event.data) if event.data else {}
if event.event == 'connected':
if data.get('reconnected'):
odds_map.clear()
print(f"Stream {data['stream_id']} connected")
elif event.event == 'snapshot':
for odd in data.get('odds', []):
odds_map[odd['id']] = odd
print(f"Snapshot: {data['count']} odds")
elif event.event == 'odds:update':
# Delta only has dynamic fields — merge by ID
for delta in data.get('odds', []):
existing = odds_map.get(delta['id'])
if existing:
existing.update(delta)
print(f"{data['book']}: {data['count']} odds updated")
elif event.event == 'ev:detected':
for opp in data:
print(f"+EV: {opp['selection']} at {opp['ev_percentage']}%")Event Types
| Event | Description |
|---|---|
connected | Stream established, returns stream ID, active filters, and channels |
snapshot / opportunities_snapshot | Full odds/opportunities data dump (all fields) |
snapshot:complete | All initial data has been sent |
odds:update | Compact delta — only dynamic fields (id, odds_american, odds_decimal, odds_probability, line, is_live, odds_changed_at). Merge by id into snapshot state. |
odds:removed | Odds removed by a sportsbook |
ev:detected | New +EV opportunity found |
ev:expired | +EV opportunity no longer available |
arb:detected | New arbitrage opportunity found |
arb:expired | Arbitrage opportunity no longer available |
middles:detected | New middle opportunity found (see Middles Summary for aggregated stats) |
middles:expired | Middle opportunity no longer available |
low_hold:detected | New low-hold opportunity found |
low_hold:expired | Low-hold opportunity no longer available |
heartbeat | Keep-alive sent every 30 seconds |
error | Recoverable error (connection stays open) |
SSE vs WebSocket
| Feature | SSE | WebSocket |
|---|---|---|
| Protocol | HTTP (one-way) | ws:// / wss:// (bidirectional) |
| Endpoint | GET /api/v1/stream | wss://ws.sharpapi.io |
| Channel subscriptions | Set once via query params | Update anytime via subscribe message |
| Filter updates | Reconnect with new params | Send subscribe message |
| Reconnection | Automatic (Last-Event-ID) | Manual (with backoff) |
| Browser support | Native EventSource | Native WebSocket |
| Best for | Simple consumers, SSR | Dynamic filters, two-way comms |
Both protocols deliver the same event types and data payloads.
Full API Reference
- SSE Stream API Reference — Query parameters, event payloads, reconnection
- WebSocket API Reference — Connection lifecycle, message protocol, close codes
- WebSocket Streaming Guide — Quick start, reconnection patterns, best practices
SDKs with Streaming Support
- TypeScript SDK — Built-in SSE client with type-safe event handlers
- Python SDK — Handler-based and iterator-based streaming patterns
Example Projects
- Arbitrage Scanner — Real-time arb detection with Discord alerts
- Value Betting Bot — +EV alert bot for Discord/Telegram