Streaming WebSocket
Actualizaciones de cuotas y oportunidades en tiempo real mediante WebSocket en wss://ws.sharpapi.io.
Para consultar la documentación completa de la API, incluidos todos los tipos de mensajes, esquemas y códigos de cierre, consulta la Referencia de la API WebSocket.
SSE frente a WebSocket
SharpAPI ofrece dos protocolos de streaming. Ambos entregan los mismos datos a la misma velocidad.
| SSE | WebSocket | |
|---|---|---|
| URL | https://api.sharpapi.io/api/v1/stream | wss://ws.sharpapi.io |
| Dirección | Servidor → Cliente | Bidireccional |
| Filtros de actualización | Requiere reconexión | Enviar mensaje subscribe |
| Reconexión | Automática (Last-Event-ID) | Manual (implementar backoff) |
| Idóneo para | Consumidores sencillos, navegadores | Aplicaciones interactivas, paneles |
Elige SSE si configuras los filtros una vez y solo quieres que los datos fluyan. Elige WebSocket si necesitas cambiar los filtros sobre la marcha o prefieres un protocolo bidireccional.
Inicio rápido
1. Conectar
Usa el parámetro channels para suscribirte únicamente a los datos que necesites:
// Only EV opportunities + odds — no middles, low_hold, or arbitrage
const ws = new WebSocket(
'wss://ws.sharpapi.io?api_key=YOUR_KEY&channels=ev,odds&sport=basketball&sportsbook=draftkings&league=nba'
);Canales disponibles: ev, arbitrage, middles, low_hold, odds. Omite channels para recibir todo lo que admita tu nivel de suscripción.
Filtros disponibles: sport, sportsbook, league, market, event_id — todos separados por comas.
Filtros de umbral: min_ev (predeterminado 2.0), min_profit (predeterminado 0.5, se aplica solo a arbitrage, no a low-hold) — descartan oportunidades de bajo valor en el lado del servidor.
2. Gestionar mensajes
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'connected':
console.log(msg.message); // "Welcome to SharpAPI real-time odds stream"
break;
case 'opportunities_snapshot':
if (msg.ev) console.log('EV:', msg.ev); // EV opportunity snapshot
break;
case 'initial':
console.log('Odds:', msg.data); // Per-sportsbook odds snapshot
break;
case 'snapshot:complete':
console.log('All initial data loaded'); // Safe to hide loading state
break;
case 'odds:update':
console.log(msg.source, msg.data); // Incremental odds update
break;
case 'ev:detected':
console.log('+EV:', msg.data); // New or updated +EV opportunities (Pro+) — upsert by id
break;
}
};3. Mantener viva la conexión
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 25000);4. Actualizar canales y filtros (sin reconectar)
ws.send(JSON.stringify({
type: 'subscribe',
channels: ['low_hold'],
filters: { sports: ['football'], sportsbooks: ['fanduel', 'betmgm'], leagues: ['nfl'] }
}));Flujo de mensajes
Al conectarte recibes los mensajes en este orden:
connected— Mensaje de bienvenida con tu nivel de suscripción, características y canales activossubscribed— Confirmación de los canales y filtros activosopportunities_snapshot— Uno por cada canal de oportunidades suscrito (ev, arbitrage, middles, low_hold)initial— Snapshot de cuotas por sportsbook (solo si está suscrito el canalodds)snapshot:complete— Indica que se han enviado todos los datos iniciales
Fragmentación de snapshots y tamaño de frame: Los snapshots de gran tamaño se dividen en varios frames para evitar la contrapresión. Cada frame de snapshot está limitado a 256KB serializados; los snapshots de cuotas se dividen además por sportsbook, y los de oportunidades en hasta 300 elementos por frame. Espera al snapshot:complete antes de considerar que los datos iniciales se han cargado por completo. Si tu librería cliente impone un tamaño máximo de mensaje entrante, déjalo en 512KB o más — el valor por defecto de 1MB de Python websockets (max_size=2**20) es suficiente. Un cliente con un límite inferior a 256KB se cierra con 1009 (message too big) a mitad del snapshot y entrará en un bucle de reconexión sin llegar a recibir datos.
A partir de ahí, recibes actualizaciones incrementales de los canales suscritos:
odds:update— Han cambiado las cuotas de un sportsbook (requiere el canalodds)odds:removed— Un sportsbook ha retirado cuotas (requiere el canalodds)ev:detected/ev:expired— Oportunidades de +EV (requiere el canalev)arb:detected/arb:expired— Oportunidades de arbitraje (requiere el canalarbitrage)middles:detected/middles:expired— Oportunidades de middle (requiere el canalmiddles)low_hold:detected/low_hold:expired— Oportunidades de low-hold (requiere el canallow_hold)heartbeat— Keepalive del servidor cada 30 segundos
Reconexión
A diferencia de SSE, WebSocket no se reconecta automáticamente. Implementa un backoff exponencial:
let reconnectDelay = 1000;
let lastGlobalSeq = 0;
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'connected') lastGlobalSeq = msg.global_seq;
if (msg.global_seq) lastGlobalSeq = msg.global_seq;
};
ws.onclose = (event) => {
if (event.code === 1000) return; // intentional close
if (event.code === 4001 && event.reason.includes('displaced')) return; // newer session owns the slot
setTimeout(() => {
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
// Pass from_seq to attempt a resume (best-effort, ~5-min replay window).
// If the server can't resume it falls back to a full snapshot and says so.
connect({ from_seq: lastGlobalSeq });
}, reconnectDelay);
};Restablece reconnectDelay a 1000 cuando la conexión sea correcta.
El servidor mantiene un búfer de reproducción de aproximadamente los últimos 5 minutos de eventos. Pasa from_seq=N al reconectar para reproducir los eventos perdidos en lugar de recibir un snapshot completo. La reanudación es de mejor esfuerzo: en interrupciones más largas o reconexiones durante el mantenimiento del servidor, el servidor te restablece con un snapshot completo, marcado como mode: "full_resync" en snapshot:complete — y una reanudación que encuentra un hueco establece gap_detected: true. Tu cliente debe manejar ambos casos — consulta el Streaming Reliability Contract y Reconexión con reproducción para más detalles.
Códigos de cierre
| Código | Significado | Acción |
|---|---|---|
1000 | Cierre normal | No se requiere acción |
1006 | Cierre anómalo (lado cliente) | Caída de red — reconectar siempre |
4001 | Fallo de autenticación o desplazamiento por una sesión más reciente con la misma clave — lee el motivo de cierre | Clave incorrecta → corrígela. "displaced by newer session" → no reconectes automáticamente (single-connection) |
4003 | Los permisos cambiaron durante el stream (bajada de plan, clave revocada, complemento eliminado) | Reconecta para reautorizar con los permisos actuales |
El código 1006 está reservado por la RFC 6455 y el servidor nunca lo envía. Tu librería WebSocket lo genera localmente cuando la conexión TCP se cae sin un handshake de cierre (fallo de red, terminación del proceso). Reconecta siempre que lo veas — el ejemplo siguiente ya lo hace correctamente: omite la reconexión solo en 1000 y en un desplazamiento 4001, nunca en 1006.
Referencia completa de la API
Para acceder a la documentación completa, incluidos todos los esquemas de mensajes, la gestión de errores y los ejemplos en varios lenguajes, consulta la Referencia de la API WebSocket.