Swagger UI
Base URL

Todos os caminhos desta documentação são relativos à base URL abaixo.

http://localhost:3000

Por exemplo, POST /api/videos corresponde a http://localhost:3000/api/videos.

Autenticação

Todas as rotas /api/* exigem o header x-api-key com uma API Key ativa.

x-api-key: sua-api-key-aqui

Gere e gerencie suas chaves na página API Keys. Sem o header a API responde 403.

Rate Limiting

As rotas têm limite por minuto. Ao exceder, a API retorna 429 Too Many Requests.

HeaderDescrição
X-RateLimit-LimitLimite de requisições por minuto da sua conta
X-RateLimit-RemainingRequisições restantes na janela atual
X-RateLimit-ResetTimestamp (Unix) de quando a janela reseta

O limite padrão é 60 req/min. Para aumentar, fale com o administrador.

Fluxo de uso
Publicar um vídeo

O arquivo vai do seu servidor (ou do navegador do seu usuário) direto para o storage, por URLs assinadas. Ele não passa pela nossa aplicação, o que permite enviar arquivos de vários GB sem prender uma conexão.

1
POST /api/videos com size_bytes devolve as URLs assinadas para o envio.
2
Envie o arquivo (ou cada parte) com PUT nas URLs recebidas. As partes podem subir em paralelo.
3
POST /api/videos/{video}/upload/complete fecha o envio e coloca o vídeo na fila de processamento.
4
Acompanhe por GET /api/videos/{video} ou espere o webhook video.ready.
5
Use a URL de embed num iframe, ou o manifest hls no seu próprio player.
Vídeos
GET /api/videos

Lista os vídeos da conta, com filtros e paginação.

Query params
CampoTipoDescrição
statusstringawaiting_upload, uploading, queued, processing, ready ou failed
folder_idintegerId da pasta. Use root para o que está fora de pastas
privacystringpublic ou signed
searchstringBusca no título e na descrição
orderstringrecent (padrão), oldest, title, size, duration
limitintegerItens por página (padrão 50, máximo 200)
offsetintegerDeslocamento da paginação
Resposta
{ "status": true, "videos": [ { "id": 12, "playback_id": "kR7pQm2xVt9bLnCsWd4Hga", "title": "Aula 01 - Introdução", "status": "ready", "privacy": "signed", "duration_seconds": 1834.2, "width": 1920, "height": 1080, "size_bytes": 512000000, "qualities": [{ "name": "1080p", "height": 1080 }], "thumbnail_token": "eyJwIjoia1I3cFFtMngi....", "urls": { "thumbnail": "https://.../stream/kR7pQm2xVt9bLnCsWd4Hga/thumb.jpg?token=...", "poster": "https://.../stream/kR7pQm2xVt9bLnCsWd4Hga/poster.jpg?token=..." }, "created_at": "2026-08-31T18:12:04.000Z" } ], "total": 1, "limit": 50, "offset": 0 }
O token da listagem

Cada item vem com um thumbnail_token que abre apenas a miniatura e a capa, já embutido nas URLs de urls. Ele existe para montar uma grade de vídeos: entregar aqui um token de reprodução por item deixaria, no HTML de uma tela de lista, dezenas de links capazes de assistir aos vídeos. Para reproduzir, use o detalhe do vídeo ou POST /api/videos/{video}/playback-token. Vídeos públicos não precisam de token nenhum, e a lista sai sem ele.

POST /api/videos

Cria o vídeo. Informando size_bytes, a resposta já traz as URLs para o envio do arquivo.

Body (JSON)
CampoTipoDescrição
titlestringObrigatório. Título do vídeo
descriptionstringDescrição livre
folder_idintegerPasta onde o vídeo fica
privacystringsigned (padrão) exige token para assistir; public não
filenamestringNome do arquivo, usado para deduzir a extensão
size_bytesintegerTamanho do arquivo. Informando, o envio já vem aberto na resposta
content_typestringTipo do arquivo, ex.: video/mp4
forensic_watermarkbooleanAplica a marca forense. Força privacy: signed, dobra o tempo de processamento e ocupa até 30% a mais de espaço. Dá para ligar depois com reprocess
Resposta
{ "status": true, "video": { "id": 12, "playback_id": "kR7pQm2xVt9bLnCsWd4Hga", "status": "uploading" }, "upload": { "mode": "multipart", // "single" quando cabe em uma parte "part_size": 16777216, "total_parts": 44, "expires_in": 3600, "parts": [ { "part": 1, "url": "https://...", "size": 16777216 } ] } }

No modo single vem uma url só, acompanhada dos headers que o PUT precisa repetir.

GET /api/videos/{video}

Detalhe de um vídeo. Aceita o id numérico ou o playback_id.

Path param
CampoTipoDescrição
videostringId numérico ou playback_id
Resposta (durante o processamento)
{ "status": true, "video": { "id": 12, "status": "processing", "processing": { "progress": 42, "stage": "gerando 720p", "attempts": 1 } } }
Resposta (pronto)
{ "status": true, "video": { "status": "ready", "duration_seconds": 1834.2, "qualities": [{ "name": "720p" }, { "name": "480p" }], "urls": { "embed": "{BASE}/embed/kR7pQm2xVt9bLnCsWd4Hga?token=...", "hls": "{BASE}/stream/kR7pQm2xVt9bLnCsWd4Hga/master.m3u8?token=...", "thumbnail": "{BASE}/stream/kR7pQm2xVt9bLnCsWd4Hga/thumb.jpg?token=..." } } }

Estados: awaiting_upload → uploading → queued → processing → ready. Em erro, failed com error_message.

PUT /api/videos/{video}

Atualiza os dados do vídeo.

Body (JSON) — todos opcionais
CampoTipoDescrição
titlestringNovo título
descriptionstringNova descrição
folder_idintegerMove para outra pasta. null tira de qualquer pasta
privacystringpublic ou signed
Resposta
{ "status": true, "video": { /* vídeo atualizado */ } }
POST /api/videos/{video}/reprocess

