Stream Unificado
GET /api/v1/stream — Atualizações em tempo real de odds e oportunidades via Server-Sent Events (SSE).
Requer Add-on WebSocket ($99/mês) em qualquer plano pago, ou Enterprise (incluído). O plano gratuito não oferece suporte a streaming.
Autenticação
Passe sua API key via cabeçalho ou parâmetro de consulta:
# Cabeçalho (recomendado para uso server-side)
curl -H "X-API-Key: sk_live_your_key" \
https://api.sharpapi.io/api/v1/stream
# Parâmetro de consulta (obrigatório para EventSource no navegador)
https://api.sharpapi.io/api/v1/stream?api_key=sk_live_your_keyParâmetros de Consulta
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
channel | string | opportunities | O que transmitir: odds, opportunities, gamestate (apenas Enterprise) ou all |
sport | string | todos | Filtrar por esporte(s), separados por vírgula (ex.: basketball, football, ice_hockey) |
sportsbook | string | permitidos pelo plano | Filtrar por sportsbook(s), separados por vírgula |
league | string | todas | Filtrar por liga(s), separadas por vírgula |
event | string | todos | Filtrar por ID(s) de evento, separados por vírgula |
market | string | todos | Filtrar por tipo(s) de mercado, separados por vírgula (ex.: moneyline, point_spread, total_points, player_points) |
min_ev | number | 2.0 | Porcentagem mínima de EV para eventos de oportunidade +EV |
min_profit | number | 0.5 | Porcentagem mínima de lucro apenas para eventos de arbitragem (não se aplica à filtragem de low-hold) |
state | string | — | Código de estado dos EUA para deep links de sportsbooks em eventos de odds e oportunidades (ex.: nj, ny, il). Garante que as URLs deep_link redirecionem para o domínio do sportsbook específico do estado. |
api_key | string | — | API key (alternativa à autenticação por cabeçalho para EventSource no navegador) |
Opções de Channel
| Channel | Eventos Entregues | Caso de Uso |
|---|---|---|
odds | snapshot, odds:update, odds:removed, heartbeat | Acompanhar movimentações de odds |
opportunities | snapshot, ev:detected/expired, arb:detected/expired, middles:detected/expired, low_hold:detected/expired, heartbeat | Alertar sobre oportunidades |
gamestate | gamestate:snapshot, gamestate:update, gamestate:removed, heartbeat | Placares ao vivo, períodos, cronômetros e dados situacionais por evento. Apenas plano Enterprise. Veja Live Game State para o catálogo completo de campos. |
all | Todos os tipos de evento | Visão completa em tempo real |
Rotas de Conveniência
| Rota | Equivalente A |
|---|---|
GET /api/v1/stream/odds | /api/v1/stream?channel=odds |
GET /api/v1/stream/opportunities | /api/v1/stream?channel=opportunities |
GET /api/v1/stream/gamestate | /api/v1/stream?channel=gamestate |
GET /api/v1/stream/all | /api/v1/stream?channel=all |
GET /api/v1/stream/events/:eventId | /api/v1/stream?channel=odds&event=:eventId |
Tipos de Evento SSE
connected
Enviado imediatamente quando o stream é estabelecido.
event: connected
data: {"stream_id":"stream_1704960637000","channel":"all","filters":{"sportsbook":null,"sport":["basketball"],"league":["nba"],"event":null,"market":null},"reconnected":false}| Campo | Tipo | Descrição |
|---|---|---|
stream_id | string | Identificador único do stream |
channel | string | Eco do channel solicitado (odds, opportunities ou all) |
filters | object | Eco dos filtros ativos |
reconnected | boolean | true se esta for uma reconexão via Last-Event-ID |
trial | object | undefined | Presente se o usuário estiver em um trial de streaming. Contém active, expires_at, remaining_hours, max_streams |
snapshot
Despejo completo de dados enviado após connected. Contém todas as odds ou oportunidades atuais que correspondem aos seus filtros. Conjuntos grandes são divididos em múltiplos eventos snapshot (até 1000 itens cada).
Cada objeto de odds no snapshot contém todos os campos — esta é a forma completa de Odds que seu cliente deve armazenar localmente. Eventos odds:update subsequentes enviam apenas os campos alterados (veja abaixo).
event: snapshot
id: evt_00001
data: {"odds":[{"id":"123456","sportsbook":"draftkings","event_id":"nba_phosuns_phi76ers_2026-02-08","sport":"basketball","league":"nba","home_team":"PHI 76ers","away_team":"PHO Suns","market_type":"moneyline","selection":"PHO Suns","selection_type":"away","odds_american":-155,"odds_decimal":1.645,"odds_probability":0.608,"line":null,"event_start_time":"2026-02-08T19:00:00Z","is_live":false,"last_seen_at":"2026-02-08T18:47:20Z","odds_changed_at":"2026-02-08T18:47:20Z","deep_link":"https://sportsbook.draftkings.com/event/..."}],"count":1000,"total":3200,"offset":0,"has_more":true}| Campo | Tipo | Descrição |
|---|---|---|
odds | array | Array de objetos Odds completos (veja endpoint Odds para todos os campos) |
count | number | Número de odds neste fragmento |
total | number | Número total de odds que correspondem aos filtros |
offset | number | Offset deste fragmento no resultado completo |
has_more | boolean | true se mais fragmentos snapshot virão a seguir |
snapshot:complete
Sinaliza que todos os snapshots iniciais foram enviados. Seguro para ocultar estados de carregamento após recebê-lo.
event: snapshot:complete
id: evt_00005
data: {"status":"ready","books":["draftkings","fanduel"],"total_odds":3200}odds:update
Disparado quando as odds mudam para um sportsbook. Enviado apenas nos channels odds ou all.
Payload delta compacto. Eventos delta contêm apenas campos que podem mudar entre atualizações — id, odds_american, odds_decimal, odds_probability, line, is_live e odds_changed_at. Campos estáticos como sportsbook, sport, league, home_team, away_team, market_type, selection, deep_link e event_start_time não são incluídos nos deltas. Mescle cada delta no seu mapa local de odds por id usando os objetos completos recebidos no snapshot inicial. Veja Migração: Deltas SSE Compactos abaixo.
event: odds:update
id: evt_00042
data: {"odds":[{"id":"123456","odds_american":-150,"odds_decimal":1.667,"odds_probability":0.6,"line":null,"is_live":false,"odds_changed_at":"2026-02-08T18:47:38Z"}],"count":1,"book":"draftkings","partial":false}Campos do objeto delta (OddsDelta):
| Campo | Tipo | Descrição |
|---|---|---|
id | string | ID único da odd — corresponde ao id do snapshot inicial |
odds_american | number | Odds americanas atualizadas (ex.: -150) |
odds_decimal | number | Odds decimais atualizadas (ex.: 1.667) |
odds_probability | number | Probabilidade implícita atualizada (ex.: 0.6) |
line | number | null | Linha/spread atualizada (ex.: -3.5), ou null para moneyline |
is_live | boolean | Se o evento está atualmente ao vivo |
odds_changed_at | string | Timestamp ISO 8601 da atualização da própria fonte do sportsbook para esta linha, quando disponível. Na Pinnacle, é mantido enquanto o preço/linha/flag is_live subjacentes permanecerem inalterados — veja Entendendo o odds_changed_at da Pinnacle. |
Campos do envelope:
| Campo | Tipo | Descrição |
|---|---|---|
odds | array | Array de objetos OddsDelta (compactos — apenas campos dinâmicos) |
count | number | Número de odds neste fragmento |
book | string | Sportsbook que sofreu alteração (ex.: "draftkings") |
partial | boolean | true se mais fragmentos virão para este lote de atualização |
ev:detected
Uma nova oportunidade de valor esperado positivo foi encontrada. Enviado apenas nos channels opportunities ou all.
event: ev:detected
id: evt_00043
data: [{"id":"a1b2c3d4e5f6","game_id":"nba_phosuns_phi76ers_2026-02-08","ev_percentage":4.35,"odds_american":-105,"odds_decimal":1.952,"no_vig_odds":-101,"selection":"PHO Suns -3.5","market":"point_spread","line":-3.5,"sportsbook":"draftkings","game":"PHO Suns @ PHI 76ers","sport":"basketball","league":"nba","home_team":"PHI 76ers","away_team":"PHO Suns","start_time":"2026-02-08T19:00:00.000Z","is_live":false,"confidence_score":72,"kelly_percent":3.8,"book_count":4,"detected_at":"2026-02-08T18:47:20.000Z"}]ev:expired
Uma oportunidade +EV detectada anteriormente não está mais disponível.
event: ev:expired
id: evt_00044
data: {"expired":["a1b2c3d4e5f6"],"timestamp":"2026-02-08T18:47:25.000Z"}arb:detected
Uma nova oportunidade de arbitragem foi encontrada. Enviado apenas nos channels opportunities ou all.
event: arb:detected
id: evt_00045
data: [{"id":"61c501b83ce932d1","event_id":"nba_phosuns_phi76ers_2026-02-08","event_name":"PHO Suns @ PHI 76ers","sport":"basketball","league":"nba","market_type":"moneyline","line":null,"profit_percent":2.8,"implied_total":97.2,"is_live":false,"legs":[{"sportsbook":"draftkings","selection":"PHO Suns","odds_american":150,"odds_decimal":2.5,"implied_probability":0.4,"stake_percent":41.4},{"sportsbook":"fanduel","selection":"PHI 76ers","odds_american":-130,"odds_decimal":1.769,"implied_probability":0.5652,"stake_percent":58.6}],"detected_at":"2026-02-08T18:47:21.000Z"}]arb:expired
Uma oportunidade de arbitragem detectada anteriormente não está mais disponível.
event: arb:expired
id: evt_00046
data: {"expired":["evt_abc123:moneyline:opp_a1b2c3"],"timestamp":"2026-01-26T02:10:39.500Z"}middles:detected
Uma nova oportunidade de middle foi encontrada. Enviado apenas nos channels opportunities ou all.
event: middles:detected
id: evt_00047
data: [{"id":"middle_abc123","event_id":"nba_phosuns_phi76ers_2026-02-08","event_name":"PHO Suns @ PHI 76ers","sport":"basketball","league":"nba","market_type":"player_points","side1":{"book":"draftkings","selection":"Over 22.5","line":22.5,"odds":{"american":-110,"decimal":1.909,"probability":0.5238,"fair_probability":0.51},"stake_percent":50,"odds_age_seconds":2.1,"deep_link":null},"side2":{"book":"fanduel","selection":"Under 23.5","line":23.5,"odds":{"american":-105,"decimal":1.952,"probability":0.5122,"fair_probability":0.49},"stake_percent":50,"odds_age_seconds":1.5,"deep_link":null},"middle_size":1,"middle_numbers":[23],"middle_probability":0.12,"expected_value":3.5,"quality_score":85,"detected_at":"2026-02-08T18:47:22.000Z"}]middles:expired
Uma oportunidade de middle detectada anteriormente não está mais disponível.
event: middles:expired
id: evt_00048
data: {"expired":["middle_abc123"]}low_hold:detected
Uma nova oportunidade de low-hold foi encontrada. Enviado apenas nos channels opportunities ou all.
event: low_hold:detected
id: evt_00049
data: [{"id":"lowhold_abc123","event_id":"nba_phosuns_phi76ers_2026-02-08","event_name":"PHO Suns @ PHI 76ers","sport":"basketball","league":"nba","market_type":"moneyline","line":null,"home_team":"PHI 76ers","away_team":"PHO Suns","start_time":"2026-02-08T19:00:00.000Z","hold_percentage":1.2,"is_live":false,"all_books":["draftkings","fanduel"],"side1":{"selection":"PHO Suns","books":["draftkings"],"line":null,"odds":{"american":-108,"decimal":1.926,"implied_probability":0.5192,"fair_probability":0.5096},"deep_links":{"draftkings":"https://sportsbook.draftkings.com/event/..."}},"side2":{"selection":"PHI 76ers","books":["fanduel"],"line":null,"odds":{"american":110,"decimal":2.1,"implied_probability":0.4762,"fair_probability":0.4904},"deep_links":{"fanduel":"https://sportsbook.fanduel.com/event/..."}},"detected_at":"2026-02-08T18:47:22.000Z"}]low_hold:expired
Uma oportunidade de low-hold detectada anteriormente não está mais disponível.
event: low_hold:expired
id: evt_00050
data: {"expired":["lowhold_abc123"]}odds:removed
Odds removidas por um sportsbook (ex.: mercado retirado, evento liquidado). Enviado apenas nos channels odds ou all.
event: odds:removed
id: evt_00051
data: {"ids":["123456","789012"],"count":2,"book":"draftkings"}| Campo | Tipo | Descrição |
|---|---|---|
ids | string[] | IDs de odds a serem removidos do estado local |
count | number | Número de odds removidas |
book | string | Sportsbook que removeu as odds |
heartbeat
Keep-alive enviado a cada 30 segundos. Se você não receber um heartbeat dentro de 60 segundos, a conexão pode estar obsoleta.
event: heartbeat
data: {"timestamp":"2026-01-26T02:11:07.846Z"}error
Erro recuperável no stream. A conexão permanece aberta.
event: error
data: {"code":"upstream_error","message":"Temporary issue fetching DraftKings data. Will retry."}Reconexão
SSE oferece suporte a reconexão automática via cabeçalho Last-Event-ID. Cada evento inclui um campo id. Quando o cliente se reconecta, o servidor entrega um novo snapshot completo — não uma repetição de eventos individuais perdidos. Isso significa que seu cliente recebe uma visão completa e atualizada a cada reconexão.
// Navegadores lidam com isso automaticamente com EventSource.
// Para clientes personalizados, defina o cabeçalho na reconexão:
const headers = {
'X-API-Key': 'YOUR_KEY',
'Last-Event-ID': 'evt_00042'
};Na reconexão, limpe seu estado local antes de processar o novo snapshot. O evento connected inclui "reconnected": true para que você possa detectar isso. Se você não limpar o estado, odds obsoletas da sessão anterior se misturarão com dados novos.
O EventSource do navegador lida com Last-Event-ID e a reconexão automaticamente. Nenhum código extra é necessário para a reconexão em si, mas você deve cuidar da limpeza de estado no lado do cliente.
Exemplos de Código
Browser
// Mapa local de odds — indexado pelo ID da odd, armazena objetos Odds completos do snapshot.
// Eventos delta são mesclados neste mapa pelo ID.
const oddsMap = new Map();
const eventSource = new EventSource(
'https://api.sharpapi.io/api/v1/stream?channel=all&league=nba&api_key=YOUR_KEY'
);
eventSource.addEventListener('connected', (e) => {
const { stream_id, channel, reconnected } = JSON.parse(e.data);
if (reconnected) oddsMap.clear(); // Snapshot novo a caminho
console.log(`Stream ${stream_id} connected (${channel})`);
});
eventSource.addEventListener('snapshot', (e) => {
const { odds, count, total, has_more } = JSON.parse(e.data);
// Armazena objetos Odds completos indexados por ID
for (const odd of odds) {
oddsMap.set(odd.id, odd);
}
console.log(`Snapshot chunk: ${count} odds (${oddsMap.size}/${total} total)`);
});
eventSource.addEventListener('snapshot:complete', (e) => {
console.log(`Snapshot complete: ${oddsMap.size} odds loaded`);
});
eventSource.addEventListener('odds:update', (e) => {
const { odds, book } = JSON.parse(e.data);
// Mescla deltas compactos no estado local — apenas campos dinâmicos são enviados
for (const delta of odds) {
const existing = oddsMap.get(delta.id);
if (existing) {
Object.assign(existing, delta); // Mescla campos alterados
}
// Se não houver entrada existente, a odd apareceu após nosso snapshot — aguarde o próximo snapshot
}
console.log(`${book}: ${odds.length} odds updated`);
});
eventSource.addEventListener('odds:removed', (e) => {
const { ids, book } = JSON.parse(e.data);
for (const id of ids) {
oddsMap.delete(id);
}
console.log(`${book}: ${ids.length} odds removed`);
});
eventSource.addEventListener('ev:detected', (e) => {
const opps = JSON.parse(e.data);
opps.forEach(opp => console.log(`+EV: ${opp.selection} at ${opp.ev_percentage}%`));
});
eventSource.addEventListener('arb:detected', (e) => {
const arbs = JSON.parse(e.data);
arbs.forEach(arb => console.log(`Arb: ${arb.profit_percent}% profit`));
});
eventSource.addEventListener('middles:detected', (e) => {
const middles = JSON.parse(e.data);
middles.forEach(m => console.log(`Middle: ${m.event_name} — EV ${m.expected_value}%`));
});
eventSource.addEventListener('low_hold:detected', (e) => {
const holds = JSON.parse(e.data);
holds.forEach(h => console.log(`Low hold: ${h.hold_percentage}%`));
});
eventSource.addEventListener('heartbeat', () => {
console.log('Connection alive');
});
eventSource.onerror = () => {
console.log('Connection lost, auto-reconnecting...');
};Limites de Streams Concorrentes
Cada conexão SSE aberta conta como um stream contra o seu limite.
| Plano | Streams Concorrentes Máximos |
|---|---|
| Add-on WebSocket ($99/mês) | 10 |
| Enterprise | Personalizado |
Exceder o limite de streams retorna um erro 429 com o código too_many_streams. Feche streams não utilizados antes de abrir novos.
Gerenciando Streams
- Cada conexão única
GET /api/v1/streamconta como um stream - Fechar a conexão HTTP (ou chamar
eventSource.close()) libera o slot imediatamente - Use filtros mais amplos em menos streams ao invés de muitos streams restritos
- O payload do evento
connectedinclui seustream_idpara rastreamento
Tratamento de Erros
Erros de Nível de Stream
Erros enviados como eventos SSE são recuperáveis — a conexão permanece aberta:
event: error
data: {"code":"upstream_error","message":"Temporary issue fetching data. Will retry."}Erros de Nível de Conexão
Estes encerram a conexão. Trate-os no onerror:
| Código de Erro | Status HTTP | Descrição | Resolução |
|---|---|---|---|
too_many_streams | 429 | Muitos streams concorrentes | Feche streams não utilizados |
tier_restricted | 403 | Streaming não disponível no seu plano | Adicione o add-on WebSocket |
invalid_api_key | 401 | API key ausente ou inválida | Verifique sua API key |
validation_error | 400 | Parâmetros de filtro inválidos | Verifique os parâmetros de consulta |
Boas Práticas
- Use o channel correto —
channel=oddsapenas para odds,channel=opportunitiesapenas para oportunidades,channel=allpara tudo - Use filtros para reduzir o consumo de banda — Passe os parâmetros
sport,league,sportsbook,marketeeventpara restringir os dados - Defina limiares — Use
min_evemin_profitpara filtrar oportunidades de baixo valor no servidor - Aguarde por
snapshot:complete— Isso sinaliza que todos os dados iniciais foram enviados. Oculte os estados de carregamento após recebê-lo - Trate
odds:removed— Remova odds do estado local quando recebidas para evitar exibir dados obsoletos - Trate a reconexão de forma elegante — O
EventSourcese reconecta automaticamente, mas redefina o estado local quando receber um novo eventosnapshot - Processe atualizações de forma assíncrona — Não bloqueie o handler de eventos; enfileire as atualizações para processamento em segundo plano
- Monitore os heartbeats — Se nenhum heartbeat chegar em 60 segundos, considere a conexão obsoleta e reconecte
- Feche streams não utilizados — Cada stream aberto conta contra seu limite concorrente
- Use
Last-Event-ID— Permite que o servidor reproduza eventos perdidos após uma reconexão
Migração: Deltas SSE Compactos
Mudança incompatível para consumidores de odds:update SSE. O evento odds:update agora envia objetos OddsDelta compactos contendo apenas campos dinâmicos (id, odds_american, odds_decimal, odds_probability, line, is_live, odds_changed_at). Campos estáticos como sportsbook, sport, league, home_team, away_team, market_type, selection, deep_link e event_start_time são enviados apenas no evento snapshot inicial.
Por quê: O payload anterior enviava o objeto Odds completo a cada alteração, gerando ~170 KB/s por conexão. O delta compacto reduz o consumo de banda em ~5x, enviando apenas os 6-7 campos que realmente mudaram.
O que alterar no seu cliente:
-
Armazene as odds do snapshot em um mapa local indexado por
id. O eventosnapshotainda envia objetosOddscompletos com todos os campos. -
Mescle os deltas de
odds:updateporidao invés de tratá-los como objetos independentes. Cada delta contém apenas os campos que podem mudar — procure o objeto completo no seu mapa local e aplique a atualização. -
Não acesse campos estáticos em objetos delta. Campos como
event_id,market_type,selection,home_teamesportsbooknão estão presentes nos deltas. Leia-os do seu mapa local em vez disso.
Antes (incorreto — acessando campos não presentes no delta):
eventSource.addEventListener('odds:update', (e) => {
const { odds } = JSON.parse(e.data);
for (const o of odds) {
// ❌ o.event_id, o.market_type, o.selection são undefined nos deltas
console.log(`${o.event_id} ${o.market_type}: ${o.selection} → ${o.odds_american}`);
}
});Depois (correto — mescla no estado local):
eventSource.addEventListener('odds:update', (e) => {
const { odds } = JSON.parse(e.data);
for (const delta of odds) {
const full = oddsMap.get(delta.id);
if (full) {
Object.assign(full, delta); // Mescla campos alterados
// ✅ full.event_id, full.market_type, full.selection ainda estão disponíveis
console.log(`${full.event_id} ${full.market_type}: ${full.selection} → ${full.odds_american}`);
}
}
});Endpoints Relacionados
- Oportunidades +EV - Endpoint REST para dados de EV (transmitido via
ev:detected) - Oportunidades de Arbitragem - Endpoint REST para arbs (transmitido via
arb:detected) - Oportunidades de Low Hold - Endpoint REST para low hold (transmitido via
low_hold:detected) - Resumo de Middles - Estatísticas agregadas de middles para polling de dashboard
- WebSocket API - Alternativa bidirecional ao SSE