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)
GET /v1/export
Seção intitulada “GET /v1/export”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âmetros de query
Seção intitulada “Parâmetros de query”| 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.
Recursos
Seção intitulada “Recursos”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.
Limites
Seção intitulada “Limites”| 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.
Resposta
Seção intitulada “Resposta”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.
Exemplos
Seção intitulada “Exemplos”Exportar todos os posts como NDJSON:
curl -sS "https://api.canverly.com/v1/export?resource=posts&format=ndjson" \ -H "Authorization: Bearer ck_live_xxx" \ -o posts-export.ndjsonExportar os leads limpos de um formulário em um período, como CSV:
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.csvExportar a série de analytics de uma janela:
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. |
POST /v1/import
Seção intitulada “POST /v1/import”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:
importeposts:write— importar é escrever.
Parâmetros de query
Seção intitulada “Parâmetros de query”| 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.
{"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.
Limites
Seção intitulada “Limites”| 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.
Idempotência
Seção intitulada “Idempotência”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.
Exemplo
Seção intitulada “Exemplo”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.ndjsonResposta
Seção intitulada “Resposta”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.