Refaz a conversão a partir do arquivo original. Serve para consertar um vídeo que saiu com defeito, aproveitar melhorias do processamento em vídeos antigos, ou ligar a marca forense em quem foi enviado sem ela, que é o único caminho para isso depois do envio.

O vídeo não sai do ar. A versão nova é gerada ao lado e só assume quando fica pronta. Quem estiver assistindo durante a troca não percebe nada. O preço é ocupar o dobro do espaço enquanto a conversão dura, e essa margem é conferida contra a sua cota antes de começar.

Body (JSON) — opcional
CampoTipoDescrição
forensic_watermark boolean Liga ou desliga a marca forense na versão nova. Ligar força privacy: signed e descarta os links forenses já emitidos, porque eles apontam para trechos que vão deixar de existir. Omitir mantém como está.
Resposta
{ "status": true, "job_id": 4821, "reprocessing": true, "forensic_enabled": true, "forensic_sessions_discarded": 37, "message": "A conversao foi enfileirada..." }
Acompanhando

O GET /api/videos/{video} continua devolvendo status: "ready", porque a versão atual segue no ar. O andamento aparece em processing, com reprocessing: true para distinguir de uma primeira conversão:

{ "status": "ready", "processing": { "progress": 62, "stage": "720p", "reprocessing": true } }

Quando processing some, a versão nova entrou no lugar e a antiga foi apagada do storage.

Se a conversão falhar

O vídeo não cai. O status continua ready apontando para a versão que já estava no ar, sem error_message, e a versão pela metade é apagada do storage. O aviso vem pelo webhook video.reprocess_failed, depois de esgotadas as três tentativas.

