Betamigos / Desarrolladores

Referencia de la API

API de Betamigos

Registra tus apuestas en Betamigos desde tu propio sistema: un bot, un script de hoja de cálculo, una herramienta interna. Cada apuesta que envías cae en el mismo lugar que una escrita en la app, así que CLV, P&L, exposición y el extracto de la cartera siguen funcionando igual.

Es una API pequeña a propósito. Cubre el ciclo que importa (registrar, corregir, liquidar, reconciliar) y nada más.

Consigue un token

Abre Perfil > Acceso por API en la app y crea un token. El secreto se muestra una sola vez, al crearlo, porque solo guardamos su hash. Si lo pierdes, revoca ese token y crea otro.

Envíalo en cada petición, en la cabecera X-Service-Token:

X-Service-Token: bmst_xxxxxxxxxxxxxxxxxxxxxxxx

El token actúa como tú: lee y escribe solo tus datos, y nunca ve los de otra cuenta. Revocar surte efecto al instante.

URL base

https://betamigos.io

Cada ruta de aquí abajo ya empieza con /api, así que la URL completa del primer endpoint es https://betamigos.io/api/bets. Todos los cuerpos son JSON. Todas las fechas son ISO 8601 en UTC.

Registrar una apuesta

POST /api/bets

Hay dos formas de registrarla, y la diferencia no es de formato: es cuánto trabajo te queda después.

Apuesta suelta. Describes la apuesta en texto y ya está. Sirve para cualquier cosa, incluso mercados que ni siquiera seguimos. El precio es que nadie la liquida por ti: cuando termine el partido llamas al endpoint de liquidación con el resultado, y el CLV solo existe si mandas la cuota de cierre junto.

Apuesta ligada al mercado. Incluyes el selection_id de la selección real. A partir de ahí la apuesta se liquida sola cuando acaba el partido, y nosotros capturamos la línea de cierre, lo que te da CLV sin hacer nada. Cómo encontrar ese id está en la sección siguiente.

La recomendación es obvia: siempre que la apuesta exista en nuestro catálogo, manda el selection_id. Cuando no exista, la apuesta suelta es el camino, y no se pierde nada salvo la automatización.

# Suelta: solo etiquetas. La liquidas tú después.
curl -X POST https://betamigos.io/api/bets \
  -H "X-Service-Token: $BETAMIGOS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "stake": 25,
    "odds": 2.10,
    "sport": "Soccer",
    "league": "Premier League",
    "event_label": "Arsenal vs Chelsea",
    "market": "Match winner",
    "selection": "Arsenal",
    "starts_at": "2026-08-09T19:00:00Z",
    "external_source": "mi-bot",
    "external_ref": "trade-8814"
  }'
# Ligada al mercado: se liquida y calcula el CLV sola.
curl -X POST https://betamigos.io/api/bets \
  -H "X-Service-Token: $BETAMIGOS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": 3,
    "stake": 25,
    "odds": 2.10,
    "blv_odds": 1.96,
    "blv_devig": "conservative",
    "root_event_id": 45012,
    "selection_id": 918273,
    "starts_at": "2026-08-09T19:00:00Z",
    "sport": "Soccer",
    "league": "Premier League",
    "event_label": "Arsenal vs Chelsea",
    "market": "Asian Handicap -0.5",
    "selection": "Arsenal",
    "tag_names": ["bot", "valuebet"],
    "external_source": "mi-bot",
    "external_ref": "trade-8814"
  }'

Devuelve 201 con la apuesta creada. Nace pending.

Solo stake y odds son obligatorios. El resto enriquece los números:

