API e embed de filmes, séries, canais e jogos ao vivo
API de streaming para plugar no seu site, app ou projeto: catálogo de filmes, séries, canais de TV e jogos ao vivo em endpoints REST que respondem JSON, e um player em iframe pronto para embedar — com legendas e troca automática de fonte quando uma falha. Escolha um endpoint no menu ao lado para ver parâmetros, exemplos e respostas de erro.
<iframe>
O player do SuperFlixAPI é consumido por embed: você não baixa o vídeo nem pede um link direto — você aponta um <iframe> para uma URL nossa e nós resolvemos a fonte, as legendas e os fallbacks do outro lado. Cada tipo de conteúdo tem uma URL própria, sempre no mesmo formato.
Formato das URLs
O domínio é sempre o nosso (https://www.superflixapi.lat) — é ele que vai no src do iframe, não o do seu site. Repare que não é o mesmo domínio do site: é o nosso host de reprodução, dedicado a embed, justamente pra um bloqueio no domínio principal não derrubar os iframes de quem já usa. O que está entre chaves é você que preenche; cada linha traz um exemplo pronto ao lado.
https://www.superflixapi.lat/filme/{tmdb_id}— filmes. Ex:https://www.superflixapi.lat/filme/350https://www.superflixapi.lat/serie/{tmdb_id}/{temporada}/{episodio}— episódios de série. Ex:https://www.superflixapi.lat/serie/1416/15/4https://www.superflixapi.lat/canal/{nome}— canais de TV ao vivo. Ex:https://www.superflixapi.lat/canal/espn-brasilhttps://www.superflixapi.lat/jogo/{nome}— jogos ao vivo. Ex:https://www.superflixapi.lat/jogo/corinthians-x-internacionalhttps://www.superflixapi.lat/evento/{slug}— eventos ao vivo. Ex:https://www.superflixapi.lat/evento/ufc-jonjones-vs-poatan
Exemplo mínimo (com fallback)
Cole o trecho inteiro — o <script> incluso é o que salva o iframe se o endereço for bloqueado por ordem legal. Ele sonda o endereço e, se não responder, tenta a mesma URL com barra no fim e depois o domínio alternativo. Sem esse script, um bloqueio derruba o embed até você trocar o src na mão.
<iframe data-sb-embed data-sb-alt='["https://www.superflixapi.lat/filme/350/"]' src="https://www.superflixapi.lat/filme/350" width="640" height="360" frameborder="0" allow="autoplay; encrypted-media; picture-in-picture" allowfullscreen></iframe>
<script>
(function(){
var f=document.querySelectorAll("iframe[data-sb-embed]"); if(!f.length) return;
var el=f[f.length-1], alts=JSON.parse(el.getAttribute("data-sb-alt")||"[]");
(function tenta(i){ if(i>=alts.length) return;
fetch(el.src,{method:"HEAD",mode:"cors",cache:"no-store"})
.then(function(r){ if(r.ok) return; throw 0; })
.catch(function(){ el.src=alts[i]; tenta(i+1); });
})(0);
})();
</script> Boas práticas
- Sempre inclua
allowfullscreene oallowacima — sem eles o botão de tela cheia e o autoplay não funcionam. - Deixe o iframe responsivo com um wrapper de proporção 16:9 em vez de altura fixa em pixels.
Não bloqueie os anúncios do player
Não use o atributo sandbox no iframe, nem bloqueadores de anúncio, proxies ou qualquer outra forma de suprimir a publicidade do player. Se detectarmos isso, o conteúdo não será exibido. Se você quiser um player sem anúncios, veja a página de planos.
Quando dá erro
As rotas respondem 404 com uma mensagem em texto quando o conteúdo não existe ou ainda não tem fonte cadastrada.
Progresso de reprodução (postMessage)
O iframe é de outra origem, então seu site não consegue ler o <video> de dentro dele. Pra saber quanto já foi assistido, o player manda o progresso pra fora com postMessage — a cada ~15s enquanto toca, e na hora em play/pause/seeked/ended.
window.addEventListener('message', (event) => {
// Obrigatório, não opcional: sem checar a origem, qualquer outro iframe
// na sua página (ex: um anúncio) pode forjar 'superflixapi:progress' e
// envenenar o que você guarda como progresso do usuário.
if (event.origin !== 'https://superflixapi.lat') return;
if (event.data?.type !== 'superflixapi:progress') return;
const { currentTime, duration, state, tmdbId, season, episode } = event.data;
const percent = (currentTime / duration) * 100;
// state: 'playing' | 'paused' | 'ended' — use 'ended' pra marcar como
// assistido e tirar o card de "continuar assistindo", em vez de inferir
// isso por currentTime chegando perto de duration.
// season/episode vêm só em episódio de série; em filme os dois são null.
}); - Conferir
event.originé obrigatório, não uma boa prática opcional — é a única coisa que impede outro iframe da sua página (rede de anúncio, outro widget) de forjar uma mensagem com o mesmotypee envenenar o progresso que você guarda. Filtrar só porevent.data?.typenão basta. tmdbId/season/episodeidentificam a que conteúdo o progresso pertence — necessário porque o player troca de fonte sozinho (autoplay do próximo episódio) sem o iframe recarregar, então o conteúdo pode mudar no meio da reprodução sem seu site saber pelo contexto da página.
A mensagem sai com targetOrigin: '*' porque o embed pode estar em qualquer domínio — isso não é o mesmo problema de segurança do event.origin acima: aqui é a origem de QUEM MANDA (nós, sempre https://superflixapi.lat) que importa conferir; o alvo aberto só significa que não escondemos pra quem escuta, e o conteúdo da mensagem não é sensível.
Posição inicial (?start=)
Se você guarda o progresso do seu lado (com o postMessage acima), pode abrir o vídeo de onde parou passando ?start= (segundos) na URL do embed — ex: https://www.superflixapi.lat/filme/350?start=1800 começa aos 30 minutos. Sem isso, o player sempre começa do zero pra visitante sem sessão nossa, mesmo que seu site já saiba a posição certa.
Escolher a fonte que carrega primeiro (?fonte=)
Passe o rótulo da fonte em minúsculas e com hífens: https://www.superflixapi.lat/canal/globo?fonte=painel-externo-p. Ela vai pra frente da fila e as outras continuam atrás como reserva; se o nome não existir naquele conteúdo, a ordem normal vale. Os rótulos disponíveis vêm no campo label de cada fonte em /api/v1/stream, que também aceita &fonte= e devolve a escolhida em sources[0]. Funciona em canal, jogo, evento, filme e série, em qualquer plano.
Mudar a posição sem recarregar (superflixapi:seek)
Pra mudar a posição durante a reprodução sem remontar a URL e recarregar o iframe inteiro, mande uma mensagem pro player:
iframeEl.contentWindow.postMessage(
{ type: 'superflixapi:seek', seconds: 1800 },
'https://superflixapi.lat'
); Use o domínio real do embed (nunca '*') como targetOrigin aqui — é você quem sabe pra onde está mandando, diferente do player, que não sabe onde você está embedado.
Play e pause (superflixapi:play / superflixapi:pause)
Mesmo canal, sem parâmetros:
iframeEl.contentWindow.postMessage({ type: 'superflixapi:pause' }, 'https://superflixapi.lat');
iframeEl.contentWindow.postMessage({ type: 'superflixapi:play' }, 'https://superflixapi.lat'); O navegador pode recusar o play se o visitante ainda não interagiu com a página — nesse caso o vídeo continua pausado. O pause sempre funciona. O estado novo volta pelo superflixapi:progress (state: 'playing'/'paused').
Limites de uso
- Paginação:
limitvai até100itens por página em todas as rotas JSON — valor acima disso é reduzido ao máximo, não recusado. Para varrer o catálogo, prefira o cursorafter_idde/api/titles(cada página custa só o tamanho dela);pagecontinua valendo, mas pular muitas páginas fica caro do nosso lado e é a primeira coisa a degradar num pico. - Rotas públicas:
120requisições por minuto por IP em/api/titles,/api/recentse/api/season-episodes, com429ao estourar (o cabeçalhoRetry-Afterdiz quando voltar). Guarde o resultado do seu lado — o catálogo muda em horas, não em segundos, então uma consulta em loop a cada poucos segundos gasta o limite sem trazer nada novo. - Plano API:
60requisições por minuto por chave, com429ao estourar. Veja API de link direto.
/filme/{tmdb_id}
Toca um filme dentro de um iframe. Resolve todas as fontes cadastradas para aquele título, já com legendas e fallback automático caso a primeira fonte falhe.
Parâmetros da rota
tmdb_id: number - Obrigatório. O ID do filme no TMDB (o mesmo campotmdb_iddevolvido por/api/titles).
Exemplo
<iframe
src="https://www.superflixapi.lat/filme/350"
width="100%"
height="100%"
frameborder="0"
allowfullscreen
allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
></iframe> Respostas de erro
404— "Filme não encontrado." (otmdb_idnão está no catálogo)404— "Nenhuma fonte cadastrada para este filme."
/serie/{tmdb_id}/{temporada}/{episodio}
Toca um episódio específico. Além das fontes e legendas, já vai com o próximo episódio engatilhado para autoplay quando ele existe e tem fonte cadastrada.
Parâmetros da rota
Os três fazem parte do caminho da URL, não são query params.
tmdb_id: number - Obrigatório. O ID da série no TMDB.temporada: number - Obrigatório. Número da temporada.episodio: number - Obrigatório. Número do episódio.
Exemplo
<!-- Grey's Anatomy, temporada 15, episódio 4 -->
<iframe
src="https://www.superflixapi.lat/serie/1416/15/4"
width="100%"
height="100%"
frameborder="0"
allowfullscreen
allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
></iframe> Respostas de erro
404— "Série não encontrada." / "Temporada não encontrada." / "Episódio não encontrado."404— "Nenhuma fonte cadastrada para este episódio."
/api/season-episodes
Lista os episódios de uma temporada que o nosso player toca e que o TMDB não lista (especiais, bônus e episódios que o TMDB ainda não cadastrou). Use para completar a lista de episódios que você monta a partir do TMDB: o que vier aqui entra na temporada, e cada episódio abre pelo mesmo /serie/{tmdb_id}/{temporada}/{episodio} do embed acima.
Parâmetros da URL (Query Params)
tmdb_id: number - Obrigatório. O ID da série no TMDB.season: number - Obrigatório. Número da temporada (0a100).
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial, e funciona em chamada de outro domínio pelo navegador. Guarde a resposta do seu lado por alguns minutos, junto com a lista do TMDB.
curl "https://superflixapi.lat/api/season-episodes?tmdb_id=200875&season=1" // episódios do TMDB da temporada (você já tem) + os extras daqui
const extras = await fetch("https://superflixapi.lat/api/season-episodes?tmdb_id=200875&season=1")
.then((r) => r.json())
.then((d) => d.episodes);
const porNumero = new Map(tmdbEpisodes.map((e) => [e.episode_number, e]));
for (const ep of extras) {
if (!porNumero.has(ep.number)) {
porNumero.set(ep.number, { episode_number: ep.number, name: ep.title });
}
}
const lista = [...porNumero.values()].sort((a, b) => a.episode_number - b.episode_number); Exemplo de Resposta
{
"success": true,
"tmdb_id": 200875,
"season": 1,
"episodes": [
{ "number": 9, "title": "Especial: A Story of Derry" }
]
} episodes: lista vazia quando a temporada não tem nada além do que o TMDB já lista. Cada item traz sónumberetitle, nunca o link do vídeo.- Junte pelo
number: se o TMDB passar a listar o episódio, ele deixa de vir aqui e não duplica. - Animes não retornam episódios nesta rota (a numeração deles é diferente da do TMDB).
Respostas de erro
400— "Informe tmdb_id e season válidos."429— limite de120requisições por minuto por IP; o cabeçalhoRetry-Afterdiz quando voltar.
/canal/{nome}
Toca um canal de TV ao vivo. O nome vai "slugificado": minúsculo, sem acento e com hífen no lugar dos espaços.
Parâmetros da rota
nome: string - Obrigatório. Slug do canal.ESPN Brasilviraespn-brasil.
A comparação é feita nos dois lados, então acento e caixa não quebram nada — os nomes válidos saem do campo name de /api/channels.
Exemplo
<iframe
src="https://www.superflixapi.lat/canal/espn-brasil"
width="100%"
height="100%"
frameborder="0"
allowfullscreen
allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
></iframe> Respostas de erro
404— "Canal não encontrado."
/api/channels
Lista os canais de TV ao vivo, junto com as categorias disponíveis para filtrar. É daqui que saem os nomes usados no embed acima. Os dados vêm em cache de 10 minutos, então a rota responde rápido e não sobrecarrega a origem.
Parâmetros da URL (Query Params)
category: number | string - Filtra por categoria, aceitando oidou oname(os dois vêm no arraycategoriesda resposta). O nome é necessário porque canal cadastrado aqui tem categoria em texto livre, sem id.q: string - Busca por nome do canal.page: number - Número da página. Padrão:1.limit: number - Itens por página. Padrão:12. Máximo:100.
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial. Chamada de outro domínio pelo navegador funciona normalmente.
curl "https://superflixapi.lat/api/channels?q=espn" const res = await fetch("https://superflixapi.lat/api/channels?q=espn");
const data = await res.json(); Exemplo de Resposta
{
"success": true,
"categories": [
{ "id": 1, "name": "Esportes" }
],
"channels": [
{
"name": "ESPN",
"image": "https://...",
"url": "https://...",
"categories": [1]
}
],
"pagination": {
"totalItems": 140,
"totalPages": 12,
"currentPage": 1,
"limit": 12
}
} /jogo/{nome}
Toca uma partida ao vivo. Aceita tanto o slug do título da partida quanto o par de times no formato time-x-time.
Parâmetros da rota
nome: string - Obrigatório. Slug do jogo, ex:corinthians-x-internacional.
Jogos são efêmeros: a lista só existe enquanto a partida está no ar. Consulte /api/games para saber o que está rolando agora.
Exemplo
<iframe
src="https://www.superflixapi.lat/jogo/corinthians-x-internacional"
width="100%"
height="100%"
frameborder="0"
allowfullscreen
allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
></iframe> Respostas de erro
404— "Jogo não encontrado ou não está mais ao vivo."
/api/games
Lista as partidas ao vivo do momento, com times, campeonato, horário de início e os links de player. É daqui que saem os títulos usados no embed acima.
Parâmetros da URL (Query Params)
Sem paginação: a lista é naturalmente pequena e se renova sozinha (cache de 1 minuto).
q: string - Busca pelo título da partida.sport: string - Mostra só um esporte (ex:Futebol,Basquete,Hóquei). Use os nomes que vêm emsportsna resposta.
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial. Chamada de outro domínio pelo navegador funciona normalmente.
curl "https://superflixapi.lat/api/games"
curl "https://superflixapi.lat/api/games?sport=Basquete" const res = await fetch("https://superflixapi.lat/api/games");
const data = await res.json(); Exemplo de Resposta
sport vem em cada jogo e sports lista os esportes disponíveis agora, com a quantidade de jogos de cada um, sem aplicar o filtro sport — use para montar os botões de filtro. Jogo sem esporte informado vem como Futebol.
{
"success": true,
"total": 7,
"sports": [
{ "name": "Futebol", "count": 6 },
{ "name": "Basquete", "count": 1 }
],
"games": [
{
"title": "Corinthians x Internacional",
"sport": "Futebol",
"players": ["https://..."],
"data": {
"league": "Brasileirão Série A",
"teams": {
"home": { "name": "Corinthians", "image": "https://..." },
"away": { "name": "Internacional", "image": "https://..." }
},
"timer": { "start": 1786064400 }
}
}
]
} /evento/{slug}
Toca um evento ao vivo. Use /api/events (logo abaixo) para listar os eventos disponíveis e pegar os slugs.
Parâmetros da rota
slug: string - Obrigatório. Slug do evento, gerado a partir do título no cadastro (ex:UFC JonJones vs Poatanviraufc-jonjones-vs-poatan). Os slugs válidos saem do camposlugde/api/events.
Exemplo
<iframe
src="https://www.superflixapi.lat/evento/ufc-jonjones-vs-poatan"
width="100%"
height="100%"
frameborder="0"
allowfullscreen
allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
></iframe> Respostas de erro
404— "Evento não encontrado."404— "Nenhuma fonte cadastrada para este evento."
/api/events
Lista os eventos ao vivo cadastrados manualmente, com título, slug, capa, horário de início e a URL pronta pra tocar em /evento/{slug}.
Parâmetros da URL (Query Params)
Sem paginação: a lista some sozinha dos eventos já encerrados (3h após o início).
q: string - Busca pelo título do evento.
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial. Chamada de outro domínio pelo navegador funciona normalmente.
curl "https://superflixapi.lat/api/events" const res = await fetch("https://superflixapi.lat/api/events");
const data = await res.json(); Exemplo de Resposta
{
"success": true,
"total": 1,
"events": [
{
"id": 12,
"slug": "final-do-mundial-de-mma",
"title": "Final do Mundial de MMA",
"cover_url": "https://...",
"starts_at": "2026-08-20T22:00:00",
"url": "https://www.superflixapi.lat/evento/final-do-mundial-de-mma"
}
]
} /api/titles?type=movie
Lista apenas os filmes cadastrados no banco de dados, do mais recente para o mais antigo. Use o tmdb_id de cada item para montar a URL de embed /filme/{tmdb_id}.
Parâmetros da URL (Query Params)
type: string - Obrigatório aqui. Usemoviepara filtrar só filmes.page: number - Número da página para paginação. Padrão:1.limit: number - Quantidade de itens por página. Padrão:12. Máximo:100.
Exemplo de Resposta
{
"success": true,
"titles": [
{
"id": 3,
"tmdb_id": "350",
"title": "O Diabo Veste Prada",
"type": "movie",
"poster_path": "/wPKD810IOsLgYldcKzcAh4OLmnx.jpg"
}
],
"pagination": {
"totalItems": 11554,
"totalPages": 963,
"currentPage": 1,
"limit": 12
}
} /api/titles?type=tv
Lista apenas as séries cadastradas no banco de dados. Para tocar um episódio, monte /serie/{tmdb_id}/{temporada}/{episodio} — se aquele episódio não existir, a própria página do embed avisa.
Parâmetros da URL (Query Params)
type: string - Obrigatório aqui. Usetvpara filtrar só séries.page: number - Número da página para paginação. Padrão:1.limit: number - Quantidade de itens por página. Padrão:12. Máximo:100.
Exemplo de Resposta
{
"success": true,
"titles": [
{
"id": 41,
"tmdb_id": "1416",
"title": "Grey's Anatomy",
"type": "tv",
"poster_path": "/jcEl8SISNfGdlQFwLzeEtsjDvpw.jpg"
}
],
"pagination": {
"totalItems": 3204,
"totalPages": 267,
"currentPage": 1,
"limit": 12
}
} /api/titles
Lista todos os filmes e séries cadastrados no banco de dados.
Parâmetros da URL (Query Params)
type: string - Filtro por tipo. Valores:all(padrão),movie,tv.page: number - Número da página para paginação. Padrão:1.limit: number - Quantidade de itens por página. Padrão:12. Máximo:100.after_id: number - Paginação por cursor, no lugar depage. Passe onextAfterIddevolvido na resposta anterior. Cada página custa só o tamanho dela (sem o custo de pular as anteriores), então é o jeito recomendado para varrer o catálogo inteiro. Quando presente,pageé ignorado.
Exemplos de uso
-
Só filmes
/api/titles?type=movie -
Só séries
/api/titles?type=tv -
Paginando (página 2, 24 por página)
/api/titles?type=movie&page=2&limit=24 -
Varrendo com cursor (o
nextAfterIdvem na resposta)/api/titles?type=movie&limit=100&after_id=15230
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial. Chamada de outro domínio pelo navegador funciona normalmente.
curl "https://superflixapi.lat/api/titles?type=movie&limit=5" const res = await fetch("https://superflixapi.lat/api/titles?type=movie&limit=5");
const data = await res.json(); Exemplo de Resposta
{
"success": true,
"titles": [...],
"pagination": {
"totalItems": 15,
"totalPages": 2,
"currentPage": 1,
"limit": 12,
"nextAfterId": 1530
}
} /api/titles/ids
Todos os tmdb_id do catálogo numa resposta só, separados por categoria. Sem paginação.
Para saber se um título está disponível, guarde esta lista do seu lado e atualize a cada poucas horas. Não percorra /api/titles página por página.
Parâmetros da URL (Query Params)
type: string - Opcional.movie,tv,animeoudorama. Com ele,idsecountstrazem só essa chave. Valor inválido responde400.
Como usar a resposta
- Cada lista é exatamente o conjunto de
tmdb_idde/api/filmes,/api/series,/api/animese/api/doramas.animeedoramase sobrepõem atv/movie. - Confira
counts[x] === ids[x].lengthantes de substituir a sua lista. - Mande o
ETagrecebido emIf-None-Matchna próxima chamada: sem mudança, a resposta é304sem corpo. - Só substitua a sua lista numa resposta
200comsuccess: true. Em503, mantenha a lista que você já tem e tente de novo mais tarde. Em429, espere oRetry-After.
Chamando a rota
curl --compressed "https://superflixapi.lat/api/titles/ids" Exemplo de Resposta
{
"success": true,
"updated_at": "2026-09-23T12:00:00Z",
"counts": { "movie": 4, "tv": 3, "anime": 1, "dorama": 1 },
"ids": {
"movie": [1378742, 1329471, 1576, 7737],
"tv": [80400, 312585, 125910],
"anime": [80400],
"dorama": [312585]
}
} /api/filmes · /api/series · /api/animes · /api/doramas
As mesmas quatro categorias do catálogo, cada uma na sua própria rota — pra quem quer montar
um site ou app de um único tipo de conteúdo (ex: um site só de anime) sem filtrar /api/titles?type= do
lado dele nem correr o risco de esquecer o parâmetro e misturar conteúdo. Mesmo formato de resposta, parâmetros e
paginação de /api/titles — só o filtro já vem aplicado no servidor.
Endpoints disponíveis
/api/filmes— todo filme, anime e dorama em formato de filme incluídos./api/series— toda série, anime e dorama em formato de série incluídos./api/animes— só anime, filme ou série./api/doramas— só dorama, filme ou série.
/api/filmes//api/series filtram por TIPO: anime e dorama entram junto, do mesmo jeito
que aparecem nas abas "Filmes"/"Séries" do catálogo. /api/animes//api/doramas filtram
pela classificação do título, não pelo tipo — por isso dorama pode vir como filme ou como série. Use as quatro
juntas só se seu site trata anime/dorama como categoria própria, separada de filme/série, senão vai contar o
mesmo título duas vezes.
Parâmetros da URL (Query Params)
page: number - Número da página para paginação. Padrão:1.limit: number - Quantidade de itens por página. Padrão:12. Máximo:100.
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial. Chamada de outro domínio pelo navegador funciona normalmente.
curl "https://superflixapi.lat/api/animes?limit=5" const res = await fetch("https://superflixapi.lat/api/animes?limit=5");
const data = await res.json(); Exemplo de Resposta
{
"success": true,
"titles": [...],
"pagination": {
"totalItems": 340,
"totalPages": 29,
"currentPage": 1,
"limit": 12
}
} /api/recents/*
Os conteúdos adicionados ao catálogo mais recentemente, do mais novo pro mais antigo. Só entra o que já tem fonte cadastrada — um título sem nada pra assistir não aparece aqui.
Endpoints Disponíveis
/api/recents- Filmes e Séries misturados./api/recents/movies- Apenas Filmes./api/recents/series- Apenas Séries./api/recents/episodes- Apenas Episódios de Séries.
A ordem é a de entrada no catálogo, não a de mexida na fonte: cadastrar ou trocar a fonte de um filme antigo não faz dele novidade. Nas rotas de título, o que conta é quando o filme/série entrou; em /api/recents/episodes, quando o episódio entrou.
Estas rotas devolvem no máximo os 500 itens mais recentes — totalItems reflete esse teto, não o tamanho do catálogo. São novidades, não listagem: pra percorrer o catálogo inteiro use /api/titles, que pagina no banco.
Parâmetros da URL (Query Params)
page: number - Página para paginação. Padrão:1.limit: number - Quantidade por página. Padrão:12. Máximo:100.force: boolean - Ignora o cache de 5 minutos e recompila a lista na hora. Útil logo depois de cadastrar algo. Padrão:false.
Exemplos de uso
-
Filmes e séries misturados
/api/recents -
Só episódios recém-adicionados
/api/recents/episodes -
Paginando (página 2, 24 por página)
/api/recents?page=2&limit=24 -
Ignorando o cache de 5 minutos
/api/recents?force=true
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial. Chamada de outro domínio pelo navegador funciona normalmente.
curl "https://superflixapi.lat/api/recents" const res = await fetch("https://superflixapi.lat/api/recents");
const data = await res.json(); Exemplo de Resposta (Títulos)
added_at não é uma data, apesar do nome: é a posição de entrada no catálogo, um número que só cresce. Serve pra comparar dois itens entre si (maior = entrou depois), nunca pra converter em data. A lista já vem ordenada por ele, do maior pro menor.
{
"success": true,
"titles": [
{
"id": "local_3",
"tmdb_id": 350,
"title": "O Diabo Veste Prada",
"type": "movie",
"poster_path": "https://image.tmdb.org/t/p/w500/wPKD810IOsLgYldcKzcAh4OLmnx.jpg",
"overview": "Uma recém-formada em jornalismo consegue um emprego...",
"added_at": 3
}
],
"pagination": {
"totalItems": 2400,
"totalPages": 200,
"currentPage": 1,
"limit": 12
}
} /api/calendario
Próximo episódio de série já cadastrada e próximos filmes a estrear (dados do TMDB), cada um marcado com has_source: já dá pra assistir agora ou ainda não. Mesma fonte da página /calendario.
Parâmetros da URL (Query Params)
kind: string - Filtro por tipo. Valores:movie,episode. Sem o parâmetro, vêm os dois misturados.page: number - Número da página para paginação. Padrão:1.limit: number - Quantidade de itens por página. Padrão:12. Máximo:100.
A janela é sempre "de 7 dias atrás pra frente" — não dá pra pedir uma data no passado distante nem um período customizado. Um filme sai da lista sozinho depois de velho o bastante (ver pruneUpcomingReleases).
Exemplos de uso
-
Filme e série misturados (sem o parâmetro
kind, vem tudo)/api/calendario -
Só série (próximo episódio de cada uma)
/api/calendario?kind=episode -
Só filme
/api/calendario?kind=movie -
Paginando (página 2, 24 por página)
/api/calendario?page=2&limit=24
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial. Chamada de outro domínio pelo navegador funciona normalmente.
curl "https://superflixapi.lat/api/calendario?kind=movie&limit=5" const res = await fetch("https://superflixapi.lat/api/calendario?kind=movie&limit=5");
const data = await res.json(); Exemplo de Resposta
season_number/episode_number/episode_title só existem no objeto quando kind é "episode" — filme não tem essas chaves, em vez de vir com elas em null. title_id só vem preenchido quando has_source é true — é o id de /api/titles pra quem quiser buscar mais detalhe do título já cadastrado.
{
"success": true,
"releases": [
{
"kind": "episode",
"tmdb_id": 1399,
"title_id": 42,
"season_number": 8,
"episode_number": 1,
"episode_title": "Um Novo Começo",
"release_title": "Nome da Série",
"poster_path": "https://image.tmdb.org/t/p/w500/xxx.jpg",
"air_date": "2026-09-10",
"has_source": false
},
{
"kind": "movie",
"tmdb_id": 1684016,
"title_id": null,
"release_title": "Nome do Filme",
"poster_path": "https://image.tmdb.org/t/p/w500/yyy.jpg",
"air_date": "2026-09-12",
"has_source": false
}
],
"pagination": {
"totalItems": 57,
"totalPages": 5,
"currentPage": 1,
"limit": 12
}
} /api/epg
Guia de programação (EPG) dos canais de TV ao vivo já listados em /api/channels: o que já passou, o que está passando agora e o que vai passar, com horário. A grade é sincronizada em segundo plano, então esta rota só lê o que já está salvo — não bate em nenhuma API externa a cada request.
Parâmetros da URL (Query Params)
channel: string - Obrigatório. Um ou mais slugs de canal — o mesmonomeusado em/canal/{nome}, derivado do camponamede/api/channels(minúsculo, sem acento, hífen no lugar dos espaços). Separe múltiplos com vírgula (até 20 por requisição), ou useallpara trazer todos os canais mapeados de uma vez.hoursPast: number - Quantas horas para trás incluir na grade. Padrão:3. Máximo:48.hoursFuture: number - Quantas horas para frente incluir na grade. Padrão:24. Máximo:72.
Exemplos de uso
-
Um canal específico
/api/epg?channel=espn-brasil -
Vários canais de uma vez (separados por vírgula)
/api/epg?channel=espn-brasil,band-sports -
Todos os canais que já têm programação mapeada
/api/epg?channel=all -
Alargando a janela de tempo (6h pra trás, 48h pra frente)
/api/epg?channel=espn-brasil&hoursPast=6&hoursFuture=48
Chamando a rota
Rota pública: responde JSON sem chave nem cabeçalho especial. Chamada de outro domínio pelo navegador funciona normalmente.
curl "https://superflixapi.lat/api/epg?channel=espn-brasil" const res = await fetch("https://superflixapi.lat/api/epg?channel=espn-brasil");
const data = await res.json(); Exemplo de Resposta
{
"success": true,
"channels": {
"espn-brasil": [
{
"title": "SportsCenter",
"startAt": 1786060800,
"stopAt": 1786064400,
"isLive": true,
"image": "https://.../sportscenter.jpg",
"description": "Resumo dos principais lances e notícias do dia no esporte."
},
{
"title": "Linha de Combate",
"startAt": 1786064400,
"stopAt": 1786068000,
"isLive": false,
"image": null,
"description": null
}
]
},
"serverTime": 1786061532
} image e description só vêm preenchidos no programa que está no ar (isLive: true) — image é uma prévia do que está passando (não a logo do canal, essa já está em /api/channels) e description é a sinopse. Nem todo canal tem: quando não há, os dois campos voltam null.
Notas
startAt/stopAtsão timestamps unix (UTC).- Nem todo canal tem programação mapeada — quando não tem, o array daquele
channel_idvolta vazio. 400— falta o parâmetrochannel.
Chave pública no embed
No plano Premium, o seu embed roda sem anúncios, com o botão de download liberado. Como o embed roda no seu site, não temos sessão de login para saber que ele é seu — quem prova isso é a chave.
1. Gere a chave
Em seu perfil, clique em + Chave pública. Ela aparece uma única vez: guardamos apenas o hash, então não conseguimos exibi-la de novo. Se perder, revogue e gere outra.
2. Cadastre seus domínios
Obrigatório — sem nenhum domínio cadastrado, a chave não funciona.
A chave vai na URL do iframe, ou seja, qualquer visitante do seu site consegue
lê-la no código-fonte. É a trava de domínio que impede que ela seja copiada e
usada no site de outra pessoa. Cadastrar seusite.com já cobre
www.seusite.com e qualquer subdomínio.
Domínio recém-cadastrado pode levar até 1 minuto para valer. Guardamos a validação em cache por pouco tempo para não consultar o banco a cada carregamento de player — se você acabou de adicionar o domínio e ainda vê anúncio, espere um minuto e recarregue antes de suspeitar da configuração.
3. Use no iframe
<iframe
src="https://www.superflixapi.lat/filme/550?key=sb_pk_SUACHAVE"
allowfullscreen
allow="autoplay; fullscreen; picture-in-picture"
></iframe>
Funciona igual em /serie/{id}/{t}/{e},
/canal/{nome}, /jogo/{nome} e
/evento/{slug} — é só acrescentar ?key=.
4. Personalize as cores (opcional)
Ainda no Premium, o player pode sair com a sua cara. A personalização vai na própria URL do embed, então cada site seu pode ter um visual diferente — e o jeito mais rápido de montar isso é o gerador em seu perfil, que já mostra a prévia com as cores escolhidas.
accent: hex - Cor de destaque: barra de progresso, volume e realces. Ex:accent=ff0000. Aceita 3 ou 6 dígitos, com ou sem#.surface: hex - Cor de fundo dos menus de qualidade, legenda e fonte.brand: string - Marca do seu site, em texto, no canto superior esquerdo. Até 24 caracteres; some junto com os controles.
<iframe
src="https://www.superflixapi.lat/filme/550?key=sb_pk_SUACHAVE&accent=ff0000&brand=MeuSite"
allowfullscreen
allow="autoplay; fullscreen; picture-in-picture"
></iframe> Valor que não for uma cor válida é ignorado e o player usa a cor padrão — o vídeo nunca deixa de tocar por causa de um parâmetro digitado errado. Se as cores não estiverem valendo, confira antes se a chave está sendo aceita: sem plano Premium ativo os parâmetros são ignorados.
Se o embed voltar a mostrar anúncio
Quase sempre é um destes: a chave venceu junto com o plano, o domínio de onde o
iframe está sendo servido não foi cadastrado, ou o ?key= se perdeu
ao montar a URL. Confira em seu perfil se a chave está
ativa e se o domínio bate exatamente com o do navegador. Se não for nenhum
desses casos, contate o suporte no Telegram.
Quando a chave chega mas é recusada, o player avisa no console do navegador
([superflixapi] chave pública recusada: motivo) e marca o
<html> com data-sb-key-error. O motivo é o que
resolve o caso: domain_not_allowed é domínio fora da lista,
missing_origin é iframe sem Referer (o de sempre:
referrerpolicy="no-referrer") e plan_expired é a
validade do plano. Sem chave na URL não há aviso — é o plano Free normal.
Xtream (app de IPTV)
Com um plano Xtream, você assiste ao nosso catálogo de filmes e séries direto num app de IPTV (TiviMate, IPTV Smarters e qualquer outro que aceite Xtream). Aqui não há chamada de API nem iframe: você só configura o app com um servidor, um usuário e uma senha.
1. Assine e crie o seu login
Em /planos, escolha o pacote de conexões. Depois de pago, abra seu perfil, vá em Acesso Xtream e escolha o usuário e a senha do seu login (a senha precisa ter pelo menos 6 caracteres). É um login só, com o número de conexões do seu pacote.
2. Configure o app
No app, escolha o tipo de login Xtream Codes (ou "Xtream Codes API") e preencha:
- Servidor / URL:
https://superflixapi.lat - Usuário: o que você escolheu no perfil
- Senha: a que você escolheu no perfil
Se o app pedir a URL completa da lista M3U, não use: este servidor entrega o catálogo pelo login Xtream, não por M3U.
3. O que aparece no app
- Filmes: categorias Filmes, Animes (filmes) e Doramas (filmes).
- Séries: categorias Séries, Animes e Doramas, com temporadas e episódios.
- Idioma: aparece a versão dublada. Quando só existe a legendada, o título vem com
(Legendado)no nome. - Sem TV ao vivo: canais e jogos não fazem parte deste plano, e essas abas ficam vazias.
O catálogo é o que já está hospedado por nós, então cresce com o tempo. Um título que ainda não aparece no app pode aparecer depois.
Conexões simultâneas
O número de conexões é quantos aparelhos podem assistir ao mesmo tempo com o mesmo login. Cada aparelho ocupa uma vaga quando você dá play num filme ou episódio.
- Com todas as vagas ocupadas, o aparelho novo é recusado com a mensagem
Max connections reached. Quem já está assistindo não é derrubado. - Dar play de novo no mesmo aparelho reaproveita a vaga dele, não ocupa outra.
- A vaga é liberada sozinha depois de algumas horas sem novo play naquele aparelho.
Precisa de mais conexões do que o maior pacote? Fale com a gente pelo Telegram.
Se algo não funcionar
- "Usuário ou senha inválidos" / login recusado: confira os dois no app, sem espaço sobrando. Se esqueceu a senha, defina outra em Acesso Xtream, no perfil.
- Conta vencida (
Account expired) ou listas vazias: o plano venceu. Renove em /planos; vale em poucos minutos. Max connections reached: feche o app em outro aparelho, ou aguarde a vaga liberar.- Filme não abre: tente de novo; se persistir com um título só, avise no Telegram com o nome dele.
Para quem desenvolve
É o protocolo Xtream padrão, que o app usa por baixo. Você só precisa disto se for
montar o seu próprio app. Todas as chamadas levam username e
password.
GET https://superflixapi.lat/player_api.php?username={usuario}&password={senha}— login, validade e limite de conexões.&action=get_vod_categories·get_series_categories— as categorias.&action=get_vod_streams— lista de filmes, cada um com umstream_id.&action=get_series— lista de séries, cada uma com umseries_id.&action=get_series_info&series_id={series_id}— temporadas e episódios da série, e cada episódio tem o próprioid.&action=get_vod_info&vod_id={stream_id}— detalhes de um filme.
Como chegar ao play
O endereço de reprodução usa o id do próprio filme ou episódio, não o número da temporada nem do episódio:
- Filme:
https://superflixapi.lat/movie/{usuario}/{senha}/{stream_id}.mp4, com ostream_iddeget_vod_streams. - Episódio:
https://superflixapi.lat/series/{usuario}/{senha}/{id_do_episodio}.mp4, com oiddo episódio dentro deepisodes, emget_series_info.
Exemplo para uma série: get_series devolve series_id 2;
get_series_info&series_id=2 devolve a temporada 1 com o episódio 1 de
id 39; o play fica https://superflixapi.lat/series/{usuario}/{senha}/39.mp4.
O play responde com um redirecionamento para o arquivo, e a extensão pode ser omitida.
Usuário e senha vão na URL, então não compartilhe o login e não o coloque em código público.
GET /api/v1/stream
APIDevolve o link direto (m3u8/mp4) em JSON, para você tocar no seu próprio player em vez de embedar o nosso. Exclusivo do plano API.
Começar
Autenticação
Header Authorization: Bearer sb_sk_..., com a
chave secreta gerada em seu perfil.
A chave pública do plano Premium não funciona aqui — ela é
visível no embed, e aceitá-la publicaria o link direto do catálogo inteiro.
Chame sempre do seu servidor, nunca do navegador: não enviamos headers de CORS justamente para que a chave não acabe no seu front-end.
Parâmetros
tmdb_id+type=movie— filmetmdb_id+type=tv+season+episode— episódiochannel={slug}— canal de TVgame={slug}— jogo ao vivoevent={id}— evento
Exemplo
curl -H "Authorization: Bearer sb_sk_SUACHAVE" \
"https://superflixapi.lat/api/v1/stream?tmdb_id=550&type=movie" {
"success": true,
"type": "movie",
"title": "Clube da Luta",
"sources": [
{
"kind": "stream",
"url": "https://cdn.exemplo.com/hls/arquivo.m3u8",
"format": "m3u8",
"headers": {
"User-Agent": "Mozilla/5.0 ...",
"Referer": "https://exemplo-player.com/",
"Origin": "https://exemplo-player.com",
"Accept": "*/*"
},
"subtitle_url": null,
"subtitle_lang": null,
"subtitle_label": null
}
]
} Arquivo hospedado por nós (expires_at)
Quando o conteúdo está hospedado por nós, sources traz uma fonte por idioma
— no máximo duas, audio: "dublado" e audio: "legendado" —, cada uma com
format: "mp4" e o campo expires_at. Ofereça as duas ao usuário quando vierem. É um .mp4 comum em
https: toque a url direto no seu player — <video>, ExoPlayer,
AVPlayer, VLC — sem proxy, sem headers e sem a receita abaixo. Busca por posição
(Range) e reprodução no navegador a partir de qualquer domínio funcionam.
{
"success": true,
"type": "movie",
"title": "Clube da Luta",
"sources": [
{
"kind": "stream",
"url": "https://...r2.cloudflarestorage.com/...mp4?X-Amz-Signature=...",
"format": "mp4",
"audio": "dublado",
"expires_at": "2026-10-01T18:00:00.000Z",
"thumbnails_vtt": "https://superflixapi.lat/api/v1/thumbnails?t=...",
"subtitle_url": null,
"subtitle_lang": null,
"subtitle_label": null
}
]
} - O link vale até
expires_at(algumas horas). Guarde só pela sessão de quem assiste; depois disso, peça a fonte de novo a esta rota. - Não edite a URL: ela é assinada, e qualquer mudança (inclusive na query string) faz o arquivo recusar a requisição.
- O link serve para quem o tiver até expirar — entregue ao player de quem vai assistir, não publique em página aberta.
- Uma fonte
audio: "legendado"semexpires_até de terceiro: siga okinddela como nas demais fontes (ver Ramifique pelo campo kind). thumbnails_vtté a prévia da barra de progresso: um WebVTT de miniaturas (sprites com#xywh), no formato que Video.js, Plyr, JW Player, Shaka e similares aceitam como "thumbnails". Passe a URL como está ao player — ela já funciona em qualquer domínio e vale o mesmo tempo que o vídeo. Vemnullquando a prévia ainda não foi gerada.
Passo a passo: do link ao player
Receita completa para tocar uma fonte que exige cabeçalhos. O resumo é: o navegador nunca fala com a CDN — ele fala com o seu servidor, e o seu servidor fala com a CDN.
Pule esta receita inteira quando headers vier vazio
(ver Se headers vier vazio): ali o link toca direto, sem proxy e sem renovação.
- 1. Peça a fonte (no seu servidor, com a chave secreta).
Se vier
expires_at, o arquivo é hospedado por nós: toque aurldireto e pare por aqui (ver Arquivo hospedado por nós). Usesources[0]; se ela vier comerror, tente a próxima. Confira okindantes de seguir: os passos abaixo valem parakind: "stream". Se vier"iframe", não há link direto para proxiar — embede aurle pare por aqui. - 2. Busque o manifesto na URL devolvida, enviando
todos os pares do objeto
headers. Se essa busca falhar — timeout, conexão recusada,403— trate como fonte ruim e tentesources[1], mesmo semerrorna resposta original (ver Sobre o campo kind). - 3. Reescreva os segmentos. As linhas do manifesto que não
começam com
#são segmentos (ou sub-manifestos de qualidade), e costumam ser URLs absolutas, num host diferente do manifesto. Troque cada uma por uma rota do seu proxy. - 4. Sirva os segmentos pela sua rota, repetindo os
mesmos
headers. A exigência é da rede, não do host — reutilizar o objeto que veio na resposta funciona mesmo quando o segmento está noutro domínio. - 5. Aponte o player (hls.js, Video.js, ExoPlayer…) para a URL do seu manifesto proxiado, não para a da CDN.
error não é o único jeito de uma fonte ser ruim.
Esse campo só existe quando a FALHA NOSSA (a extração do link, do nosso lado,
não resolveu). Uma fonte pode vir perfeitamente limpa, sem error
nenhum, e mesmo assim recusar conexão só quando o SEU servidor tenta buscá-la —
algumas CDNs bloqueiam por faixa de IP de datacenter, e isso varia por quem
está chamando, não por nós. sources[0] sem error é a
melhor aposta, não uma garantia: trate qualquer falha de rede/403
no passo 2 como "tenta a próxima fonte", exatamente como trataria um
error explícito.
Exemplo mínimo em Node/Express — dois endpoints, um para o manifesto e outro para os segmentos:
const API = "https://superflixapi.lat/api/v1/stream";
const KEY = process.env.SB_SECRET_KEY; // sb_sk_... NUNCA no front-end
// 1 + 2 + 3: manifesto reescrito
app.get("/stream/:slug", async (req, res) => {
const r = await fetch(API + "?channel=" + req.params.slug, {
headers: { Authorization: "Bearer " + KEY }
});
const data = await r.json();
const src = (data.sources || []).find(s => s.url);
if (!src) return res.status(404).send("sem fonte");
const manifest = await (await fetch(src.url, { headers: src.headers })).text();
// guarde os headers para os segmentos (cache/sessao/assinado, como preferir)
const id = saveSession({ headers: src.headers });
const rewritten = manifest.split("
").map(line => {
if (!line || line.startsWith("#")) return line; // diretiva HLS
const abs = new URL(line, src.url).toString(); // cobre relativa tambem
return "/seg/" + id + "?u=" + encodeURIComponent(abs);
}).join("
");
res.type("application/vnd.apple.mpegurl").send(rewritten);
});
// 4: segmentos, com os MESMOS headers
app.get("/seg/:id", async (req, res) => {
const { headers } = loadSession(req.params.id);
const upstream = await fetch(req.query.u, { headers });
res.status(upstream.status);
res.set("Content-Type", upstream.headers.get("content-type") || "video/mp2t");
Readable.fromWeb(upstream.body).pipe(res);
}); Cuidados que valem tempo de depuração: o link resolvido é
assinado e expira (cerca de 1h em algumas redes) — para conteúdo
ao vivo, peça a fonte de novo antes disso em vez de guardar a URL; não confie na
extensão do arquivo (segmentos chegam como .doc, .js,
.pdf); e o limite é de 60 requisições por minuto por chave,
então busque a fonte uma vez por sessão, não a cada segmento.
Lendo a resposta
Como saber se um conteúdo é reproduzível
Nem todo tmdb_id do TMDB está no nosso catálogo, e nem todo título do
catálogo tem fonte cadastrada. A resposta distingue os dois casos — não trate os
dois 404 abaixo como o mesmo erro:
-
404—"error": "Conteúdo não encontrado. ...": otmdb_id/channel/game/eventnão existe no nosso catálogo. -
404—"error": "Nenhuma fonte cadastrada para este conteúdo.": o título existe, mas ninguém registrou uma fonte pra ele ainda.
200 (success: true) só garante que existe pelo
menos uma fonte cadastrada — não que ela toca agora. Cada item de
sources pode vir com url: null e
"error": "extraction_failed" quando a extração falhou na hora da
chamada (CDN de origem instável, ou a fonte morreu de verdade). Pra saber se dá
pra tocar, verifique se pelo menos um item tem url preenchido — não
pare só no success do topo:
const playable = data.success && (data.sources || []).some(s => s.url);
Pra checar disponibilidade em massa sem gastar o limite de 60/min desta rota,
use GET /api/titles?tmdb_id=... primeiro (pública, sem chave, sem
esse teto) — ela só diz se o título existe no catálogo, não se
tem fonte tocável, mas já descarta rápido quem nem chegou a ser cadastrado.
Ramifique pelo campo kind
Nem toda fonte é um link direto — e mesmo quando é, o formato pode não ser
o que seu player espera. O kind é o que diz qual é qual: um
loader que ignore o kind e tente tocar qualquer url
vai descartar em silêncio parte do catálogo.
-
kind: "stream"— link direto (m3u8/mp4). Toque no seu player, respeitando o objetoheadersquando ele vier. -
kind: "iframe"— não há link direto. Aurlé uma página de player que só toca dentro de um<iframe>. Oformatvemnull. -
kind: "dash"— link direto, mas em MPEG-DASH com DRM ClearKey, não HLS/MP4. Precisa de um player com suporte a EME (Shaka Player, dash.js —<video>/hls.js sozinho não toca isto). Vem com um objetodrm({ type: "clearkey", keyId, key }) que seu player precisa configurar ANTES de carregar aurl, e um camponotecom esta mesma explicação em texto. -
kind: "vidmoly"— não é um link de vídeo, é a URL de uma página de embed. Diferente de"iframe", não é "sem link direto" — é "o link direto só existe depois de uma extração que precisa rodar no seu frontend". Ver a seção dedicada logo abaixo antes de tratar isto como um"iframe"qualquer. -
kind: "hubby"— é um.mp4direto, mas o CDN de origem só responde a partir de um IP residencial: se o seu servidor buscar esse link, a resposta é403sempre. Repasse aurlpro navegador de quem assiste (ele toca normal num<video>) e nunca tente baixá-la ou proxiá-la pela sua infraestrutura. Vem com um camponoterepetindo isso. O link expira em algumas horas: não guarde, peça de novo a esta rota quando for tocar. Estas fontes vêm sempre no fim da lista, então prefira as anteriores quando existirem. -
kind: "streamflash"— mesma ideia do"vidmoly": é a URL de uma página, e o link de vídeo precisa ser extraído no aparelho de quem assiste. Ver a seção dedicada abaixo.
if (source.kind === "iframe") {
// sem link direto: embede a página
render(`<iframe src="${source.url}" allowfullscreen></iframe>`);
} else {
// link direto: seu player + os headers da resposta
play(source.url, source.headers);
} Conteúdo ao vivo é onde isso mais aparece. Canal e jogo sem
fonte de link direto cadastrada respondem com um único item
kind: "iframe", em vez de 404. Não é limitação da API:
esse conteúdo simplesmente não existe como link direto em lugar nenhum, então
embedar a página é a única forma de exibi-lo.
{
"success": true,
"type": "game",
"title": "Time A x Time B",
"sources": [
{
"kind": "iframe",
"url": "https://exemplo-player.com/live/abc",
"format": null,
"subtitle_url": null,
"subtitle_lang": null,
"subtitle_label": null
}
]
}
Quando existem os dois, o link direto vem primeiro e o
iframe por último, como rede de segurança — então
sources[0] continua sendo a melhor opção disponível.
Sobre o campo kind
stream é link direto, tocável. Uma fonte com
"error": "extraction_failed" é uma fonte cuja
extração falhou na hora; tente a próxima da lista.
Quando o problema é nosso — conteúdo fora do catálogo, título sem fonte
cadastrada, fonte que falhou na extração, ou uma falha de verdade do lado de
cá — a resposta vem com um campo support
({ message, telegram }) junto do error. Não aparece
em erro de requisição (parâmetro faltando, chave inválida, limite de taxa
estourado): esses são seus pra corrigir, um link de suporte ali estaria
apontando pro lado errado.
Não decida o player pela extensão da URL: várias CDNs servem o manifesto HLS
com nome disfarçado (.txt, .doc, .js).
Use o campo format, que já leva isso em conta.
Os links são temporários (a maioria expira em minutos) e
costumam exigir os mesmos headers de Referer/User-Agent
que o CDN de origem pede. Resolva na hora do play, não guarde em cache.
kind: "vidmoly" — extração no seu frontend
O CDN de vídeo do Vidmoly assina o link do .m3u8 amarrado à
rede (ASN) de quem buscou a página de embed, não de quem
busca o vídeo depois. Um link extraído pelo NOSSO servidor nasceria preso à
rede da Cloudflare e falharia pra qualquer pessoa assistindo de fora dela —
inclusive de uma rede residencial comum. Não existe extração do lado do
servidor (nosso ou seu) que produza um link que sobreviva até chegar no seu
usuário final. Por isso não devolvemos um .m3u8 pronto pra esta
fonte: url aqui é a página de embed, matéria-prima
pra você extrair.
A extração em si é simples e roda no navegador do seu usuário
(nunca no seu backend — o link precisa nascer na mesma rede de quem vai
assistir). O detalhe que decide tudo é qual página você busca: o
url que devolvemos pode vir em dois formatos, e só um deles serve.
A página clássica (/embed-{id}.html) tem a configuração do player e
libera CORS; a página nova (/v/{id}) virou uma SPA que monta o
player em JavaScript e não manda cabeçalho CORS nenhum — buscar
ela devolve erro de CORS, ou HTML sem nada pra casar. Como o id é o mesmo nos
dois formatos, dá pra reconstruir a versão que funciona:
function vidmolyId(pageUrl) {
const path = new URL(pageUrl).pathname;
const m = path.match(/\/v\/([a-zA-Z0-9]+)/) ||
path.match(/\/embed-([a-zA-Z0-9]+)\.html/);
return m ? m[1] : null;
}
async function readSource(url) {
const res = await fetch(url);
const html = await res.text();
const m = html.match(/sources:\s*\[\s*\{\s*file:\s*['"]([^'"]+)['"]/i);
return m ? m[1] : null;
}
async function extractVidmolyUrl(pageUrl) {
const id = vidmolyId(pageUrl);
// As duas tentativas saem JUNTAS, não uma depois da outra: a página que não
// serve pode demorar bastante pra falhar, e esperar por isso antes de tentar
// a outra só atrasa o começo do vídeo.
const tentativas = [readSource(pageUrl).catch(() => null)];
if (id) tentativas.push(readSource(`https://vidmoly.biz/embed-${id}.html`).catch(() => null));
for (const link of await Promise.all(tentativas)) {
if (link) return link; // .m3u8 pronto pro seu player nativo
}
return null;
} O resultado toca direto no seu player (hls.js, Shaka, etc.), sem anúncio nenhum — a UI/player do Vidmoly (onde o anúncio deles mora) nunca chega a renderizar, porque você nunca carrega a página, só lê o HTML dela. É o mesmo código que usamos no nosso próprio player para esta fonte.
Risco honesto: essa extração depende do HTML atual da página deles — se o Vidmoly mudar a estrutura, para de casar dos dois lados ao mesmo tempo (no nosso player e no seu). Não temos como avisar antecipadamente; se parar de funcionar, volte nesta seção: o código aqui é o mesmo que roda no nosso player, então é aqui que a versão corrigida aparece.
kind: "streamflash" — extração no aparelho
O servidor do StreamFlash só entrega o vídeo pra mesma rede que
abriu a página. Extrair no seu backend produz um link que funciona
no seu servidor e falha no aparelho do usuário, com
403 Forbidden invalid token — por isso url aqui é a
página, e a extração tem que rodar no dispositivo que vai
reproduzir. O link resolvido também não dá pra reconstruir de volta pra
página: os ids não têm relação entre si.
const HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'pt-BR,pt;q=0.9,en;q=0.8',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none'
};
async function extractStreamflashUrl(pageUrl) {
const res = await fetch(pageUrl, { headers: HEADERS });
const html = await res.text();
const m = html.match(/<source\s+src=["']([^"']+)["']\s+type=["']video\/mp4["']/i);
if (!m) return null;
const src = m[1].replace(/&/g, '&'); // sem isto: 403 "invalid token"
return new URL(src, new URL(pageUrl).origin).href;
} Os dois detalhes acima não são cosméticos — cada um falha de um jeito que não aponta pra causa:
- Os headers de navegador são obrigatórios. Sem eles a
página responde
200com o corpoBot detected— não é erro, não redireciona, e o seu regex simplesmente não acha nada. - Decodifique
&para&. O atributosrcvem com a entidade HTML. Usando o valor cru, o servidor responde403 Forbidden invalid token— a mensagem engana: o token está correto, o que falta é o servidor enxergar o parâmetro.
App nativo: rode o código acima no próprio aparelho e toque o link resultante ali mesmo. É o caminho mais simples e não tem CORS no meio.
Site: a extração não roda no navegador — a página
deles não manda Access-Control-Allow-Origin, então o
fetch é bloqueado. Faça as duas pontas no seu
servidor: ele busca a página, extrai o link e serve o vídeo pro
navegador do visitante. O que nunca funciona é dividir as pontas — extrair num
lugar e reproduzir de outra rede é exatamente o que devolve
403 Forbidden invalid token.
Qualidade: quando vem no manifesto e quando vem em qualities
A maioria das fontes é HLS: o próprio url (manifesto .m3u8)
já carrega os vários níveis de qualidade dentro dele — seu player (hls.js, Shaka,
etc.) lê e deixa escolher, sem precisar de nada extra daqui.
Uma fonte sem manifesto único, onde cada qualidade é um arquivo separado, chega
com um campo qualities a mais — lista de { label, url }
da melhor pra pior, com url igual a qualities[0].url. Ignore
o campo se só quiser a melhor qualidade; use-o se quiser deixar seu usuário
escolher. Ausente ou com 1 item só = não há o que escolher pra essa fonte.
O campo headers não é opcional
Boa parte das CDNs recusa a requisição (403) se ela não vier com o
Referer, o Origin e o User-Agent que
o objeto headers traz. Repita-os exatamente como vieram ao buscar
o link — inclusive nos segmentos do HLS, não só no manifesto.
Isso implica que essas fontes não tocam direto num player do navegador. Referer e Origin são forbidden headers: o
JavaScript da página não consegue defini-los. Busque a partir do
seu servidor (ou de um proxy seu) e sirva ao seu player a partir dali.
Fonte cujo headers não seja necessário funciona direto.
Se headers vier vazio, toque direto
Algumas fontes chegam com "headers": . Isso não é um descuido da
resposta: é a garantia de que aquele link não precisa de proxy nenhum.
Aponte o player direto para a url — inclusive um player web, que nos
outros casos não conseguiria por causa dos forbidden headers.
Nessas fontes nós entregamos a playlist já resolvida: escolhemos a maior qualidade disponível e servimos o manifesto com a lista de segmentos definitiva. A consequência importante é que nada no caminho de reprodução expira, então o vídeo toca do início ao fim sem o seu app precisar renovar link nenhum. Em troca, não há troca automática de qualidade (ABR) nessas fontes — a qualidade é fixa, sempre a melhor.
Os segmentos continuam vindo direto da CDN; só o manifesto passa
por nós. Guarde a url à vontade: ela vale por 7 dias. Se ela vencer,
a falha aparece como 403 no play — nunca no meio do filme —
e basta pedir a fonte de novo.
É assim que essa resposta chega. Repare que kind e format
são os mesmos de sempre — só a url aponta para nós e o
headers vem vazio:
{
"success": true,
"type": "movie",
"title": "Exemplo",
"sources": [
{
"kind": "stream",
"url": "https://superflixapi.lat/api/v1/playlist?t=SEU_TOKEN",
"format": "m3u8",
"headers": {},
"subtitle_url": null,
"subtitle_lang": null,
"subtitle_label": null
}
]
}
Ou seja: player.load(source.url) e acabou. Não precisa proxiar, não
precisa mandar cabeçalho e não precisa renovar nada durante a reprodução.
Legenda: subtitle_url null não quer dizer "sem legenda"
O subtitle_url só traz legenda avulsa, cadastrada
à parte da fonte. Em boa parte do catálogo a legenda vem dentro do
manifesto HLS, como faixa alternativa — nesses casos os três campos
subtitle_* vêm null e a legenda existe assim mesmo.
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subtitles",LANGUAGE="und",NAME="Ptbr",URI="/md/..." Duas armadilhas aí, as duas silenciosas — nenhuma delas devolve erro, só uma tela sem legenda:
- O
URI=não é o arquivo de legenda, é outra playlist (#EXTM3Ucom um único#EXTINF). O arquivo está uma camada abaixo, e costuma ficar em outro host. - O arquivo costuma ser SRT, não WebVTT — e servido com
Content-Typeerrado, às vezestext/html. Players baseados em hls.js só parseiam WebVTT: carregam a faixa, não encontram nenhuma cue e seguem sem avisar.
A conversão é menor do que parece — SRT e WebVTT só diferem no cabeçalho e na
vírgula dos milissegundos. Busque por fetch (assim o
Content-Type errado deixa de importar) e entregue um Blob ao
<track>:
let text = await (await fetch(trackUri)).text();
// 1) o URI da faixa pode ser uma playlist: siga até o arquivo
if (text.trimStart().startsWith("#EXTM3U")) {
const file = text.split("\n").map(l => l.trim())
.find(l => l && !l.startsWith("#"));
text = await (await fetch(new URL(file, trackUri))).text();
}
// 2) SRT -> WebVTT
const vtt = text.startsWith("WEBVTT")
? text
: "WEBVTT\n\n" + text.replace(/(\d+:\d{2}:\d{2}),(\d{3})/g, "$1.$2");
const track = document.createElement("track");
track.src = URL.createObjectURL(new Blob([vtt], { type: "text/vtt" }));
track.kind = "subtitles";
track.default = true;
video.appendChild(track);
Resolva a URL relativa contra o destino final da resposta
(response.url), não contra a que você pediu: quando o arquivo está
em outro host há redirecionamento no caminho, e usar a URL pedida gera um link
errado só em parte dos títulos.
Plano e limites
Vencimento do plano
Toda resposta 200 traz o cabeçalho X-Plan-Expires-At
com a data de vencimento do seu plano, em ISO 8601 (UTC):
X-Plan-Expires-At: 2026-09-24T02:55:01.244Z
Quando faltam 48h ou menos, o JSON também vem com o campo
plan_warning:
"plan_warning": {
"message": "Seu plano API vence em menos de 48h. Renove em /planos para não interromper a integração.",
"expires_at": "2026-09-24T02:55:01.244Z",
"renew_url": "https://superflixapi.lat/planos"
}
Registre esse campo no seu log ou dispare um alerta quando ele aparecer.
Depois do vencimento, a chave passa a receber 403 com
"error": "plan_expired" até a renovação.
Cache: não chame a API a cada request do seu usuário
A causa mais comum de 429 — e de custo desnecessário do seu lado,
se você proxia o vídeo como no passo a passo acima — não é volume de usuários,
é buscar a fonte de novo a cada requisição que chega no seu
servidor, em vez de reaproveitar o que já foi resolvido.
- Resolva uma vez por sessão de reprodução, nunca por segmento.
Um filme de 2h reproduzido em segmentos de ~6s são milhares de requisições
do player — se cada uma chamar
/api/v1/streamde novo, você multiplica seu próprio tráfego (e o limite da sua chave) por milhares sem necessidade nenhuma. Peça a fonte uma vez, guarde aurle osheaders, e reutilize para todos os segmentos daquela sessão. - Nunca ponha
setInterval/polling nesta rota esperando "link novo" ou "conteúdo atualizado". Não existe nada pra descobrir chamando de novo sem motivo — só peça de novo quando o link atual falhar de verdade (403/expirado) ou quando um usuário abrir uma nova sessão de reprodução. - Cacheie do seu lado pela janela de validade do link, não
indefinidamente: a maioria expira em minutos, algumas redes em ~1h, e o
link resolvido por nós (
headers: {}, ver Se headers vier vazio) vale 7 dias. Guardar menos que isso gasta chamada à toa; guardar mais só adia o403pro meio da reprodução, quando é mais difícil de recuperar. - Falha de rede não é motivo pra tentar de novo na hora, em loop.
Se a busca ao manifesto/segmento falhar, trate como fonte ruim e passe para
sources[1](ver passo 2 acima) em vez de bater na mesma URL repetidamente — isso só empilha requisições sem chance de o resultado mudar no mesmo segundo. - O limite é por chave, não por espectador seu. Se o seu app
chama
/api/v1/streamdireto do lado do usuário final (ou uma vez por espectador simultâneo, em vez de uma vez por título), qualquer pico de audiência estoura o limite pra todo mundo ao mesmo tempo. Centralize a chamada no seu servidor com o cache acima e sirva o resultado já cacheado pros seus usuários.
Limites
60 requisições por minuto por chave, e um teto diário (padrão 10.000/dia,
pensado pra cobrir um site com uso real de sobra — se você resolve uma vez
por sessão de reprodução como recomendado acima, não deve nem chegar perto).
Estourou qualquer um dos dois, volta 429 (a mensagem diz qual
dos dois foi). Chave inválida ou revogada volta 401; plano sem o
benefício, 403.
Boas práticas ao cadastrar
Qualquer pessoa logada pode cadastrar fonte, canal, jogo ou evento — e é isso que mantém o catálogo vivo. Em troca, o cadastro precisa ser confiável: fonte quebrada ou enganosa queima a experiência de todo mundo que abrir aquele título.
O que fazer
- Teste antes de salvar. Abra o link e confirme que ele toca de verdade, do começo ao fim, e que é o conteúdo certo (idioma, temporada e episódio corretos).
- Use o link direto da stream (
.m3u8,.txtou.mp4). Página de embed e link encurtado não funcionam aqui. - Dê um rótulo honesto — "Servidor 1 - Dublado", "1080p Legendado". O rótulo aparece no menu de fontes do player pra quem for assistir.
- Cadastre no alvo certo. Fonte de episódio vai no episódio, não no título; canal vai no canal.
- Uma fonte boa vale mais que cinco ruins. Se já existe fonte funcionando, só adicione outra se ela trouxer algo novo (outro idioma, outra qualidade, outro servidor de reserva).
O que não fazer
- Cadastrar link que você não testou, que já expirou ou que exige login em outro serviço.
- Cadastrar conteúdo diferente do que o título diz — trailer, prévia, gravação de tela, propaganda ou qualquer coisa fora do tema.
- Encher um mesmo título de fontes repetidas ou inventadas.
Como a verificação funciona
Toda fonte cadastrada é checada automaticamente logo depois de salva. Link que responde errado (não existe, devolve página de erro, formato inválido) é retirado do catálogo. Link que não respondeu por algum motivo inconclusivo (CDN fora do ar no momento, tempo esgotado) fica no catálogo mesmo assim — a checagem não tenta de novo, então vale testar bem antes de cadastrar.
Onde hospedar o vídeo
Se você ainda não tem um link direto, dois hosts aceitam upload de filme/série e devolvem justamente o formato que este catálogo pede:
Suba o arquivo lá e cadastre aqui a URL da própria página de vídeo (o link que abre no navegador) — nada de encurtador nem de link de download direto de outro serviço.
No caso do vidmoly, é diferente do outro: não tem um link direto de stream pra
pegar. Cadastre a própria página .html que ele te dá depois do upload — é
ela que serve de fonte aqui.
Onde encontrar filme/série pra baixar
Sites de torrent, caso você precise baixar o arquivo antes de subir num dos hosts acima:
Isso é levado a sério
Cadastro repetidamente falso ou enganoso leva à remoção das fontes e, em caso de reincidência, ao banimento da conta. Na dúvida sobre um cadastro, não cadastre.
Rede de Anúncios
As redes abaixo servem para você monetizar o seu próprio projeto. Cadastre-se, gere suas tags de anúncio e insira no seu site normalmente.
Paga em dólar. Saque mínimo de US$ 25 via PayPal.
Monetag
https://monetag.com/?ref_id=t5g8Paga em dólar. Saque mínimo de US$ 5 via PayPal — o menor da lista.
CPM alto e não rouba nas views.
Paga em euro ou em dólar, à sua escolha.
Exclusiva para aplicativos Android — não serve para monetizar um site.
Evitar bloqueio do site
Sinais de que seu domínio pode estar sendo afetado
- Queda repentina no número de visualizações/acessos.
- Problemas de carregamento apenas em redes de determinadas operadoras.
- Usuários relatando que o site está fora do ar, mesmo funcionando via VPN.
- Indexação parou de funcionar de um dia pra noite.
Dicas pra evitar isso
- 1 — Troque seu domínio de tempos em tempos, especialmente se estiver usando o mesmo há meses.
- 2 — Evite TLDs comuns: domínios como
.com,.net,.org,.live,.tv,.to,.cc,.xyz,.cam,.cloud,.site,.stream,.icu,.buzz,.watch,.movie,.online,.link,.click, entre outros, estão mais suscetíveis a bloqueios por já terem sido amplamente usados em sites de pirataria. Priorize domínios alternativos ou menos populares, de países menos visados ou mais neutros — sempre com nomes discretos. - 3 — Tenha um plano de contingência: mantenha domínios reservas prontos para redirecionamento rápido em caso de bloqueio.
- 4 — Utilize canais alternativos para informar mudanças aos usuários: como grupos em redes sociais, Telegram, Reddit ou sites espelho.
Em resumo
Se você administra um site com esse perfil e ainda está usando o mesmo domínio há um tempo, considere trocá-lo o quanto antes. Os bloqueios estão mais agressivos, e a prevenção é o melhor caminho para manter seu tráfego estável.