Betamigos / Developers

API reference

Betamigos API

Log your bets into Betamigos from your own system: a bot, a spreadsheet script, a private tool. Every bet you send lands in the same place as one you would have typed in the app, so CLV, P&L, exposure and the wallet ledger all keep working.

This is a small, deliberate API. It covers the loop that matters (log a bet, correct it, settle it, reconcile) and nothing else.

Get a token

Open Profile > API access in the app and create a token. The secret is shown once, at creation time, because we only store a hash of it. If you lose it, revoke that token and create another.

Send it on every request in the X-Service-Token header:

X-Service-Token: bmst_xxxxxxxxxxxxxxxxxxxxxxxx

A token acts as you. It reads and writes your data only, and it never sees anyone else's. Revoking takes effect immediately.

Base URL

https://betamigos.io

Every path below already starts with /api, so the full URL of the first endpoint is https://betamigos.io/api/bets. All bodies are JSON. All timestamps are ISO 8601 in UTC.

Log a bet

POST /api/bets

There are two ways to log one, and the difference is not about format: it is about how much work stays with you afterwards.

A standalone bet. You describe the bet in plain text and that is it. It works for anything, including markets we do not even track. The price is that nobody settles it for you: when the game ends you call the settle endpoint with the outcome, and CLV only exists if you send the closing odds along with it.

A bet linked to the market. You include the selection_id of the real selection. From then on the bet settles itself when the game ends, and we capture the closing line for you, which gives you CLV with no work. How to find that id is the next section.

The recommendation is obvious: whenever the bet exists in our catalogue, send the selection_id. When it does not, the standalone bet is the way, and nothing is lost beyond the automation.

# Standalone: labels only. You settle it later.
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": "my-bot",
    "external_ref": "trade-8814"
  }'
# Linked to the market: settles and computes CLV on its own.
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": "my-bot",
    "external_ref": "trade-8814"
  }'

Returns 201 with the created bet. The bet is born pending.

Only stake and odds are required. Everything else makes the numbers richer:

FieldWhy you want it
account_idWhich wallet the bet belongs to. Leave it out and we use your favourite wallet.
selection_id, root_event_idTies the bet to the real market. This is what enables automatic settlement and closing line capture.
sport, league, event_label, market, selectionLabels you will read later in the dashboard. Send them even with a selection_id: they are what shows up in the table.
starts_atKickoff. Used to decide when the line is closed.
oddsThe odds you actually got. On an exchange, send the net odds, after commission.
gross_odds, commission_type, commission_pctThe commission you paid, if you would rather we did the maths. All three or none: with them the server works out the net odds from the gross ones and ignores the odds you sent. commission_type is on_profit, on_stake or on_return.
blv_oddsThe fair (de-vigged) odds at the moment you bet, if you compute them. Drives the value metric.
originalert, explorer or manual. Tags the bet by where it came from.
tag_namesAttaches your tags by name, so you do not need their ids. Unknown names are ignored.
external_source, external_refYour own identifiers. See idempotency below.

Idempotency

Always send external_source and external_ref. Re-posting the same pair returns the bet that already exists, with status 200 instead of 201, and creates nothing new.

That matters more than it looks: without it, a retry after a timeout logs the bet twice and posts the money twice in the wallet ledger. With it, retrying is free.

Consistency check

If you send root_event_id together with starts_at, we compare the kickoff you declared against the real one. A gap of more than 6 hours is rejected with 400, because it means the bet got linked to the wrong fixture (the classic case is two matches between the same teams, days apart). If the odds feed is unreachable at that moment we let the bet through rather than block you.

Finding the event and the selection

Two steps: search for the game, then open it. The first gives you the event id, the second gives you the selection_ids inside it.

1. Search for the game

GET /api/events/upcoming lists what has not started yet, soonest first. Filters: q (free text, matches team names), sport, league_id, date_from, date_to, limit (up to 200) and 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 }
  ]
}

That id is the bet's root_event_id. The siblings /api/events/pending (in play) and /api/events/settled (finished, with the score) take the same filters, and /api/events/leagues returns the league catalogue if you would rather filter by league_id than by text.

If your system speaks in bookmaker names, matching a name to an event is your step, and it is worth being suspicious: two matches between the same teams, days apart, are the classic mistake. That is why we check starts_at against the real kickoff and reject a gap over 6 hours.

2. Open the game and pick the selection

GET /api/events/{id} returns the event with every market and, inside each one, its selections.

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 }
      ]
    }
  ]
}

How to read it when mapping:

FieldWhat it is for
code, period, unitThey identify the market. period: 0 is the full game; unit separates goals from corners, sets from games, and so on. period_name and unit_label are the human labels for the same values.
line, labelThey identify the selection inside the market. On handicaps and totals the line is part of the identity: Arsenal -0.5 and Arsenal -1 are different selections.
selection_idWhat you send on the POST.
priceThe current odds with margin, as the source publishes them.
no_vigThe fair odds under all four de-vig methods. Pick yours, send it as blv_odds, and name the method in blv_devig.
max_limitHow much the source accepts on that line. A good gauge of how trustworthy the price is.
settleablefalse means the source never publishes a result for that market (it happens with e-sports kills and volleyball points). The bet is accepted, but you are the one who reports the outcome.

