Pular para o conteúdo

Guia de integração para agentes

Esta página foi escrita para agentes autônomos (e para os engenheiros que os configuram). Ela descreve o contrato sem nenhuma ambiguidade, para que um modelo chame a API corretamente na primeira tentativa. Tudo o que um agente precisa também está disponível em arquivos legíveis por máquina:

  • /llms.txt — o contrato completo em um único arquivo de texto puro. Baixe este arquivo e você já consegue publicar.
  • https://api.canverly.com/openapi.json — schema OpenAPI 3.1 para tool/function calling.
  • Endpoint: POST https://api.canverly.com/v1/posts
  • Cabeçalhos: Authorization: Bearer <CANVERLY_API_KEY>, Content-Type: application/json
  • Corpo (mínimo): { "title": string, "blocks_json": { "v": 1, "blocks": Block[] }, "status": "published" }
  • Block: { "id": string, "type": "paragraph"|"heading"|"list"|"code"|"quote"|"image", "text"?: string, "attrs"?: object }
  • Sucesso: 201 → { "id", "slug", "status", "public_url" }
  • O site é fixado pela chave. Nunca coloque site_id em lugar nenhum; você não consegue apontar para outro site.

Publicar é o caso mais comum, mas a API não serve só para publicar. Todas as rotas abaixo são acessíveis com a mesma chave bearer, sujeitas aos escopos dessa chave.

Objetivo Chamada Escopo
Publicar um artigo POST /v1/posts posts:write
Corrigir ou mudar a data de um artigo publicado PATCH /v1/posts/{id} posts:write
Ler o que já está publicado GET /v1/posts (keyset, ?since= para sincronização incremental) posts:read
Ler um artigo com o corpo GET /v1/posts/{reference} (id ou slug) posts:read
Evitar duplicar um artigo existente GET /v1/posts?q=<title> antes de escrever posts:read
Definir title/description/canonical/no_index PATCH /v1/posts/{reference}/seo seo:write
Enviar uma imagem para usar em um post POST /v1/media (multipart, campo file) media:write
Verificar quais tipos de post existem GET /v1/post-types post_types:read
Resolver o domínio público GET /v1/sites/me sites:read
Ler números de tráfego GET /v1/analytics/summary analytics:read
Carregar muitos posts de uma vez POST /v1/import (NDJSON) import + posts:write
Extrair o acervo GET /v1/export?resource=posts (NDJSON) export

Parâmetros e respostas completos: a referência da API.

Passe isto ao seu agente literalmente (forneça a chave por uma tool/secret, nunca escrita diretamente no prompt):

