API de cuotas en vivo: cuotas en tiempo real por WebSocket
Una API de cuotas en vivo envía a tu código la cuota de una casa de apuestas en el momento en que cambia. Odds-API.io transmite cuotas prepartido y en vivo de 450+ casas de apuestas en 34 deportes por una sola conexión WebSocket, con marcadores en vivo, estado del partido y reloj del partido en el mismo stream.
El streaming por WebSocket es un complemento para cualquier plan de pago. REST está incluido en todos los planes.
- Casas de apuestas
- 450+
- Deportes
- 34
- Latencia
- <150ms
- Ventana de reenvío
- 24h
Por qué WebSocket en lugar de polling
Con polling pides las cuotas cada cierto tiempo y casi siempre recibes datos sin cambios. Un WebSocket se mantiene abierto y solo transporta los precios que se movieron, así que ves cada cambio antes y transfieres muchos menos datos.
| Característica | Polling REST | WebSocket |
|---|---|---|
| Cómo te llegan los cambios | Vuelves a preguntar cada cierto tiempo | Se envían en cuanto cambia un precio |
| Latencia | Tu intervalo de polling más la petición | <150ms |
| Conexiones | Una petición HTTP nueva en cada consulta | Una conexión que se mantiene abierta |
| Mercados retirados | Hay que volver a leer el evento para detectarlos | Mensajes explícitos deleted y no_markets |
| Ideal para | Scripts, cargas históricas y consultas rápidas | Apps de apuestas en vivo, modelos in-play y escáneres de arbitraje |
Conéctate en pocas líneas
Abre wss://api.odds-api.io/v3/ws con tu API key y los mercados que quieres. El primer mensaje es una bienvenida que confirma tus filtros y, a partir de ahí, las cuotas llegan a medida que cambian.
// npm i ws
import WebSocket from 'ws'
const API = 'https://api.odds-api.io/v3'
const apiKey = process.env.ODDS_API_KEY
const markets = 'ML,Spread,Totals'
const eventIds = '63017989,63017990' // up to 10, for example from GET /v3/events/live
const bookmakers = 'Bet365,Unibet' // the bookmakers selected on your account
const board = new Map()
let lastSeq = 0
let needsRebuild = false
function connect() {
const params = new URLSearchParams({ apiKey, markets, eventIds, channels: 'odds,scores,status' })
if (lastSeq) params.set('lastSeq', String(lastSeq))
const ws = new WebSocket(`wss://api.odds-api.io/v3/ws?${params}`)
ws.on('message', (raw) => {
const msg = JSON.parse(raw)
if (msg.type === 'resync_required') {
needsRebuild = true
return ws.close()
}
if (msg.seq > lastSeq) lastSeq = msg.seq
const key = `${msg.id}:${msg.bookie}`
if (msg.type === 'created' || msg.type === 'updated') board.set(key, msg.markets)
if (msg.type === 'deleted' || msg.type === 'no_markets') board.delete(key)
if (msg.type === 'score') console.log(msg.id, msg.scores.home, msg.scores.away)
})
ws.on('error', (err) => console.error('WebSocket error:', err.message || err.code))
ws.on('close', async () => {
if (needsRebuild) lastSeq = await rebuildFromRest()
setTimeout(connect, 2000)
})
}
// Replay was not possible: reload the subscription from REST and resume from its seq
async function rebuildFromRest() {
for (let attempt = 1; ; attempt++) {
try {
const params = new URLSearchParams({ apiKey, eventIds, bookmakers, markets, includeSeq: 'true' })
const res = await fetch(`${API}/odds/multi?${params}`)
const seq = Number(res.headers.get('X-OddsAPI-Seq'))
if (!res.ok || !(seq > 0)) throw new Error(`snapshot not usable: ${res.status}`)
const events = await res.json()
board.clear()
for (const event of events) {
for (const [bookie, odds] of Object.entries(event.bookmakers)) board.set(`${event.id}:${bookie}`, odds)
}
needsRebuild = false
return seq
} catch (err) {
console.error(`${err.message}, retry ${attempt}`)
await new Promise((resolve) => setTimeout(resolve, Math.min(30000, 1000 * 2 ** attempt)))
}
}
}
connect()# pip install websockets
import asyncio
import json
import os
from urllib.parse import urlencode
import websockets
params = urlencode({
"apiKey": os.environ["ODDS_API_KEY"],
"markets": "ML,Spread,Totals",
"leagues": "england-premier-league",
"status": "live",
})
async def main():
async with websockets.connect(f"wss://api.odds-api.io/v3/ws?{params}") as ws:
async for message in ws:
msg = json.loads(message)
if msg["type"] == "updated":
for market in msg["markets"]:
print(msg["id"], msg["bookie"], market["name"], market["odds"][0])
asyncio.run(main())La compresión es obligatoria
Tu cliente debe ofrecer permessage-deflate al conectarse. Reduce el ancho de banda de 3 a 5 veces, y una conexión sin ella se rechaza con HTTP 426. La mayoría de las librerías lo gestionan por ti:
| Plataforma | Librería | Compresión |
|---|---|---|
| Node.js | ws | Activada por defecto |
| Navegador | WebSocket nativo | Activada por defecto |
| Python | websockets | Activada por defecto |
| Python | websocket-client | No compatible, usa websockets |
| PHP | amphp/websocket-client | Activada por defecto (requiere zlib) |
| PHP | textalk/websocket | No compatible, usa amphp/websocket-client |
| Go | gorilla/websocket | Configura EnableCompression = true |
| .NET | ClientWebSocket | Configura DangerousDeflateOptions (.NET 6+) |
Tipos de mensaje
Cada mensaje es un objeto JSON con un type. Los mensajes de cuotas usan el mismo formato que el endpoint /v3/odds, así que un solo parser sirve para ambos.
created- Una casa de apuestas añadió un evento al que estás suscrito
updated- Las cuotas cambiaron. El mensaje lleva el conjunto actual completo de tus mercados para ese evento y esa casa
deleted- El evento se eliminó o su casa de apuestas dejó de estar disponible
no_markets- El evento sigue publicado, pero ninguno de tus mercados está abierto
score- El marcador en vivo cambió (canal scores)
status- El evento pasó a pendiente, en vivo, liquidado o cancelado, con el marcador actual (canal status)
clock- Una instantánea del reloj del partido aproximadamente cada 30 segundos para cada evento en vivo con el reloj en marcha
Reemplaza, nunca combines
Un mercado suspendido no aparece en el siguiente mensaje updated, en lugar de venir marcado. Sobrescribe con cada mensaje lo que tienes para ese evento y esa casa, y una cuota retirada desaparece de tu app por sí sola.
{
"type": "updated",
"seq": 482917,
"timestamp": 1723992773,
"id": "63017989",
"bookie": "Bet365",
"markets": [
{
"name": "ML",
"updatedAt": "2026-11-08T15:42:10Z",
"odds": [{ "home": "1.85", "draw": "3.25", "away": "2.10" }]
},
{
"name": "Totals",
"updatedAt": "2026-11-08T15:42:10Z",
"odds": [{ "hdp": 2.5, "over": "1.85", "under": "2.10" }]
}
]
}Canales y filtros
Elige los streams que quieres con channels y acótalos con parámetros de consulta para recibir solo los eventos que muestras. No puedes combinar leagues y eventIds.
Canales
odds- El canal por defecto.
created,updated,deletedyno_marketspara las casas de apuestas seleccionadas en tu cuenta scores- Mensajes
scoreen cada cambio de marcador, más instantáneasclock status- Mensajes
statuspara pendiente, en vivo, liquidado y cancelado, más instantáneasclock
Filtros
markets- Obligatorio para el canal odds. Hasta 40 nombres de mercado, como
ML,Spread,Totals sport- Hasta 10 deportes, como
football,basketball leagues- Hasta 20 slugs de liga, como
england-premier-league eventIds- Hasta 50 IDs de evento
statuslivesolo para en vivo,prematchsolo para prepartido
channels es una lista de permitidos. Envía channels=odds,scores,status para mantener las cuotas junto a los marcadores; channels=scores,status por sí solo deja de enviar las cuotas.
Reconecta sin huecos
Las redes se caen. Cada mensaje de cuotas lleva un número seq, y el feed lo usa para ponerte al día tras una reconexión en lugar de obligarte a empezar de cero.
- 1
Guarda el último seq
Almacena el
seqmás alto que hayas recibido. Los seq solo aumentan dentro de una conexión, pero se comparten entre todos los suscriptores, así que un salto no es un hueco. - 2
Reconecta con lastSeq
Añade
lastSeqa la URL de conexión. Recibes el estado más reciente de cada evento y casa de apuestas que cambió mientras no estabas, y después continúa el stream en vivo. - 3
Sabe cuándo estás en vivo
Añade
replayComplete=truepara recibir un mensajereplay_completecuando termine la puesta al día. Los datos de reenvío se conservan hasta 24 horas. - 4
Reconstruye si se te pide
Si no es posible un reenvío limpio, recibes
resync_required. Obtén una instantánea de/v3/oddsconincludeSeq=truey reconecta con el seq de la cabeceraX-OddsAPI-Seq.
Cada API key admite dos conexiones a la vez, por ejemplo una principal y otra de reserva. Hay más disponibles bajo petición.
Marcadores en vivo y reloj del partido
Las cuotas en vivo tienen más sentido junto al marcador. Suscríbete a los canales scores y status en la misma conexión, o consulta el estado actual por REST.
- GET /v3/events/live
- Todos los eventos en juego ahora mismo, con el marcador y un objeto
clock. Filtra por deporte - clock
minute,playedSeconds,period,runningystatusDetail, más el tiempo añadido en fútbol y quién saca en tenis- Marcadores por periodo
- Partes, cuartos, sets, prórroga y penaltis con un mismo conjunto de claves para todos los deportes
- Reloj en marcha
- Cuenta hacia delante desde
playedSecondsmientrasrunningsea true, y reajusta al valor del servidor en cada instantánea
REST como alternativa: /odds y /odds/updated
Todos los planes incluyen la API REST, que sirve las mismas cuotas en vivo. Úsala para instantáneas o haz polling donde un socket no encaje.
- GET /v3/odds
- Una instantánea completa de un evento en las casas de apuestas que elijas.
/v3/odds/multiadmite hasta 10 eventos en una petición - GET /v3/odds/updated
- Un delta real: solo los mercados que cambiaron desde
since, que puede tener como máximo 90 segundos de antigüedad. Pasa la cabecera de respuestaX-Next-Sincecomosinceen la siguiente llamada para continuar sin huecos - includeSeq=true
- Añade la posición actual del stream como
X-OddsAPI-Seq, para pasar de una instantánea REST al WebSocket sin perder ninguna actualización
Un delta no puede indicarte que un mercado se retiró, así que un cliente que hace polling debería volver a leer los eventos con /v3/odds de vez en cuando. El WebSocket envía deleted y no_markets en su lugar.
Qué cubre el feed de cuotas en vivo
- Casas de apuestas
- 450+, entre ellas Bet365, DraftKings, FanDuel, BetMGM, Betfair Exchange y casas regionales de Europa, Asia, Australia y Latinoamérica
- Deportes
- 34, entre ellos fútbol, baloncesto, tenis, fútbol americano, béisbol, hockey sobre hielo, críquet y esports
- En vivo
- Cuotas en vivo de todas las casas de apuestas que ofrecen apuestas en directo, actualizadas aproximadamente cada segundo
- Mercados
- Moneyline, hándicaps, totales, hándicap asiático, córners, tarjetas y props de jugadores, según la casa de apuestas
- Latencia
- <150ms para las actualizaciones enviadas por WebSocket
- Formato
- El mismo JSON que
/v3/odds, por WebSocket y REST
Planes y acceso a WebSocket
Todos los planes de pago incluyen la API REST con cuotas en vivo. El streaming por WebSocket es un complemento que puedes añadir a cualquier plan.
- El complemento cuesta otra vez el precio del plan, es decir, 2× el precio del plan en total, mensual o anual
- El stream incluye todas las casas de apuestas seleccionadas en tu cuenta, así que el plan decide cuántas casas recibes
- Marcadores, estado y reloj del partido llegan por la misma conexión
- Cancela cualquier mes
Preguntas frecuentes sobre la API de cuotas en vivo
¿Qué es una API de cuotas en vivo?
Una API de cuotas en vivo da a tu código las cuotas actuales de los eventos en juego, en JSON, y las mantiene al día a medida que las casas de apuestas mueven sus precios. Odds-API.io transmite cuotas en vivo y prepartido de 450+ casas de apuestas por WebSocket, y sirve las mismas cuotas por REST.
¿Con qué rapidez llegan las cuotas en vivo?
Las actualizaciones por WebSocket llegan con una latencia de <150ms, y las cuotas en vivo se actualizan aproximadamente cada segundo. Hacer polling por REST suma tu intervalo de consulta, por eso recomendamos WebSocket para apps en vivo.
¿Qué pasa si se cae mi conexión WebSocket?
Reconecta con el parámetro lastSeq igual al último seq que recibiste. El servidor reenvía el estado más reciente de todo lo que cambió mientras no estabas, para cortes de hasta 24 horas, y luego continúa en vivo. Si no es posible un reenvío limpio, envía resync_required y reconstruyes a partir de una instantánea REST.
¿Qué clientes WebSocket puedo usar?
Cualquier cliente compatible con la compresión permessage-deflate, que es obligatoria. ws de Node.js, WebSocket del navegador y websockets de Python la activan por defecto. websocket-client de Python y textalk/websocket de PHP no la admiten y se rechazan con HTTP 426. En Go, activa EnableCompression en gorilla/websocket.
¿El feed incluye marcadores en vivo y el reloj del partido?
Sí. Añade channels=odds,scores,status a la URL de conexión para recibir cambios de marcador, cambios de estado y una instantánea del reloj del partido aproximadamente cada 30 segundos junto a las cuotas. GET /v3/events/live devuelve los mismos marcadores y reloj por REST.
¿Cómo obtengo acceso a WebSocket?
Elige un plan de pago y añade el streaming por WebSocket. Cuesta otra vez el precio del plan y transmite todas las casas de apuestas seleccionadas en tu cuenta, con dos conexiones por API key. La API REST, incluidas las cuotas en vivo y /v3/odds/updated, forma parte de todos los planes.
Recibe cuotas en vivo hoy
Elige un plan, añade el streaming por WebSocket y abre una sola conexión para recibir cuotas en vivo de las 450+ casas de apuestas del feed.
Cancela cuando quieras · El mismo JSON por REST y WebSocket