The line-movement history is not open to tokens, and you do not need it here: to map a selection, the event detail is enough.

Settle a bet

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 is one of won, lost, void, half_won, half_lost. Settling computes P&L, posts it to the wallet balance, and captures CLV.

Closing line: if the bet carries a selection_id and the event has already started, we fetch the closing odds ourselves. If you would rather supply your own fair closing odds, send closing_odds and yours wins.

Settling again with a different outcome is safe. The ledger reverses the old entry and posts the new one, so the balance stays correct.

Log a bet that is already decided

POST /api/bets/settled

Same body as POST /api/bets. We try to grade it immediately from the results feed. If it cannot be graded yet, the bet stays pending and our worker settles it later. This endpoint is idempotent on the same external_source and external_ref pair.

Correct a bet

PATCH /api/bets/{id}

Send only what changed, for example {"stake": 12.5}. Stake and odds are editable while the bet is pending; after settlement they are frozen and you get 409. Labels and value metrics stay editable.

Reconcile

GET /api/bets

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

Filters: account_id, status (pending or settled), external_ref, plus limit (up to 5000) and offset. Newest first. Looking a bet up by your own external_ref is the cheapest way to answer "did this one land?".

GET /api/bets/{id} returns a single bet.

Bet value: EV and CLV

Both metrics come from the same arithmetic, the odds you took ÷ the fair odds − 1, taken at two different moments. What changes is which fair odds go in the denominator.

EV at the time of the bet (we call it BLV). Send blv_odds on the POST: the fair, no-vig odds of the market when you bet. It answers "did this bet have value when I made it?", and it is the one that depends on you, because only your system knows which line it read at that instant. Without blv_odds we try to work it out server-side when the bet carries a selection_id; without either, the bet lands with no EV and nothing fills it in later.

CLV, at the closing line. If the bet has a selection_id, do nothing: we fetch the fair closing odds ourselves and record CLV when it settles. Without a selection_id (a book we do not follow, a market you built yourself), send closing_odds on the settle, and yours always wins, including over the automatic one.

The GET hands both back, along with what you need to check them:

FieldWhat it is
blv_odds, blvThe fair odds you sent, and the EV they produced. 0.05 means 5% of value.
closing_odds, clvThe fair closing odds (yours or ours) and the CLV.
clv_statusok has CLV; closed means the line was already closed when the bet was placed, so we do not invent a number; none means there was no close. Null while the bet is pending.
pnlNet result, filled in at settlement.

One consequence that tends to surprise people: if your wallet charges commission, send the net odds in odds, and then EV and CLV come out net too. That is the honest number for your pocket, but it is not comparable to gross CLV published elsewhere.

Supporting reads

EndpointWhat it gives you
GET /api/accountsYour wallets, with ids, balances and which one is the favourite.
GET /api/tagsYour tags, system and custom, with ids.
GET /api/events/upcoming, /pending, /settledEvent search by text, sport, league and date. See "Finding the event and the selection".
GET /api/events/leaguesThe league catalogue, to filter by league_id.
GET /api/events/{id}The event with markets, selections, selection_id and fair odds.
GET /api/alerts/rulesThe value alert rules you configured in the app, so your bot can filter the same way you do.
GET /api/alertsThe 30 most recent value alerts that match your rules. Every leg already carries its selection_id, so you can bet straight off an alert without going through search.

What a token cannot do

Deleting bets, reopening a settled bet, moving a bet between wallets, deposits and withdrawals, and anything that touches your account, plan or billing all stay behind a normal login. A leaked token can write bets into your own tracker. It cannot move money, empty your history or lock you out.

Errors

StatusMeaning
400Invalid body, wallet that is not yours, or a fixture mismatch.
401Missing, invalid, expired or revoked token.
402Your subscription is no longer active, so the token stopped working. It works again as soon as the subscription does, with no need to create a new one.
404The bet does not exist, or is not yours.
409Editing a bet that is already settled.
429Too many writes in the last minute.

Every error carries a detail field with a human readable message.

Limits

Writes are capped at 30 per minute per action, per account: logging bets is one bucket, settling is another, editing another. Reads through a token are capped at 120 per minute per endpoint group. Both windows are rolling, so a 429 clears within a minute; retry with a short backoff instead of hammering.

Two notes worth designing around. Polling GET /api/alerts in a tight loop is the fastest way to hit the read cap, and it will not get you fresher data anyway: alerts land on their own cadence, so a poll every few seconds is plenty.

Every rejection explains itself in detail. Treat 429 as "retry in a moment", not as a bug.

Practical advice

Store the token like a password, in your secret manager, never in the repository. Rotating is easy: create the second token, deploy it, then revoke the first one. Nothing breaks in between, because tokens are independent.

Send the odds you actually got, not the odds you saw. If your book charges commission, the net odds are the honest ones, and every metric downstream inherits that honesty.

Questions, or something missing for your use case? Reach out and tell us what you are building.