$0.0015 per call, platform usage included. Betano odds as a REST API: pre-match and live with score and clock, 600+ markets per match · the same match priced across 13 country books · search by team name · one HTTP call and JSON back with no batch run.
When the extra markets (markets other than main) stop coming through during a request, the answer now returns sooner: the remaining matches come back with their listing prices straight away instead of each one being tried and timing out. Requests where the extra markets load are unchanged.
0.1.10 - 2026-11-08
Answers can come compressed. Send Accept-Encoding: gzip (most HTTP clients do this on their own) and a large answer arrives far smaller: a three-match markets=all answer went from 2.4 MB to about 120 KB. The data is unchanged.
Event answers start arriving sooner: matches are sent as they are ready instead of after the whole answer is built.
meta.requests is replaced by meta.lookups, the number of lookups the answer was charged as (one per successful request, as before).
An answer that reaches the time limit while it is being sent now returns the matches ready by then, marked meta.partial: true, instead of failing. It is charged as one lookup, like any successful answer. A request that times out before any match is ready still answers 503REQUEST_TIMEOUT and is not charged.
If an answer fails after it has started arriving, the connection is closed before the JSON is complete and nothing is charged. Treat a body that does not parse as a failed request and retry it.
Team colours, logos and flags are ready straight after a restart instead of being loaded again on the first request.
0.1.9 - 2026-11-08
Team colours, league logos and country flags are back. Betano moved the data they come from, and since then homeJerseyColor, awayJerseyColor, leagueLogo and regionFlag came back empty, as did homeTeamLogo and awayTeamLogo for the teams Betano has a logo for (mostly national teams; club sides have colours but no logo). A set that fails to load is now tried again on the next request instead of staying empty until the run restarts.
The first request after a quiet spell answers faster: country and league lookups made earlier are reused instead of being fetched again.
0.1.8 - 2026-11-08
The day pass is no longer sold. POST /day-pass now answers 410 NO_LONGER_OFFERED and charges nothing. A pass bought earlier keeps working until it expires. Every successful lookup is charged $0.0015, as before.
The message shown when your run's maximum cost is reached now tells you to raise it.
2026-11-02
Fewer empty answers when a Betano site briefly shows a holding page instead of its odds data: the request now waits briefly and tries again on a fresh connection, so a short hiccup no longer costs the whole call.
Fixed: when a run stops because of a problem with the input, the run log now shows that reason instead of a generic "unexpected error" line.
Pricing - 2026-11-04
The $0.0005 start fee is removed. api-call stays $0.0015 and day-pass stays $3.00.
0.1.7 - 2026-10-21
Player markets now say which player, which player id and which line every price belongs to. Betano puts the player in a different place on each kind of player market, and many rows came out without one: on a Premier League page, about 1,900 prices on the per-player ladders (shots, shots on target, assists, passes, tackles, fouls, saves, offsides) had only "1+", "2+" with no player, id or line; about 550 prices on player lists such as "Scorer 2+ goals" had no player; and all 168 shots-on-target prices had no player id and the line "0". Every player price on that page now has its player, player id and line (2,674 of 2,674), and the per-player ladders also carry the team id. A player that no other market on the page names is left empty rather than guessed. Two smaller fixes: the "Next goal before 10:00 … 80:00" markets reported "10:00" as a player and now report it as the line, and the "No scorer" outcome no longer carries a team or a player. Prices, market fields and change detection are unchanged: only the player, playerId, line and teamId values on player markets are filled in.
Player stat tables are named after the statistic, not after one player. Betano names each table of players after the player in its first row, so a passes table covering 38 players was called "Chuba Akpom Total Passes". It is now "Total Passes", and every price still carries its own player. Market ids are unchanged.
0.1.6 - 2026-10-19
POST /day-pass now activates the pass only after its charge is recorded. If the charge cannot be recorded the request answers 500 and no pass is granted, so it is safe to retry.
0.1.5 - 2026-10-07
Brazil is no longer offered as the default country book, and the documentation lists 13 books, while Betano's Brazilian site stays closed by government order. A request with country=BR still answers 503BOOK_OFFLINE, uncharged. Brazil comes back in a later release once the site reopens.
All notable changes to betano-odds-api are recorded here.
0.1.4 - 2026-10-07
country=BR now answers 503 with error: "BOOK_OFFLINE" at once, and is never charged. Since 6 October 2026 Betano's Brazilian site is offline by government order, like every licensed Brazilian betting site, so there are no odds to read; until now the same request answered SOURCE_UNAVAILABLE after its deadline. BR inside compare is skipped. Every other country is unaffected, and Brazil is switched back on in a later release once the site reopens.
A maximum cost per run is now honoured. Once it is spent, priced lookups answer 402 with error: "BUDGET_REACHED" instead of being served unbilled; an active day pass still answers. Runs without a limit are unaffected.
Odds and lookups - 2026-09-29
Chile works again. Betano moved its Chilean site to a new address, and every Chile lookup since then answered as if the source were down. Links copied from the old Chilean address are still recognised as Chile.
/coupon returns matches again. Betano moved the "next hours" events behind a separate calendar on 2026-09-25, and since then the coupon answered an empty list on every book.
League names the book itself does not use now find the league: common sponsor names ("Chance Liga", "Liga Portugal Betclic", "PKO Ekstraklasa"), a country adjective in front of a league ("Scottish Premiership"), and women's competitions written in words ("champions league femenina" finds "Champions League (W)").
New isMainLine on every selection: the book's own main line of a multi-line market, for example Over/Under 2.5, on both sides of it. Player markets now also carry jerseyNumber and lastGamesStats, the player's value for that statistic in recent matches as the book shows them.
Pricing - 2026-09-29
api-call $0.002 -> $0.0015 per successful lookup ($1.50 per 1,000). The billing unit is unchanged: one lookup, whatever it returns.
Platform usage (compute and data transfer) is now included in the price; it is no longer billed on top.
day-pass stays $3.00 for 24 hours, and the start fee stays $0.0005. No code change.
0.1.3 - 2026-08-31
A match that has already kicked off returns the markets the book prices for it on every route, not just /live. A running match keeps only a remnant on its pre-match page; measured on the batch sibling, in-play rows enriched from that page carried 33 markets between 34 of them against the 736 the book advertised, one of them none at all, and none of them a score. Those rows now carry the score, the clock, the period and the suspension flag too.
Every record carries fetchedAt, when THAT event's odds were read. scrapedAt is when the request started, and a markets=all request over many events spans tens of seconds.
compareCountries compares the SAME market across books. Some books lead with an enhanced-odds variant of the match result ("SuperOdds", "MegaCote"); taking each book's first few markets put a promotional price beside a standard one, and 3 of 44 comparisons measured shared no market at all. A book with nothing comparable now returns nothing for that book.
An outright no longer reports a home and an away side. 11 of 48 outright rows carried one, naming the first two entrants of a field — or, on one race, the two halves of its own title.
In-play markets carry qualifier and instance, the fields that tell markets of one type apart: 66 of 804 markets on a single match were otherwise indistinguishable.
0.1.2 - 2026-08-31
/live?markets=all returns every market the book prices for a running match, not the two or three its in-play list summarises. Measured on the twelve deepest matches running at the time: 47 markets before, 561 after, against the 569 the book advertises. The lookup behind it had been answering with an empty stub because it stopped reading its query parameters and now reads two headers instead.
0.1.1 — 2026-08-31
Fractional prices are the ones a board prints. 1.67 is 4/6, not 67/100; 2.5 is 6/4, not 3/2. Measured over 14,543 live selections from five country books, 560 of them (3.9%) carried a reduced fraction no bookmaker shows, and six prices just over evens came back empty.
A live match now reports its SET score. liveScore on tennis, table tennis and volleyball is the score of the game being played, so a match standing at 2-1 in sets read "1 / 2". The new liveSets and liveSetsWon carry the match itself.
A market, or a whole event, that the book has pulled is flagged suspended, so a price that cannot be taken is no longer indistinguishable from one that can. Pre-match rows report null there, because those pages publish no such flag at all.
/live?markets=all reads the match page as well as the in-play feed. It used to fetch that page and then discard it whenever the feed answered with nothing, which is what the feed does for almost every running match.
A finished match still sitting in the in-play list is no longer reported as running.
A table column that carries only a rendering hint no longer arrives as a market type: 216 selections reported columnType: "market", which is not a Betano market code.
Flat selection rows carry participantId, teamId and playerId, so a player row can be joined on an id rather than on a name.
0.1.0 — 2026-08-28
First version. The standby (always-on HTTP) sibling of betano-scraper: the same odds, asked
for one request at a time instead of one batch run at a time.
Endpoints
GET /health and GET /sports — free. Version, and the 19 sports with their keys and ids.
GET /leagues — the league catalogue of one country book and sport, with the ids /league
takes.
GET /coupon — matches starting in the next 3, 12 or 24 hours. When the book does not
publish the window asked for, the next larger one it does publish is served.
GET /league/{leagueId} and GET /league?name= — one league's remaining fixture list, by id
or by name resolved against the book's own catalogue.
GET /event/{eventId} — one match. markets=all returns every market the book prices
(measured: 637 markets and 3,882 selections on a Premier League match).
GET /live — every match in play right now, with score, clock and period.
GET /search?term= — Betano's own site search, resolved to full event records.
GET /team?url= and GET /player?url= — a team's or player's page, taken as the address the
caller pasted. A player's page carries their prop markets inline.
GET /day-pass and POST /day-pass — status and purchase.
Every priced endpoint takes country, markets, odds, compare and logos; the fixture-list
endpoints also take max, daysAhead and includeLive.
Pricing
api-call $0.002, charged once per successful request to a priced endpoint whatever it
returns. A request that fails validation, 404s or times out is not charged.
day-pass $3.00, charged once by POST /day-pass; for 24 hours no api-call is charged,
across every run of that account.
apify-actor-start $0.0005. Platform usage is billed by Apify at the caller's plan rates.
Notes
The parsing, transport and normalisation layers are vendored byte-identical from
betano-scraper, so both actors emit the same EventRecord.
changeType is present on every row and always null: change classification needs state
between runs, which a single request does not have. Compare priceHash instead.