Skip to content

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.

ParamEnTipoSignificado
sportquerystringLimita a un deporte amplio, como baseball. Omítelo para todos los deportes.
leaguequerystringAcota dentro de un deporte a una liga, sin distinguir mayúsculas, como MLB. Se ignora cuando signal=arb.
statusquerystringSeparados por comas: open, live, closed, settled. Por defecto open,live.
signalquerystringspread (vista de catálogo por defecto), arb (con precio, solo filas de arb confirmadas), o candidates (filas en banda de revisión).
sortquerystringstart_time (por defecto) o -spread_pts para poner primero las brechas más amplias entre plataformas.
includequerystringBloques opcionales separados por comas: ids, depth, settlement. Ver Includes.
limitqueryinteger1 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.

ParamEnTipoSignificado
sportquerystringLimita a un deporte amplio, como baseball.
includequerystringBloques opcionales separados por comas: ids, depth, settlement.
limitqueryinteger1 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".

ParamEnTipoSignificado
sportquerystringLimita a un deporte amplio.
leaguequerystringAcota dentro de un deporte a una liga.
includequerystringBloques opcionales separados por comas: ids, depth, settlement.
limitqueryinteger1 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.

ValorAñadeSignificado
idsvenues, confidenceIdentificadores de plataforma (kalshi.event_ticker, polymarket.condition_id) y la confianza de entidad/resolución del motor de coincidencia, de 0 a 1.
depthoutcomes[].kalshi_book, outcomes[].polymarket_bookDetalle 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.
settlementsettlementparity (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.

ParamEnTipoSignificado
market_idpathstringEl 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.

CampoTipoSignificado
opp_idstringEl valor id del mercado (dino_<uuid>).
reasonstringUna razón o nota breve.
sportstringEl deporte, cuando no hay opp_id.
marketstringEl mercado o partido.
detailstringCualquier 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.