Pular para o conteúdo

Exportação e importação em massa

Dois endpoints para mover conteúdo em volume: GET /v1/export transmite um recurso do site dono da chave, e POST /v1/import cria posts a partir de um arquivo NDJSON. Os dois operam sobre o próprio site da chave — não existe parâmetro de site.

Método Caminho Escopo
GET /v1/export export
POST /v1/import import e posts:write
  • URL base: https://api.canverly.com
  • Autenticação: chave de API bearer (somente chave secreta ck_…) — veja Autenticação
  • Limite de requisição: 60 req/min por chave (exportações de leads também consomem o limite mais restrito de 10 req/min de leads)

Transmite o recurso pedido à medida que ele é lido — a API nunca coloca o acervo inteiro em buffer. NDJSON significa um objeto JSON por linha; CSV significa colunas planas.

Parâmetro Obrigatório Valores Observações
resource sim posts | leads | analytics Ausente ou desconhecido é um 400 — não há padrão.
format não ndjson (padrão) | csv Valor desconhecido é um 400; nunca é convertido em silêncio para NDJSON.
form_id não string somente leads — restringe a um formulário.
status não clean | spam | all somente leads — filtro de status do envio.
de não data somente leads — início do período.
ate não data somente leads — fim do período.
from não YYYY-MM-DD somente analytics — início da janela.
to não YYYY-MM-DD somente analytics — fim da janela.
days não string somente analytics — janela predefinida.

Filtros que não pertencem ao recurso escolhido são ignorados.

resource Formatos Escopo extra Formato dos dados
posts somente ndjson — Um objeto de post por linha, a projeção pública, paginada internamente por cursor.
leads ndjson, csv leads:read Envios de formulário. O CSV é a exportação de formulários, com as colunas corretas de cada formulário.
analytics ndjson, csv analytics:read A série temporal diária. As colunas do CSV são date,views,visitors.

posts + csv é um 400. O corpo de um post é um documento de blocos aninhados (veja Blocos de conteúdo); colunas planas não conseguem representá-lo sem mutilá-lo, então posts são exportados só como NDJSON e o erro diz isso.

leads são dados pessoais. Exigem leads:read além de export, toda exportação é registrada na trilha de auditoria como leads.export com a chave de API como autor da ação, e ela é cobrada no limite mais restrito de 10 req/min de leads. Esse escopo é opt-in por chave — nunca é concedido por padrão.

analytics é uma única leitura de uma série limitada a 366 dias, então é produzida de uma vez, em vez de transmitida página a página.

Limite Valor Motivo
Linhas por exportação 50 000 (posts, leads) Uma exportação paginada é truncada nesse número de linhas; ela não pode virar uma leitura sem fim segurando conexões do pool. Reduza a janela (ou os filtros) e exporte em partes.
Tamanho de página na origem 200 linhas O stream busca a origem em páginas de 200 e mantém uma página por vez, com um canal limitado aplicando backpressure. Não é um parâmetro visível ao cliente.

A truncagem em 50 000 linhas é silenciosa no corpo — o stream simplesmente termina. Se um recurso puder ultrapassar esse limite, divida a exportação por período (leads) ou janela (analytics) em vez de pedir tudo.

200 OK com o stream:

Cabeçalho Valor
Content-Type application/x-ndjson ou text/csv; charset=utf-8
Content-Disposition attachment; filename="<resource>-<YYYY-MM-DD>.<ext>", ex.: posts-2026-08-26.ndjson
X-Content-Type-Options nosniff

A primeira página é buscada antes de o 200 ser enviado, então uma falha de autorização ou da origem chega com o código de status correto, e não como um corpo truncado. Uma falha depois que o stream começou encerra o corpo antes da hora — confira se você recebeu uma última linha completa.

Exportar todos os posts como NDJSON:

Janela do terminal
curl -sS "https://api.canverly.com/v1/export?resource=posts&format=ndjson" \
-H "Authorization: Bearer ck_live_xxx" \
-o posts-export.ndjson

Exportar os leads limpos de um formulário em um período, como CSV:

Janela do terminal
curl -sS "https://api.canverly.com/v1/export?resource=leads&format=csv&status=clean&de=2026-08-01&ate=2026-08-31" \
-H "Authorization: Bearer ck_live_xxx" \
-o leads-august.csv

Exportar a série de analytics de uma janela:

