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_xxxxxxxxxxxxxxxxxxxxxxxxEl 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.ioCada 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:
| Campo | Por qué enviarlo |
|---|---|
account_id | Cartera de la apuesta. Si lo omites, va a tu cartera favorita. |
selection_id, root_event_id | Atan 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, selection | Las etiquetas que vas a leer después en el dashboard. Envíalas incluso con selection_id: son las que aparecen en la tabla. |
starts_at | Hora de inicio. Define cuándo se considera cerrada la línea. |
odds | La cuota que realmente conseguiste. En exchange, envía la neta, ya con la comisión. |
gross_odds, commission_type, commission_pct | La 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_odds | La cuota justa (sin vig) en el momento de apostar, si la calculas. Alimenta la métrica de valor. |
origin | alert, explorer o manual. Marca de dónde vino la apuesta. |
tag_names | Adjunta tus etiquetas por nombre, sin necesitar sus ids. Un nombre inexistente se ignora. |
external_source, external_ref | Tus 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:
| Campo | Para qué sirve |
|---|---|
code, period, unit | Identifican 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, label | Identifican 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_id | Lo que envías en el POST. |
price | La cuota actual con margen, tal como la publica la fuente. |
no_vig | La 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_limit | Cuánto acepta la fuente en esa línea. Buen termómetro de lo fiable que es el precio. |
settleable | false 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:
| Campo | Qué es |
|---|---|
blv_odds, blv | La cuota justa que enviaste, y el EV que produjo. 0.05 es 5% de valor. |
closing_odds, clv | La cuota justa de cierre (la tuya o la nuestra) y el CLV. |
clv_status | ok 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. |
pnl | Resultado 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
| Endpoint | Qué devuelve |
|---|---|
GET /api/accounts | Tus carteras, con ids, saldos y cuál es la favorita. |
GET /api/tags | Tus etiquetas, de sistema y propias, con ids. |
GET /api/alerts/rules | Las reglas de alerta de valor que configuraste en la app, para que tu bot filtre igual que tú. |
GET /api/events/upcoming, /pending, /settled | Búsqueda de eventos por texto, deporte, liga y fecha. Mira "Encontrar el evento y la selección". |
GET /api/events/leagues | Catálogo de ligas, para filtrar por league_id. |
GET /api/events/{id} | El evento con mercados, selecciones, selection_id y cuotas justas. |
GET /api/alerts | Las 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
| Estado | Significado |
|---|---|
400 | Cuerpo inválido, cartera que no es tuya, o partido incompatible. |
401 | Token ausente, inválido, expirado o revocado. |
402 | Tu 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. |
404 | La apuesta no existe, o no es tuya. |
409 | Edición de una apuesta ya liquidada. |
429 | Demasiadas 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.