Erros
CódigoQuando
ALREADY_PROCESSINGJá existe uma conversão em andamento para este vídeo
ORIGINAL_NOT_AVAILABLEO arquivo original não está mais no storage
FORENSIC_VIDEO_TOO_SHORTPediu a marca num vídeo curto demais para comportá-la (mínimo de 48 segundos)
STORAGE_QUOTA_EXCEEDEDNão há espaço na cota para as duas versões ao mesmo tempo
DELETE /api/videos/{video}

Remove o vídeo e todos os arquivos dele no storage. Não tem volta.

Resposta
{ "status": true, "deleted": true, "storage_objects_removed": 842 }
Envio do arquivo
Enviando as partes

As partes podem subir em paralelo, e uma que falhe se reenvia sozinha.

// Node.js for (const p of upload.parts) { const inicio = (p.part - 1) * upload.part_size; await fetch(p.url, { method: 'PUT', body: arquivo.subarray(inicio, inicio + p.size) }); }
POST /api/videos/{video}/upload

Abre (ou reabre) o envio de um vídeo já criado.

Body (JSON)
CampoTipoDescrição
size_bytesintegerObrigatório. Tamanho do arquivo
content_typestringTipo do arquivo
Resposta
{ "status": true, "upload": { /* mesmo formato de POST /api/videos */ } }
POST /api/videos/{video}/upload/complete

Fecha o envio e coloca o vídeo na fila de processamento.

Body (JSON)
CampoTipoDescrição
partsarrayOpcional. Sem ela, perguntamos ao storage quais partes chegaram. Se você já tem os ETag em mãos, envie e poupe uma consulta
Exemplo
{ "parts": [{ "part": 1, "etag": "\"9b2cf5...\"" }] }
Resposta
{ "status": true, "queued": true, "video": { "status": "queued" } }

O tamanho no storage é conferido antes de aceitar. Um envio truncado é recusado com UPLOAD_INCOMPLETE.

DELETE /api/videos/{video}/upload

Cancela um envio em andamento e descarta as partes já enviadas.

Resposta
{ "status": true, "aborted": true }
POST /api/videos/upload

Cria e envia em uma chamada só, com multipart/form-data. Simples de integrar, mas o arquivo passa pela nossa aplicação: para arquivos grandes, prefira o fluxo de URLs assinadas.

Campos (form-data)
CampoTipoDescrição
filefileObrigatório. O arquivo de vídeo
titlestringPadrão: o nome do arquivo
descriptionstringDescrição livre
folder_idintegerPasta onde o vídeo fica
privacystringpublic ou signed
Exemplo
curl -X POST "{BASE}/api/videos/upload" \ -H "x-api-key: sua-api-key-aqui" \ -F "file=@aula01.mp4" \ -F "title=Aula 01"
Reprodução
POST /api/videos/{video}/playback-token

Emite um token para assistir. Vídeos public dispensam; vídeos signed exigem.

Body (JSON) — todos opcionais
CampoTipoDescrição
ttl_minutesintegerValidade do token (padrão 240)
viewer_ipstringAmarra o token a este endereço de rede
viewer_refstringQuem vai assistir, no seu sistema. Só usado em vídeo com marca forense
viewer_namestringNome legível de quem vai assistir, para a verificação forense
Resposta
{ "status": true, "token": "eyJwIjoia1I3...", "expires_in_minutes": 120, "urls": { "embed": "{BASE}/embed/kR7pQm2xVt9bLnCsWd4Hga?token=...", "hls": "{BASE}/stream/kR7pQm2xVt9bLnCsWd4Hga/master.m3u8?token=...", "thumbnail": "{BASE}/stream/kR7pQm2xVt9bLnCsWd4Hga/thumb.jpg?token=..." } }
Incorporar o player
<iframe src="{BASE}/embed/kR7pQm2xVt9bLnCsWd4Hga?token=..." width="720" height="405" frameborder="0" allowfullscreen allow="autoplay; fullscreen; picture-in-picture"></iframe>
Vídeo em pé

