Qué es la API de cuotas de Pinnacle
La API de cuotas de Pinnacle que documenta esta guía es un feed de terceros que sirve los precios en vivo y prepartido de Pinnacle como JSON por REST, envía caídas de precio por server-sent events y, como complemento de pago, retransmite frames WebSocket sin procesar. Es un producto de datos: lee precios, no hace apuestas ni toca una cuenta de Pinnacle.
No es la API de la propia Pinnacle. Pinnacle cerró el registro público en su API el 23 de julio de 2025 y ahora concede acceso caso por caso a socios comerciales y a algunos usos individuales y de investigación. La página alternativa a la API de Pinnacle en pnclFEED explica qué cambió y qué vía encaja con cada uso; esta guía cubre el feed.
Todo lo que sigue procede de la documentación publicada del feed, comprobada el 26 de septiembre de 2026. Donde la documentación se contradice, la guía lo dice. Nada de esto es un resultado medido salvo que la sección diga cómo se midió.
Autenticación y la primera solicitud
Toda solicitud lleva tu clave en el encabezado x-portal-apikey. Los endpoints de streaming también la aceptan como parámetro de consulta ?key= para clientes que no pueden fijar encabezados, pero una clave en la URL acaba en los registros, así que mantén la forma de encabezado en los servidores. La URL base viene de tu cuenta y de la documentación; los ejemplos de esta página la leen de una variable de entorno.
Al registrarse se emite una clave gratuita, sin tarjeta y sin caducidad, que sirve además como inicio de sesión de la cuenta. Permite 20 solicitudes por minuto y 100 al día en REST, suficiente para validar un parser, ejecutar todos los ejemplos de esta página y sondear un tablero cada quince minutos durante todo el día. Una demo completa de tres días, con SSE, WebSocket sin procesar y 10 solicitudes por segundo, está disponible bajo petición (comprobado el 26 de septiembre de 2026).
export ODDS_API_KEY="your key"
export ODDS_BASE_URL="https://..." # from your account
curl -s -H "x-portal-apikey: $ODDS_API_KEY" \
"$ODDS_BASE_URL/kit/v1/markets?sport_id=1" \
| jq '{last, events: (.events | length)}'Un 200 con un cursor last y un recuento de eventos distinto de cero significa que la clave funciona y el tablero de fútbol está activo. El quickstart de curl y jq en pnclFEED lo extiende a un listado de una línea por partido, y la página clave de la API de Pinnacle en pnclODDS cubre el registro y la primera solicitud paso a paso.
Endpoints
El feed tiene una superficie pequeña. Cuatro rutas REST, dos streams, una comprobación de salud y un WebSocket sin procesar opcional cubren todo lo que describe la documentación.
| Ruta | Propósito | Notas |
|---|---|---|
GET /kit/v1/markets?sport_id=N | El tablero en vivo de un deporte | Devuelve un objeto con events y un cursor last. Envía last de vuelta como since para recibir solo lo que cambió. |
GET /kit/v1/prematch/fixtures?sport_id=N | El tablero prepartido de un deporte | Fixtures con sus mercados. Una ruta de nombre parecido, más abajo, recibe un evento en lugar de un deporte. |
GET /kit/v1/prematch/markets?event_id=N | Los mercados de un evento prepartido | Confundirla con la ruta de fixtures hace que un feed válido parezca vacío o devuelve un error de parámetro. |
GET /api/drops?mode=live|prematch | Caídas recientes, bajo demanda | min_drop_pct fija el tamaño del movimiento y max_age_sec la ventana. Conserva aproximadamente las últimas tres horas. |
GET /odds-drop?min_drop=N | Alertas de caída en vivo, en stream | Server-sent events. Una conexión por clave y stream; una segunda conexión en vivo cierra la primera. |
GET /odds-drop-prematch?min_drop=N | Alertas de caída prepartido, en stream | Añade recheck=N para retener cada caída N segundos y enviarla solo si el precio no ha rebotado. |
| Endpoint de salud | Cuánto hace que el feed en vivo escribió por última vez | Indicado en la documentación; consúltalo junto a un stream silencioso para distinguir un mercado tranquilo de un feed roto. |
| WebSocket en bruto | Cada frame, sin procesar | Un complemento de pago en los tres planes REST. El estado del mercado lo reconstruyes tú. |
Los ids de deporte son propios del feed, del 1 al 13, y cada respuesta publica junto a ellos el id interno de Pinnacle. Fútbol es 1, tenis 2, baloncesto 3, hockey 4, fútbol americano 5, béisbol 6, rugby 7, MMA 8, boxeo 9, voleibol y balonmano 10, esports 11, golf 12 y críquet 13. Mapéalos una vez en el borde de tu código y nunca mezcles los dos espacios de nombres.
La forma de la respuesta
La respuesta de un tablero es un objeto, no una lista. events contiene un registro por partido y last es el cursor de la siguiente llamada. Cada evento lleva sus equipos, su hora de inicio, el id de deporte del feed y el de Pinnacle, y sus mercados agrupados bajo periods. periods.num_0 es el partido completo; las demás claves son mitades, cuartos, sets o mapas según el deporte.
{
"last": 141,
"events": [
{
"home": "Home FC",
"away": "Away FC",
"periods": {
"num_0": {
"money_line": { "home": 2.10, "draw": 3.40, "away": 3.60 },
"spreads": { "-0.5": { "home": 2.05, "away": 1.85 } },
"totals": { "2.5": { "over": 1.95, "under": 1.90 } },
"team_total": { "home": { "points": 1.5, "over": 2.00, "under": 1.85 } }
}
}
}
]
}Los precios son cuotas decimales. Money line, spreads, totales y totales por equipo tienen formas distintas, y un total del partido completo y un total de la primera mitad son mercados distintos aunque compartan una línea. La presencia varía por evento y período: un mercado puede estar ausente, vacío o suspendido, y tu modelo debe mantener esos tres estados separados. Nunca sustituyas un precio ausente por cero.
Los mercados especiales son una expansión opcional mediante include_specials. Llegan como filas extra enlazadas por parent_id y estructuras special_markets separadas, aumentan el payload, y no todos sus cambios de precio siguen el mismo comportamiento de alerta que los mercados principales. Mantén separados el parser de mercado completo y el parser de alertas de caída. La guía de normalización muestra las claves de evento, período, mercado, línea, selección y timestamp que hacen comparables los registros de este feed con cualquier otro.
Sondeo con el cursor since
El cursor es todo el truco para sondear este feed a bajo coste. La primera llamada de un deporte devuelve el tablero completo y un valor last; cada llamada siguiente envía ese valor como since y recibe solo los eventos que cambiaron. Guarda el cursor con tu snapshot para que un reinicio reanude en lugar de volver a descargar, y trata el tablero completo como la forma de resincronizar, no como la forma de sondear.
import os, time, requests
BASE = os.environ["ODDS_BASE_URL"]
HEADERS = {"x-portal-apikey": os.environ["ODDS_API_KEY"]}
def poll(sport_id=1, interval=900):
since = None
while True:
params = {"sport_id": sport_id}
if since is not None:
params["since"] = since
r = requests.get(f"{BASE}/kit/v1/markets", headers=HEADERS, params=params, timeout=30)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "5")))
continue
r.raise_for_status()
body = r.json()
since = body["last"]
for event in body["events"]:
line = event["periods"].get("num_0", {}).get("money_line")
if line:
print(event["home"], event["away"], line)
time.sleep(interval)
poll()Quince minutos entre llamadas mantienen un tablero dentro de las 100 solicitudes al día de la clave gratuita. En un nivel de pago, el intervalo es una decisión de presupuesto: la página de planificación de capacidad en pnclENGINE convierte deportes, fases y un intervalo en solicitudes por segundo y por día, y la calculadora de coste de la API de aquí convierte la misma programación en una cifra mensual. El quickstart de Python y el quickstart de Node.js en pnclFEED traen versiones más largas de este bucle.
Alertas de caída por SSE
Los streams envían las caídas de precio en el momento en que el feed las detecta. Un GET a /odds-drop para mercados en vivo o /odds-drop-prematch para los prepartido, con la clave y Accept: text/event-stream, devuelve un 200 que nunca termina. Fija min_drop tú mismo: el ejemplo documentado usa 5 y el mínimo es 1, y el valor por defecto está documentado de forma inconsistente, así que fiarse de él es un error.
Cada mensaje es una línea data: con JSON. La primera es un objeto de control, {"type": "connected"}; después, cada lote de alertas es un array. Un objeto más adelante en el stream es otro mensaje de control, como plan_lacks_sse cuando el plan de la clave no tiene stream o rate_limited cuando un cliente reconecta en bucle cerrado, que además lleva un encabezado Retry-After: 60.
data: {"type": "connected", "id": "d648beb8-..."}
data: [{"home": "Sunshine Coast Phoenix", "away": "Cairns Dolphins",
"league": "Australia - NBL1 Women", "sport": "Basketball",
"sect": "Moneyline", "outcome": "Home", "period": 4,
"from_price": 2.86, "to_price": 2.7, "nvp": 3.04,
"id": 1629729400, "alerted": 1777625196}]Cada alerta identifica la línea con sport, league, sect, outcome y period, lleva el precio antes y después como from_price y to_price, añade nvp, el precio justo sin margen tras el movimiento, y marca el alerta en segundos Unix como alerted. El porcentaje lo calculas tú. Los alertas perdidos no se reenvían: rellena un hueco desde /api/drops, cuyos registros usan nombres de campo distintos (from, to, drop_pct, market, side), así que comprueba cuál lee cada ejemplo.
// Node.js 18+: read the live drop stream and reconnect with backoff.
const BASE = process.env.ODDS_BASE_URL;
const KEY = process.env.ODDS_API_KEY;
async function listen(minDrop = 5) {
let delay = 1000;
for (;;) {
try {
const res = await fetch(`${BASE}/odds-drop?min_drop=${minDrop}`, {
headers: { "x-portal-apikey": KEY, accept: "text/event-stream" },
});
if (res.status === 429) {
await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") || 60) * 1000));
continue;
}
delay = 1000;
let buffer = "";
for await (const chunk of res.body) {
buffer += Buffer.from(chunk).toString("utf8");
let end;
while ((end = buffer.indexOf("\n\n")) >= 0) {
const frame = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
const line = frame.split("\n").find((l) => l.startsWith("data:"));
if (!line) continue;
const payload = JSON.parse(line.slice(5));
if (Array.isArray(payload)) payload.forEach(handle);
else if (payload.type !== "connected") console.warn("control", payload);
}
}
} catch (err) {
console.error("stream closed", err.message);
}
await new Promise((r) => setTimeout(r, delay + Math.random() * delay));
delay = Math.min(delay * 2, 60000);
}
}
function handle(alert) {
const pct = ((alert.from_price - alert.to_price) / alert.from_price) * 100;
console.log(alert.sport, alert.home, alert.away, alert.sect, alert.outcome, pct.toFixed(1) + "%");
}
listen();Los umbrales, los conjuntos de alertas vistos y las ventanas de deduplicación van junto al proceso lector, no en una pestaña del navegador. Las páginas de configuración SSE y de deduplicación y cooldowns en pnclPULSE cubren el lado de las reglas, y la página de alertas en Telegram en pnclDATA muestra el mismo stream entregado en un teléfono.
Límites de tasa, errores y backoff
Todo plan tiene un techo por segundo en REST, y la clave gratuita tiene además techos por minuto y por día. Superar un límite devuelve 429 Too Many Requests, normalmente con un encabezado Retry-After en segundos. Ese encabezado manda: espera exactamente ese tiempo y continúa. Un recuento creciente de 429 es el aviso temprano de que la carga estable se acerca al techo.
| Estado | Significado | Qué hacer |
|---|---|---|
| 401 | Falta el encabezado o la clave es incorrecta | Corrige la clave; no reintentes |
| 403 | La clave es válida, pero el plan no incluye lo que pediste | Revisa el plan, o el derecho al stream |
| 429 | Por encima de la cuota por segundo, por minuto o por día | Espera el Retry-After y luego reintenta con jitter |
| 5xx | El feed tuvo un problema | Reintenta unas pocas veces con esperas crecientes y luego da el ciclo por fallido |
Mantén el uso estable por debajo de aproximadamente el 70 por ciento de la cuota por segundo para que los reintentos, las herramientas administrativas y un segundo entorno quepan en el resto. Comparte el estado de backoff entre los workers; diez procesos retrocediendo educadamente siguen sumando diez veces las solicitudes. La página de límites de tasa y el runbook de errores en pnclENGINE van estado por estado.
Planes y precios
Precios publicados en dólares, facturación mensual, comprobados el 26 de septiembre de 2026. El checkout muestra las cifras que se aplican el día de la compra, e impuestos, comisiones y prorrateo no están publicados.
| Plan | Mensual | Asignación REST | Alertas SSE | WebSocket en bruto |
|---|---|---|---|---|
| Clave gratuita | $0 | 20 por minuto, 100 al día | No | No |
| Alertas de caída SSE | $99 | 20 por minuto, 100 por hora, 100 al día | Incluido | No elegible |
| Snapshots REST | $99 | 10 solicitudes por segundo | No | +99 $ por 30 días |
| REST + SSE | $149 | 10 solicitudes por segundo | Incluido | +99 $ por 30 días |
| REST de alto volumen | $229 | 30 solicitudes por segundo | Incluido | +99 $ por 30 días |
Existen períodos prepagados: tres meses por 249 $ en los dos planes de 99 $, 379 $ en REST + SSE y 599 $ en REST de alto volumen, y seis meses por 449 $ y 679 $ solo en los dos planes REST. El complemento WebSocket sin procesar se cotiza en bloques de 30, 90 y 180 días a 99 $, 297 $ y 594 $. Un equivalente mensual es una cifra de comparación, no una factura mensual. La página precios de la API de Pinnacle en pnclODDS tiene el estimador y las decisiones de ajuste de plan, y el quickstart de precios en pnclFEED lee las mismas cifras desde el lado del desarrollador.
Cobertura
La documentación enumera fútbol, tenis, baloncesto, hockey, fútbol americano, béisbol, rugby, MMA, boxeo, voleibol y balonmano, esports, golf y críquet. En vivo y prepartido están ambos documentados, y la disponibilidad de eventos varía por deporte y por día. Los mercados REST son money line, spreads, totales y totales por equipo bajo períodos. Un deporte listado no garantiza todas las competiciones ni mercados, así que ejecuta una muestra representativa con la clave gratuita mientras los eventos que te importan estén en el tablero.
La cadencia de actualización se indica de forma inconsistente: la página de inicio habla de cinco segundos para el prepartido y la referencia describe treinta. Ninguna es una latencia medida de extremo a extremo. No hay archivo histórico; el feed es un servicio de datos actuales, y registrar el histórico es una tarea operativa tuya, con sus propios derechos de almacenamiento. La página de cobertura en pnclODDS mantiene la tabla de evidencias, y la página API de cuotas de esports en pnclHUB muestra un deporte documentado en la práctica.
Medir la latencia y la frescura por tu cuenta
Esta guía no publica ninguna cifra de latencia porque ninguna se ha medido bajo un protocolo declarado. La forma honesta de obtener una es medirla: registra el timestamp del propio feed junto a tu hora de recepción para cada alerta o actualización durante al menos una semana, alinea antes los relojes, compara mercados en lugar de eventos e informa de la distribución, no de una media.
El protocolo de benchmark define las cargas de trabajo, la alineación de relojes y el formato del informe, y la página de investigación enumera los relojes que necesita cada resultado. Cuando se publique una medición en esta red, aparecerá con ese método y su fecha.
Referencia de campos
Los campos que un parser encuentra con más frecuencia, por endpoint, con los nombres que usa la documentación. Los tipos son los que muestran los ejemplos documentados; valídalos en lugar de darlos por supuestos.
| Campo | Dónde | Significado |
|---|---|---|
events | Respuestas de tablero | Array de registros de eventos del deporte solicitado. |
last | Respuestas de tablero | Cursor que se devuelve como since en la siguiente llamada. |
home, away | Evento, alerta, caída | Nombres de equipos o jugadores tal como los imprime el feed. |
periods | Evento | Objeto con claves num_0, num_1 y así sucesivamente; cada período contiene sus propios mercados. |
money_line | Periodo | home, draw (cuando el deporte tiene empate) y away como cuotas decimales. |
spreads | Periodo | Indexado por la línea de hándicap; cada línea contiene los precios home y away. |
totals | Periodo | Indexado por la línea de total; cada línea contiene over y under. |
team_total | Periodo | Totales por equipo con una línea points y precios over y under. |
parent_id, special_markets | Evento, con include_specials | Enlaza una fila de mercado especial con el evento al que pertenece. |
type | Mensaje de control SSE | connected al abrir; después, nombres de error como plan_lacks_sse o rate_limited. |
sport, league, sect, outcome, period | Alerta SSE | La línea que se movió: deporte, competición, sección de mercado, lado y número de período. |
from_price, to_price, nvp | Alerta SSE | Precio antes, precio después y el precio justo sin margen tras el movimiento. |
id, alerted | Alerta SSE | Id del evento y hora del alerta en segundos Unix; junto con los campos de la línea forman una clave de deduplicación. |
market, side, from, to, drop_pct, nvp | REST /api/drops | El mismo movimiento como registro REST, con el porcentaje ya calculado. |
Retry-After | Respuestas 429, frames rate_limited | Segundos que hay que esperar antes de la siguiente solicitud o reconexión. |
Leer una caída por REST y por SSE
La misma detección alimenta el stream y el endpoint de caídas, pero los dos registros tienen formas distintas. Un parser escrito para uno leerá mal el otro en silencio, así que mapea los campos de forma explícita.
| Qué quieres | Alerta SSE | Caída REST |
|---|---|---|
| La línea | sport, league, sect, outcome, period | sport_name, market, side |
| Precio antes y después | from_price, to_price | from, to |
| Tamaño del movimiento | Calcula: (from_price menos to_price) dividido por from_price | drop_pct |
| Precio justo tras el movimiento | nvp | nvp |
| Cuándo | alerted, segundos Unix | Un timestamp en cada registro |
| Filtrado | min_drop en la conexión, recheck en prepartido | mode, min_drop_pct, max_age_sec en la solicitud |
| Reenvío | Ninguno; los alertas perdidos se pierden | Aproximadamente las últimas tres horas |
Glosario
- Tablero
- Todos los eventos que el feed lista en este momento para un deporte en una fase, en vivo o prepartido, con sus mercados.
- Cursor
- El valor
lastque lleva una respuesta de tablero; devuelto comosince, limita la siguiente respuesta a lo que cambió. - Caída
- Un precio que cae al menos el umbral que fijaste, medido desde el precio anterior que vio el feed hasta el actual.
- Precio sin margen (nvp)
- El precio justo implícito en un mercado una vez retirado el margen de la casa, de modo que las probabilidades de todos los resultados sumen uno.
- Periodo
- La parte del partido sobre la que se liquida un mercado:
num_0para el partido completo, luego mitades, cuartos, sets o mapas según el deporte. - Fase
- Prepartido, antes de que empiece el evento, o en vivo, mientras se disputa. El feed sirve ambos por rutas y streams distintos.
- Snapshot
- Una respuesta de tablero guardada en tu propio almacén, el estado contra el que reconcilias y que actualizas desde el stream.
Elegir un transporte
El sondeo REST es el valor por defecto correcto para snapshots y reconciliación: es barato con el cursor, se recupera solo y no necesita una conexión de larga duración. Los streams SSE son lo correcto cuando tu aplicación reacciona a caídas y a nada más, porque el feed ya ha hecho la detección. Los frames WebSocket sin procesar solo son lo correcto cuando necesitas cada movimiento de precio, incluidos los demasiado pequeños para alertar, y estás preparado para reconstruir el estado del mercado y recuperar huecos por tu cuenta.
La mayoría de las configuraciones en producción usan dos: REST para el arranque y la reconciliación periódica, un stream para la ruta caliente. La guía de transportes sopesa recuperación, frescura, estado y coste operativo, y la página stream SSE frente a endpoint REST en pnclPULSE compara las dos formas de leer caídas.
Preguntas frecuentes
- ¿Es esta la API oficial de Pinnacle?
- No. Pinnacle cerró el registro público en su propia API el 23 de julio de 2025 y concede acceso previa solicitud. El feed documentado aquí sirve los precios de Pinnacle a cualquiera con una clave, y solo lee precios.
- ¿Existe una API de cuotas de Pinnacle gratuita?
- Sí. La clave gratuita permite 20 solicitudes por minuto y 100 al día en REST, sin tarjeta y sin caducidad. Los streams y las tasas más altas necesitan un plan de pago o la demo de tres días.
- ¿La API tiene cuotas históricas?
- No. La documentación describe un servicio de datos actuales, sin archivo histórico. Registra lo que necesites desde el día en que empieces y comprueba los derechos de almacenamiento que se aplican a tu uso.
- ¿Con qué frecuencia se actualizan los precios?
- Las páginas del proveedor discrepan: cinco segundos en la página de inicio, treinta segundos en la referencia, para el prepartido. Trata ambos como cadencia documentada, no latencia medida, y mide tu propio camino si importa.
- ¿Puedo realizar apuestas a través de él?
- No. Es un feed de datos. Apostar a través de una API significa la API de apuestas de la propia Pinnacle, disponible solo para usuarios aprobados.
- ¿Qué plan necesito para las alertas de caída?
- El plan de alertas de caída SSE de 99 $, o REST + SSE a 149 $ y REST de alto volumen a 229 $, que incluyen los streams. El plan de snapshots REST de 99 $ no tiene stream.
- ¿Qué pasa si supero el límite de tasa?
- La API responde 429 con un encabezado Retry-After. Espera esos segundos y continúa; un bucle de reintentos cerrado convierte un límite corto en una restricción más larga, y en los streams produce un mensaje de control rate_limited.
Dónde se profundiza cada tema
Esta guía es la referencia; siete sitios del mismo editor llevan más lejos, cada uno, una parte del feed.
- pnclFEED: quickstarts en curl, Python, Node.js, Google Sheets y WebSocket sin procesar, todos ejecutables con la clave gratuita.
- pnclENGINE: planificación de capacidad REST, el cursor since, límites de tasa, manejo de errores, almacenamiento de histórico y monitorización de pipeline.
- pnclPULSE: alertas de caída por SSE, de umbrales y ventanas a deduplicación, entrega y reconexiones.
- pnclODDS: planes, precios, acceso, cobertura y el complemento WebSocket, comparados al completo.
- pnclDATA: alertas de cuotas a la baja con precios justos, y calculadoras de EV, cuotas a la baja, CLV y Kelly.
- pnclHUB: una mesa de partidos de esports en vivo para CS2, League of Legends y Dota 2, con guías de mercado.
- Herramientas de pnclAPI: el conversor de cuotas, la calculadora sin margen y la calculadora de coste de la API, aquí en este sitio.
Las correcciones de cualquier dato de esta página siguen la política editorial: la página cambia, y también su fecha de actualización.