Integre dados esportivos em minutos.
A SportsAPI oferece uma interface REST para consultar jogos, placares, horários, ligas e transmissões de diferentes fontes em um único formato.
1. Crie uma chave
No dashboard da sua conta.
2. Faça a chamada
Envie a chave no header.
3. Use os dados
Receba JSON normalizado.
Guia de integração para o seu agente de IA
Baixe (ou copie) um guia Markdown com um prompt de implementação organizado, os fluxos principais, autenticação, dados opcionais, odds, limites e erros. Use-o como contexto para o Claude Code, Cursor, Copilot ou outro agente; o contrato OpenAPI publicado complementa a referência quando precisar de detalhes completos de qualquer endpoint.
Autenticação
Envie sua API key no header X-API-Key. Mantenha a chave somente no servidor e nunca publique em código do navegador.
X-API-Key: sapi_xxxxxxxxxxxxUse chaves separadas para desenvolvimento e produção. Se uma chave for exposta, revogue-a imediatamente no dashboard.
Primeira requisição
Consulte os jogos de futebol ao vivo com uma chamada GET. A referência completa está em /docs (OpenAPI).
curl "https://sportsapi.com.br/api/v1/games?sport=football&status=live&limit=20" \
-H "X-API-Key: sapi_xxxx"Para jogos agendados em uma data específica, combine date=YYYY-MM-DD com status=scheduled.
curl "https://sportsapi.com.br/api/v1/games?sport=football&date=2026-08-20&status=scheduled&limit=100&offset=0" \
-H "X-API-Key: sapi_xxxx"Resposta 200 OK
A resposta inclui partidas paginadas (`matches`, `total`, `limit`, `offset`) e se os dados vieram do cache.
curl "https://sportsapi.com.br/api/v1/account" \
-H "X-API-Key: sapi_xxxx"Consultar jogos
Monte uma consulta e copie o código para sua linguagem.
Parâmetros
/api/v1/games?sport=football&status=live&limit=20curl "https://sportsapi.com.br/api/v1/games?sport=football&status=live&limit=20" \
-H "X-API-Key: sapi_xxxx"Filtros avançados
Use GET /api/v1/games/filter para consultar somente as partidas necessárias sem perder campos do objeto Match. Você pode enviar um único filtro ou combinar vários na mesma URL.
Combinação AND
Todos os filtros enviados precisam corresponder. region=Brasil + team=Flamengo retorna jogos do Flamengo no Brasil.
Texto flexível
q, competition, region e team aceitam trechos e ignoram diferenças entre maiúsculas, minúsculas e acentos.
Uma data por chamada
date usa YYYY-MM-DD. Sem date, a API consulta hoje em UTC; para intervalos, repita a chamada por data.
curl "https://sportsapi.com.br/api/v1/games/filter?sport=football®ion=Brasil&competition=Serie%20A&team=Flamengo&status=live&limit=20" \
-H "X-API-Key: sapi_xxxx"sportstringModalidade (football, basketball, tennis...). Padrão: football. Veja a lista completa liberada no seu plano em GET /sports.
Exemplo: football
dateYYYY-MM-DDData exata consultada. Para um intervalo, faça uma chamada por data. Padrão: hoje em UTC.
Exemplo: 2026-07-17
qstring (2–100)Busca geral por trecho no nome do campeonato, mandante ou visitante. Use quando você ainda não sabe em qual campo o texto está.
Exemplo: flamengo
statusenumEstado exato: live (ao vivo), scheduled (agendado), finished (finalizado), postponed (adiado) ou cancelled (cancelado).
Exemplo: live
competitionstring (1–100)Trecho do nome ou id do campeonato. A busca ignora caixa e acentos. league continua aceito como alias legado.
Exemplo: brasileirao
regionstring (1–100)Trecho do país da competição ou do país do local da partida. A busca ignora caixa e acentos.
Exemplo: brasil
teamstring (1–100)Trecho do nome ou id do mandante ou visitante. Não é necessário informar se o time joga em casa ou fora.
Exemplo: flamengo
statusInCSV de statusVários estados separados por vírgula. Não combine com status.
Exemplo: live,scheduled
matchIdstringId canônico exato da partida retornado pela SportsAPI.
Exemplo: a3f9c1e6b2d84f70
competitionIdstringId exato do campeonato, como devolvido em league.id (ou nos facets).
Exemplo: Qx7mP2aVn9sK
teamIdstringId exato de qualquer um dos times, como devolvido em homeTeam.id / awayTeam.id.
Exemplo: Rk3tY8wLc0dE
homeTeam / awayTeamstringTrecho do nome ou id, restringindo especificamente mandante ou visitante.
Exemplo: Flamengo
homeTeamId / awayTeamIdstringId exato do mandante ou visitante, como devolvido pela API.
Exemplo: Rk3tY8wLc0dE
venuestringTrecho do nome do estádio, ginásio, pista ou quadra.
Exemplo: Maracanã
citystringTrecho da cidade do local da partida.
Exemplo: Rio de Janeiro
stagestringTrecho da fase ou estágio, incluindo fases internas de esportes com sets, períodos ou sessões.
Exemplo: Semifinal
roundstringTrecho do nome da rodada.
Exemplo: Rodada
roundNumberintegerNúmero exato da rodada.
Exemplo: 15
tvNetworkstringTrecho do nome, país ou id do canal de transmissão.
Exemplo: ESPN
eventTypestringTipo de evento presente na partida, quando disponível.
Exemplo: goal
startsAfter / startsBeforeepoch msLimites inclusivos para o horário de início dentro da data consultada.
Exemplo: 1784311200000
minHomeScore / maxHomeScoreintegerFaixa inclusiva do placar do mandante. Jogos sem placar não correspondem.
Exemplo: 1
minAwayScore / maxAwayScoreintegerFaixa inclusiva do placar do visitante. Jogos sem placar não correspondem.
Exemplo: 0
minTotalScore / maxTotalScoreintegerFaixa inclusiva da soma dos dois placares.
Exemplo: 3
hasScorebooleanExige (true) ou exclui (false) partidas com algum placar disponível.
Exemplo: true
hasVenuebooleanExige ou exclui dados do local.
Exemplo: true
hasBroadcastbooleanExige ou exclui canais de transmissão.
Exemplo: true
hasOfficialsbooleanExige ou exclui árbitros/oficiais.
Exemplo: true
hasEventsbooleanExige ou exclui eventos da partida.
Exemplo: true
hasStandingsbooleanExige ou exclui classificação/leaderboard embutido.
Exemplo: true
hasLiveClockbooleanExige ou exclui cronômetro, período ou tempo ao vivo.
Exemplo: true
sortByenumOrdena por startTime, homeScore, awayScore ou totalScore. Padrão: startTime.
Exemplo: startTime
sortOrderasc | descDireção da ordenação. Valores nulos de placar ficam por último.
Exemplo: asc
limitnumberItens por página (1–100). Padrão: 50.
Exemplo: 20
offsetnumberQuantidade de itens ignorados antes da página atual. Comece em 0 e depois use pagination.nextOffset.
Exemplo: 0
curl "https://sportsapi.com.br/api/v1/games/filter?sport=football&date=2026-08-20&team=Palmeiras&status=scheduled&limit=100" \
-H "X-API-Key: sapi_xxxx"curl "https://sportsapi.com.br/api/v1/games/filter?sport=football&q=libertadores&limit=50&offset=0" \
-H "X-API-Key: sapi_xxxx"Resposta completa do filtro
matches traz os objetos completos da página; filters confirma o que foi aplicado; pagination controla as próximas páginas; e facets permite montar seletores de campeonato, região e time com contagens.
{
"matches": [
{
"id": "a3f9c1e6b2d84f70",
"sport": "football",
"homeTeam": {
"id": "5981",
"name": "Flamengo",
"logo": "/api/v1/images/media/team/5981",
"color": "#D2122E"
},
"awayTeam": {
"id": "1963",
"name": "Palmeiras",
"logo": "/api/v1/images/media/team/1963"
},
"homeScore": 2,
"awayScore": 1,
"status": "live",
"startTime": 1784311200000,
"league": {
"id": "325",
"name": "Brasileirão Série A",
"country": "Brazil",
"logo": "/api/v1/images/media/tournament/325"
},
"gameTimeDisplay": "74'",
"period": "Segundo tempo",
"roundName": "Rodada",
"roundNum": 15,
"venue": {
"name": "Maracanã",
"capacity": 78838
},
"officials": [
"Anderson Daronco"
],
"tvNetworks": [
{
"id": 42,
"name": "ESPN",
"logo": "/api/v1/images/media/tv-channel/42"
}
]
}
],
"filters": {
"sport": "football",
"date": "2026-07-17",
"query": null,
"competition": "Serie A",
"region": "Brasil",
"team": "Flamengo",
"status": "live"
},
"pagination": {
"total": 1,
"limit": 20,
"offset": 0,
"returned": 1,
"hasNext": false,
"nextOffset": null
},
"statusCounts": {
"live": 1,
"scheduled": 0,
"finished": 0,
"postponed": 0,
"cancelled": 0
},
"facets": {
"competitions": [
{
"id": "325",
"name": "Brasileirão Série A",
"country": "Brasil",
"count": 1
}
],
"regions": [
{
"name": "Brasil",
"count": 1
}
],
"teams": [
{
"id": "5981",
"name": "Flamengo",
"count": 1
}
],
"venues": [
{
"name": "Maracanã",
"city": "Rio de Janeiro",
"country": "Brasil",
"count": 1
}
],
"broadcasters": [
{
"id": 42,
"name": "ESPN",
"country": "Brasil",
"count": 1
}
],
"stages": [
{
"name": "Rodada 15",
"count": 1
}
]
},
"cached": true,
"timestamp": 1784315678123
}Como paginar
Enquanto pagination.hasNext for verdadeiro, repita a mesma consulta usando offset=pagination.nextOffset. Não some valores manualmente.
Como usar as facetas
As contagens consideram todo o resultado depois dos filtros, antes de limit/offset. Há facetas de competições, regiões, times, locais, canais e fases. Use o id ou name retornado como próximo filtro.
const params = new URLSearchParams({ sport: 'football', region: 'Brasil', status: 'scheduled', limit: '100', offset: '0' });
const allMatches = [];
do {
const response = await fetch(`https://sportsapi.com.br/api/v1/games/filter?${params}`, {
headers: { 'X-API-Key': 'sapi_xxxx' },
});
if (!response.ok) throw new Error(`SportsAPI: ${response.status}`);
const data = await response.json();
allMatches.push(...data.matches);
if (!data.pagination.hasNext) break;
params.set('offset', String(data.pagination.nextOffset));
} while (true);matches: [] e HTTP 200 significa que nenhum jogo correspondeu à combinação informada. Remova um filtro por vez ou consulte as facetas de uma busca mais ampla. Parâmetros inválidos retornam HTTP 400; chave ausente/inválida retorna 401; esporte fora do plano retorna 403; cota do plano esgotada retorna 402; rate limit retorna 429.Formato da resposta
Todo dado é normalizado para o mesmo contrato. Nem todo campo aparece em toda partida — eles variam por esporte, então sempre trate campos ausentes como opcionais em vez de assumir que estarão sempre presentes.
{
"matches": [
{
"id": "a3f9c1e6b2d84f70",
"sport": "football",
"homeTeam": {
"id": "5981",
"name": "Flamengo",
"logo": "/api/v1/images/media/team/5981",
"color": "#D2122E"
},
"awayTeam": {
"id": "1963",
"name": "Palmeiras",
"logo": "/api/v1/images/media/team/1963"
},
"homeScore": 2,
"awayScore": 1,
"status": "live",
"startTime": 1784311200000,
"league": {
"id": "325",
"name": "Brasileirão Série A",
"country": "Brazil",
"logo": "/api/v1/images/media/tournament/325"
},
"gameTimeDisplay": "74'",
"period": "Segundo tempo",
"roundName": "Rodada",
"roundNum": 15,
"venue": {
"name": "Maracanã",
"capacity": 78838
},
"officials": [
"Anderson Daronco"
],
"tvNetworks": [
{
"id": 42,
"name": "ESPN",
"logo": "/api/v1/images/media/tv-channel/42"
}
]
}
],
"total": 1,
"limit": 50,
"offset": 0,
"cached": false,
"timestamp": 1784315678123
}Referência completa de campos
idstringId único e opaco da partida.
sportstringModalidade da partida.
statusstringscheduled · live · finished · postponed · cancelled.
startTimenumberHorário de início em epoch ms (UTC).
homeScore / awayScorenumber | nullPlacar atual. null antes do jogo começar.
gameTimeDisplaystring (opcional)Cronômetro legível, ex.: "74'" ou "Q3 08:12".
periodstring (opcional)Tempo/quarto/set atual, quando informado.
roundName / stageNamestring (opcional)Fase do torneio, ex.: "Quarterfinal", "Grupo A". Em automobilismo, roundName é a sessão do fim de semana ("Treino Livre 1", "Classificação", "Corrida") — cada sessão vem como uma partida separada.
roundNumnumber (opcional)Número da rodada dentro da fase atual, quando a competição usa esse formato.
homeTeam.id / awayTeam.idstringId do time.
homeTeam.name / awayTeam.namestringNome do time.
homeTeam.logo / awayTeam.logostring (opcional)Caminho relativo — veja a seção "Imagens" abaixo antes de usar.
homeTeam.color / awayColorstring (opcional)Cor do time em hex, quando disponível.
league.id / league.name / league.countrystringDados da competição.
league.logostring (opcional)Caminho relativo — veja a seção "Imagens" abaixo.
venue.name / venue.capacitystring / number (opcional)Local da partida — populado para a maioria dos jogos, mesmo sem transmissão.
venue.city / venue.country / venue.courtstring (opcional)Detalhes adicionais do local, quando disponíveis (court é a quadra específica, comum em tênis).
officials[]string[] (opcional)Árbitro(s)/oficiais da partida, quando reportado.
tvNetworks[].name / tvNetworks[].logostring (opcional)Canais de transmissão — cobertura naturalmente esparsa fora dos jogos de maior perfil.
stages[]array (opcional)Parciais por set/quarto/tempo extra — comum em tênis, vôlei, basquete.
events[]array (opcional)Linha do tempo de lances (gols, cartões, etc.), quando reportado.
standings[]array (opcional)Classificação completa para golfe (todos os jogadores) e automobilismo (todos os pilotos da sessão) — essas modalidades não são "A vs B"; homeTeam/awayTeam trazem só os 2 primeiros, standings[] traz o campo inteiro (position, name, logo, isWinner, score).
Detalhes da partida e odds
Use o mesmo id recebido em /games para buscar estatísticas, escalações, incidentes, histórico, previsões, odds e narração. Os blocos são opcionais: a fonte pode não fornecer todos os dados para todos os esportes.
Nem todos os jogos têm odds, estatísticas ou informações detalhadas publicadas. A cobertura depende do que nossos provedores disponibilizam; partidas de menor audiência ou sem interesse em apostas podem não ter esses dados. Quando um campo ou mercado não aparece, significa que a fonte não forneceu essa informação — isso, por si só, não é erro da API.
Se uma partida de grande relevância estiver sem dados, informe ao suporte o ID da partida, o endpoint consultado e o cabeçalho X-Request-Id da resposta. Vamos analisar o caso imediatamente para evitar impacto no seu produto.
curl "https://sportsapi.com.br/api/v1/games/a3f9c1e6b2d84f70/details?sport=football" \
-H "X-API-Key: sapi_xxxx"O campo matchId identifica a partida. O campo timestamp identifica o momento em que a resposta foi gerada. O cache da API pode manter dados por alguns segundos, especialmente em partidas ao vivo.
{
"matchId": "a3f9c1e6b2d84f70",
"info": {
"intro": "Flamengo recebe o Palmeiras pela 15ª rodada.",
"referee": {
"name": "Anderson Daronco",
"yellowCardsPerGame": 4.2,
"redCardsPerGame": 0.2
},
"weather": {
"conditionCode": 1,
"temperature": "24°C",
"wind": "12 km/h",
"humidity": "72%",
"pressure": "1015 hPa"
}
},
"statistics": [
{
"key": "possession",
"label": "Possession",
"home": "55%",
"away": "45%"
},
{
"key": "shots",
"label": "Shots",
"home": "10",
"away": "7"
},
{
"key": "corners",
"label": "Corners",
"home": "6",
"away": "3"
}
],
"periodStatistics": [
{
"period": "1ST",
"stats": [
{
"key": "goals",
"label": "Goals",
"home": "1",
"away": "0"
}
]
},
{
"period": "2ND",
"stats": [
{
"key": "goals",
"label": "Goals",
"home": "1",
"away": "1"
}
]
}
],
"incidents": [
{
"type": "goal",
"minute": "35'",
"side": "home",
"playerName": "Pedro",
"assistName": "Arrascaeta",
"homeScore": 1,
"awayScore": 0
},
{
"type": "yellow_card",
"minute": "61'",
"side": "away",
"playerName": "Gustavo Gómez",
"homeScore": 1,
"awayScore": 0
},
{
"type": "substitution",
"minute": "70'",
"side": "home",
"playerInName": "Everton",
"playerOutName": "Bruno Henrique",
"homeScore": 1,
"awayScore": 0
}
],
"lineups": {
"confirmed": true,
"home": {
"formation": "4-3-3",
"manager": "Filipe Luís",
"teamRating": 7.2,
"starters": [
{
"playerName": "Pedro",
"shirtNumber": 9,
"position": "F",
"rating": 7.8,
"isCaptain": false,
"substitute": false,
"minutesPlayed": 90,
"isBest": true
}
],
"substitutes": [
{
"playerName": "Everton",
"shirtNumber": 11,
"position": "F",
"rating": 6.8,
"substitute": true,
"minutesPlayed": 20
}
]
},
"away": {
"formation": "4-2-3-1",
"manager": "Abel Ferreira",
"teamRating": 6.9,
"starters": [
{
"playerName": "Gustavo Gómez",
"shirtNumber": 15,
"position": "D",
"rating": 6.7,
"isCaptain": true,
"substitute": false,
"minutesPlayed": 90
}
],
"substitutes": []
},
"injuries": [
{
"side": "away",
"playerName": "Nome do Jogador",
"reason": "Lesão muscular"
}
]
},
"playerStats": [
{
"playerName": "Pedro",
"playerId": 123,
"side": "home",
"minutesPlayed": 90,
"rating": 7.8,
"goals": 1,
"assists": 0,
"shots": 4,
"shotsOnTarget": 2,
"expectedGoals": 0.72,
"expectedAssists": 0.18,
"passes": 28,
"tackles": 1,
"interceptions": 0,
"duelsWon": 5,
"yellowCards": 0,
"redCards": 0
}
],
"standings": [
{
"name": "Classificação Geral",
"groupName": "Série A",
"rows": [
{
"position": 1,
"teamName": "Flamengo",
"played": 14,
"won": 10,
"drew": 2,
"lost": 2,
"goalsFor": 30,
"goalsAgainst": 12,
"goalDiff": 18,
"points": 32,
"note": "Classificação para a Libertadores"
}
]
}
],
"headToHead": {
"totalMatches": 10,
"homeWins": 5,
"draws": 2,
"awayWins": 3,
"homeGoals": 16,
"awayGoals": 12,
"meetings": [
{
"startTime": 1780000000000,
"competitionName": "Brasileirão Série A",
"homeTeamName": "Flamengo",
"awayTeamName": "Palmeiras",
"homeScore": 2,
"awayScore": 1
}
]
},
"form": [
{
"side": "home",
"matches": 5,
"wins": 3,
"draws": 1,
"losses": 1,
"goals": 9,
"conceded": 5,
"avgPossessionPercent": 56,
"avgShots": "12.4",
"winStreak": 2,
"unbeatenRun": 3,
"overGoalsPercent": 60,
"bothTeamsScorePercent": 80,
"underCornersPercent": 40,
"recent": []
},
{
"side": "away",
"matches": 5,
"wins": 2,
"draws": 2,
"losses": 1,
"goals": 7,
"conceded": 6,
"avgPossessionPercent": 48,
"avgShots": "9.8",
"winStreak": 1,
"unbeatenRun": 2,
"overGoalsPercent": 40,
"bothTeamsScorePercent": 60,
"underCornersPercent": 50,
"recent": []
}
],
"prediction": {
"h2h": {
"totalMatches": 10,
"homeWinPercent": 50,
"drawPercent": 20,
"awayWinPercent": 30
},
"homeForm": {
"matches": 5,
"wins": 3,
"draws": 1,
"losses": 1,
"goals": 9,
"conceded": 5,
"avgPossessionPercent": 56,
"avgShots": "12.4",
"winStreak": 2,
"unbeatenRun": 3,
"overGoalsPercent": 60,
"bothTeamsScorePercent": 80,
"underCornersPercent": 40
},
"awayForm": {
"matches": 5,
"wins": 2,
"draws": 2,
"losses": 1,
"goals": 7,
"conceded": 6,
"avgPossessionPercent": 48,
"avgShots": "9.8",
"winStreak": 1,
"unbeatenRun": 2,
"overGoalsPercent": 40,
"bothTeamsScorePercent": 60,
"underCornersPercent": 50
},
"goalTiming": [
{
"side": "home",
"scored": [
{
"fromMinute": 0,
"toMinute": 15,
"percent": 20
}
],
"conceded": [
{
"fromMinute": 76,
"toMinute": 90,
"percent": 30
}
]
},
{
"side": "away",
"scored": [],
"conceded": []
}
]
},
"odds": {
"markets": [
{
"key": "1x2",
"positions": [
"home",
"draw",
"away"
],
"quotes": [
{
"companyName": "Bet365",
"opening": [
2.1,
3.4,
3.2
],
"latest": [
1.95,
3.5,
3.6
]
},
{
"companyName": "Betano",
"latest": [
2,
3.4,
3.55
]
}
]
},
{
"key": "handicap",
"positions": [
"home",
"line",
"away"
],
"quotes": [
{
"companyName": "Bet365",
"opening": [
1.9,
-0.5,
1.95
],
"latest": [
1.85,
-0.5,
2
]
}
]
},
{
"key": "over_under",
"positions": [
"over",
"line",
"under"
],
"quotes": [
{
"companyName": "Bet365",
"opening": [
1.9,
2.5,
1.9
],
"latest": [
1.8,
2.5,
2.05
]
}
]
},
{
"key": "corners",
"positions": [
"over",
"line",
"under"
],
"quotes": [
{
"companyName": "Bet365",
"latest": [
1.9,
9.5,
1.9
]
}
]
}
],
"impliedProbabilities": {
"homePercent": 48.7,
"drawPercent": 27.1,
"awayPercent": 24.2
}
},
"commentary": [
{
"chapter": 1,
"minute": "35'",
"content": "Gol do time da casa",
"scoreLine": "1-0",
"isGoal": true
}
],
"ballByBall": [
{
"over": 1,
"inning": 1,
"runs": 6,
"wickets": 0,
"balls": 6
}
],
"scorecards": [
{
"number": 1,
"battingTeamName": "Time Exemplo",
"runs": 180,
"ballsFaced": 120,
"fours": 15,
"sixes": 4,
"runRate": "9.00",
"bowlingTeamName": "Time Adversário",
"overs": 20,
"wickets": 5
}
],
"timestamp": 1784315678123
}statistics / periodStatisticsEstatísticas gerais e separadas por período. Valores home/away são strings porque podem conter percentuais, médias ou textos da fonte.
incidents / commentaryIncidentes estruturados e narração textual. Gol, cartões e substituições podem ter campos diferentes.
lineups / playerStatsEscalações, reservas, lesões e estatísticas individuais quando disponíveis.
headToHead / form / predictionConfrontos anteriores, forma recente, tendências e sinais estatísticos. Não são garantia de resultado.
oddsMercados por casa de aposta, com odds de abertura e mais recentes quando a fonte informa ambas.
ballByBall / scorecardsDados específicos para críquete e beisebol; normalmente ausentes no futebol.
Estrutura de odds
Cada posição do array latest ou opening corresponde à mesma posição em positions. Em 1x2, por exemplo, a ordem é casa, empate, visitante. impliedProbabilities são probabilidades normalizadas sem a margem da casa, calculadas a partir das melhores odds 1X2 recentes.
Se a sua aplicação só precisa de cotações, chame GET /games/:id/odds no lugar desta: a resposta é menor e o bloco odds aqui vem junto de estatísticas, escalações e narração que você não vai usar. A lista completa de mercados está na seção Odds por partida.
Odds por partida
Chame GET /api/v1/games/:id/odds quando você quiser apenas as cotações de uma partida — odds de abertura e as mais recentes de cada casa de aposta, mercados de handicap, gols, escanteios e cartões, e as probabilidades sem margem. Basta o id devolvido em /games; na maioria dos casos você nem precisa informar data ou esporte.
Mais leve que /details
Só a seção de odds é coletada. É a chamada certa para telas de cotação, comparadores e pré-jogo.
Nome de mercado estável
Cada mercado vem com uma chave canônica em markets[].key — sempre a mesma, independentemente de como a fonte escreve o nome do mercado.
Time só com id e nome
homeTeam e awayTeam trazem id e name — sem logo, cor ou placar. Busque esses campos em /games.
curl "https://sportsapi.com.br/api/v1/games/a3f9c1e6b2d84f70/odds?sport=football" \
-H "X-API-Key: sapi_xxxx"curl "https://sportsapi.com.br/api/v1/games/a3f9c1e6b2d84f70/odds?sport=football&market=over_under&bookmaker=bet365" \
-H "X-API-Key: sapi_xxxx"Parâmetros
idpathId da partida, exatamente como devolvido em /games. É o mesmo id usado em /games/:id e /games/:id/details.
Exemplo: a3f9c1e6b2d84f70
sportenumEsporte da partida. Padrão: football. Acelera a resolução quando você já sabe o esporte.
Exemplo: football
dateYYYY-MM-DDDia da partida. Sem esse parâmetro, a API procura hoje, os sete dias anteriores e o dia seguinte — por isso partidas finalizadas há pouco continuam resolvendo.
Exemplo: 2026-08-20
marketstring (1–60)Mantém apenas mercados cuja key contenha esse texto. A comparação ignora caixa e acentos: 1x2, handicap, over_under, corners, cards, both_teams_score, double_chance, first_goal.
Exemplo: over_under
bookmakerstring (1–80)Mantém apenas cotações de casas de aposta cujo nome contenha esse texto. A comparação ignora caixa e acentos.
Exemplo: bet365
{
"matchId": "a3f9c1e6b2d84f70",
"sport": "football",
"status": "scheduled",
"startTime": 1784311200000,
"homeTeam": {
"id": "5981",
"name": "Flamengo"
},
"awayTeam": {
"id": "1963",
"name": "Palmeiras"
},
"league": {
"id": "325",
"name": "Brasileirão Série A"
},
"markets": [
{
"key": "1x2",
"label": "Resultado Final",
"positions": [
"home",
"draw",
"away"
],
"quotes": [
{
"companyName": "Bet365",
"opening": [
2.1,
3.4,
3.2
],
"latest": [
1.95,
3.5,
3.6
]
},
{
"companyName": "Betano",
"latest": [
2,
3.45,
3.55
]
}
]
},
{
"key": "double_chance",
"label": "Dupla Chance",
"positions": [
"home",
"draw_or_away",
"draw_or_home",
"away"
],
"quotes": [
{
"companyName": "Bet365",
"opening": [
1.32,
1.55,
1.36,
2.3
],
"latest": [
1.28,
1.6,
1.32,
2.45
]
}
]
},
{
"key": "both_teams_score",
"label": "Ambos Marcam",
"positions": [
"yes",
"no"
],
"quotes": [
{
"companyName": "Bet365",
"opening": [
1.72,
2.05
],
"latest": [
1.65,
2.15
]
}
]
},
{
"key": "handicap",
"label": "Handicap -0.5",
"positions": [
"home",
"line",
"away"
],
"line": -0.5,
"quotes": [
{
"companyName": "Bet365",
"opening": [
1.9,
-0.5,
1.95
],
"latest": [
1.85,
-0.5,
2
]
}
]
},
{
"key": "over_under",
"label": "Total de Gols (2.5)",
"positions": [
"over",
"line",
"under"
],
"line": 2.5,
"quotes": [
{
"companyName": "Bet365",
"opening": [
1.9,
2.5,
1.9
],
"latest": [
1.8,
2.5,
2.05
]
},
{
"companyName": "Betano",
"latest": [
1.95,
2.5,
1.9
]
}
]
},
{
"key": "corners",
"label": "Escanteios (9.5)",
"positions": [
"over",
"line",
"under"
],
"line": 9.5,
"quotes": [
{
"companyName": "Bet365",
"latest": [
1.9,
9.5,
1.9
]
}
]
},
{
"key": "cards",
"label": "Cartões (5.5)",
"positions": [
"over",
"line",
"under"
],
"line": 5.5,
"quotes": [
{
"companyName": "Bet365",
"opening": [
2,
5.5,
1.8
],
"latest": [
1.95,
5.5,
1.85
]
}
]
},
{
"key": "first_goal",
"label": "Primeiro Gol",
"positions": [
"home",
"draw",
"away"
],
"quotes": [
{
"companyName": "Bet365",
"latest": [
4.5,
7,
5
]
}
]
}
],
"impliedProbabilities": {
"homePercent": 47,
"drawPercent": 26.9,
"awayPercent": 26.1
},
"bookmakers": [
"Bet365",
"Betano"
],
"timestamp": 1784315678123
}No topo da resposta, bookmakers lista todas as casas de aposta presentes na resposta (sem repetição, em ordem alfabética) e timestamp diz quando ela foi gerada. status e startTime são os mesmos da partida em /games.
Como ler positions, line, opening e latest
Os arrays de cotação são posicionais: o valor na posição i de opening e de latest significa o que estiver na posição i de positions. Nunca assuma a ordem — leia positions e localize o índice.
positionsNomeia cada posição dos arrays de cotação, sem depender da casa de aposta. Também pode conter "line", o slot da linha do mercado.
lineA linha cotada (handicap ou total, como -0.5 ou 2.5). Quando a fonte manda a linha dentro do array, ela aparece nos dois lugares: em markets[].line e na posição "line".
openingPreço de abertura em decimal. Uma casa de aposta pode publicar só a abertura, só a cotação mais recente, ou as duas.
latestPreço mais recente em decimal — o que você mostra na tela. Fica ausente quando a fonte não publica um preço novo.
const market = data.markets.find((item) => item.key === '1x2');
const quote = market.quotes.find((item) => item.companyName === 'Bet365');
const homeIndex = market.positions.indexOf('home');
const homePrice = quote.latest?.[homeIndex]; // 1.95
const openedAt = quote.opening?.[homeIndex]; // 2.1
const drifted = Number((homePrice - openedAt).toFixed(2)); // -0.15
// Mercado com linha: a linha também vem em markets[].line
const total = data.markets.find((item) => item.key === 'over_under');
console.log(total.line, total.positions); // 2.5 ['over', 'line', 'under']Mercados canônicos
markets[].key é sempre a mesma palavra-chave para o mesmo mercado, mesmo que a casa de aposta ou a fonte escreva o nome de outro jeito (Resultado Final, Full Time Result, Dupla Chance, +/-, Cantinho...). Use a key para decidir o que renderizar e label para exibir. Um mercado fora deste vocabulário volta com o nome da fonte em forma de slug — nesse caso, use apenas para exibição.
1x2[home, draw, away]Resultado final: casa, empate e visitante.
double_chance[home, draw_or_away, draw_or_home, away]Casa ou empate, visitante ou empate, casa ou visitante, e só visitante. Quatro posições.
both_teams_score[yes, no]Ambos os times marcam.
handicap[home, line, away]Handicap: preço do mandante, linha e preço do visitante.
over_under[over, line, under]Total de gols/pontos: acima, linha e abaixo.
corners[over, line, under]Total de escanteios: acima, linha e abaixo.
cards[over, line, under]Total de cartões: acima, linha e abaixo.
first_goal[home, draw, away]Quem marca o primeiro gol. Aqui draw significa "ninguém marca".
impliedProbabilities
Probabilidades sem a margem da casa (soma 100%), derivadas do melhor preço disponível para cada desfecho do mercado 1X2 — no exemplo, 2.00 · 3.50 · 3.60 geram { homePercent: 47.0, drawPercent: 26.9, awayPercent: 26.1 }. O campo só existe quando a resposta traz o mercado 1X2: filtrando com market=over_under, ele simplesmente não vem.
Odds vazias, limites e cota
market= / bookmaker= que eliminou tudo — não é erro: a resposta é HTTP 200 com markets: [] e bookmakers: []. Isso significa que nossos provedores não publicaram cotações para esse jogo. O 404 fica reservado para o id que não pode ser localizado. Não faça retry em laço apertado — trate como "sem cotação" e siga. Se for uma partida de grande relevância, avise o suporte para análise imediata.RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset dizem até quando esperar. Cada chamada de odds é uma requisição da sua cota, e respostas 4xx — inclusive o 404 de id inexistente — também contam. Somente falhas internas (HTTP 5xx) não são contabilizadas. Odds mudam o tempo todo durante a partida: cache a resposta por alguns segundos e não faça polling agressivo em todo o evento ao vivo.const response = await fetch(
'https://sportsapi.com.br/api/v1/games/a3f9c1e6b2d84f70/odds?sport=football&market=1x2',
{ headers: { 'X-API-Key': 'sapi_xxxx' } }
);
if (response.status === 404) {
// Id inexistente: não é uma partida válida.
return null;
}
if (!response.ok) throw new Error(`SportsAPI: ${response.status}`);
const odds = await response.json();
if (!odds.markets.length) {
// Sem cotação publicada (ou filtro que eliminou tudo): siga sem mercado.
return null;
}
const oneXTwo = odds.markets.find((market) => market.key === '1x2');
const best = oneXTwo.quotes.map((quote) => ({
bookmaker: quote.companyName,
home: quote.latest?.[oneXTwo.positions.indexOf('home')],
draw: quote.latest?.[oneXTwo.positions.indexOf('draw')],
away: quote.latest?.[oneXTwo.positions.indexOf('away')],
}));
console.log(best, odds.impliedProbabilities, odds.bookmakers);Esportes disponíveis
Chame /api/v1/sports para descobrir, em tempo real, quais esportes o seu plano libera — em vez de fixar essa lista no seu código. O resultado muda automaticamente se você fizer upgrade de plano.
curl "https://sportsapi.com.br/api/v1/sports" \
-H "X-API-Key: sapi_xxxx"{
"sports": [
{
"slug": "football",
"sport": "football",
"label": "Futebol"
},
{
"slug": "basketball",
"sport": "basketball",
"label": "Basquete"
},
{
"slug": "tennis",
"sport": "tennis",
"label": "Tênis"
}
],
"total": 3
}Imagens (logos e ícones)
Todo campo logo (time, liga, canal de TV) já vem como um caminho relativo, servido pelo nosso próprio proxy de imagens — nunca uma URL absoluta de terceiros.
Basta concatenar o caminho recebido com a URL base da API para exibir a imagem:https://sportsapi.com.br + match.homeTeam.logo
Esses endpoints de imagem são públicos (não exigem X-API-Key) e ficam por trás de um limite de requisições próprio, separado da sua cota de dados — então usá-los para renderizar logos não consome sua franquia mensal.
/api/v1/images/proxy?url=Proxy genérico para uma URL https já conhecida, de um host permitido.
/api/v1/images/media/team/:idLogo de um time, a partir do id numérico.
/api/v1/images/media/tournament/:idLogo de um torneio.
/api/v1/images/media/tv-channel/:idLogo de um canal de TV.
/api/v1/images/media/league?uniqueId=Logo de competição/categoria (aceita uniqueId, tournamentId ou categoryId).
Erros
400Requisição inválidaRevise os parâmetros enviados.401Não autorizadoAPI key ausente, inválida ou revogada.402Assinatura necessáriaTrial expirado ou cota mensal esgotada.403Plano insuficienteEsporte não incluso no seu plano.404Não encontradoO recurso solicitado não existe.429Limite excedidoAguarde a janela indicada nos headers RateLimit-*.500Erro internoTente novamente e contate o suporte se persistir.