Headless
No modo headless, o conteúdo continua sendo escrito e publicado no Canverly, mas quem desenha as páginas é um front seu. Esse front lê os posts pela API pública de leitura, que já está em produção, e monta o HTML do jeito que quiser.
O preço é que tudo o que a plataforma faz na hora de renderizar a página passa a ser responsabilidade do seu front. A lista está em O que o front próprio deixa de ter.
Autenticação
Seção intitulada “Autenticação”Qual chave usar
Seção intitulada “Qual chave usar”Use uma chave secreta (ck_…) criada só com o escopo posts:read.
- Crie a chave no admin, em Configurações → Integrações API. Ela aparece uma única vez; o Canverly guarda apenas o hash.
- Os escopos são opcionais por chave: a chave recebe só o que foi pedido na
criação. Para ler posts basta
posts:read. Não peçaposts:writepara o front: com ele, a chave consegue publicar e editar. - Cada chave vale para um site. O site é deduzido da chave, nunca de um parâmetro da requisição.
A chave vai no cabeçalho, em toda chamada:
curl -s "https://api.canverly.com/v1/posts?limit=10" \ -H "Authorization: Bearer ck_SUA_CHAVE"A chave fica no servidor
Seção intitulada “A chave fica no servidor”A ck_ é segredo. Ela vive numa variável de ambiente ou num cofre de segredos
do servidor que renderiza o seu front, e nunca vai para o navegador.
- Não coloque a chave em variável com prefixo público (
PUBLIC_*,NEXT_PUBLIC_*,VITE_*). Esses prefixos existem justamente para embutir o valor no JavaScript que o navegador baixa. - As rotas de leitura não devolvem cabeçalho de CORS para uma
ck_. Chamar a API com ela a partir do navegador não funciona, de propósito. - Mandar uma
ck_na query (?key=ck_…) é recusado com400, porque ela apareceria em log de acesso, noReferere no histórico do navegador.
Por que não a chave pública pk_
Seção intitulada “Por que não a chave pública pk_”A chave pública de leitura (pk_…) foi feita para o widget:
ela fica no HTML da página e só funciona nas origens cadastradas na própria
chave, conferidas pelo cabeçalho Origin. Para um front que busca o conteúdo
no servidor, a ck_ só com posts:read é o caminho. Detalhes em
Chaves, escopos e restrições.
Restrições e limites
Seção intitulada “Restrições e limites”- A chave pode ter restrições por IP/CIDR, região e janela de horário. Para um
front com IP de saída fixo, a restrição de IP é uma boa camada extra.
Requisição barrada recebe
403com a dimensão que barrou. - As rotas de leitura aceitam 600 requisições por minuto por chave. Acima
disso,
429comRetry-After. Respeite o cabeçalho e use cache no seu lado (ver Cache e atualização).
Os endpoints de leitura
Seção intitulada “Os endpoints de leitura”Dois endpoints cobrem o front inteiro.
| Endpoint | Para quê |
|---|---|
GET /v1/posts |
Listagem paginada do acervo público, com filtros. Sem o corpo do post. |
GET /v1/posts/{reference} |
Um post, por slug ou id, com o corpo já em HTML (content_html). |
Os dois só enxergam o que está publicado. Rascunho, post agendado para o futuro e post restrito nunca são selecionados.
Campos de cada post
Seção intitulada “Campos de cada post”A listagem devolve items (cada um no formato PublicPost) e next_cursor.
| Campo | Observação |
|---|---|
id |
ULID de 26 caracteres. |
slug |
Use na URL do seu front. |
title, excerpt |
Texto puro. |
published_at, updated_at |
RFC-3339, em UTC. |
cover_url |
URL absoluta da capa, ou null quando não há imagem. |
author |
Autor do post, ou null. Trate o null. |
categories, tags |
Listas de termos. |
url |
Endereço canônico do post no site da plataforma, não no seu front. |
reading_time |
Minutos, no mínimo 1. |
language, post_type |
Ex.: pt-BR, post. |
O detalhe (GET /v1/posts/{reference}) traz os mesmos campos e mais o
content_html, o corpo já renderizado e sanitizado.
Post inexistente é 404
Seção intitulada “Post inexistente é 404”Post que não existe, rascunho, agendado, restrito e post de outro site
respondem o mesmo 404. É proposital: um 403 confirmaria que o rascunho
existe. No seu front, trate o 404 da API como 404 da página.
Paginação
Seção intitulada “Paginação”A paginação é por cursor, do mais novo para o mais antigo. Não existe
número de página, total nem has_more.
- Peça a primeira página:
GET /v1/posts?limit=10. - Se a resposta trouxer
next_cursor, a próxima página é a mesma chamada com&cursor=<next_cursor>. next_cursorausente ounullsignifica fim do acervo.
curl -s "https://api.canverly.com/v1/posts?limit=10&cursor=eyJwIjoi..." \ -H "Authorization: Bearer ck_SUA_CHAVE"limitvai de 1 a 50 (padrão 20). Fora da faixa é400: o valor nunca é cortado em silêncio.- O cursor é opaco. Não o decodifique nem o monte; cursor inválido é
400. - Publicar um post no meio da navegação não faz ninguém pular ou repetir item.
- Na prática, o seu front oferece “mais antigos” (e “voltar ao início”). Página N direta não é possível com cursor.
Filtros
Seção intitulada “Filtros”Os filtros de GET /v1/posts combinam com a paginação:
| Parâmetro | Efeito |
|---|---|
category |
Slug de categoria. |
tag |
Slug de tag. |
q |
Busca em título e resumo. |
since |
RFC-3339. Só posts com updated_at >= since. Serve para sincronização incremental. |
language |
Tag BCP-47, ex. pt-BR. |
A página de categoria do starter é GET /v1/posts?category=<slug>. Os detalhes
de cada parâmetro estão em Listar e ler posts.
Blocos: o corpo do post
Seção intitulada “Blocos: o corpo do post”No Canverly, o corpo de um post é um documento de blocos, não HTML. Para o
front, a API renderiza esse documento no servidor e entrega o resultado pronto
em content_html. O seu front não precisa conhecer o formato de
blocos.
O que cada bloco vira
Seção intitulada “O que cada bloco vira”| Bloco | HTML em content_html |
|---|---|
paragraph |
<p> |
heading |
<h1> a <h6> (padrão <h2>) |
list |
<ul class="cv-list cv-list-bullet"> ou <ol class="cv-list cv-list-ordered"> |
quote |
<blockquote class="cv-quote"> |
callout |
<aside class="cv-callout …"> |
code |
<pre class="cv-code"> |
image |
<figure class="cv-img"><img …><figcaption class="cv-caption"> |
gallery |
<div class="cv-gallery"> |
table |
<table class="cv-table"> |
divider |
<hr class="cv-divider"> |
columns |
Uma <section class="cv-column"> por coluna, em sequência (sem grade) |
embed, video |
Um link: <p class="cv-embed-link"><a href="…"> (sem player) |
html |
<div class="cv-html"> com o HTML do bloco, sanitizado |
As classes cv-* são as mesmas do site da plataforma. Estilize-as no seu CSS; a
API não manda folha de estilo.
O que nunca aparece em content_html
Seção intitulada “O que nunca aparece em content_html”- Blocos de anúncio (
ad), cartão de produto (product_card) e página de story (story_page) saem vazios, por decisão documentada na OpenAPI. - Qualquer outro tipo de bloco que o renderizador não conhece também sai vazio,
sem aviso. É o caso, hoje, do bloco de posts relacionados (
related_posts): se quiser a seção, monte-a comGET /v1/posts?category=, usando a primeira categoria do post, e tire o próprio post da lista. - Vídeo e embed não viram
<iframe>: viram link. Se quiser o player, monte-o no seu front a partir do link.
Por que é seguro injetar content_html
Seção intitulada “Por que é seguro injetar content_html”A API sanitiza o HTML na saída, com uma allowlist fechada: sem <script>,
<iframe> nem <form>, e embed de vídeo vira link. Mesmo assim, trate
content_html como HTML vindo de outro sistema e sanitize de novo no seu
servidor, com a mesma allowlist, antes de injetar. O starter faz isso. Se um
dia algo mudar do lado de lá, o seu front continua sem XSS.
Imagens
Seção intitulada “Imagens”- Capa:
cover_urlé uma URL absoluta da CDN, ounull. A API ainda não informa as dimensões da capa: reserve o espaço pelo CSS (proporção fixa) para evitar deslocamento de layout. - Imagens do corpo: chegam como
<img src="…" alt="…" loading="lazy">dentro de<figure class="cv-img">, semwidtheheight. Reserve o espaço pelo CSS. Bloco de imagem sem endereço sai vazio. - Política de conteúdo: as imagens vêm de um domínio de CDN, não do seu.
Libere esse domínio no
img-srcda sua CSP.
Cache e atualização
Seção intitulada “Cache e atualização”| Rota | Cache-Control da API |
|---|---|
GET /v1/posts |
public, max-age=60, stale-while-revalidate=300 |
GET /v1/posts/{reference} |
public, max-age=300, stale-while-revalidate=600 |
- As respostas trazem
ETag. Mande de volta emIf-None-Matche, se nada mudou, a resposta é304sem corpo. - Não existe, hoje, aviso da plataforma para o seu front quando um post é
publicado, editado ou despublicado. Um post novo aparece no seu front quando o
cache (o da API e o seu) expira. Se o seu front guarda cópia local, use
GET /v1/posts?since=<última sincronização>para buscar só o que mudou.
O corpo de erro e a lista completa de códigos estão em Erros e limites. No front:
| Status | Quando | O que o front faz |
|---|---|---|
304 |
If-None-Match casou |
Reusa a cópia que já tem. |
400 |
limit fora de 1..50, cursor inválido, ck_ na query |
Corrija a chamada. |
401 |
Chave ausente, inválida ou revogada | Confira a variável de ambiente. |
403 |
Falta o escopo posts:read, ou uma restrição da chave barrou |
Confira a chave no admin. |
404 |
Post inexistente ou não público | Responda 404 na sua página. |
429 |
Mais de 600 req/min na chave | Espere o Retry-After; aumente o seu cache. |
Outras leituras úteis (com escopos extras)
Seção intitulada “Outras leituras úteis (com escopos extras)”Estas rotas também aceitam ck_, mas cada uma pede um escopo além de
posts:read. Só adicione o escopo se o seu front precisar do dado.
| Endpoint | Escopo | O que traz |
|---|---|---|
GET /v1/sites/me |
sites:read |
Identidade do site: id, slug, primary_domain, default_language. |
GET /v1/sites/me/seo |
seo:read |
SEO do site. |
GET /v1/posts/{reference}/seo |
seo:read |
SEO do post. |
GET /v1/sites/me/settings |
site:read |
O objeto settings do site, inteiro. |
O que o front próprio deixa de ter
Seção intitulada “O que o front próprio deixa de ter”O site da plataforma faz uma série de coisas na hora de renderizar a página. No modo headless, nada disso chega ao seu front. Cada item abaixo passa a ser trabalho seu, ou deixa de existir.
Cache de página da plataforma
Seção intitulada “Cache de página da plataforma”O site da plataforma guarda a página HTML inteira em cache e a invalida quando o conteúdo muda. O seu front recebe só o cache HTTP da API, descrito acima, e não é avisado de publicação. Montar e invalidar o cache das suas páginas é com você.
SEO técnico
Seção intitulada “SEO técnico”- Sitemaps. A plataforma gera os sitemaps do site. O seu front precisa gerar
os dele, a partir de
GET /v1/posts(percorrendo o cursor) ou de uma cópia local sincronizada comsince. - Dados estruturados (JSON-LD). A API não entrega JSON-LD; o seu front monta o seu, fiel ao conteúdo.
- Canonical. O campo
urlde cada post aponta para o site da plataforma. O canonical das suas páginas é decisão sua. Se o site da plataforma continuar no ar com o mesmo conteúdo, os dois endereços concorrem entre si nos buscadores: decida qual é o canônico antes de publicar o front. - hreflang e idiomas. A plataforma monta os
hreflange o prefixo de idioma nas URLs. O seu front recebe olanguagede cada post; a API não informa, hoje, quais posts são traduções uns dos outros. - Também saem de cena:
robots.txt,llms.txt, feeds RSS/Atom e os metadados Open Graph/Twitter das páginas.
As Web Stories da plataforma são páginas AMP. Elas não existem no modo
headless: o bloco story_page sai vazio em content_html.
Consentimento e banner de cookies
Seção intitulada “Consentimento e banner de cookies”No site da plataforma, o controle de consentimento decide se o código de terceiros colado pelo dono é emitido. No seu front não há banner nem esse controle: a gestão de consentimento, e a conformidade com a LGPD, passam a ser sua.
Anúncios
Seção intitulada “Anúncios”Os blocos de anúncio (ad) e os espaços de anúncio do layout não chegam ao seu
front. Se o site vive de anúncio, a monetização precisa ser montada de novo.
Outras peças do site
Seção intitulada “Outras peças do site”Também ficam só no site da plataforma: páginas de autor, busca, comentários,
formulários e newsletter, e o tema. Menu, logo e cores do tema não têm leitura
própria na API: só existem dentro do objeto settings inteiro (ver o aviso em
Outras leituras úteis). Também não
há rota pública que liste as categorias do site: o front só conhece uma
categoria pelos categories dos posts.