API REST
Los endpoints para el feed emparejado, la vista de arb en vivo, el detalle de pares, y las rutas de catálogo y feedback. Cada solicitud lleva tu clave de API como token bearer.
La API REST sirve el feed emparejado entre plataformas, la vista de arb sobre él, y el detalle de pares. Funciona en todos los planes, incluido Free. La URL base es https://api.dino.markets.
Autenticación
Cada solicitud lleva tu clave de API como token bearer. Crea una clave en el panel de control; se muestra una sola vez.
Authorization: Bearer sk_live_...
Límites de tasa
Las solicitudes están limitadas por plan, por minuto: Free 60, Basic 300, Pro 1200. Al superar el límite, una solicitud devuelve 429. El stream no se mide de esta forma; en su lugar está acotado por el límite de conexiones.
GET /v2/markets
La lista canónica de mercados. Sin filtrar, este es el catálogo de identidad: una fila por partido emparejado, con los tickers de plataforma y las banderas de cobertura, y los precios en null. Añade signal=arb para obtener las mismas filas con precio de la instantánea de oportunidades en vivo, con solo los arbs confirmados devueltos. Añade signal=candidates para las filas en banda de revisión, con precio del historial del canal de candidatos.
| Param | En | Tipo | Significado |
|---|---|---|---|
sport | query | string | Limita a un deporte amplio, como baseball. Omítelo para todos los deportes. |
league | query | string | Acota dentro de un deporte a una liga, sin distinguir mayúsculas, como MLB. Se ignora cuando signal=arb. |
status | query | string | Separados por comas: open, live, closed, settled. Por defecto open,live. |
signal | query | string | spread (vista de catálogo por defecto), arb (con precio, solo filas de arb confirmadas), o candidates (filas en banda de revisión). |
sort | query | string | start_time (por defecto) o -spread_pts para poner primero las brechas más amplias entre plataformas. |
include | query | string | Bloques opcionales separados por comas: ids, depth, settlement. Ver Includes. |
limit | query | integer | 1 a 1000. Por defecto 500. |
Las claves de deporte válidas son baseball, basketball, football, hockey, soccer, tennis, cricket, lol, dota2, valorant, cs2, r6, ow, y mma. Cada fila también tiene un campo league, que puede ser null cuando la serie de la plataforma no nombra ninguna. Llama a /v2/leagues para ver qué deportes y ligas están en temporada.
curl -s "https://api.dino.markets/v2/markets?sport=baseball&league=MLB" \
-H "Authorization: Bearer sk_live_..."
{
"count": 1,
"sport": "baseball",
"has_more": false,
"markets": [
{
"id": "dino_8f3a1c92-47e8-4c3f-b9d2-f1a8e6c4d5f2",
"slug": null,
"sport": "baseball",
"league": "MLB",
"title": "Nyy vs Bos",
"home": null,
"away": null,
"start_time": "2026-07-01T23:05:00+00:00",
"status": "open",
"match": "high_confidence",
"signal": "spread",
"spread_pts": null,
"potential_arb_pct": null,
"coverage": { "kalshi": false, "polymarket": false },
"outcomes": [],
"result": null,
"updated_at": "2026-07-01T22:55:00+00:00",
"priced_at": "2026-07-01T22:55:00+00:00",
"links": { "kalshi": "https://kalshi.com/markets/KXMLBGAME-26JUL01NYYBOS", "polymarket": null }
}
]
}
La vista sin filtrar no lleva precios. outcomes está vacío, signal es siempre spread, y coverage marca false en ambas plataformas. Eso es lo esperado: este es el catálogo de identidad. priced_at en esta vista es el updated_at de la fila (la última vez que el par mismo cambió), no un tick de precio en vivo — todavía no existe una marca de tiempo de precio separada almacenada por fila. Para precios en vivo sobre las mismas filas, pide signal=arb (solo arbs confirmados) o añade include=depth en un solo mercado sobre /v2/pairs/{market_id}.
La vista de arb: GET /v2/markets?signal=arb
El mismo endpoint, con precio de la instantánea de oportunidades en vivo y filtrado a las filas que el motor de coincidencia ha confirmado como arb: paridad de resolución confirmada, ningún riesgo de resolución bloqueante, y ambas plataformas con precio en cada resultado. Esto reemplaza el antiguo feed de oportunidades dedicado. No hay un endpoint de arb separado en v2; es una vista sobre el catálogo de mercados. La instantánea que la respalda es de mejor esfuerzo y va unos dos minutos por detrás en todos los planes.
| Param | En | Tipo | Significado |
|---|---|---|---|
sport | query | string | Limita a un deporte amplio, como baseball. |
include | query | string | Bloques opcionales separados por comas: ids, depth, settlement. |
limit | query | integer | 1 a 1000. Por defecto 500. |
Los parámetros de catálogo de arriba también aplican a esta vista: status, sort, e include funcionan igual (el filtro league es la única excepción, ignorado aquí).
curl -s "https://api.dino.markets/v2/markets?signal=arb&sport=baseball" \
-H "Authorization: Bearer sk_live_..."
{
"count": 1,
"sport": "baseball",
"has_more": false,
"markets": [
{
"id": "dino_8f3a1c92-47e8-4c3f-b9d2-f1a8e6c4d5f2",
"slug": null,
"sport": "baseball",
"league": "MLB",
"title": "Nyy vs Bos",
"home": null,
"away": null,
"start_time": "2026-07-01T23:05:00+00:00",
"status": "open",
"match": "high_confidence",
"signal": "arb",
"spread_pts": 2.0,
"potential_arb_pct": 1.83,
"coverage": { "kalshi": true, "polymarket": true },
"outcomes": [
{ "key": "NYY", "name": "NYY", "kalshi": 0.52, "polymarket": 0.54, "cheaper": "kalshi" },
{ "key": "BOS", "name": "BOS", "kalshi": 0.50, "polymarket": 0.46, "cheaper": "polymarket" }
],
"result": null,
"updated_at": "2026-07-01T22:55:00+00:00",
"priced_at": "2026-07-01T22:55:00+00:00",
"links": { "kalshi": "https://kalshi.com/markets/KXMLBGAME-26JUL01NYYBOS", "polymarket": null }
}
]
}
Un array markets vacío con count: 0 es el estado de reposo normal entre arbs. potential_arb_pct es el retorno bruto ask-to-ask, antes de las comisiones de la plataforma. Es una observación de la instantánea, de mejor esfuerzo y potencialmente desactualizada. outcomes[].kalshi y .polymarket son el precio ask en cada plataforma, de 0 a 1, y cheaper nombra la plataforma con el ask más bajo para ese resultado. En REST, priced_at refleja updated_at (la última escritura del par) en lugar del instante real de la instantánea; el feed de WebSocket lleva la marca de tiempo real de lectura de libros en cada tick de precio.
Candidatos: GET /v2/markets?signal=candidates
Filas en banda de revisión: coincidencias que el motor de coincidencia ha hecho pero aún no ha confirmado. Con precio de mejor esfuerzo del mismo historial de canal que alimenta el canal de WebSocket candidates:{sport} (Pro). Un mercado en estado de revisión siempre resuelve a spread, sin importar qué precios tenga adjuntos; esta vista nunca devuelve una fila con signal: "arb".
| Param | En | Tipo | Significado |
|---|---|---|---|
sport | query | string | Limita a un deporte amplio. |
league | query | string | Acota dentro de un deporte a una liga. |
include | query | string | Bloques opcionales separados por comas: ids, depth, settlement. |
limit | query | integer | 1 a 1000. Por defecto 500. |
curl -s "https://api.dino.markets/v2/markets?signal=candidates&sport=baseball" \
-H "Authorization: Bearer sk_live_..."
Los parámetros de catálogo de arriba también aplican a esta vista: status, sort, e include funcionan igual.
Las filas vuelven en la misma forma canónica que la vista de catálogo. Donde el candidato tiene una coincidencia con precio, outcomes y coverage se completan de la misma forma que en la vista de arb; donde el precio aún no está disponible, los precios quedan en null.
Includes
include añade bloques opcionales a cada objeto de mercado. Los bloques ausentes significan que la clave falta por completo, no que sea null. Pide lo que necesites.
| Valor | Añade | Significado |
|---|---|---|
ids | venues, confidence | Identificadores de plataforma (kalshi.event_ticker, polymarket.condition_id) y la confianza de entidad/resolución del motor de coincidencia, de 0 a 1. |
depth | outcomes[].kalshi_book, outcomes[].polymarket_book | Detalle de libro por resultado (ask, profundidad, ticker/id de token) junto al precio ask escalar. Un campo hermano; nunca reemplaza a outcomes[].kalshi ni a .polymarket. |
settlement | settlement | parity (bool), risks[], texto de la plataforma, y verdict una vez resuelto. |
GET /v2/pairs/{market_id}
Un objeto de mercado canónico por id, con include=ids,depth,settlement aplicado por defecto. Esta es la vista de detalle. Acepta dino_<uuid> (el campo id de /v2/markets) o un UUID sin adornos.
| Param | En | Tipo | Significado |
|---|---|---|---|
market_id | path | string | El id de mercado de /v2/markets, ya sea dino_<uuid> o el UUID sin adornos. |
Ejemplo: arb de MLB (paridad de resolución confirmada)
curl -s "https://api.dino.markets/v2/pairs/dino_8f3a1c92-47e8-4c3f-b9d2-f1a8e6c4d5f2" \
-H "Authorization: Bearer sk_live_..."
{
"id": "dino_8f3a1c92-47e8-4c3f-b9d2-f1a8e6c4d5f2",
"slug": null,
"sport": "baseball",
"league": "MLB",
"title": "Nyy vs Bos",
"home": null,
"away": null,
"start_time": "2026-07-01T23:05:00+00:00",
"status": "open",
"match": "high_confidence",
"signal": "arb",
"spread_pts": 2.0,
"potential_arb_pct": 1.83,
"coverage": { "kalshi": true, "polymarket": true },
"outcomes": [
{ "key": "NYY", "name": "NYY", "kalshi": 0.52, "polymarket": 0.54, "cheaper": "kalshi",
"kalshi_book": { "ask": 0.52, "bid": null, "depth": 3100, "ticker": "KXMLBGAME-26JUL01NYYBOS-NYY" },
"polymarket_book": { "ask": 0.54, "bid": null, "depth": 2900, "token_id": "0x1a2b..." } },
{ "key": "BOS", "name": "BOS", "kalshi": 0.50, "polymarket": 0.46, "cheaper": "polymarket",
"kalshi_book": { "ask": 0.50, "bid": null, "depth": 2400, "ticker": "KXMLBGAME-26JUL01NYYBOS-BOS" },
"polymarket_book": { "ask": 0.46, "bid": null, "depth": 3300, "token_id": "0x9f8e..." } }
],
"result": null,
"updated_at": "2026-07-01T22:55:00+00:00",
"priced_at": "2026-07-01T22:55:00+00:00",
"venues": {
"kalshi": { "event_ticker": "KXMLBGAME-26JUL01NYYBOS" },
"polymarket": { "condition_id": "0x1a2b...", "slug": null }
},
"confidence": { "entity": 1.0, "resolution": 1.0 },
"settlement": { "parity": true, "risks": [], "kalshi_text": null, "poly_text": null, "verdict": null },
"links": { "kalshi": "https://kalshi.com/markets/KXMLBGAME-26JUL01NYYBOS", "polymarket": null }
}
Ejemplo: spread de KBO (riesgo de resolución entre plataformas)
curl -s "https://api.dino.markets/v2/pairs/dino_5c2b9f7e-d4a1-4e6c-8b3f-a9c2e5f1d8a4" \
-H "Authorization: Bearer sk_live_..."
{
"id": "dino_5c2b9f7e-d4a1-4e6c-8b3f-a9c2e5f1d8a4",
"sport": "baseball",
"league": "KBO",
"title": "Kia vs Kiw",
"start_time": "2026-07-15T10:00:00+00:00",
"status": "open",
"signal": "spread",
"spread_pts": null,
"potential_arb_pct": null,
"coverage": { "kalshi": false, "polymarket": false },
"outcomes": [],
"settlement": {
"parity": false,
"risks": [
{
"event": "tie",
"severity": "high",
"note": "This league can tie, but one venue's market does not carry an explicit tie->50/50 clause. A held two-leg position may not return $1 on a tie. Verify before trading."
}
],
"kalshi_text": null,
"poly_text": null,
"verdict": null
},
"links": { "kalshi": "https://kalshi.com/markets/KXMLBGAME-26JUL15KIWKIA", "polymarket": null }
}
Un riesgo de severidad high o critical contra el par fuerza signal a spread, incluso si los precios coinciden; esa comprobación es una de las cuatro puertas detrás de signal y se ejecuta sobre el conjunto completo de riesgo interno del par. Los risks[] servidos nunca incluyen eventos de cancelación (cancel_*), que se filtran del lado del servidor. Lee settlement.risks antes de actuar sobre cualquier fila, arb o spread. Si el id no resuelve, la respuesta es 404 con { "error": "pair not found" }.
GET /v2/leagues
Los deportes y ligas en temporada ahora mismo, derivados del feed activo, con conteos de ciclo de vida por deporte.
El array leagues lista las ligas no nulas vistas para ese deporte; count es cada partido emparejado en ella, incluidas las filas cuya liga es null. open refleja count hoy; live y settled son siempre 0, ya que los conteos de reloj de juego y de filas retractadas aún no están conectados a este endpoint.
curl -s "https://api.dino.markets/v2/leagues" \
-H "Authorization: Bearer sk_live_..."
{
"sports": [
{ "sport": "baseball", "leagues": ["KBO", "MLB", "NPB"], "count": 54, "open": 54, "live": 0, "settled": 0 },
{ "sport": "soccer", "leagues": ["World Cup"], "count": 15, "open": 15, "live": 0, "settled": 0 },
{ "sport": "tennis", "leagues": ["ITFW"], "count": 165, "open": 165, "live": 0, "settled": 0 }
]
}
GET /v2/coverage
El censo de cobertura por plataforma: cada liga enfrentada listada en Kalshi, conectada o no al feed emparejado, con su estado de agregación. El motor de coincidencia lo reconstruye en cada pasada, aproximadamente cada hora. Valores de estado: aggregated (emparejado en vivo), pending (conectado, esperando una contraparte o sus pasadas de confirmación), quiet (conectado, nada abierto), kalshi_only (listado ahí, sin emparejamiento conectado), dormant (la serie existe, nada abierto). poly_unpaired lleva el excedente de Polymarket a nivel de deporte: fixtures analizados para los que la pasada no encontró par de Kalshi.
curl -s "https://api.dino.markets/v2/coverage" \
-H "Authorization: Bearer sk_live_..."
{
"as_of": "2026-07-04T03:30:21+00:00",
"counts": { "aggregated": 20, "pending": 7, "quiet": 43, "kalshi_only": 13, "dormant": 139 },
"series": [
{ "series_ticker": "KXFIBAGAME", "league": "FIBA", "sport": "basketball", "wired": true,
"status": "aggregated", "kalshi_open_events": 49, "paired": 46, "sport_poly_fixtures": 88,
"last_pass_at": "2026-07-04T03:30:21+00:00" }
],
"poly_unpaired": [ { "sport": "soccer", "fixtures": 258, "paired": 7, "unpaired": 251 } ]
}
El censo es solo por API; los mismos datos respaldan la vista interna de cobertura.
POST /v1/stream/token
Emite un ticket de corta duración para el stream en tiempo real. Este endpoint no se movió a /v2; se mantiene en /v1/stream/token. Requiere Basic o Pro; una clave Free recibe un 402. El flujo completo de conexión está en la página del stream de WebSocket.
{
"ticket": "eyJhbGciOi...",
"ws_url": "wss://stream.dino.markets/connection/websocket",
"expires_in": 120,
"allowed": { "channels": ["markets:all", "status"], "max_conns": 3 }
}
POST /v2/report-bad-arb
Marca una oportunidad que parece incorrecta. Registra un reporte para nuestra cola de revisión. Incluye al menos uno de los campos de abajo. Para opp_id, pasa el valor id del mercado de /v2/markets.
| Campo | Tipo | Significado |
|---|---|---|
opp_id | string | El valor id del mercado (dino_<uuid>). |
reason | string | Una razón o nota breve. |
sport | string | El deporte, cuando no hay opp_id. |
market | string | El mercado o partido. |
detail | string | Cualquier detalle adicional. |
curl -s -X POST "https://api.dino.markets/v2/report-bad-arb" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "opp_id": "dino_8f3a1c92-47e8-4c3f-b9d2-f1a8e6c4d5f2", "reason": "legs do not settle the same way" }'
Un reporte exitoso devuelve 202 con { "status": "received" }. Envía al menos uno de opp_id, reason, sport, market, detail; un cuerpo vacío devuelve 400.