CampoPor qué enviarlo
account_idCartera de la apuesta. Si lo omites, va a tu cartera favorita.
selection_id, root_event_idAtan la apuesta al mercado real. Es lo que habilita la liquidación automática y la captura de la línea de cierre.
sport, league, event_label, market, selectionLas etiquetas que vas a leer después en el dashboard. Envíalas incluso con selection_id: son las que aparecen en la tabla.
starts_atHora de inicio. Define cuándo se considera cerrada la línea.
oddsLa cuota que realmente conseguiste. En exchange, envía la neta, ya con la comisión.
gross_odds, commission_type, commission_pctLa comisión que pagaste, si prefieres que hagamos la cuenta. Los tres juntos o ninguno: con ellos el servidor calcula la cuota neta a partir de la bruta e ignora el odds que enviaste. commission_type es on_profit, on_stake o on_return.
blv_oddsLa cuota justa (sin vig) en el momento de apostar, si la calculas. Alimenta la métrica de valor.
originalert, explorer o manual. Marca de dónde vino la apuesta.
tag_namesAdjunta tus etiquetas por nombre, sin necesitar sus ids. Un nombre inexistente se ignora.
external_source, external_refTus identificadores. Mira la idempotencia abajo.

Idempotencia

Envía siempre external_source y external_ref. Repetir el POST del mismo par devuelve la apuesta que ya existe, con estado 200 en vez de 201, y no crea nada nuevo.

Pesa más de lo que parece: sin ese par, un reintento tras un timeout registra la apuesta dos veces y asienta el dinero dos veces en el extracto de la cartera. Con él, reintentar sale gratis.

Chequeo de consistencia

Si envías root_event_id junto con starts_at, comparamos la hora declarada con la real. Una diferencia mayor a 6 horas se rechaza con 400, porque indica que la apuesta quedó ligada al partido equivocado (el caso clásico son dos partidos entre los mismos equipos, con días de diferencia). Si la base de cuotas está caída en ese instante, dejamos pasar en vez de bloquearte.

Encontrar el evento y la selección

Dos pasos: busca el partido y ábrelo. El primero te da el id del evento, el segundo los selection_id que hay dentro.

1. Buscar el partido

GET /api/events/upcoming lista lo que aún no ha empezado, lo más próximo primero. Filtros: q (texto libre, casa con los nombres de los equipos), sport, league_id, date_from, date_to, limit (hasta 200) y offset.

curl "https://betamigos.io/api/events/upcoming?q=arsenal&sport=Soccer&limit=10" \
  -H "X-Service-Token: $BETAMIGOS_TOKEN"
{
  "total": 2,
  "limit": 10,
  "offset": 0,
  "items": [
    { "id": 45012, "home": "Arsenal", "away": "Chelsea",
      "starts_at": "2026-08-09T19:00:00Z",
      "league_id": 1, "league": "Premier League", "sport": "Soccer", "n_markets": 74 }
  ]
}

Ese id es el root_event_id de la apuesta. Las hermanas /api/events/pending (en juego) y /api/events/settled (terminados, con marcador) aceptan los mismos filtros, y /api/events/leagues devuelve el catálogo de ligas si prefieres filtrar por league_id en vez de por texto.

Si tu sistema habla en nombres de casa de apuestas, casar nombre con evento es tu paso, y conviene desconfiar: dos partidos entre los mismos equipos, con días de diferencia, son el error clásico. Por eso comprobamos el starts_at contra la hora real y rechazamos una diferencia mayor a 6 horas.

2. Abrir el partido y elegir la selección

GET /api/events/{id} devuelve el evento con todos los mercados y, dentro de cada uno, sus selecciones.

curl "https://betamigos.io/api/events/45012" -H "X-Service-Token: $BETAMIGOS_TOKEN"
{
  "id": 45012, "home": "Arsenal", "away": "Chelsea", "sport": "Soccer",
  "starts_at": "2026-08-09T19:00:00Z", "status": "open",
  "markets": [
    {
      "market_id": 8801, "code": "spread", "period": 0, "unit": "regular",
      "period_name": "Match", "unit_label": "Goals",
      "team_side": null, "bet_type": null, "status": "open", "settleable": true,
      "selections": [
        { "selection_id": 918273, "label": "Arsenal", "line": -0.5, "price": 2.10,
          "status": "open", "max_limit": 2500,
          "no_vig": { "conservative": 1.96, "equal_margin": 1.95,
                      "log": 1.95, "odds_ratio": 1.94 },
          "margin": 0.038 },
        { "selection_id": 918274, "label": "Chelsea", "line": 0.5, "price": 1.80 }
      ]
    }
  ]
}

