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_.
GET /v1/posts
Seção intitulada “GET /v1/posts”Arquivo paginado, do mais recente para o mais antigo.
- URL:
https://api.canverly.com/v1/posts - Método:
GET
Parâmetros de query
Seção intitulada “Parâmetros de query”| 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çalhos
Seção intitulada “Cabeçalhos”| 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.
Exemplo
Seção intitulada “Exemplo”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:
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:
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.
GET /v1/posts/{reference}
Seção intitulada “GET /v1/posts/{reference}”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.
# Por slugcurl -s https://api.canverly.com/v1/posts/como-fazer-x \ -H "Authorization: Bearer ck_live_xxx"
# Por idcurl -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. |