# Operations Videos — Master Implementation Guide (LLM-Ready) > Hospedagem e player de vídeo para a página de quem vende ou ensina. A barra > é sempre o tempo real; o produto mede quanto de cada vídeo foi assistido e, > **entre as pessoas que se identificaram**, quem assistiu e até onde — e > manda isso por webhook assinado para onde a conta quiser. > > Este arquivo é a documentação para agentes. Cole a URL no seu agente > (Claude Code, Cursor, Windsurf, Codex) e peça a integração. **Leia a seção 9 > antes de prometer qualquer coisa**: ela diz o que o produto não faz. Worker (API, player, MCP): https://videos.worker.myoperations.click Painel: https://videos.dashboard.myoperations.click Servidor MCP: https://videos.worker.myoperations.click/mcp — ver a seção 8. O mapa da linha (Analytics, Videos, login): https://myoperations.click/llms.txt ## 0. Estado e credenciais — leia antes de tudo - **No ar desde 02/10/2026.** `GET https://videos.worker.myoperations.click/` responde `{"service":"operations-videos",…}`. - **Login:** o Auth da linha (`https://auth.worker.myoperations.click`, aplicação Operations), com código por e-mail. É o mesmo login do Analytics: mesmo login, autorização por produto. Passo humano obrigatório: a pessoa lê um código de 8 caracteres no e-mail e o informa. Não tente contornar. - **Chave da conta:** `vk_…`, criada no painel em **Integrações e eventos**. Vale para a API (`Authorization: Bearer vk_…`) e para assinar a identidade de quem está logado na página (seção 3). **Fica só no servidor**: nunca no HTML, nunca num repositório. - **MCP:** OAuth, com o mesmo login (seção 8). Não usa a `vk_`. - **Preço:** não há preço público e nada é cobrado. Não cite valor, plano, franquia em minutos nem "grátis". Se perguntarem: "Ainda não há planos, e nada é cobrado." Sem credencial de verdade, **pare e peça**. Não gere código com `vk_xxx` de placeholder e diga que terminou. ## 1. Modelo mental - **Vídeo** — tem `id` (UUID), `code` público (`v_XXXX`), título e tipo (`vsl`, `demo`, `lesson`, `other`). `vsl` e `demo` começam mudos e sozinhos; `lesson` espera o play e retoma de onde parou. - **Documento do vídeo** (`videos.config/1`) — capítulos, player, ações e domínios permitidos num documento só, com versões. O painel (ou o agente) edita um **rascunho**; **publicar** cria a versão seguinte e nunca sobrescreve; uma versão antiga reabre como rascunho. Publicar por cima de outra aba responde conflito. - **Sessão** — quem abriu o player. Só ganha dono por quatro portas (seção 3). - **Minutos, não plays** — três medidas separadas: - **entregue**: todo tempo tocado, com ou sem som; - **assistido**: só depois do clique para ouvir; salto não conta; - **com dono**: assistido por pessoa identificada. O produto não conta plays. ## 2. Enviar e incorporar **Enviar:** - Do computador: envio retomável (tus), direto do navegador ao fornecedor; MP4, MOV, MKV ou WebM até 20 GB. - Por link: um `https://` para o arquivo. Links de YouTube, Vimeo, Instagram, TikTok e Facebook são recusados no servidor. - Pela API ou pelo MCP: a criação devolve um link de envio tus válido por 1 hora. **O arquivo nunca passa pelo chat.** ``` POST https://videos.worker.myoperations.click/api/videos Authorization: Bearer vk_… { "title": "Demo v4", "kind": "demo" } → 201 { "video": { "id": "…", "code": "v_8KQ2", "status": "uploading", … }, "upload": { "protocol": "tus", "url": "…", "headers": { … }, "expiresIn": 3600 } } ``` O vídeo toca quando a primeira qualidade fica pronta, e as outras entram sozinhas; o webhook `video.ready` avisa. Substituir o arquivo (`POST /api/videos/:id/replace`) mantém código, link e dados. **Incorporar** — um script onde o vídeo deve aparecer. O código aponta para o vídeo, não para uma versão: publicar de novo não exige colar nada. ```html ``` O player é um Web Component com Shadow DOM, em JavaScript puro: não herda nem quebra o CSS da página. Autoplay mudo com "Clique para ouvir" (volta a 0:00 com som), retomada guardada no navegador de quem assiste, velocidade, tela cheia e atalhos de teclado. A barra é sempre o tempo real (`progress_bar: "real_time"` é constante no esquema). **Só toca nos domínios permitidos da conta** (`access.allowed_domains`): eles são conferidos antes de o endereço do streaming ser assinado, e a assinatura vale 6 horas. Fora deles, o player responde "Este vídeo não toca neste endereço." ## 3. Quem assistiu — as quatro portas Uma sessão só ganha dono por uma destas portas: | Porta | Como | `identifiedBy` | |---|---|---| | Link com identidade | `?vw=` na URL da página: código opaco, com validade (7, 30 ou 90 dias), gerado por lista de e-mails; só o hash fica guardado; vencido, abre anônimo | `identity_link` | | E-mail no player | a ação `email_gate` (sempre pulável) e a pessoa responde | `email_gate` | | Login provado pelo servidor da página | `data-viewer` com um token assinado pela `vk_` ou com o JWT da aplicação no Auth | `signed_login` | | Compra informada pela API | `POST /api/conversions` com a sessão que o botão levou ao checkout | — | **`data-email` sem assinatura é ignorado**: qualquer um escreveria. O token de `data-viewer` é `v1.." com a vk_>`. Em Node: ```js import { createHmac } from 'node:crypto' const key = process.env.VIDEOS_KEY // vk_…, só no servidor const b64 = text => Buffer.from(text).toString('base64url') export function viewerToken({ email, name }) { const payload = b64(JSON.stringify({ email, name, exp: Math.floor(Date.now() / 1000) + 3600 })) return `v1.${payload}.${createHmac('sha256', key).update(`v1.${payload}`).digest('base64url')}` } ``` **Anônimo continua anônimo:** sem fingerprint, sem IP gravado. Identificar liga a sessão daquele momento em diante; visitas anônimas antigas não são ligadas a quem se identificou depois. Visitante frio de anúncio vira contagem. ## 4. Medir - **Retenção** (`GET /api/videos/:id/retention?days=7|30`): de quem escolheu assistir (com som), quantos ainda estavam lá a cada trecho, para todo o público e só para as pessoas conhecidas; maior queda (trecho e capítulo), quanto se assiste em média, quantos chegaram à metade, clicaram no botão e terminaram. Marcos de 25, 50, 75 e 100%. - **Quem assistiu** (`GET /api/viewers`, `GET /api/videos/:id/viewers`): só pessoas identificadas, com `filter=all|cta|half_no_cta` ("passaram da metade e não clicaram"). - **Apagar uma pessoa** (`DELETE /api/viewers/:id`) apaga também as sessões dela. ## 5. O documento `videos.config/1` ``` player autoplay (muted|off) · resume · speeds · accent (#RRGGBB) poster_at_s · progress_bar ("real_time", constante) chapters [{ id, start_s, title }] o primeiro em 0; até 50 actions [{ id, trigger, effect, audience, once_per_session: true }] até 20 access allowed_domains (até 20) · signed_urls · download ``` - `trigger.on`: `time` (`at_s`) · `chapter_start` (`chapter`) · `progress` (`percent`: 10, 25, 50, 75 ou 90) · `first_pause` · `end`. - `effect.type`: `button` (`text`, `url`, `placement`: `below_video` | `over_video`) · `reveal` (`selector`) · `card` (`title`, `body`, `link`) · `email_gate` (`prompt`, `skippable: true`) · `emit` (`event: "video.reached"`, `name`). - `audience`: `all` · `known` · `anon`. Toda ação dispara uma vez por sessão. - Referência sempre por `id`, nunca por posição. **Verificações antes de publicar:** capítulo em 0:00, capítulo de 10 s ou mais, ação apontando para capítulo que existe, link `https://`, domínio válido. Com erro, o worker recusa publicar. **Mostrar um bloco no minuto certo:** marque a seção com a classe `videos-oculto`; o player esconde, e uma ação `reveal` tira a classe no momento escolhido. ```html
O preço e o botão de compra.
``` ## 6. Rotas Base: `https://videos.worker.myoperations.click`, com `Authorization: Bearer vk_…` (ou a sessão do painel). Coleções respondem `{items, page}`; erros, `{error: {code, message}}`. ``` GET /api/videos os vídeos, com os números de 30 dias POST /api/videos cria um vídeo e devolve o envio (tus, 1 hora) POST /api/videos/import cria por link https:// para o arquivo GET /api/videos/:id o vídeo, a versão publicada e o rascunho PATCH /api/videos/:id título e tipo (fora do rascunho) DELETE /api/videos/:id apaga o vídeo — só com pedido explícito de uma pessoa PUT /api/videos/:id/draft salva o rascunho do videos.config/1 POST /api/videos/:id/publish publica o rascunho GET /api/videos/:id/versions as versões POST /api/videos/:id/versions/:n/reopen reabre uma versão como rascunho POST /api/videos/:id/replace substitui o arquivo, mantendo código e dados POST /api/videos/:id/thumbnail miniatura (JPEG) GET /api/videos/:id/retention?days=30 GET /api/videos/:id/viewers quem assistiu, só pessoas conhecidas GET /api/viewers?filter=half_no_cta passaram da metade e não clicaram DELETE /api/viewers/:id apaga a pessoa e as sessões dela POST /api/identity-links um link com identidade por e-mail POST /api/conversions uma venda liga a sessão (vs=) a quem comprou GET /api/webhooks os endpoints e os eventos que cada um assina POST /api/webhooks { url, description?, events?, secret? } PATCH /api/webhooks/:id { url?, description?, events?, isActive? } POST /api/webhooks/:id/secret troca o segredo (gerado, ou o seu) POST /api/webhooks/:id/test um evento de teste assinado, agora DELETE /api/webhooks/:id GET /api/deliveries?endpointId= as últimas entregas POST /api/deliveries/:id/retry reenvia uma entrega que falhou GET /api/keys · POST /api/keys a chave vk_ da conta GET /api/plan o uso do mês ``` **Vendas:** o botão leva `vs=` com a sessão no link. Qualquer checkout guarda esse valor e, quando a venda confirmar, o seu servidor chama: ``` POST /api/conversions Authorization: Bearer vk_… { "session": "s_Q2x8Rk91LmFb", "email": "…", "name": "…", "externalId": "pedido-1042", "value": 197, "currency": "BRL" } → 200 { "session": "s_Q2x8Rk91LmFb", "video": "v_7PX4", "becameKnown": true, "converted": true } ``` Nenhuma plataforma de venda tem integração própria; todas usam esta chamada. ## 7. Webhooks Até 20 endpoints por conta, cada um com os eventos que assina (sem `events`, assina todos). Padrão **Standard Webhooks** com o prefixo `x-ccm`: ``` x-ccm-id: s_Q2x8Rk91LmFb.video.watched.75 x-ccm-timestamp: 1790265731 x-ccm-signature: v1, ``` A chave do HMAC é o base64 depois de `whsec_`. Recuse timestamp com mais de 5 minutos e ignore um `x-ccm-id` já visto. O segredo é gerado (mostrado uma vez) ou o seu — uma fonte do People já traz o dela. Corpo: `{ type, timestamp, data: { person, channel, properties } }`. Evento de pessoa só existe com pessoa conhecida. | Evento | Quando | |---|---| | `video.watched` | a pessoa passou de 25, 50, 75 e 100% — um evento por marco | | `video.cta_clicked` | clicou no botão do vídeo, com a UTM da página | | `video.reached` | uma ação `emit` disparou, com o nome e o minuto | | `video.converted` | uma compra chegou por `/api/conversions` | | `person.identified` | a sessão ganhou dono | | `video.ready` | a conversão terminou e o vídeo toca (sem pessoa) | Se o destino falhar: nova tentativa em 1 min, 5 min, 30 min, 2 h e 12 h. **Para a ficha no People** (Customers): o People é um endpoint como qualquer outro — a URL e o segredo de uma fonte criada no People, assinando só os eventos com pessoa. Não é automático. ## 8. Servidor MCP - **Endereço:** `https://videos.worker.myoperations.click/mcp` (Streamable HTTP), para a conta inteira — a conta vem do token, nunca de um argumento. - **OAuth 2.1:** metadados em `https://videos.worker.myoperations.click/.well-known/oauth-protected-resource`. O token só vale para este servidor (audiência conferida). Escopos: `auth:read` lista as quatro de leitura; `auth:write` acrescenta as três de escrita. - **Limite:** 120 chamadas por minuto por conta. | Tool | Escopo | O que faz | |---|---|---| | `videos_list` | leitura | os vídeos, com os números de 30 dias; filtra por tipo ou título | | `videos_get_retention` | leitura | a curva de retenção (7 ou 30 dias), maior queda, metade, botão, fim | | `videos_list_viewers` | leitura | as pessoas conhecidas dos últimos 90 dias; `filter=half_no_cta` | | `videos_usage` | leitura | o uso do mês (minutos entregues, guardados, vídeos) | | `videos_create_upload` | escrita | cria um vídeo e devolve o link de envio tus (1 hora) | | `videos_update_player` | escrita | altera **só o rascunho** e devolve as verificações | | `videos_identity_links` | escrita | gera um link com identidade por e-mail | As de escrita levam `destructiveHint`: um cliente sério pede confirmação à pessoa. **Publicar e apagar não são ferramentas** — são de uma pessoa, no painel. O agente escreve no rascunho. Quem publica é você. ## 9. Escopo — o que este produto NÃO faz - **Teste A/B**, conversa com IA sobre os vídeos. - **Legendas, marca d'água, DRM**, proteção contra download como recurso. - **Domínio próprio** no player ou na entrega: o endereço do streaming leva o nome do fornecedor. - **Contagem de plays.** O produto conta minutos. - **Identificar todo espectador.** Só quem se identificou por uma das quatro portas; o resto é contagem. - **Integração pronta com plataformas de venda ou com o Payments.** A venda entra pela chamada `POST /api/conversions`, que alguém precisa fazer. - **Área de membros, live, tutor por IA, dublagem.** - **Cobrança.** Não há plano publicado, e nada é cobrado. - **Armazenamento no Brasil.** Um fornecedor de vídeo guarda, converte e entrega; o armazenamento principal fica na **Alemanha, com réplica em São Paulo**. O player, a medição, o documento e as integrações são do Videos. Não afirme "vídeo guardado no Brasil". ## 10. Checklist para agentes de IA 1. **Peça a `vk_`** para a API; para o MCP, o login por código no e-mail. 2. **Cadastre o domínio da página** em `access.allowed_domains` antes de testar: fora dele, o vídeo não toca. 3. **Nunca ponha a `vk_` no HTML.** O token de `data-viewer` é feito no servidor. 4. **Não mande o arquivo pelo chat.** Entregue o link de envio tus. 5. **Escreva no rascunho e peça a uma pessoa para publicar.** 6. **"Quem assistiu" vem sempre com "entre quem se identificou".** 7. **Não invente rota nem tool.** A seção 6 lista as rotas de integração; a 8, as tools.