O player preenche o espaço que o iframe der e mostra o vídeo inteiro dentro dele, então o formato do iframe é o que decide o resultado. Para um vídeo em pé, use as medidas dele — num iframe deitado o vídeo aparece pequeno entre duas barras, como em qualquer player:

<iframe src="{BASE}/embed/..." width="360" height="640" ...></iframe>

O painel já monta o código de incorporação com as medidas do arquivo, e o GET /api/videos/{video} devolve width e height para quem monta o iframe pela API. Num container de altura livre, o player tira a altura da proporção do vídeo sozinho.

Parâmetros do embed
CampoTipoDescrição
autoplaystring1 inicia sozinho (entra sem som, exigência dos navegadores)
mutedstring1 começa sem som
loopstring1 repete ao terminar
colorstringCor dos controles em hexadecimal, ex.: 5b47f5
Usando o seu próprio player

A URL hls é um master playlist comum, compatível com hls.js, Video.js, Shaka e o player nativo do Safari e do iOS. Os segmentos são cifrados em AES-128, que faz parte do padrão HLS e não exige configuração extra no player.

Player e templates

A aparência e o comportamento do player saem de três camadas, aplicadas nesta ordem:

padrão do sistema < template escolhido < ajustes do próprio vídeo

Um template guarda cores, quais controles o espectador tem, botão de ação, início automático, retomada e progresso. Um vídeo aponta para um template e pode sobrescrever campos específicos sem duplicar o resto. O que não estiver em nenhuma das camadas cai no padrão do sistema, então acrescentar uma opção nova não invalida o que já existe.

O limite é de 50 templates por conta.

Montando a sua própria tela de edição

GET /api/player-templates devolve, junto com os templates, o catálogo de tudo que pode ser configurado: grupos, campos, tipos, valores padrão, rótulos e opções. Montar o formulário a partir dele em vez de escrever os campos à mão significa que uma opção nova da plataforma aparece na sua interface sozinha, sem novo deploy do seu lado.

A chave de API não pode ir para o navegador. Ela dá acesso à conta inteira, incluindo apagar vídeos. Se você vai deixar o seu próprio usuário final editar o player, as chamadas precisam sair do seu backend, que decide o que aquele usuário pode tocar antes de repassar. Um vídeo ou template desta conta é visível para qualquer chamada autenticada com a chave: o recorte por usuário é seu.

GET /api/player-templates

Lista os templates da conta, com o catálogo de opções e os valores padrão.

Resposta
{ "status": true, "templates": [ { "id": 4, "name": "VSL padrão", "description": "Sem avanço, com botão aos 7 minutos", "config": { "controls": { "allow_seek": false } }, "is_default": true, "video_count": 12, "created_at": "2026-08-30T14:02:11.000Z", "updated_at": "2026-09-01T09:41:00.000Z" } ], "catalog": [ /* grupos e campos, veja abaixo */ ], "defaults": { /* o valor de fábrica de cada campo */ } }

config traz só o que foi decidido explicitamente. Um grupo de catalog tem esta forma:

{ "key": "autoplay", "label": "Início automático", "description": "O vídeo começa sozinho quando a página abre.", "toggle": "enabled", "sections": { "convite": "Convite para ativar o som" }, "fields": [ { "key": "overlay_style", "type": "enum", "default": "spotlight", "label": "Modelo", "section": "desenho", "options": ["spotlight", "card", "bar", "minimal"], "labels": { "spotlight": "Destaque central" } } ] }
Campos do catálogo
CampoTipoDescrição
keystringNome do grupo ou do campo, usado em config
labelstringTexto para mostrar ao usuário
descriptionstringUma linha explicando o grupo
togglestringQual campo liga e desliga o grupo inteiro, quando existe
sectionsobjectSubdivisões do grupo; cada campo diz a qual pertence em section
typestringbool, int, color, url, enum ou text
defaultvárioO valor quando ninguém decidiu nada
hintstringExplicação curta do campo, quando ajuda
options / labelsarray / objectSó em enum: valores aceitos e o nome legível de cada um
min / maxintegerSó em int: faixa aceita
POST /api/player-templates

