Skip to content

Home — página inicial montada pelo painel

ADR-0197. Toda fileira da home é um cadastro (home_sections): ordem, título, plataforma, público e itens vêm do painel. O app monta a página com uma rota, igual em web, celular e TV.

Erros no formato da API: {"error": {"code", "message", "details", "field", "timestamp"}}.


App: GET /api/v1/home

Authorization: Bearer <JWT> de assinante (admin passa direto). Cabeçalhos e parâmetros:

Quem Valor Efeito
platform (query) web (padrão) · mobile · tv só as seções que valem nessa plataforma
X-Profile-Id id do perfil perfil infantil vê só seções all/kids e itens até a faixa dele (ADR-0184); sem o header, o perfil padrão
Accept-Language en-US,en;q=0.9 título em title_i18n[idioma]; sem tradução, title
{
  "sections": [
    {
      "id": "6af0…", "title": "Destaques", "layout": "hero", "type": "manual", "see_all": null,
      "items": [
        {"kind": "video",  "video":  {"…documento de GET /videos…": ""},
         "preview": {"type": "trailer", "video_id": "<trailer_video_id>"}},
        {"kind": "series", "season": {"…documento de GET /seasons…": ""}, "episodes_count": 5,
         "preview": {"type": "image"}}
      ]
    },
    {
      "id": "6af1…", "title": "Continuar assistindo", "layout": "row",
      "type": "continue_watching", "see_all": null,
      "items": [{"kind": "video", "video": {"…": ""}, "progress_seconds": 812.0}]
    }
  ],
  "generated_at": "2026-11-04T12:00:00Z"
}
  • layout: hero (carrossel) · row (capas) · ranking (números, o antigo "Top").
  • see_all: {"kind": "category", "category_id": "…"} (página da categoria), {"kind": "section"} (use GET /home/sections/{id}) ou null.
  • video é o mesmo documento de GET /videos (categorias, tags, effective_age_rating); season é o de GET /seasons. preview só vem no carrossel.
  • preview.type = "trailer" quando o item tem carousel_media = image_and_trailer e trailer_video_id; senão "image". Sem trailer cadastrado é só a foto: não cai mais no trecho do próprio vídeo. O video_id do preview é o trailer_video_id (URL do iframe do Bunny ou GUID); para tocar, use GET /videos/{id}/trailer-url (ou /seasons/{id}/…).

Como a API monta cada seção

  1. Vale se está ligada (enabled), no período (starts_at/ends_at), na platform e no público do perfil. Ordem: order.
  2. Itens: manual = items na ordem. Automáticas e categoria = fixados (items) primeiro, depois a regra, sem os excluded, sem show_on_home: false, sem repetir.
  3. Todo item precisa estar publicado (enable), dentro da janela de exibição (availability_*, exceto em coming_soon) e dentro do teto do perfil infantil. Item barrado some sem deixar buraco.
  4. Corta em max_items (padrão 20; carrossel 10). Seção sem item não vem na resposta.
type Regra Itens no painel
manual só items toda a lista
category vídeos da categoria, o mais recém-editado primeiro fixar + excluir
coming_soon availability_start_date no futuro, a mais próxima primeiro fixar + excluir
recently_added release_date nos últimos rules.days dias (padrão 30), a mais nova primeiro fixar + excluir
continue_watching histórico do perfil, o mais recente primeiro, um por vídeo; traz progress_seconds —
recommended GET /me/recommendations do perfil —
because_you_watched parecidos (categoria/tag) com o último visto; {title} já vem substituído —

Série. Em categoria is_serial_type, recently_added, coming_soon e nas pessoais, os episódios (season_id + season_number + episode_number) viram um card (kind: "series", ordenado por Season.index). Em categoria comum entram os episódios soltos, como o app já fazia. No card da série vale o show_on_home da temporada.

show_on_home: false tira o vídeo das fileiras automáticas e de categoria. Continua na busca, na página da categoria, em "Minha Lista" e por link. Se ele estiver numa seção manual, vale a seção (decisão do curador).

GET /api/v1/home/sections/{section_id}: "Ver tudo"

Mesmos parâmetros da home, mais qty_docs_page (1–100, padrão 20) e current_page (0…). Devolve todos os itens da seção, sem o corte do max_items:

{"section": {"id": "…", "title": "…", "layout": "row", "type": "coming_soon",
             "see_all": {"kind": "section"}, "items": [ … ]},
 "current_page": 0, "qty_docs_page": 20, "qty_total_docs": 37}

Seção desligada, fora do período, de outra plataforma ou público: 404.

