API B2B de odds, eventos e resultados
Eventos ao vivo e pré-jogo, odds normalizadas da bet365 e de outras casas, e resultados com o status de cada seleção (ganhou, perdeu, devolvida) para fechar bilhetes. REST + WebSocket, autenticação OAuth2.
{ "data": [ { "event_id": "01a14699-c3d7-7203-b132-134a73ed510b", "bookmaker": { "id": 1, "slug": "bet365", "name": "Bet365" }, "market": "match_winner", "selection_key": "home", "line": null, "price": "1.6000", "suspended": false, "source_updated_at": "2026-10-16T21:32:07Z" }, // … "draw" → "5.2500", "away" → "3.3000" ], "meta": { "served_at": "2026-10-16T21:32:09Z", "stale": false, "request_id": "51e80cc5-4d7f-449a-…", "sequence": 1842 } }
{ "data": [{ "id": "01a14a2e-d4d8-716f-91e8-5b1a21f346fc", "sport": "soccer", "league": { "id": 2265, "name": "Austria Bundesliga", "country": "AT" }, "home_team": { "id": 3837, "name": "Austria Lustenau" }, "away_team": { "id": 3838, "name": "LASK Linz" }, "starts_at": "2026-10-17T15:00:00Z", "status": "scheduled", "scores": null, "source_updated_at": null }], "meta": { "stale": false, "pagination": { "per_page": 50, "next_cursor": "eyJkIjoi…" } } } // ao vivo: "status": "live" e "scores": { "home": "4", "away": "3", // "periods": [{…}], "summary": "4-3" }
{ "data": { "status": "finished", "scores": { "home": "4", "away": "5", "summary": "4-5" }, "markets": [ { "market": "match_winner", "line": null, "selections": { "home": "lost", "draw": "lost", "away": "won" }, "others": null }, { "market": "both_teams_to_score", "line": null, "selections": { "yes": "won", "no": "lost" }, "others": null }, { "market": "correct_score", "line": null, "selections": { "4-5": "won" }, "others": "lost" } ], "finished_at": "2026-10-16T21:27:03Z", "purge_after": "2026-10-21T21:27:03Z" } }
Cobertura
Os 28 esportes da fonte já estão mapeados na API. Futebol, basquete e tênis estão ligados hoje; os demais são ligados por configuração. Filtre por sport usando o slug.
Catálogo completo: pré-jogo e ao vivo, todos os mercados publicados pela casa. Preços ao vivo publicados em fração são convertidos para decimal.
Ativadas por configuração. Entregam vencedor (1X2 / moneyline), handicap asiático e total (over/under); algumas publicam só o vencedor. A presença varia por liga e região.
Mercados
Cada odd traz market, selection_key e line. Os mercados mais usados têm código fixo, igual em todas as casas, no pré-jogo e no ao vivo. Os demais da bet365 chegam como bet365_ + nome do mercado.
| Esporte | market | Descrição | selection_key |
|---|---|---|---|
| Futebol | match_winner | Resultado final (1X2) | home draw away |
double_chance · half_time_double_chance | Dupla chance (jogo / intervalo) | home_or_draw home_or_away draw_or_away | |
draw_no_bet | Empate anula | home away | |
both_teams_to_score | Ambos marcam | yes no | |
half_time_result · half_time_full_time | Intervalo · intervalo/final | home … away-away | |
correct_score · half_time_correct_score | Placar exato (jogo / intervalo) | 2-1 | |
asian_handicap · first_half_asian_handicap | Handicap asiático (com line) | home away | |
over_under · first_half_over_under · total_goals | Total de gols (goal line e linhas europeias) | over under | |
asian_corners · first_half_asian_corners · total_corners | Escanteios | over under | |
| Basquete | moneyline · first_half_moneyline | Vencedor (com prorrogação) / 1º tempo | home away |
spread · first_half_spread | Handicap de pontos | home away | |
over_under · first_half_over_under | Total de pontos | over under | |
| Demais | match_winner | Vencedor | home (draw) away |
asian_handicap | Handicap (games, sets, pontos, gols, corridas) | home away | |
over_under | Total | over under |
No futebol: escanteios, cartões, ambos marcam por tempo, total por time, mercados de jogador (bet365_goalscorers, bet365_shots_on_target, bet365_goalkeeper_saves…), janelas de minutos ao vivo e mais. Lista completa no guia de cobertura.
Mercado listado nem sempre tem odds. Ligas grandes recebem dezenas ou centenas de mercados; ligas menores costumam ter só vencedor, handicap e total. Mercados abrem e fecham conforme o momento do jogo, e ao vivo a casa suspende em lances perigosos: a seleção chega com suspended: true. Preços sempre em decimal com 4 casas, como string ("1.8696").
Resultados e liquidação
Além do placar, cada resultado traz o bloco markets com o status de cada seleção dos mercados liquidados, um item por linha que teve odds no jogo. Chega por REST e em tempo real no WebSocket (result.updated).
[ {"market": "match_winner", "line": null, "selections": {"home": "won", "draw": "lost", "away": "lost"}, "others": null}, {"market": "asian_handicap", "line": "-0.750", "selections": {"home": "half_won", "away": "half_lost"}, "others": null}, {"market": "correct_score", "line": null, "selections": {"2-1": "won"}, "others": "lost"} ]
wonGanhoulostPerdeuvoidDevolvida (linha inteira empatada, empate anula com empate, evento cancelado)half_wonMetade ganha, metade devolvida (linhas de quarto)half_lostMetade perdida, metade devolvidamatch_winner, double_chance, draw_no_bet, both_teams_to_score, intervalo, intervalo/final, placar exato, handicap asiático, totais de gols e escanteios (jogo e 1º tempo).moneyline, spread, over_under (com prorrogação) e as versões do 1º tempo.Resultados ficam disponíveis por 5 dias após o fim do jogo (purge_after). Mercados genéricos bet365_* não são liquidados.
Recursos
Sempre {"data", "meta"}. Paginação por cursor, datas em UTC ISO 8601 e IDs internos (UUID) estáveis.
Snapshot na assinatura e deltas odds.updated com sequence para reconciliar. Canal ao vivo por esporte com placar, tempo e resultados.
Token Bearer com scopes por recurso. Várias credenciais por conta (produção e homologação), revogáveis uma a uma no portal.
Sua requisição nunca aciona o fornecedor. Alvos de coleta: eventos ao vivo 3–5 s, odds ao vivo 2–5 s, pré-jogo 15–60 s.
Toda resposta traz source_updated_at e stale. Se a origem atrasar, você recebe o último snapshot e decide se ainda serve.
Cotas por minuto, hora, dia e mês, cabeçalhos X-RateLimit-* e GET /usage para acompanhar o consumo.
Documentação
Guia de integração, endpoints, WebSocket, erros e limites. A referência completa (OpenAPI 3.1) é pública.
https://api.bestoddsapi.com/api/v1https://api.bestoddsapi.com/oauth/tokenwss://ws.bestoddsapi.com (endereço exato vem no ticket)https://api.bestoddsapi.com/portalOAuth2 Client Credentials (máquina a máquina): client_id público e client_secret sigiloso, exibido só na criação ou rotação. Emitidas no portal do cliente após a contratação.
curl -s -X POST "https://api.bestoddsapi.com/oauth/token" \
-d grant_type=client_credentials \
-d client_id="$BESTSODDSAPI_CLIENT_ID" \
-d client_secret="$BESTSODDSAPI_CLIENT_SECRET" \
-d scope="events:read odds:read results:read usage:read ws:connect"
# { "token_type": "Bearer", "expires_in": 3600, "access_token": "eyJ..." }Envie Authorization: Bearer <access_token> em todas as chamadas e reutilize o token até perto de expires_in.
curl -s "https://api.bestoddsapi.com/api/v1/events/live?sport=soccer&per_page=20" \ -H "Authorization: Bearer $TOKEN" -H "Accept: application/json"
Use sempre o id interno (UUID). IDs do fornecedor de origem não são expostos.
curl -s "https://api.bestoddsapi.com/api/v1/events/$EVENT_ID/odds?market=match_winner,over_under&bookmaker=bet365" \ -H "Authorization: Bearer $TOKEN" -H "Accept: application/json"
price e line são strings decimais: use tipo decimal (bcmath, decimal.js, BigDecimal), nunca float. meta.sequence serve para reconciliar com o WebSocket.
Peça um ticket em POST /ws/authorize e conecte (protocolo Pusher 7). Detalhes na aba WebSocket. Exemplos executáveis em cURL, PHP e Node.js na documentação oficial.
/api/v1)| Método | Rota | Scope |
|---|---|---|
| GET | /sports | events:read |
| GET | /leagues?sport_id= | events:read |
| GET | /events/live | events:read |
| GET | /events/upcoming?from=&to= | events:read |
| GET | /events/{id} | events:read |
| GET | /events/{id}/odds?bookmaker=&market= | odds:read |
| GET | /events/{id}/results | results:read |
| GET | /results?date=YYYY-MM-DD | results:read |
| GET | /usage | usage:read |
| POST | /ws/authorize | ws:connect |
Além do scope, o plano precisa habilitar a funcionalidade (events, odds, results, websocket) e, quando configurado, o esporte.
| Parâmetro | Onde | Formato |
|---|---|---|
sport | eventos, resultados | slug (soccer) |
sport_id | /leagues | inteiro |
league_id | eventos, resultados | inteiro |
from, to | /events/upcoming | ISO 8601 UTC, janela máx. 7 dias |
bookmaker, market | /events/{id}/odds | lista separada por vírgula |
date | /results | YYYY-MM-DD (UTC), dentro da retenção |
Cursor opaco: per_page (1–100, padrão 50) e cursor. Continue enquanto meta.pagination.next_cursor não for null.
Protocolo Pusher 7 atrás de um gateway de admissão. A conexão só é aceita com um ticket de uso único (TTL ~45 s) emitido pela API.
curl -s -X POST "https://api.bestoddsapi.com/api/v1/ws/authorize" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"sport":"soccer"}'
# data.connection:
# url: wss://ws.bestoddsapi.com/app/APP_KEY?protocol=7&…&ticket=wst_…
# channel_template: private-customer.{id}.event.{eventId}.odds
# live_channel_template: private-customer.{id}.live.{sport}
# max_subscriptions: 100odds.snapshot: estado atual do evento logo após assinar o canal.odds.updated: delta com o estado completo do mercado e sequence crescente. Aplique só os de sequence maior que o snapshot.live.snapshot / live.updated: lista ao vivo do esporte, só com eventos cujo status, placar ou tempo mudou, e removed[].result.updated: resultado final com markets liquidados, no canal do evento e no canal ao vivo do esporte.max_ws_connections vale por esporte; cada conexão aceita até max_subscriptions canais (padrão 100). Entrega best effort: se a sequence pular, refaça o snapshot. Reconecte com backoff exponencial e jitter, sempre com ticket novo.
delay = min(30s, 1s * 2^tentativa); sleep(random(0, delay))
{ "error": { "code": "rate_limited", "message": "Too many requests.", "details": {} },
"request_id": "01J9..." }| HTTP | code | Quando |
|---|---|---|
| 400 | bad_request | Parâmetro não permitido ou requisição malformada |
| 401 | unauthenticated | Token ausente, inválido, expirado ou revogado |
| 403 | forbidden | Scope ausente, conta suspensa, funcionalidade fora do plano |
| 404 | not_found | Recurso inexistente ou fora do seu alcance |
| 422 | validation_failed | Valor de parâmetro inválido (details por campo) |
| 429 | rate_limited | Limite excedido: respeite Retry-After |
| 500 | internal_error | Erro inesperado: informe o request_id |
| 503 | service_unavailable | Indisponibilidade temporária: retry com backoff |
Cotas por minuto, hora, dia e mês (janelas fixas UTC), por cliente, somando todas as credenciais. Requisições rejeitadas (429) não consomem cota.
Versão maior no caminho (/api/v1). Dentro da v1 só entram mudanças compatíveis: novos campos, endpoints e valores de enum. Ignore campos que não conhece.
Plano
Preço de lançamento. Setup de R$ 1.200,00 e mensalidade de R$ 500,00 valem somente para contratações feitas durante o período de lançamento. Após esse período, os valores serão reajustados para novas assinaturas.
Você entra no nosso grupo do WhatsApp, tira as dúvidas e fecha a assinatura. O setup libera suas credenciais OAuth2 e o acesso ao portal, e acompanhamos a integração até as primeiras odds chegarem.
A mensalidade cobre o acesso contínuo, sem cobrança por esporte ou por mercado. Limites de requisição e de conexões WebSocket são definidos no plano e visíveis em GET /usage.
Quer validar antes? Leia a documentação e a referência da API: tudo é público.