Cria um template. O primeiro da conta vira o padrão sozinho.

Body (JSON)
CampoTipoDescrição
namestringObrigatório. Nome do template
descriptionstringPara você lembrar do uso
configobjectSó os grupos e campos do catálogo. Envie apenas o que quiser mudar
Exemplo
{ "name": "VSL padrão", "config": { "appearance": { "accent_color": "#22c55e" }, "controls": { "allow_seek": false, "show_speed": false }, "autoplay": { "enabled": true }, "cta": { "enabled": true, "show_at_seconds": 420, "text": "Quero garantir", "url": "https://exemplo.com/checkout" } } }

Um campo fora do catálogo é recusado com 400 UNKNOWN_PLAYER_OPTION, dizendo quais foram. Um valor com o tipo errado volta ao padrão em vez de derrubar a chamada; um link que não seja http ou https é descartado.

GET /api/player-templates/{id}

Um template, com a configuração já resolvida.

Resposta
{ "status": true, "template": { "id": 4, "name": "VSL padrão", "config": { "controls": { "allow_seek": false } }, "is_default": true }, "resolved": { /* todos os campos, com padrão preenchido */ } }

config é o que foi decidido; resolved é o que o player receberia. Use resolved para preencher um formulário, e mande de volta em config só o que mudou.

PUT /api/player-templates/{id}

Atualiza nome, descrição ou configuração. Aceita os mesmos campos da criação.

Resposta
{ "status": true, "template": { "id": 4, "name": "VSL padrão", "config": { } } }

config substitui o anterior por inteiro, e não faz mesclagem. Mande o conjunto completo do que deve valer, não só o campo alterado.

DELETE /api/player-templates/{id}

Remove o template. Os vídeos que o usavam voltam para o padrão da conta.

Resposta
{ "status": true, "deleted": true }

O template padrão não pode ser removido enquanto houver outros: responde 409 com TEMPLATE_IS_DEFAULT. Sem padrão, todo vídeo da conta cairia na configuração de fábrica de uma vez. Marque outro como padrão antes. O último template restante pode ser removido normalmente.

POST /api/player-templates/{id}/default

Marca como padrão da conta. Vale imediatamente para todo vídeo que não escolheu outro, inclusive os que já estão publicados. Só um template é padrão por vez.

Resposta
{ "status": true, "message": "Este template passa a valer para os vídeos sem template próprio" }
POST /api/player-templates/{id}/duplicate

Copia o template com o nome seguido de (cópia). A cópia nunca nasce como padrão.

Resposta
{ "status": true, "template": { "id": 9, "name": "VSL padrão (cópia)" } }
GET /api/videos/{video}/player

O que vale para um vídeo: qual template, quais ajustes próprios, e o resultado das duas coisas.

Resposta
{ "status": true, "template_id": 4, "overrides": { "cta": { "url": "https://exemplo.com/oferta-b" } }, "resolved": { /* o que o player recebe de fato */ }, "applied_template": "VSL padrão" }

Com template_id: null, o vídeo segue o padrão da conta, e applied_template diz qual acabou valendo.

PUT /api/videos/{video}/player

Troca o template do vídeo e os ajustes específicos dele.

Body (JSON)
CampoTipoDescrição
template_idintegerTemplate a aplicar. null devolve o vídeo ao padrão da conta
overridesobjectAjustes só deste vídeo, por cima do template. Mesmos campos do catálogo
Exemplo
{ "template_id": 4, "overrides": { "cta": { "url": "https://exemplo.com/oferta-b", "show_at_seconds": 180 } } }

Cada campo é opcional: mandar só overrides mantém o template, e só template_id mantém os ajustes. É o caminho para deixar cada vídeo com o seu link de checkout sem criar um template por vídeo.

Marca forense