Cache

O pedaço comum (manual, category, coming_soon, recently_added) fica em cache de 60 s por processo, por platform + público + teto de faixa. As pessoais saem do banco a cada pedido. Salvar seção, vídeo, série ou categoria limpa o cache da réplica que recebeu o pedido; a outra atualiza em até 60 s. Reordenar no painel chega ao app em, no máximo, 1 minuto.


Painel: cadastro das seções

Só administrador (user_type = admin). Escrever exige a permissão de video_* do perfil: criar = create, editar/ordenar/itens = update, apagar = delete. Ler (GET) fica liberado a qualquer admin, como GET /videos.

Rota Uso
GET /admin/home-sections Todas, na ordem, com items_count, deletable e items_preview (5 primeiros)
GET /admin/home-sections/{id} Uma seção, com items e excluded resolvidos
POST /admin/home-sections Criar (201); entra no fim
PUT /admin/home-sections/{id} Editar campos, sem items/excluded/order/type
DELETE /admin/home-sections/{id} Apagar (204); as 3 pessoais e o carrossel dão 409
PUT /admin/home-sections/order {"order": ["<id>", …]} — todas as seções; incompleta, repetida ou id desconhecido: 422
PUT /admin/home-sections/{id}/items {"items": [{"kind","id"}], "excluded"?: […]} — substitui a lista; pessoal ou item inexistente: 422
GET /admin/home-sections/{id}/candidates?q= vídeos e séries que podem entrar (publicados, fora da lista; na category, só os da categoria)

Corpo de POST (os de PUT são subconjuntos):

{
  "title": "Comédias", "title_i18n": {"en": "Comedies"}, "type": "category",
  "layout": "row", "enabled": true, "max_items": 20,
  "starts_at": null, "ends_at": null,
  "platforms": ["web", "mobile", "tv"], "audience": "all",
  "category_id": "…", "rules": {}, "see_all": {"kind": "category", "category_id": "…"},
  "items": [{"kind": "video", "id": "…"}], "excluded": []
}

Regras (422): category_id obrigatório só em category; layout: hero só em manual; rules.days (1–365) só em recently_added; ends_at depois de starts_at; platforms não vazia; title_i18n só en/es; categoria, vídeo ou série inexistente.

Item resolvido (no detalhe e na prévia): {kind, id, order, title, thumbnail_horizontal, thumbnail_vertical, badges}. badges explica por que o item não sairia no app, e ele continua na lista: disabled · not_yet_available · no_longer_available · show_on_home_off · missing (apagado).

Apagar ou desabilitar um vídeo (ou apagar uma série) o tira de items e excluded de todas as seções, e a ordem é renumerada.

Pelo vídeo (ou pela série)

Rota Uso
GET /admin/videos/{id}/home-sections · GET /admin/seasons/{id}/home-sections as caixas
PUT /admin/videos/{id}/home-sections · PUT /admin/seasons/{id}/home-sections {"sections": {"<id da seção>": true\|false}}

GET devolve {"sections": [{section_id, title, type, layout, checked, disabled, reason, item, in_rule, pinned}]}; PUT devolve o mesmo mais changed (ids que mudaram).

  • manual: marcado = está em items; marcar põe no fim.
  • Automática/categoria em que o item entraria pela regra: marcado = não está em excluded; desmarcar exclui.
  • Em que não entraria: disabled: true com o motivo em reason (ex.: "Não é desta categoria."). Marcar uma assim, ou citar seção pessoal/desconhecida: 422 e nada é gravado.
  • item diz o que é gravado. Num episódio de fileira que agrupa séries é {"kind": "series", …}: vale para a série inteira.

show_on_home e carousel_media entram no PUT /videos e no PUT /seasons normais.


Campos novos

Onde Campo Padrão
Vídeo e temporada show_on_home (bool) true
Vídeo e temporada carousel_media: image · image_and_trailer image_and_trailer

main_highlight, section_highlight e is_show_in_slide_carousel continuam no vídeo, só para a migração.

Auditoria e migração

Criar, editar, ordenar, trocar itens, apagar e a escolha pelo vídeo gravam em audit_logs com resource = home_sections e o antes/depois: home_section.create|update|delete|reorder| items_update|item_update.

scripts/migrar_secoes_da_home.py (relatório por padrão, --apply grava, roda uma vez) cria as seções que reproduzem a home de hoje: Destaques · Continuar assistindo · Recomendados · Porque você assistiu · Em breve · Recém-adicionados · uma por categoria (por index; ranking para séries/Filmes/Séries). A fileira "Outros" não é migrada.