Cómo leerlo para mapear:

CampoPara qué sirve
code, period, unitIdentifican el mercado. period: 0 es el partido completo; unit separa goles de córners, sets de juegos, y así. period_name y unit_label son las etiquetas humanas de esos mismos valores.
line, labelIdentifican la selección dentro del mercado. En hándicaps y totales la línea es parte de la identidad: Arsenal -0.5 y Arsenal -1 son selecciones distintas.
selection_idLo que envías en el POST.
priceLa cuota actual con margen, tal como la publica la fuente.
no_vigLa cuota justa en los cuatro métodos de de-vig. Elige el tuyo, mándalo en blv_odds y nombra el método en blv_devig.
max_limitCuánto acepta la fuente en esa línea. Buen termómetro de lo fiable que es el precio.
settleablefalse significa que la fuente nunca publica resultado de ese mercado (pasa con kills de e-sports y puntos de voleibol). La apuesta se acepta, pero el resultado lo informas tú.

El histórico de movimiento de línea no está abierto al token, y aquí no hace falta: para mapear una selección, el detalle del evento basta.

Liquidar una apuesta

POST /api/bets/{id}/settle

curl -X POST https://betamigos.io/api/bets/1234/settle \
  -H "X-Service-Token: $BETAMIGOS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"outcome": "won"}'

outcome es uno de won, lost, void, half_won, half_lost. Liquidar calcula el P&L, lo asienta en el saldo de la cartera y captura el CLV.

Línea de cierre: si la apuesta tiene selection_id y el evento ya empezó, buscamos la cuota de cierre nosotros. Si prefieres informar tu propia cuota justa de cierre, envía closing_odds y la tuya manda.

Volver a liquidar con otro resultado es seguro. El extracto reversa el asiento anterior y registra el nuevo, así que el saldo sigue correcto.

Registrar una apuesta ya decidida

POST /api/bets/settled

Mismo cuerpo que POST /api/bets. Intentamos calificarla al instante con la base de resultados. Si aún no se puede, la apuesta queda pendiente y nuestro worker la liquida después. Este endpoint también es idempotente por el par external_source y external_ref.

Corregir una apuesta

PATCH /api/bets/{id}

Envía solo lo que cambió, por ejemplo {"stake": 12.5}. Stake y cuota son editables mientras la apuesta está pendiente; tras liquidarla se congelan y recibes 409. Las etiquetas y las métricas de valor siguen editables.

Reconciliar

GET /api/bets

curl "https://betamigos.io/api/bets?external_ref=trade-8814" \
  -H "X-Service-Token: $BETAMIGOS_TOKEN"

Filtros: account_id, status (pending o settled), external_ref, más limit (hasta 5000) y offset. Las más recientes arriba. Buscar por tu propia external_ref es la forma más barata de responder "¿esta entró?".

GET /api/bets/{id} devuelve una sola apuesta.

Valor de la apuesta: EV y CLV

Las dos métricas salen de la misma cuenta, la cuota que tomaste ÷ la cuota justa − 1, en dos momentos distintos. Lo que cambia es qué cuota justa entra en el denominador.

EV en el momento de la apuesta (lo llamamos BLV). Manda blv_odds en el POST: la cuota justa, sin margen, del mercado cuando apostaste. Responde "¿esta apuesta tenía valor cuando la hice?", y es la que depende de ti, porque solo tu sistema sabe qué línea leyó en ese instante. Sin blv_odds intentamos calcularla en el servidor cuando la apuesta trae selection_id; sin ninguna de las dos, la apuesta entra sin EV y nada lo completa después.