A criptografia AES-128 e o token assinado impedem que alguém sem acesso assista. Não impedem que alguém com acesso baixe e distribua. A marca forense resolve a outra metade: descobrir de quem saiu a cópia.

Cada segmento marcado existe em duas versões visualmente idênticas, e cada espectador recebe uma combinação própria delas. Essa combinação é o identificador, e sobrevive a regravação de tela e recompressão porque está na imagem, não no player. Não há elemento para remover.

segmento: 01 02 03 04 05 06 07 08 aluno-42: A B B A B A A B = 01101001 aluno-77: B B A A A B A B = 11000101

Os dois assistem ao mesmo vídeo, na mesma qualidade, sem notar diferença. Mas os arquivos que saíram para cada um são distintos.

Como usar
1
Envie o vídeo com forensic_watermark: true. O processamento leva o dobro do tempo e o vídeo ocupa até 30% a mais de espaço.
2
Ao emitir o token, informe viewer_ref com o identificador de quem vai assistir no seu sistema.
3
Se um arquivo vazar, envie-o para POST /api/videos/{video}/forensic/verify, ou use a tela Verificação no painel.
4
A resposta traz os candidatos em ordem de confiança, com quem recebeu cada link.

Sem viewer_ref, a verificação identifica a sessão (data de emissão, IP e navegador) mas não a pessoa. O identificador é seu, e só você sabe a quem ele corresponde.

GET /api/videos/{video}/forensic/sessions

Os links emitidos para este vídeo, com quem os recebeu e se chegaram a ser usados.

Query params
CampoTipoDescrição
limitintegerPadrão 100, máximo 500
Resposta
{ "status": true, "enabled": true, "segments": 32, "capacity": 4294967296, "total": 418, "sessions": [ { "session_key": "a3f9...", "viewer_ref": "aluno-42", "viewer_name": "Maria Silva", "issued_at": "2026-09-02T14:32:00.000Z", "first_seen_at": "2026-09-02T14:35:11.000Z", "last_seen_at": "2026-09-02T15:02:44.000Z" } ] }

capacity é quantas identidades distintas o vídeo comporta: 2 elevado ao número de trechos marcados. first_seen_at em branco significa que o link foi emitido mas nunca usado.

POST /api/videos/{video}/forensic/verify

Envie o arquivo suspeito em multipart/form-data, campo file. A API lê a marca e devolve os candidatos.

Exemplo
curl -X POST "{BASE}/api/videos/kR7pQm2xVt9bLnCsWd4Hga/forensic/verify" \ -H "x-api-key: sua-api-key-aqui" \ -F "file=@vazado.mp4"
Resposta
{ "status": true, "sequence_read": "01101001...", "segments_analyzed": 32, "sessions_compared": 418, "matches": [ { "viewer_ref": "aluno-42", "viewer_name": "Maria Silva", "issued_at": "2026-09-02T14:32:00.000Z", "distancia": 1, "confianca": "alta" }, { "viewer_ref": "aluno-77", "distancia": 14, "confianca": "inconclusiva" } ] }
Como ler o resultado
CampoSignificado
distanciaQuantos trechos divergem entre o que foi lido e o que aquele link recebeu. Zero é correspondência perfeita
confiancaexata, alta, media, baixa ou inconclusiva
sequence_readA marca lida do arquivo. Um ? é um trecho que não deu para ler

A comparação tolera erro: um trecho lido errado num vídeo muito recomprimido não derruba a identificação. O que separa o culpado dos demais é a distância entre o primeiro colocado e o segundo. Se nenhum candidato passa de media, trate como inconclusivo.

O que a marca não resolve

Conluio. Duas pessoas comparando suas cópias descobrem onde os arquivos diferem e podem montar um híbrido que não corresponde a nenhuma delas. Defender disso exige códigos anti-conluio, que precisam de muito mais trechos.

Corte no começo. A leitura assume que o arquivo suspeito começa onde o vídeo original começa. Um corte no início desloca as janelas e quebra a correspondência.