Janela do terminal
curl -sS "https://api.canverly.com/v1/export?resource=analytics&format=csv&from=2026-08-01&to=2026-08-26" \
-H "Authorization: Bearer ck_live_xxx" \
-o analytics-august.csv
Status error.code Quando
400 bad_request resource ausente ou desconhecido, format desconhecido, ou resource=posts&format=csv.
401 unauthorized Chave ausente/inválida/revogada.
403 forbidden A chave não tem export — ou não tem leads:read para leads, analytics:read para analytics.
404 not_found O recurso não existe para este site (ex.: o site não tem o app de formulários instalado).
429 rate_limited Acima de 60 req/min — ou acima do limite de 10 req/min de leads. Respeite Retry-After.

Cria posts em massa a partir de um corpo NDJSON: uma linha = um post, cada linha usando exatamente o mesmo schema JSON de POST /v1/posts.

  • Content-Type: application/x-ndjson
  • Escopos: import e posts:write — importar é escrever.
Parâmetro Obrigatório Valores Observações
resource não posts (padrão) Qualquer outro valor é um 400. A importação de mídia propositalmente ainda não é oferecida.

Cada linha é um objeto de post completo: title e blocks_json são obrigatórios, todo o resto (status, slug, excerpt, language, post_type, category_slugs, tag_slugs) é opcional e se comporta exatamente como em POST /v1/posts.

posts.ndjson
{"title":"Choosing a static host","status":"published","language":"en","category_slugs":["guides"],"blocks_json":{"v":1,"blocks":[{"id":"p1","type":"paragraph","text":"A short comparison of static hosting options for a docs site."}]}}
{"title":"Migrating from example.com to the new domain","status":"draft","language":"en","blocks_json":{"v":1,"blocks":[{"id":"p1","type":"paragraph","text":"Keep the old URLs alive with redirects before you switch the DNS."}]}}

As linhas são separadas por \n; linhas em branco são ignoradas, e espaços em branco ou \r no final são removidos. O arquivo precisa estar em UTF-8.

Limite Valor Resultado se ultrapassado
Tamanho do corpo 8 MiB 413
Linhas 1000 400 — too many lines (max 1000 per import)
Corpo vazio (ou só com linhas em branco) — 400
Corpo fora de UTF-8 — 400

Uma migração maior vira várias chamadas; espace-as em cerca de 1 requisição/segundo para ficar abaixo do limite de requisição.

A chave de idempotência de cada linha é derivada do conteúdo daquela linha, restrita à sua chave de API, e fica em cache por 24 horas. Por isso, rodar o mesmo arquivo de novo não duplica posts: uma linha já vista retorna o id original com status: "replayed". Mude o conteúdo de uma linha e ela conta como um post novo.

Janela do terminal
curl -sS -X POST "https://api.canverly.com/v1/import" \
-H "Authorization: Bearer ck_live_xxx" \
-H "Content-Type: application/x-ndjson" \
--data-binary @posts.ndjson

200 OK com um resumo por linha:

{
"created": 1,
"failed": 1,
"results": [
{
"line": 1,
"status": "created",
"id": "01K8Z9K3F7T8M2QYV5N6B4WJ8R",
"slug": "choosing-a-static-host",
"post_status": "published"
},
{
"line": 2,
"status": "error",
"error": "title is required"
}
]
}
Campo Tipo Observações
created integer Linhas que produziram um post — incluindo as replayed.
failed integer Linhas que retornaram status: "error".
results[].line integer Número da linha a partir de 1, contando só as linhas não vazias.
results[].status string "created", "replayed" ou "error".
results[].id string ULID do post — em created/replayed.
results[].slug string Slug final — em created/replayed.
results[].post_status string "draft" ou "published" — em created/replayed.
results[].error string Por que a linha falhou — somente em error.

Um 200 pode conter falhas. Uma linha inválida nunca aborta a importação: ela vira uma entrada status: "error" e as linhas restantes continuam sendo processadas. Nunca trate o código de status como prova de que a importação deu certo — inspecione failed e reenvie só as linhas cuja entrada em results[] diz error.

Status error.code Quando
400 bad_request Corpo vazio, corpo fora de UTF-8, mais de 1000 linhas, ou resource diferente de posts.
401 unauthorized Chave ausente/inválida/revogada.
403 forbidden A chave não tem import, ou não tem posts:write.
413 — Corpo acima de 8 MiB (mensagem em texto puro, não o envelope JSON).
429 rate_limited Acima de 60 req/min — respeite Retry-After.

Falhas por linha não são erros HTTP — veja a tabela de results[] acima. A tabela completa de status está em Erros e limites de requisição.