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.
O contrato, em resumo
Seção intitulada “O contrato, em resumo”- 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_idem lugar nenhum; você não consegue apontar para outro site.
Toda a superfície que um agente pode operar
Seção intitulada “Toda a superfície que um agente pode operar”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.
System prompt pronto para uso
Seção intitulada “System prompt pronto para uso”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/postsHEADERS: 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.Procedimento determinístico de publicação
Seção intitulada “Procedimento determinístico de publicação”- (opcional) Resolva o site —
GET /v1/sites/me→ guarde em cacheprimary_domainedefault_language. - (opcional) Verifique os tipos de post —
GET /v1/post-types→ confirme que o slug do seupost_typeexiste (padrãopost). - (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. - Publique —
POST /v1/postscom o corpo acima. Espere201. - (opcional) Defina o SEO —
PATCH /v1/posts/{id}/seocom o objeto de SEO completo. - Verifique —
GET https://<primary_domain>/<slug>retorna200, ou leia o post de novo porGET /v1/posts/{id}.
Requisição mínima válida
Seção intitulada “Requisição mínima válida”{ "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" } } } } } } } }}Modos de falha que um agente precisa tratar
Seção intitulada “Modos de falha que um agente precisa tratar”| 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.