Estado de Jogo ao Vivo
Estado agregado de jogos ao vivo — placares, períodos, cronômetros, posse de bola e dados situacionais específicos do esporte — mesclados entre sportsbooks em uma única visão autoritativa por evento. Uma linha por partida ao vivo, selecionada por consenso entre os books que a cobrem. Uma partida que acabou de terminar também permanece aqui por uma janela curta como linha terminal — veja Eventos finalizados.
GET /api/v1/gamestate
GET /api/v1/gamestate/{sport}Autenticação
Requer API key. Disponível no add-on Game State (US$ 79/mês) ou no
tier Enterprise. Keys sem nenhum dos dois recebem 403 tier_restricted
com addon: "game_state" no corpo do erro. Adicione o add-on pela
página de billing em qualquer plano pago
(Hobby / Pro / Sharp); keys Enterprise já o incluem.
O estado de jogo ao vivo também é transmitido pelo canal gamestate via
SSE e WebSocket
— veja Streaming abaixo. (O acesso ao streaming requer o
add-on WebSocket ou Enterprise, além do requisito de Game State
acima.)
Parâmetros de Path
| Parâmetro | Tipo | Descrição |
|---|---|---|
sport | string | (opcional) Esporte único a ser retornado. Corresponde a um esporte conhecido do Atlas (soccer, tennis, esports, basketball, table_tennis, baseball, hockey, cricket, volleyball, handball, football, olympics, darts, boxing, rugby_league, mma, aussie_rules, snooker, rugby_union, golf, floorball, water_polo, lacrosse, futsal, other). Não diferencia maiúsculas/minúsculas. Um esporte desconhecido retorna um objeto data vazio — e um esporte suportado sem nada ao vivo no momento também; veja a nota abaixo. |
Um objeto data vazio não significa que o esporte não é suportado. O game state
é mesclado por esporte em uma chave gamestate:{sport} com TTL de 60 segundos
(GAMESTATE_TTL, deliberadamente o dobro da janela livestate de 30 segundos);
sem nada ao vivo a chave expira e a resposta é {"data":{}}, idêntica à de um
esporte não coberto. Vários dos esportes acima são sazonais.
Use GET /api/v1/sports para ver event_count e live_count por esporte.
other é o bucket de reserva para esportes que o Atlas não mapeou.
Sem o parâmetro de path, retorna todos os esportes com eventos ao vivo.
Filtros de consulta
Ambos valem para /api/v1/gamestate e /api/v1/gamestate/{sport}. Os valores
não diferenciam maiúsculas e são aparados. Se você omitir os dois, recebe o
conjunto padrão: tudo o que está ao vivo, mais as linhas finalizadas que ainda
estão dentro da janela de retenção.
| Parâmetro | Aceito | Descrição |
|---|---|---|
status | final | Retorna apenas as linhas finalizadas retidas — o espelho de /events?status=final. Qualquer outro valor é um 400 validation_error (veja abaixo). Não existe o valor live: linhas ao vivo são o conjunto padrão, e ?is_live=true pede só elas. |
is_live | true, false | true retorna apenas linhas ao vivo, o que exclui todas as linhas finalizadas. false retorna apenas linhas não ao vivo, que hoje são as linhas finalizadas retidas. Qualquer outro valor — incluindo 1 e 0 — é um 400 validation_error. |
Um valor não reconhecido é rejeitado, não ignorado. Tanto ?status=zzz
quanto ?is_live=1 retornam 400 com um corpo validation_error que nomeia
o conjunto aceito, em vez de devolver silenciosamente as linhas sem filtro ou
as do lado errado.
{
"error": {
"code": "validation_error",
"message": "is_live must be one of: true, false",
"details": { "parameter": "is_live", "accepted": ["true", "false"] }
}
}Antes ?is_live=1 era lido como false e devolvia exatamente as linhas que
quem chamava queria excluir; agora é um 400. Se você envia 1 / 0, mude
para true / false.
Envelope da Resposta
{
"data": {
"<sport>": {
"<event_id>": { ...event state... }
}
},
"updated_at": "2026-04-23T23:55:01.234Z"
}Cada estado de evento é indexado pelo seu event_id canônico dentro do
bucket do esporte. updated_at é o horário do servidor quando esta resposta foi construída
— use-o para avaliar a atualidade se estiver fazendo polling.
Campos do Estado do Evento
Todos os campos são opcionais, exceto onde indicado. A presença varia por esporte —
eventos de tênis carregam sets_home / server; eventos de hóquei carregam
power_play; eventos de futebol carregam corners_* / yellow_cards_*; etc.
Sempre presentes
| Campo | Tipo | Notas |
|---|---|---|
home_team | string | Nome normalizado do time da casa |
away_team | string | Nome normalizado do time visitante |
sport | string | Bucket de esporte (baseball, soccer, …) |
league | string | ID da liga no Atlas (ex. mlb, england_-_premier_league) |
home_score | integer | Placar atual da casa na unidade natural do esporte (corridas, pontos, gols, sets — tênis usa pontos somados de sets) |
away_score | integer | Placar atual do visitante |
is_live | boolean | true em um evento ao vivo. false em uma linha finalizada retida — veja Eventos finalizados |
primary_book | string | O book de onde os placares mesclados foram selecionados. Respeita o consenso — books de maior qualidade vencem empates |
book_count | integer | Número de sportsbooks que contribuíram com estado ao vivo para este evento no momento da mesclagem |
“Sempre presentes” significa sempre presentes em uma linha ao vivo. Uma
linha finalizada retida é montada a partir do registro de conclusão, não de
uma mesclagem ao vivo, então não carrega primary_book nem book_count, e
nenhum game_period / game_clock. Os campos de placar também dependem do
esporte: esportes por sets carregam sets_home / sets_away em vez de
home_score / away_score. Verifique status === "final" antes de ler
qualquer um desses campos. A forma terminal completa está em
Eventos finalizados.
Temporais (maioria dos esportes em equipe)
| Campo | Tipo | Exemplo |
|---|---|---|
game_period | string | "T5" (MLB topo do 5º), "Q3" (NBA Q3), "2H" (futebol 2º tempo), "P2" (NHL), "S2" (tênis 2º set), "FT" (tempo final) |
game_clock | string | "49:06" para esportes com contagem progressiva (futebol), "5:42" para esportes com contagem regressiva (basquete, hóquei) |
Situacionais
| Campo | Tipo | Notas |
|---|---|---|
possession | "home" | "away" | Qual time tem a posse (esportes em equipe) ou está sacando (tênis) |
server | "home" | "away" | Apenas tênis — sacador atual |
last_play | string | Descrição da última jogada específica do esporte, quando disponível |
is_timeout | boolean | true durante um timeout |
Específicos do esporte
| Campo | Esporte | Tipo |
|---|---|---|
fouls_home, fouls_away | basketball, soccer | integer |
corners_home, corners_away | soccer | integer |
yellow_cards_home, yellow_cards_away | soccer | integer |
red_cards_home, red_cards_away | soccer | integer |
power_play | hockey | "home" | "away" |
sets_home, sets_away | tennis | integer (sets vencidos) |
hits_home, hits_away | baseball | integer |
home_pitcher, away_pitcher | baseball | string — nome de exibição do arremessador titular |
wickets_home, wickets_away | cricket | integer |
overs | cricket | float |
batting_team | cricket | "home" | "away" |
Atualidade
| Campo | Tipo | Significado |
|---|---|---|
stale | true (omitido caso contrário) | O TTL da chave de livestate do primary_book caiu abaixo do limite de atualidade do agregador (~10s) no momento da mesclagem. Os placares são os últimos valores conhecidos; o book ficou em silêncio. Trate os placares e o período do evento como potencialmente atrasados em relação ao estado real do jogo. |
aggregator_stale | true (omitido caso contrário) | O próprio agregador SharpAPI não escreveu um novo shard gamestate:{sport} para este esporte em mais de ~30s. Sinal mais amplo do que stale — indica um soluço no pipeline, não um problema isolado de um book. Se você ver isso de forma persistente, entre em contato com o suporte. |
Ausência = atualizado. Tanto stale quanto aggregator_stale só são
escritos quando verdadeiros, mantendo o payload compacto. Se você não os
vê em um evento, os dados são considerados atualizados.
Eventos finalizados
Antes, uma partida sumia de /gamestate no instante em que terminava — o placar
final só era visível para o cliente que por acaso fizesse polling durante o
último ciclo ao vivo. Agora os eventos finalizados permanecem neste endpoint por
15 minutos após a conclusão como linha terminal, de modo que qualquer
cliente com um intervalo razoável veja o resultado final pelo menos uma vez.
A janela é do lado do servidor e hoje é de 15 minutos
(GAMESTATE_TERMINAL_CARRY_SECONDS, padrão 900). Depois disso a linha some de
vez; o resultado encerrado continua disponível em
/api/v1/events?status=final, que é a origem da linha
terminal.
Como é uma linha terminal
{
"home_team": "Skelleftea AIK",
"away_team": "Geneva Servette HC",
"sport": "hockey",
"league": "champions_hockey_league",
"home_score": 2,
"away_score": 1,
"score_type": "points",
"winner": "home",
"status": "final",
"completed_at": "2026-09-10T18:54:03Z",
"is_live": false,
"stale": false
}| Campo | Tipo | Notas |
|---|---|---|
status | "final" | Presente apenas em uma linha terminal. Linhas ao vivo não carregam nenhuma chave status, então status === "final" é o teste confiável — não deduza a partir de is_live. |
completed_at | string | ISO 8601 — quando a SharpAPI detectou a conclusão, não o apito final oficial da liga. A janela de retenção é medida a partir deste timestamp. |
score_type | "points" | "sets" | Quais campos carregam o resultado. "sets" para os esportes por sets (tennis, table_tennis, volleyball, badminton); "points" em todo o resto. |
winner | "home" | "away" | "draw" | Opcional — omitido quando o resultado não pode ser determinado; veja abaixo. "draw" só é produzido em esportes nos quais um placar igual é um resultado real (soccer, hockey, football, rugby_union, rugby_league). |
is_live | false | Sempre false de forma explícita, para que qualquer renderização condicionada a is_live trate a linha como não ao vivo. |
stale | false | Sempre false de forma explícita. É o único lugar em que stale aparece sem ser verdadeiro — a regra “ausência = atualizado” acima vale só para linhas ao vivo. |
Os placares finais usam os nomes de campo ao vivo, conforme o esporte. Um
esporte por pontos coloca sua contagem em home_score / away_score. Um
esporte por sets coloca sua contagem de sets em sets_home / sets_away e
não carrega home_score / away_score — um final de tênis é 2-1 em sets, e
colocar isso em home_score colidiria com os pontos de game da linha ao vivo:
{
"home_team": "Mikhail Biserov",
"away_team": "Vasily Yugov",
"sport": "table_tennis",
"league": "pro_league",
"sets_home": 2,
"sets_away": 1,
"score_type": "sets",
"winner": "home",
"status": "final",
"completed_at": "2026-09-10T18:55:25Z",
"is_live": false,
"stale": false
}winner pode faltar, e isso significa algo. O campo é omitido quando o
resultado registrado não encerra a partida:
- um placar igual em um esporte que não admite empate (basquete, handebol, esports, beisebol…) — o jogo foi para a prorrogação, foi abandonado, ou o feed parou antes da decisão;
- uma contagem de sets sub-terminal em um esporte por sets (quem lidera com menos de dois sets não pode ter vencido nenhum formato);
- um snapshot cujos placares nunca chegaram, registrado como
0-0. Por isso um0-0semwinnernão é o mesmo que um empate sem gols real — o real carrega"winner": "draw".
Trate um winner ausente como “não decidido”, não como empate. Se você está
gradando ou liquidando algo, pule essas linhas.
Quais campos uma linha terminal não carrega
Uma linha terminal é projetada a partir do registro de conclusão, não de uma
mesclagem ao vivo entre books, então estes campos ao vivo simplesmente não
existem: primary_book, book_count, game_period, game_clock e todos os
campos situacionais (possession, corners_*, power_play, in_play, a
família de probabilidade de vitória do tênis, e assim por diante). Leia-os
somente depois de conferir que a linha não é status: "final".
Filtrando eventos finalizados
| O que você quer | Requisição |
|---|---|
| Só linhas ao vivo (comportamento anterior à retenção) | ?is_live=true |
| Só linhas finalizadas | ?status=final |
| Ambas (padrão) | nenhum parâmetro |
# Só os finais dos últimos 15 minutos, um esporte
curl "https://api.sharpapi.io/api/v1/gamestate/soccer?status=final" \
-H "X-API-Key: YOUR_API_KEY"
# Exatamente o que você recebia antes da retenção existir
curl "https://api.sharpapi.io/api/v1/gamestate?is_live=true" \
-H "X-API-Key: YOUR_API_KEY"Se um evento finalizado ainda estiver ao vivo em algum lugar, a linha ao
vivo vence. Um event_id que aparece tanto no conjunto ao vivo quanto no
retido é retornado uma única vez, como linha ao vivo. Um book que ainda
precifica o jogo o mantém ao vivo até que todos o tenham largado.
Exemplos de Requisição
cURL
# Todos os eventos ao vivo de todos os esportes
curl "https://api.sharpapi.io/api/v1/gamestate" \
-H "X-API-Key: YOUR_API_KEY"
# Um esporte
curl "https://api.sharpapi.io/api/v1/gamestate/soccer" \
-H "X-API-Key: YOUR_API_KEY"Exemplo de Resposta
{
"data": {
"soccer": {
"argentina_-_primera_division_bocajuniors_defensayjusticia_2026-04-23": {
"home_team": "Defensa y Justicia",
"away_team": "Boca Juniors",
"sport": "soccer",
"league": "argentina_-_primera_division",
"home_score": 0,
"away_score": 1,
"game_period": "2H",
"game_clock": "49:06",
"is_live": true,
"possession": "away",
"corners_home": 1,
"corners_away": 0,
"fouls_home": 0,
"fouls_away": 0,
"yellow_cards_home": 0,
"yellow_cards_away": 0,
"red_cards_home": 0,
"red_cards_away": 0,
"primary_book": "draftkings",
"book_count": 6
},
"chile_-_primera_division_concepcion_palestino_2026-04-24": {
"home_team": "Palestino",
"away_team": "Concepción",
"sport": "soccer",
"league": "chile_-_primera_division",
"home_score": 0,
"away_score": 0,
"is_live": true,
"primary_book": "unibet",
"book_count": 1,
"stale": true
},
"montenegro_-_prva_liga_bokelj_otrantolympiculcinj_2026-04-23": {
"home_team": "Bokelj",
"away_team": "FK Otrant-Olympic Ulcinj",
"sport": "soccer",
"league": "montenegro_-_prva_liga",
"home_score": 2,
"away_score": 1,
"score_type": "points",
"winner": "home",
"status": "final",
"completed_at": "2026-04-23T23:48:55Z",
"is_live": false,
"stale": false
}
}
},
"updated_at": "2026-04-23T23:55:01.234Z"
}A terceira linha é uma linha terminal: a partida
terminou cerca de seis minutos antes de esta resposta ser montada, então ela é
retida com status: "final" e seu placar final, e não carrega primary_book,
book_count, game_period nem game_clock.
Modelo de Mesclagem Cross-Book
Os campos de cada evento são mesclados a partir do snapshot de livestate de cada sportsbook por meio de um algoritmo de três classes:
- Classe A — Placares são escolhidos por consenso: entre os books no rank de período mais avançado, vence o placar de maior total com ≥2 apoiadores. Um único book relatando um placar discrepante (comum nos primeiros segundos de uma mudança de placar, ou de um adapter com mau funcionamento) é rejeitado.
- Classe B — Campos temporais (
game_period,game_clock) são selecionados pela direção do cronômetro de acordo com o esporte — regressivo vs progressivo — e rank de período. - Classe C — Campos situacionais (
possession,corners_*, etc.) são preenchidos por prioridade a partir de um ranking fixo de books.
primary_book informa de qual book vieram os placares vencedores.
book_count é o número total de books que contribuíram com qualquer estado
para o evento no momento da mesclagem.
Streaming
Toda atualização ao vivo que o endpoint REST exibiria no próximo poll
também dispara um evento gamestate:update nos canais de streaming:
- SSE:
GET /api/v1/stream/gamestate - WebSocket: assine
{channels: ["gamestate"]}emwss://ws.sharpapi.io/ws
Clientes de streaming recebem um gamestate:snapshot inicial com o
estado atual completo (uma lista plana de linhas de eventos), depois eventos gamestate:update
carregando linhas alteradas conforme são mescladas, e eventos gamestate:removed
quando eventos saem do conjunto ao vivo.
Um evento finalizado continua no slate transmitido como sua linha
status: "final" durante a janela descrita em
Eventos finalizados, e é anunciado uma vez com um
frame gamestate:final.
Avisos de evento finalizado
Quando um evento termina, cada transporte envia um único frame
gamestate:final para ele. Ele carrega a linha terminal do evento — o
mesmo formato da linha REST, mais o event_id.
WebSocket:
{"type":"gamestate:final","seq":48213,"global_seq":"48213","data":[{"event_id":"atp_jdoe_rroe_2026-07-03","home_team":"J. Doe","away_team":"R. Roe","sport":"tennis","league":"atp","sets_home":1,"sets_away":2,"score_type":"sets","winner":"away","status":"final","completed_at":"2026-07-03T19:42:10Z","is_live":false,"stale":false}]}SSE (o mesmo envelope {"data": [...]} do gamestate:update):
event: gamestate:final
data: {"data":[{"event_id":"atp_jdoe_rroe_2026-07-03","home_team":"J. Doe","away_team":"R. Roe","sport":"tennis","league":"atp","sets_home":1,"sets_away":2,"score_type":"sets","winner":"away","status":"final","completed_at":"2026-07-03T19:42:10Z","is_live":false,"stale":false}]}- Mesmo acesso e mesmos filtros do
gamestate:update. Quem recebegamestate:updaterecebegamestate:final, e seus filtrossport/leaguese aplicam. Sem acesso a game state, você não recebe nenhum dos dois. - O WebSocket faz replay. Como
gamestate:updateegamestate:removed, ele fica no buffer de replay e é reenviado numa reconexão comfrom_seq. Frames de gamestate no SSE não são retomáveis. - É um aviso, não a única entrega. A mesma linha também está no
slate (
gamestate:snapshotegamestate:update) durante toda a janela.
“Uma vez por evento” pode se repetir ocasionalmente — deduplique por
(event_id, completed_at). Você pode receber gamestate:final mais de
uma vez para o mesmo evento. event_id e completed_at são idênticos em
toda repetição: descarte um frame cujo
par você já processou. Isso é sempre seguro, porque a linha continua no
slate.
Rate Limits e Quotas
Os rate limits seguem o seu tier base, não o add-on — keys Hobby mantêm
seus 120 req/min, Pro mantêm 300, Sharp mantêm 1.000, Enterprise customizado.
Veja Pricing . /gamestate conta
da mesma forma que outras chamadas REST para a quota por minuto.
Solução de Problemas
- 403
tier_restrictedcomaddon: "game_state"— sua key não tem o add-on Game State. Adicione-o pela Billing por US$ 79/mês, ou faça upgrade para Enterprise. - 400
validation_errorcomparameter: "status"— o único valor aceito de?status=éfinal. Não existe o valorlive; linhas ao vivo são o conjunto padrão, ou peça só elas com?is_live=true. - 400
validation_errorcomparameter: "is_live"—?is_live=aceita apenastrue/false.1e0são rejeitados; antes eram lidos comofalsee devolviam o oposto do que se pedia. - Uma linha com
status: "final"eis_live: false— não é bug. É um evento finalizado dentro da janela de 15 minutos. Filtre com?is_live=truepara o comportamento anterior. primary_book/book_countausentes — você está lendo uma linha terminal. Verifiquestatusantes de ler campos que só existem ao vivo.- Objeto
datavazio — não há eventos ao vivo no escopo. Isso é comum entre eventos em calendários de esportes menores. Faça polling novamente. - Eventos com
stale: true— o book primário ficou em silêncio. Os placares podem estar atrasados; considere filtrá-los ou exibir um indicador de UI. - Eventos com
aggregator_stale: true— o agregador SharpAPI não atualizou esse esporte em >30s. Se persistente, entre em contato com o suporte; um pico breve pode acontecer durante redeploys. - Mesma partida aparece duas vezes sob diferentes
event_ids — limitação conhecida para algumas ligas regionais onde os sportsbooks usam nomenclatura de liga inconsistente. Use(home_team, away_team)como uma chave secundária de deduplicação no cliente até que as lacunas restantes de aliases sejam fechadas.