# Esteira Cenbrap — manual da API Você está lendo o contrato da API da Esteira Cenbrap, o quadro de produção de conteúdo do CENBRAP. Humanos operam pelo quadro em https://esteira.cenbrap.edu.br; agentes operam por esta API. É o mesmo dado e a mesma regra dos dois lados — o que você fizer aqui aparece na tela de quem está trabalhando, e vice-versa. Base: https://esteira.cenbrap.edu.br Especificação OpenAPI (JSON): https://esteira.cenbrap.edu.br/api/openapi Este manual: https://esteira.cenbrap.edu.br/api/docs ## Como se autenticar Toda rota (menos /api/docs e /api/openapi) exige a sua chave em um header: x-api-key: SEU_TOKEN ou, se preferir: Authorization: Bearer SEU_TOKEN Cada agente tem o seu próprio token, e a API registra o seu nome em tudo que você faz. Não use o token de outro agente: o histórico ficaria errado. Sem chave, a resposta é 401 sem_credencial. Este header é o caminho dos AGENTES. Pessoas não usam token: elas entram no site com e-mail e senha e recebem um cookie de sessão. As duas portas dão no mesmo dado e obedecem às mesmas regras; o que muda é o nome que fica no histórico. ## O modelo mental (leia antes de chamar qualquer coisa) FUNIL é um quadro inteiro. A Esteira tem vários, e cada um tem as SUAS PRÓPRIAS etapas. O funil do Blog pode ir de pauta a publicado; um funil de YouTube pode ir de ideia a no-ar. Nunca presuma que as etapas de um funil valem para outro. ETAPA é a coluna onde o card está, dentro do funil dele. O identificador da etapa só existe dentro daquele funil. TEMA é o card: um assunto que vai virar conteúdo. Ele mora em um funil, está em uma etapa e tem prazo, responsável e prioridade. PEÇA é cada item do enxoval do tema — o Reels, o carrossel, o e-mail. Um tema tem várias peças, e cada uma é marcada como feita ou não. AÇÃO é a decisão estratégica que está ACIMA de tudo isso, no Plano de Ação. Ela é a origem: cada ação mantém UM tema, e os canais dela viram as peças desse tema. Nem todo tema vem de uma ação (dá para criar card solto), mas todo tema que tem acao_id é comandado por uma. Antes de reescrever um card desses, entenda a regra abaixo. ESTÁGIO (o campo estagio) é topo, meio ou fundo de funil de marketing. NÃO confunda com funil_id, que é o quadro. O campo já se chamou "funil" e foi renomeado para "estagio" justamente por causa dessa confusão. Se você mandar "funil", a API ainda aceita como estagio, mas escreva "estagio". ## A regra dura: validação antes de andar Cada funil aponta em trava_a_partir_de a etapa em que a trava começa. Dali para a frente, um tema só se move se tiver as duas validações marcadas: val_tecnica — a precisão clínica, conferida por um especialista val_regulatoria — publicidade médica e compliance liberados Se faltar alguma, a API responde 409 e diz exatamente o que falta: {"erro":"validacao_pendente","falta":["tecnica","regulatoria"],"mensagem":"..."} Isso não é sugestão: a API não move o card. Se você receber esse 409, o caminho é marcar as validações (PATCH no tema) ou avisar um humano — nunca tentar de novo do mesmo jeito. Um funil pode não ter trava, quando trava_a_partir_de vem vazio. A última etapa de cada funil é a de "pronto": ao chegar nela, a API carimba concluido_em sozinha. Você não precisa preencher esse campo. ## Por onde começar, sempre GET /api/etapas Devolve o vocabulário vivo: os funis que existem, as etapas de cada um, onde a trava começa, e quem a API achou que você é. Chame isto primeiro em toda sessão. Os ids de etapa que você vai usar nas outras chamadas saem daqui — não os invente e não os traduza. As etapas que um funil novo recebe, se ninguém disser outra coisa: criacao Criação — Tema, público e ângulo definidos validacao Validação — Precisão clínica e compliance programacao Programação — Conteúdo aprovado para entrar na agenda no-ar No ar — Publicado nos canais ## Endpoints ### Vocabulário GET /api/etapas Os funis, as etapas de cada um, a trava e a sua identidade. GET /api/vocabulario As listas editáveis do Plano de Ação: responsáveis, canais e cursos/frentes. São a fonte dos nomes que você escreve em responsavel, canais[].nome e cursos de uma ação. Use estes valores em vez de inventar: nome fora da lista é aceito pela API, mas some dos filtros da tela e vira sinônimo perdido. Vem tudo de uma vez, cada lista já na ordem em que a tela mostra: {"tipos":[responsavel, canal, curso], "vocabulario":{"responsavel":[{"id":"...","valor":"Murilo","ordem":1000}], "canal":[...],"curso":[...]}} Filtros: tipo=canal (ou vários, separados por vírgula) e arquivados=true. POST /api/vocabulario Corpo: {"tipo":"canal","valor":"Telegram"}. Entra no fim da lista. Se o nome já existe e está em uso, 409. Se existe mas foi removido um dia, a API ressuscita a linha antiga e responde 200 com "restaurado":true — assim as ações que já citam esse nome continuam casando com ele. PATCH /api/vocabulario/ {"valor":"..."} renomeia, {"ordem":2500} reposiciona, {"arquivado":false} restaura. O tipo não muda: um item não troca de lista. DELETE /api/vocabulario/ Arquiva. Nada é apagado, porque as ações guardam o NOME e o histórico precisa continuar fazendo sentido depois que a lista muda. POST /api/vocabulario/ordem {"tipo":"canal","ids":["","",...]} na ordem desejada. A API renumera a lista inteira. É o que a tela usa ao arrastar — evite PATCHes de ordem um a um, que deixam a lista inconsistente se um deles falhar. ### Funis (os quadros) GET /api/funis Lista os funis com contagem de temas e de atrasados. POST /api/funis Cria um funil. Corpo: {"nome":"YouTube","etapas":[{"nome":"Ideia"},...], "trava_a_partir_de":"gravacao"}. Sem "etapas", nasce com as seis padrão. O id de cada etapa é gerado a partir do nome quando você não manda um. Máximo de 12 etapas. GET /api/funis/ O funil e todos os temas dele de uma vez. É a chamada mais eficiente quando você vai trabalhar em um quadro inteiro. ### Temas (os cards) GET /api/temas Filtros por querystring, combináveis: funil_id só de um quadro etapa só de uma coluna responsavel por dono prioridade alta | media | baixa busca texto livre no título atrasados true, para os que passaram do prazo ativos true, para os que ainda não chegaram na última etapa desde data-hora ISO 8601; devolve só o que foi criado ou alterado depois dela. É o filtro de uma ronda periódica: guarde a hora da última verificação e passe aqui. Vazio significa que nada mudou — e nada a fazer. GET /api/temas/ Um tema com o histórico do que já aconteceu com ele. POST /api/temas Cria um tema. Obrigatório: titulo. Recomendado mandar junto funil_id, publico, objetivo, cta, estagio, prioridade, prazo e responsavel. Sem funil_id, o tema cai no primeiro funil ativo. PATCH /api/temas/ Atualiza os campos que você mandar, e só esses. Serve para marcar as validações, mudar prazo, trocar responsável — e para mover o card para outra esteira, mandando um funil_id diferente. POST /api/temas//mover Corpo: {"etapa":"copy"}. Move dentro do funil do tema. Responde 409 se a trava pegar. POST /api/temas//copiar Corpo: {"funil_id":"","manter_pecas":false}. O original fica onde está; a cópia entra na primeira etapa do destino. POST /api/temas//peca Corpo: {"canal":"Reels","feito":true}. Marca uma peça do enxoval. ### Ações (o plano que comanda a esteira) Quem manda em quê — leia isto antes de mexer num tema que tem acao_id: - o CONTEÚDO do card (titulo, objetivo, publico, responsavel, prioridade, prazo e a lista de peças) é escrito pela AÇÃO toda vez que ela é salva. Se você editar esses campos direto no tema, a próxima gravação da ação passa por cima; - a ETAPA é da esteira. A ação só escolhe a etapa de entrada, quando o card nasce. Depois disso quem move o card é quem trabalha no quadro — inclusive você; - o STATUS da ação SEGUE a etapa do card, sozinho: primeira etapa = nao-iniciada, etapa de validação = aguardando-validacao, última = concluida. A exceção é "bloqueada", que é decisão humana e nenhuma movimentação apaga; - peça já marcada como feita CONTINUA feita quando a ação é salva de novo. GET /api/acoes Filtros: status, responsavel, prioridade, busca, atrasadas=true, arquivadas=true, limite. Cada ação vem com "card": onde o trabalho dela está na esteira (funil, etapa, etapa_nome e o enxoval feitas/total). POST /api/acoes Corpo mínimo: {"titulo":"..."}. Sem funil_id, entra no primeiro funil. Criar a ação JÁ CRIA o card no funil escolhido, com os canais como peças. GET /api/acoes/ PATCH /api/acoes/ Só os campos que mudam. Alterar a ação reflete no card na mesma chamada. Mudar funil_id leva o card junto: ele entra pela primeira etapa do funil novo, porque a etapa antiga pode não existir lá. DELETE /api/acoes/ Arquiva dos dois lados: a ação sai do plano e o card sai do quadro. Campos de uma ação: titulo obrigatório na criação objetivo_final o resultado de negócio que ela persegue descricao contexto da frente conteudo_mae o texto que origina o enxoval conteudo_mae_link link para ele investimento texto livre: "R$ 5.000,00" ou "horas da equipe" prioridade alta | media | baixa responsavel nome de quem toca status nao-iniciada | em-andamento | aguardando-validacao | concluida | bloqueada prazo AAAA-MM-DD ou null cursos lista de nomes; vira o "publico" do card canais [{nome, modo, nota, copy}], modo = organico|pago|ambos; cada canal vira uma peça do enxoval validacoes [{item, feito, por}] links lista de URLs observacoes dependências, bloqueios, próximos passos funil_id em qual funil da esteira o card desta ação vive ### Anexos O arquivo não passa pela Vercel: a API assina uma URL e você sobe direto para o Storage. São três passos, nesta ordem: 1. POST /api/temas//anexos (arquivo do card) ou POST /api/acoes//anexos (arquivo do plano de ação) Corpo: {"nome":"capa.png","tipo":"image/png","tamanho":12345} Devolve o registro e uma URL de upload assinada. 2. PUT na URL que voltou, com o arquivo cru no corpo. 3. PATCH /api/anexos/ com {"pronto":true} Só depois do passo 3 o anexo aparece na listagem — um upload interrompido não vira arquivo fantasma. Os links assinados que a listagem devolve valem 1 hora. Para remover: DELETE /api/anexos/, venha o arquivo de onde vier. Anexo de ação e anexo de card são A MESMA COISA, vista de dois lugares. Não existe um segundo sistema de arquivos: toda ação mantém um card, o arquivo mora no card, e o campo escopo diz de qual gaveta ele veio: tema | acao | conteudo-mae | canal tema subido no card, pela esteira. É o padrão de POST /api/temas//anexos acao subido no Plano de Ação, na ação em si (briefing, referências) conteudo-mae subido no Plano de Ação, junto do campo conteudo_mae GET /api/temas//anexos Tudo que é material daquela campanha — os três escopos juntos. É de propósito: quem está gravando o Reels precisa ver o briefing sem trocar de tela. Restrinja com ?escopo=tema (ou vários, separados por vírgula) se quiser só uma gaveta. GET /api/acoes//anexos Tudo que está preso a esta ação, já separado por gaveta: {"acao_id":"...","tema_id":"...","total":3,"anexos":[...], "por_escopo":{"acao":[...],"conteudo-mae":[...],"tema":[...]}} "tema" vem preenchido quando alguém subiu arquivo direto no card — o plano pode mostrar ou ignorar, mas nunca fica sem saber que existe. POST /api/acoes//anexos Corpo: {"nome":"...","tipo":"...","tamanho":123,"escopo":"acao"}. escopo aqui só aceita acao ou conteudo-mae; sem escopo, assume "acao". Se a ação estiver sem card (funil apagado, por exemplo), a API recria o card antes de assinar o upload em vez de recusar o arquivo. Campos de um anexo: id, nome, tipo (MIME), tamanho (bytes), escopo, tema_id, acao_id, pronto, criado_em, criado_por, e nas listagens url (assinada, 1h) e e_imagem. ## Os campos de um tema id uuid, gerado pela API titulo obrigatório na criação publico para quem é este conteúdo funil_id uuid do quadro onde o tema vive etapa id de uma etapa DO FUNIL do tema estagio topo | meio | fundo objetivo o que este conteúdo precisa provocar cta a chamada para ação desejada prioridade alta | media | baixa prazo AAAA-MM-DD, ou null responsavel texto livre val_tecnica booleano val_tecnica_resp quem validou val_regulatoria booleano val_regulatoria_resp quem liberou pecas lista de {canal, feito}, até 40 arquivado booleano concluido_em carimbado pela API na última etapa Campos calculados que já vêm prontos em toda resposta de tema. Não recalcule: enxoval {feitas, total} das peças dias_para_o_prazo negativo quando já passou atrasado booleano vence_em_5_dias booleano ativo ainda não chegou na última etapa liberado_para_copy as duas validações marcadas ## Erros 400 dados_invalidos o corpo tem campo fora do vocabulário; o corpo da resposta traz a lista do que foi recusado e por quê 401 sem_credencial falta o header x-api-key 404 nao_encontrado id que não existe 409 validacao_pendente a trava do funil; o campo "falta" diz o que falta Todo erro vem em JSON com "erro" e "mensagem". Leia a mensagem: ela foi escrita para dizer o que fazer, não só o que houve. ## Receitas Pegar o trabalho atrasado de um quadro: GET /api/etapas (descobre o funil_id) GET /api/temas?funil_id=&atrasados=true Levar um tema da validação para a copy: PATCH /api/temas/ {"val_tecnica":true,"val_tecnica_resp":"Dr. Fulano"} PATCH /api/temas/ {"val_regulatoria":true,"val_regulatoria_resp":"Jurídico"} POST /api/temas//mover {"etapa":"copy"} Abrir uma pauta nova já com o enxoval previsto: POST /api/temas {"funil_id":"","titulo":"...","publico":"...","estagio":"topo", "objetivo":"...","cta":"...","prioridade":"media","prazo":"2026-09-30", "responsavel":"Equipe de Conteúdo", "pecas":[{"canal":"Reels","feito":false},{"canal":"Carrossel","feito":false}]} Fechar uma peça do enxoval: POST /api/temas//peca {"canal":"Reels","feito":true} Escrever uma ação com os nomes que a equipe usa (e não sinônimos seus): GET /api/vocabulario (responsáveis, canais e cursos) POST /api/acoes {"titulo":"...","responsavel":"", "cursos":[""], "canais":[{"nome":"","modo":"organico"}]} Subir o briefing de uma ação (o arquivo aparece também no card dela): POST /api/acoes//anexos {"nome":"briefing.pdf","tipo":"application/pdf", "tamanho":98765,"escopo":"acao"} PUT (o arquivo cru) PATCH /api/anexos/ {"pronto":true} ## Como se comportar aqui Comece por GET /api/etapas e use os ids que vierem de lá. Nunca invente id de etapa nem traduza o nome dela. Peça só o que vai usar: prefira GET /api/funis/, que traz o quadro inteiro em uma chamada, a varrer os temas um a um. Ao receber 409, pare e resolva a validação — não insista na mesma chamada. Escreva em PATCH apenas os campos que você realmente quer mudar. O que você não mandar fica como está, e isso é proposital: dois agentes podem trabalhar no mesmo card sem um apagar o outro. Você está mexendo no trabalho de pessoas. Quando a situação não estiver clara — um tema sem responsável, um prazo impossível, uma validação que ninguém marcou — registre o que encontrou e avise um humano em vez de decidir sozinho.