Pular para o conteúdo

Listar e ler posts

Duas rotas leem o arquivo público do site dono da chave: uma listagem paginada e o detalhe de um único post.

  • Autenticação: chave de API Bearer — escopo posts:read. Estas são as únicas duas rotas que também aceitam uma chave pública de leitura (pk_…), inclusive como parâmetro de query ?key=. Veja Chaves, escopos e restrições.
  • Limite de requisição: 600 req/min por chave, mais 60 req/min por (chave, IP) quando quem chamou usou uma chave pk_.

Arquivo paginado, do mais recente para o mais antigo.

  • URL: https://api.canverly.com/v1/posts
  • Método: GET
Parâmetro Tipo Padrão Observações
limit integer 20 1–50. Fora da faixa devolve 400 — o valor nunca é ajustado em silêncio.
cursor string — O next_cursor da página anterior. Um cursor inválido devolve 400.
category string — Slug de categoria. Separe por vírgula para casar com qualquer um (category=news,opinion).
tag string — Slug de tag. Separe por vírgula para casar com qualquer um.
q string — Busca full-text em title e excerpt.
since string — Timestamp RFC-3339. Devolve só posts com updated_at >= since — o gancho da sincronização incremental.
language string — Tag BCP-47, ex.: pt-BR.
key string — Uma chave pública de leitura pk_…, para chamadas do navegador. Uma chave ck_ aqui devolve 400. Ignorado quando há um cabeçalho Authorization.
Cabeçalho Obrigatório Valor
Authorization sim¹ Bearer ck_… ou Bearer pk_…
If-None-Match não Um ETag devolvido antes. Se casar, devolve 304 sem corpo.

¹ A menos que a requisição traga ?key=pk_… no lugar.

Janela do terminal
curl -s "https://api.canverly.com/v1/posts?limit=5&category=news" \
-H "Authorization: Bearer ck_live_xxx"

Pagine seguindo o cursor até ele vir ausente ou null:

Janela do terminal
curl -s "https://api.canverly.com/v1/posts?limit=50&cursor=eyJwIjoi…" \
-H "Authorization: Bearer ck_live_xxx"

Sincronização incremental — consulte só o que mudou desde a última execução:

Janela do terminal
curl -s "https://api.canverly.com/v1/posts?since=2026-08-01T00:00:00Z" \
-H "Authorization: Bearer ck_live_xxx"
{
"items": [
{
"id": "01J8ZQ7X4K2N5M9P0R3T6V8W1Y",
"slug": "como-fazer-x",
"title": "Como fazer X",
"excerpt": "A one-sentence summary.",
"published_at": "2026-08-01T12:00:00Z",
"updated_at": "2026-08-02T09:30:00Z",
"author": { "name": "Ana Prado", "slug": "ana-prado", "avatar_url": "https://cdn.example.com/a.png" },
"categories": [{ "slug": "financas", "name": "Finanças" }],
"tags": [{ "slug": "juros", "name": "Juros" }],
"cover_url": "https://cdn.example.com/cover.jpg",
"url": "https://blog.example.com/como-fazer-x",
"reading_time": 4,
"language": "pt-BR",
"post_type": "post"
}
],
"next_cursor": "eyJwIjoi…"
}
Campo Tipo Observações
id string ULID Crockford base32 de 26 caracteres.
slug string Slug da URL.
title string
excerpt string
published_at / updated_at string RFC-3339.
author object | null Assinatura pública (uma persona), não um usuário da plataforma. null quando nenhuma persona está atribuída — trate esse caso.
categories / tags array { slug, name }. name nunca vem vazio: um slug não cadastrado é humanizado (juros-compostos → Juros Compostos), então você nunca precisa de dois caminhos de exibição.
cover_url string | null URL absoluta da CDN; null/ausente quando não há imagem que possa ser resolvida.
url string URL pública canônica, já com o prefixo de idioma quando o post não está no idioma padrão do site.
reading_time integer Minutos, max(1, ceil(words / 200)) — a mesma fórmula que o site usa.
language string BCP-47.
post_type string ex.: post, page.
next_cursor string | null Cursor opaco para a próxima página. Ausente ou null significa o fim. Não existe total nem has_more.

O corpo da listagem não inclui o corpo do post. Busque a rota de detalhe para isso.

As respostas trazem um ETag forte e Cache-Control: public, max-age=60, stale-while-revalidate=300. Devolva o ETag como If-None-Match e você recebe um 304 sem corpo quando nada mudou — a consulta mais barata possível.

Um post publicado, com o corpo renderizado.

  • URL: https://api.canverly.com/v1/posts/{reference}
  • Método: GET
Segmento do caminho Observações
reference Um ULID (26 caracteres), um UUID (36 caracteres) ou um slug. Qualquer coisa que não tenha formato de id é tratada como slug.

As mesmas opções de query/cabeçalho da listagem para key e If-None-Match. Aqui o Cache-Control é public, max-age=300, stale-while-revalidate=600.

Janela do terminal
# Por slug
curl -s https://api.canverly.com/v1/posts/como-fazer-x \
-H "Authorization: Bearer ck_live_xxx"
# Por id
curl -s https://api.canverly.com/v1/posts/01J8ZQ7X4K2N5M9P0R3T6V8W1Y \
-H "Authorization: Bearer ck_live_xxx"

Todos os campos do item da listagem, mais:

Campo Tipo Observações
content_html string O corpo renderizado, sanitizado por allowlist na saída.

O que a sanitização significa na prática, para você se planejar:

  • Nada de <script>, <iframe>, <form> e nenhum atributo de event handler. Nunca.
  • Blocos de anúncio, cards de produto e story pages são omitidos da saída.
  • Embeds de vídeo são rebaixados a um link.
  • Uma imagem armazenada como id da biblioteca (attrs.media_id) sai com a URL já resolvida. Se o asset foi excluído ou está indisponível, o bloco é omitido em vez de emitido como um <img> quebrado.

Envelope: { "error": { "code", "message" } }. Tabela completa em Erros e limites de requisição.

Status error.code Quando
304 — If-None-Match casou; sem corpo.
400 bad_request limit ou cursor inválido; ou uma chave ck_ passada em ?key=.
401 unauthorized Chave ausente/inválida/revogada.
403 forbidden Falta o escopo posts:read — ou, para uma chave pk_, o Origin da requisição não está entre as origens permitidas da chave. A chave é válida; a página em que ela roda, não. Adicione a origem em Configurações → Integrações API.
404 not_found Rota de detalhe: o post não existe ou não é público.
429 rate_limited Acima de 600 req/min por chave, ou de 60 req/min por (chave, IP) em uma chave pk_. Respeite o Retry-After.
502 upstream Erro upstream transitório; tente de novo com backoff.