Real-Time Event Streaming API
Access Required
This API endpoint is only available to AllStar and custom plan subscribers. It is not included with basic subscription tiers. Contact support to get access.
Beta Feature
This streaming API is currently in beta. API call patterns, response formats, and functionality may change.
Our Streaming API provides real-time updates for Event objects through WebSocket connections. Instead of polling our REST endpoints, you can maintain a persistent connection to receive instant notifications when events change. This is ideal for applications that need immediate updates with minimal delay.
We use Pusher Protocol for WebSocket communication. While you can connect using any WebSocket library, we strongly recommend using any Pusher Client Library (ex: Javascript, Python)
How It Works
The streaming process involves two steps:
Get Connection Details: Make a request to
/v2/stream/eventsto receive:- WebSocket authentication credentials
- WebSocket URL/channel info
- Initial snapshot of current data
Connect and Stream: Use the provided details to connect via Pusher (or another WebSocket library) and receive real-time
eventIDnotifications for changed events
Rate Limits
Your API key will have limits on concurrent streams.
Available Feeds
Subscribe to different feeds using the feed query parameter:
| Feed | Description | Required Parameters |
|---|---|---|
events:live | All events currently in progress (started but not finished) | None |
events:upcoming | Upcoming events with available odds for a specific league | leagueID |
events:byid | Updates for a single specific event | eventID |
Feeds
The number of supported feeds will increase over time. Please reach out if you have a use case which can’t be covered by these feeds.
Quick Start Example
Here’s the minimal code to connect to live events:
const axios = require("axios");
const Pusher = require("pusher-js");
const STREAM_FEED = "events:live"; // ex: events:upcoming, events:byid, events:live
const API_BASE_URL = "https://api.sportsgameodds.com/v2";
const API_KEY = "YOUR API KEY";
const run = async () => {
// Initialize a data structure where we'll save the event data
const EVENTS = new Map();
// Call this endpoint to get initial data and connection parameters
const streamInfo = await axios.get(`${API_BASE_URL}/stream/events`, { headers: { "x-api-key": API_KEY }, params: { feed: STREAM_FEED } }).then((r) => r?.data);
// Seed initial data
streamInfo.data.forEach((event) => EVENTS.set(event?.eventID, event));
// Connect to WebSocket server
const pusher = new Pusher(streamInfo.pusherKey, streamInfo.pusherOptions);
pusher.subscribe(streamInfo.channel).bind("data", async (changedEvents) => {
// Get the eventIDs that changed
const eventIDs = changedEvents.map(({ eventID }) => eventID).join(",");
// Get the full event data for the changed events
const eventDataResponse = await axios.get(`${API_BASE_URL}/events`, { headers: { "x-api-key": API_KEY }, params: { eventIDs } }).then((r) => r?.data);
// Update our data with the full event data
eventDataResponse.data.forEach((newEventData) => EVENTS.set(newEventData?.eventID, newEventData));
});
// Use pusher.disconnect to disconnect from the WebSocket server
process.on("SIGINT", pusher.disconnect);
};
run();import requests
import pusherclient
import json
API_KEY = 'YOUR_API_KEY'
API_BASE_URL = 'https://api.sportsgameodds.com/v2'
def handle_update(data):
try:
changed_events = json.loads(data)
event_ids = ','.join([e['eventID'] for e in changed_events])
# Fetch updated event data
response = requests.get(
f"{API_BASE_URL}/events",
headers={'x-api-key': API_KEY},
params={'eventIDs': event_ids}
)
for event in response.json()['data']:
print(f"Updated: {event['eventID']}")
except Exception as e:
print(f"Error processing update: {e}")
def connect_to_live_events():
# Get connection details
response = requests.get(
f"{API_BASE_URL}/stream/events",
headers={'x-api-key': API_KEY},
params={'feed': 'events:live'}
)
stream_info = response.json()
print(f"Connected with {len(stream_info['data'])} initial events")
# Connect to Pusher (Note: private channels require custom auth in Python)
pusher_key = stream_info['pusherKey']
channel_name = stream_info['channel']
pusher = pusherclient.Pusher(pusher_key)
channel = pusher.subscribe(channel_name)
channel.bind('data', handle_update)
pusher.connection.bind('pusher:connection_established',
lambda data: print("Connected to stream"))
if __name__ == "__main__":
connect_to_live_events()require 'net/http'
require 'json'
require 'pusher-client'
require 'uri'
STREAM_FEED = "events:live" # ex: events:upcoming, events:byid, events:live
API_BASE_URL = "https://api.sportsgameodds.com/v2"
API_KEY = "API_KEY_GOES_HERE"
def convert_ruby_options(standard_options, api_key)
ruby_client_options = {}
ruby_client_options[:ws_host] = standard_options['wsHost'] if standard_options['wsHost']
ruby_client_options[:ws_port] = standard_options['wsPort'] if standard_options['wsPort']
ruby_client_options[:wss_port] = standard_options['wssPort'] if standard_options['wssPort']
ruby_client_options[:ws_path] = standard_options['wsPath'] if standard_options['wsPath']
ruby_client_options[:ws_host] = "ws-#{standard_options['cluster']}.pusher.com" if standard_options['cluster'] && !ruby_client_options[:ws_host]
ruby_client_options[:secure] = true
if (standard_options.dig('channelAuthorization', 'endpoint'))
ruby_client_options[:auth_method] = ->(socket_id, channel) {
headers = (standard_options.dig('channelAuthorization', 'headers') || {}).merge('Content-Type' => 'application/json')
params = (standard_options.dig('channelAuthorization', 'params') || {}).merge('socket_id' => socket_id, 'channel_name' => channel.name)
res = Net::HTTP.post(URI(standard_options.dig('channelAuthorization', 'endpoint')), params.to_json, headers)
res.is_a?(Net::HTTPSuccess) ? JSON.parse(res.body)['auth'] : raise("Auth failed: HTTP #{res.code} - #{res.body}")
}
end
ruby_client_options
end
def api_get(path, params = {})
uri = URI("#{API_BASE_URL}#{path}")
uri.query = URI.encode_www_form(params) unless params.empty?
res = Net::HTTP.get_response(uri, {'x-api-key' => API_KEY})
raise "HTTP #{res.code}: #{res.message}" unless res.is_a?(Net::HTTPSuccess)
JSON.parse(res.body)
end
def run
events = {}
# Get initial data and connection parameters
stream_info = api_get("/stream/events", feed: STREAM_FEED)
stream_info['data'].each { |e| events[e['eventID']] = e }
# Convert the standard options to Ruby SDK options
connect_options = convert_ruby_options(stream_info['pusherOptions'], API_KEY)
# Initialize the client
client = PusherClient::Socket.new(stream_info['pusherKey'], connect_options)
# Set up other event handlers as needed
client.bind('pusher:connection_established') { puts "Connected" }
client.bind('pusher:subscription_succeeded') { |_| puts "Subscribed to: #{stream_info['channel']}" }
client.bind('pusher:subscription_error') { |data| puts "Error: #{data}" }
# Subscribe to and handle data events in the channel
channel = client.subscribe(stream_info['channel'], stream_info['user'])
channel.bind('data') do |json|
changed_events = JSON.parse(json)
event_ids = changed_events.map { |e| e['eventID'] }.join(', ')
puts "Changed Events: #{event_ids}"
# Fetch and update full event data
api_get("/events", eventIDs: event_ids)['data'].each { |e| events[e['eventID']] = e }
rescue => e
puts "Error processing data event: #{e.message}"
end
# Print initial event IDs
puts "EVENTS: #{events.keys.join(',')}"
# Graceful shutdown
trap('INT') { puts "\nDisconnecting..."; client.disconnect; exit }
# Connect to the WebSocket server
client.connect # For async usage, you can instead call client.connect(true) after initializing the client
end
private
# Runs the script
runAPI Response Format
The /v2/stream/events endpoint returns:
{
"success": true,
"data": [
<an Event object>,
// ... more events
],
"pusherKey": <key to pass when setting up the Pusher client>,
"pusherOptions": <options to pass when setting up the Pusher client>,
"channel": <channel to subscribe to>
}Update Message Format
Real-time updates contain only the eventID of changed events:
[
{ "eventID": <an eventID> },
]You then fetch full event details using the /v2/events endpoint with the eventIDs parameter.
Complete Example: NFL Game Tracker
This example demonstrates a production-ready implementation for tracking NFL games:
const axios = require("axios");
const Pusher = require("pusher-js");
const API_KEY = "PUT YOUR API KEY HERE";
const API_BASE_URL = "https://api.sportsgameodds.com/v2";
const LEAGUE_ID = "NFL";
// State variables
let events = new Map();
let pusher = null;
let channel = null;
let connectionState = "disconnected";
let heartbeatInterval = null;
const getEventTitle = (event) => {
const awayTeam = event.teams?.away?.names?.medium || event.teams?.away?.names?.long || event.teams?.away?.names?.short;
const homeTeam = event.teams?.home?.names?.medium || event.teams?.home?.names?.long || event.teams?.home?.names?.short;
const gameTime = event.status.startsAt.toLocaleString();
return `${awayTeam} vs ${homeTeam} @ ${gameTime} (${event.eventID})`;
};
const startMonitoring = () => {
// Heartbeat
heartbeatInterval = setInterval(() => {
console.log(`💓 Heartbeat...connection: ${connectionState}, events tracked: ${events.size}`);
}, 10000);
// Connection state changes
pusher.connection.bind("state_change", (states) => {
connectionState = states.current;
console.log(`🔌 Connection state: ${states.previous} → ${states.current}`);
});
// Connection errors
pusher.connection.bind("error", (error) => {
console.error("🚨 Connection error:", error);
});
// Connection established
pusher.connection.bind("connected", () => {
console.log("🎉 Connection established successfully");
});
// Connection disconnected
pusher.connection.bind("disconnected", () => {
console.log("👋 Connection disconnected");
});
// Failed connection
pusher.connection.bind("failed", () => {
console.log("💥 Connection failed permanently");
});
};
const connect = async () => {
console.log(`🔄 Connecting upcoming events for: ${LEAGUE_ID}...`);
connectionState = "connecting";
try {
// Get stream metadata
console.log("📡 Fetching stream configuration...");
const response = await axios.get(`${API_BASE_URL}/stream/events`, {
headers: { "x-api-key": API_KEY },
params: {
feed: "events:upcoming",
leagueID: LEAGUE_ID,
},
});
console.log("✅ Stream configuration received");
const { data: initialEvents, pusherKey, pusherOptions, channel: channelName } = response.data;
console.log(`📊 Stream config...channel: ${channelName}, pusherKey: ${pusherKey}, initialEvents: ${initialEvents.length}`);
initialEvents.forEach((event) => {
events.set(event.eventID, event);
console.log(`⏳ Initial Event: ${getEventTitle(event)}`);
});
console.log("🔌 Initializing WebSocket connection...");
pusher = new Pusher(pusherKey, pusherOptions);
startMonitoring();
// Subscribe to channel with error handling
console.log(`📺 Subscribing to channel: ${channelName}`);
channel = pusher.subscribe(channelName);
channel.bind("pusher:subscription_succeeded", () => {
console.log(`✅ Successfully subscribed to channel: ${channelName}`);
connectionState = "subscribed";
});
channel.bind("data", async (changedEvents) => {
console.log(`🔔 Received change notification for ${changedEvents.length} event(s)`);
const eventIDs = changedEvents.map((e) => e.eventID).join(",");
if (!eventIDs) return;
console.log(`🔍 Fetching full data for events: ${eventIDs}`);
const response = await axios
.get(`${API_BASE_URL}/events`, {
headers: { "x-api-key": API_KEY },
params: { eventIDs },
})
.catch(({ data }) => data);
if (!response?.data?.length) return;
console.log(`📦 Received ${response.data.length} updated events`);
response.data.forEach((current) => {
const prev = events.get(current.eventID);
if (!prev) {
console.log(`🆕 New Event: ${getEventTitle(current)}`);
return;
} else if (!prev.status.started && current.status.started) {
console.log(`🏈 Event started: ${getEventTitle(current)}`);
return;
} else {
console.log(`🔄 Event updated: ${getEventTitle(current)}`);
}
events.set(current.eventID, current);
});
});
console.log("🎯 Setup complete, waiting for events...");
} catch (error) {
console.log(error);
console.error("❌ Failed to connect:", error.message);
connectionState = "failed";
}
};
const disconnect = () => {
console.log("🔌 Disconnecting from stream...");
if (heartbeatInterval) clearInterval(heartbeatInterval);
if (channel?.name) pusher.unsubscribe(channel.name);
if (pusher) pusher.disconnect();
};
console.log(`🚀 Starting upcoming event tracker for: ${LEAGUE_ID}...`);
connect();
process.on("SIGINT", () => {
console.log("\n🛑 Shutdown signal received...");
disconnect();
});Feed-Specific Examples
Live Events
Monitor all currently live games across all sports:
axios.get("https://api.sportsgameodds.com/v2/stream/events", {
headers: { 'x-api-key': API_KEY },
params: {
feed: 'events:live'
}
});Upcoming Events by League
Monitor upcoming games for a specific league:
axios.get("https://api.sportsgameodds.com/v2/stream/events", {
headers: { 'x-api-key': API_KEY },
params: {
feed: 'events:upcoming',
leagueID: 'NFL'
}
});Single Event Tracking
Track updates for one specific event:
axios.get("https://api.sportsgameodds.com/v2/stream/events", {
headers: { 'x-api-key': API_KEY },
params: {
feed: 'events:byid',
eventID: 'DENVER_NUGGETS_VS_MIAMI_HEAT_2024-05-20T00:30:00Z_NBA'
}
});Connection Management
You’ll want to be mindful of the connection state and handle errors and disconnections properly in order to maintain a reliable connection.
const pusher = new Pusher(pusherKey, pusherOptions);
// Monitor connection state
pusher.connection.bind('state_change', (states) => {
console.log('Connection state:', states.current);
// States: connecting, connected, unavailable, failed, disconnected
});
// Handle connection errors
pusher.connection.bind('error', (error) => {
console.error('Connection error:', error);
// Implement reconnection logic if needed
});
// Clean disconnect
pusher.disconnect();Troubleshooting
Connection Issues
- Verify your API key has streaming permissions
- Check that you haven’t exceeded your concurrent stream limit
- Ensure proper Pusher client library installation
No Updates Received
- Confirm you’re subscribed to the correct channel
- Verify the feed parameters match available events
- Check network connectivity and firewall settings
High Memory Usage
- Implement data cleanup for old events
- Store only necessary event properties
- Use efficient data structures (Map vs Object)
For additional support, contact our support team with your API key and specific error messages.