CLV, en la línea de cierre. Si la apuesta tiene selection_id, no hagas nada: buscamos la cuota justa de cierre por nuestra cuenta y guardamos el CLV cuando se liquida. Sin selection_id (una casa que no seguimos, un mercado que armaste tú), manda closing_odds en la liquidación, y la tuya siempre gana, incluso sobre la automática.

El GET devuelve las dos listas, junto con lo que necesitas para comprobarlas:

CampoQué es
blv_odds, blvLa cuota justa que enviaste, y el EV que produjo. 0.05 es 5% de valor.
closing_odds, clvLa cuota justa de cierre (la tuya o la nuestra) y el CLV.
clv_statusok tiene CLV; closed significa que la línea ya estaba cerrada cuando se hizo la apuesta, así que no inventamos un número; none es que no hubo cierre. Nulo mientras la apuesta está pendiente.
pnlResultado neto, completado al liquidar.

Una consecuencia que suele sorprender: si tu cartera cobra comisión, manda en odds la cuota neta, y entonces EV y CLV también salen netos. Es el número honesto de tu bolsillo, pero no es comparable con el CLV bruto que publican terceros.

Lecturas de apoyo

EndpointQué devuelve
GET /api/accountsTus carteras, con ids, saldos y cuál es la favorita.
GET /api/tagsTus etiquetas, de sistema y propias, con ids.
GET /api/alerts/rulesLas reglas de alerta de valor que configuraste en la app, para que tu bot filtre igual que tú.
GET /api/events/upcoming, /pending, /settledBúsqueda de eventos por texto, deporte, liga y fecha. Mira "Encontrar el evento y la selección".
GET /api/events/leaguesCatálogo de ligas, para filtrar por league_id.
GET /api/events/{id}El evento con mercados, selecciones, selection_id y cuotas justas.
GET /api/alertsLas 30 alertas de valor más recientes que coinciden con tus reglas. Cada leg ya trae su selection_id, así que puedes apostar directo desde la alerta sin pasar por la búsqueda.

Lo que un token no hace

Borrar apuestas, reabrir una apuesta liquidada, mover apuestas entre carteras, depósitos y retiros, y todo lo que toque tu cuenta, tu plan o el pago siguen exigiendo login normal. Un token filtrado puede escribir apuestas en tu propio tracker. No puede mover dinero, borrar tu historial ni dejarte fuera.

Errores

EstadoSignificado
400Cuerpo inválido, cartera que no es tuya, o partido incompatible.
401Token ausente, inválido, expirado o revocado.
402Tu suscripción ya no está activa, así que el token dejó de valer. Vuelve a funcionar en cuanto ella vuelva, sin necesidad de crear otro.
404La apuesta no existe, o no es tuya.
409Edición de una apuesta ya liquidada.
429Demasiadas escrituras en el último minuto.

Todo error trae un campo detail con el mensaje legible.

Límites

Las escrituras están limitadas a 30 por minuto por acción, por cuenta: registrar apuesta es un balde, liquidar es otro, editar es otro. Las lecturas por token están limitadas a 120 por minuto por grupo de endpoint. Las dos ventanas son deslizantes, así que un 429 se resuelve en menos de un minuto; reintenta con una pausa en vez de martillar.

Dos cosas que conviene diseñar con cuidado. Hacer polling de GET /api/alerts en un bucle apretado es la forma más rápida de tocar el techo de lectura, y tampoco trae datos más frescos: las alertas llegan a su propio ritmo, así que cada pocos segundos sobra.

Cada rechazo se explica en detail. Trata 429 como "reintenta en un momento", no como un fallo.

Consejo práctico

Guarda el token como una contraseña, en tu gestor de secretos, nunca en el repositorio. Rotarlo es fácil: crea el segundo token, despliega, y solo entonces revoca el primero. Nada se rompe en el medio, porque los tokens son independientes.

Envía la cuota que realmente conseguiste, no la que viste. Si tu casa cobra comisión, la cuota neta es la honesta, y toda métrica más adelante hereda esa honestidad.

¿Dudas, o falta algo para tu caso? Escríbenos y cuéntanos qué estás construyendo.