You can publish articles to a Canverly site through its public API.
ENDPOINT: POST https://api.canverly.com/v1/posts
HEADERS: Authorization: Bearer ${CANVERLY_API_KEY}
Content-Type: application/json
BODY (JSON):
- title (string, required)
- status (string): "published" to go live, "draft" to stage. Default "draft".
- blocks_json (object, required): { "v": 1, "blocks": [ ...blocks ] }
- slug (string, optional): omit to auto-generate; for retries send a STABLE slug.
- excerpt (string, optional): one-sentence summary.
- language (string, optional): BCP-47, e.g. "en" or "pt-BR".
- category_slugs (string[], optional), tag_slugs (string[], optional).
BLOCK SHAPES (each needs a unique "id"):
- { "id":"p1", "type":"paragraph", "text":"inline HTML allowed (<a>,<strong>,<em>,<code>)" }
- { "id":"h1", "type":"heading", "attrs":{"level":2}, "text":"Heading" }
- { "id":"l1", "type":"list", "attrs":{"style":"unordered"}, "text":"item one\nitem two" }
- { "id":"c1", "type":"code", "attrs":{"language":"python"}, "text":"print('hi')" }
- { "id":"q1", "type":"quote", "text":"A quote." }
RULES:
- Body content is blocks_json, NOT raw HTML and NOT markdown.
- Do NOT send site_id/org_id/author_id; the key fixes the site.
- <script>, event handlers and javascript: URLs are stripped; do not rely on them.
- For safe retries, send header "Idempotency-Key: <stable id>" (e.g. the source
item id). A replay with the same key+body returns the original 201 (no duplicate).
- On 429, read Retry-After and wait that many seconds, then retry.
- On 201, the response has "slug"; the post is live at https://<site-domain>/<slug>.
OPTIONAL DISCOVERY (each needs its own scope on the key):
- GET /v1/post-types -> valid post_type slugs (default "post").
- GET /v1/sites/me -> { id, slug, primary_domain, default_language }.
- GET /v1/posts?q=... -> check whether you already published this; the response
is { "items": [...], "next_cursor": string|null }. Absent/null cursor = end.
- GET /v1/posts?since=<RFC3339> -> only what changed since your last run.
EDITING AN EXISTING POST:
- PATCH /v1/posts/{id} with only the fields to change: title, blocks_json,
excerpt, category_slugs, tag_slugs, published_at. There is no status or slug
field: you cannot publish a draft or rename a URL through it.
- Do NOT send excerpt/category_slugs/tag_slugs if the post has SEO metadata you
need to keep - they overwrite the post's whole SEO object.
- Do NOT send published_at unless the date is genuinely wrong; it re-pings
IndexNow on every call.
SEO:
- GET /v1/posts/{id}/seo, then PATCH the SAME object back with your change.
The PATCH REPLACES the SEO object; anything you omit is cleared.
  1. (opcional) Resolva o site — GET /v1/sites/me → guarde em cache primary_domain e default_language.
  2. (opcional) Verifique os tipos de post — GET /v1/post-types → confirme que o slug do seu post_type existe (padrão post).
  3. (opcional) Verifique se há duplicata — GET /v1/posts?q=<title> → se já existir uma correspondência próxima, aplique um patch nela em vez de publicar um segundo post.
  4. Publique — POST /v1/posts com o corpo acima. Espere 201.
  5. (opcional) Defina o SEO — PATCH /v1/posts/{id}/seo com o objeto de SEO completo.
  6. Verifique — GET https://<primary_domain>/<slug> retorna 200, ou leia o post de novo por GET /v1/posts/{id}.
{
"title": "Example",
"status": "published",
"blocks_json": { "v": 1, "blocks": [
{ "id": "p1", "type": "paragraph", "text": "Body text." }
] }
}

Schema de função/tool (para modelos com tool calling)

Seção intitulada “Schema de função/tool (para modelos com tool calling)”
{
"name": "canverly_publish_post",
"description": "Create and optionally publish a post on the Canverly site bound to the API key.",
"parameters": {
"type": "object",
"required": ["title", "blocks_json"],
"properties": {
"title": { "type": "string" },
"status": { "type": "string", "enum": ["draft", "published"], "default": "draft" },
"slug": { "type": "string" },
"excerpt": { "type": "string" },
"language": { "type": "string" },
"post_type": { "type": "string", "default": "post" },
"category_slugs": { "type": "array", "items": { "type": "string" } },
"tag_slugs": { "type": "array", "items": { "type": "string" } },
"blocks_json": {
"type": "object",
"required": ["v", "blocks"],
"properties": {
"v": { "type": "integer", "const": 1 },
"blocks": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "type"],
"properties": {
"id": { "type": "string" },
"type": { "type": "string",
"enum": ["paragraph", "heading", "list", "code", "quote", "image"] },
"text": { "type": "string" },
"attrs": { "type": "object" }
}
}
}
}
}
}
}
}
Status Significado Ação do agente
401 Chave inválida/ausente Pare; mostre uma mensagem de “needs valid API key”.
403 Escopo ausente Pare; a chave precisa de posts:write.
400 / 422 Payload inválido (título vazio, blocks_json ausente, tipos errados) Corrija o corpo e tente de novo uma vez.
429 Limite de requisição atingido Aguarde Retry-After segundos e tente de novo.
404 O post/asset não está neste site (ou não existe — os dois casos são indistinguíveis) Pare; não enumere ids procurando um que funcione.
5xx Transitório Backoff exponencial (ex.: 1s, 2s, 4s), no máximo ~3 tentativas. Reutilize a mesma Idempotency-Key.

Os limites de requisição variam por rota: 60 req/min para escritas, 600 req/min para as rotas públicas de leitura, 10 req/min para leads. Um agente que lê muito e escreve pouco vai esbarrar primeiro no limite de escrita.

Veja Erros e limites de requisição para a tabela completa.