Vídeos curtos. Abaixo de 48 segundos não há trechos suficientes para gerar identidades distintas, e o vídeo é processado sem marca. O aviso aparece no log de processamento.

Audiência
GET /api/videos/{video}/stats

Views, tempo assistido, taxa de conclusão e a curva de retenção.

Query params
CampoTipoDescrição
daysintegerPeríodo em dias (padrão 30, máximo 365)
Resposta
{ "status": true, "totals": { "views": 1284, "unique_viewers": 973, "watched_seconds": 1204832, "average_watch_seconds": 938, "average_watch_percent": 51, // quanto do vídeo a pessoa média assistiu "completion_rate": 23 }, "daily": [{ "day": "2026-08-30", "views": 62 }], "retention": [{ "percent": 0, "second": 0, "views": 1284 }] }

A curva de retention divide o vídeo em 100 fatias e conta quantas reproduções chegaram a cada uma. É o que mostra em que minuto as pessoas desistem.

Pastas
GET /api/folders

Lista as pastas da conta.

Query params
CampoTipoDescrição
parent_idstringId da pasta pai, ou root para o primeiro nível
Resposta
{ "status": true, "folders": [ { "id": 3, "name": "Curso de Node", "video_count": 12, "subfolder_count": 2 } ] }
POST /api/folders

Cria uma pasta.

Body (JSON)
CampoTipoDescrição
namestringObrigatório. Nome da pasta
parent_idintegerPasta pai, para criar uma subpasta
Resposta
{ "status": true, "folder": { "id": 3, "name": "Curso de Node" } }
DELETE /api/folders/{id}

Remove uma pasta vazia. Com conteúdo, responde 409 com FOLDER_NOT_EMPTY.

Resposta
{ "status": true, "deleted": true }
Consumo
GET /api/usage

Consumo da conta contra as cotas do plano.

Resposta
{ "status": true, "usage": { "storage": { "used_bytes": 48000000000, "limit_bytes": 53687091200, "percent": 89 }, "bandwidth": { "used_bytes": 12000000000, "limit_bytes": 214748364800, "percent": 5 }, "videos": { "count": 42, "limit": 0 } }, "videos": { "total": 42, "by_status": { "ready": 40, "processing": 2 } } }

O tráfego zera na virada do mês. Em limit, o valor 0 significa sem limite.

Webhooks

Configure a URL de destino em Configurações. Enviamos POST com JSON.

Eventos
EventoQuando
video.readyO vídeo terminou de processar e pode ser reproduzido
video.failedO processamento falhou depois de esgotar as tentativas
video.reprocess_failedUm reprocessamento falhou. O vídeo continua no ar com a versão anterior
Corpo enviado
{ "event": "video.ready", "timestamp": "2026-08-31T18:42:10.512Z", "data": { "video_id": 12, "playback_id": "kR7pQm2xVt9bLnCsWd4Hga", "title": "Aula 01 - Introdução", "duration_seconds": 1834.2, "qualities": ["360p", "480p", "720p", "1080p"], "processing_seconds": 412.8 } }
Headers enviados
HeaderDescrição
x-alktea-eventNome do evento
x-alktea-signatureHMAC-SHA256 do corpo, calculado com o seu segredo
Conferindo a assinatura
// Node.js const assinatura = crypto .createHmac('sha256', SEGREDO) .update(corpoBruto) .digest('hex'); if (assinatura !== req.headers['x-alktea-signature']) return res.sendStatus(401);

Repetimos a entrega até 3 vezes em falha de rede ou erro 5xx, com espera crescente. Responda 2xx para encerrar.

Códigos de erro

Toda resposta de erro traz errorCode (estável) e message (texto que pode mudar). Decida pelo código.

{ "status": false, "errorCode": "STORAGE_QUOTA_EXCEEDED", "message": "Cota de armazenamento esgotada (48 GB de 50 GB)." }
HTTPerrorCodeQuando acontece