Publicar posts
POST /v1/posts é a única chamada que cria conteúdo. Com status: "published" ela também publica, então uma única requisição leva um post do nada até o ar.
- URL:
POST https://api.canverly.com/v1/posts - Escopo:
posts:write - Content-Type:
application/json - Tamanho máximo do corpo: 4 MiB (maior →
413)
Corpo da requisição
Seção intitulada “Corpo da requisição”| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
title |
string | sim | O título do post. |
blocks_json |
object | sim | O corpo como um documento de blocos: { "v": 1, "blocks": [...] }. |
status |
string | não | "draft" (padrão) ou "published". "publish"/"public"/"live" são aceitos como aliases de publicado. |
slug |
string | não | Slug da URL. Derivado automaticamente de title quando omitido. |
excerpt |
string | não | Resumo curto; armazenado como a descrição de SEO. |
language |
string | não | Tag BCP-47. O padrão é "pt-BR". |
post_type |
string | não | "post" (padrão), "page" ou o slug de um tipo de post personalizado vindo de GET /v1/post-types. |
category_slugs |
string[] | não | Slugs de categoria. Alimentam os arquivos de categoria do site. |
tag_slugs |
string[] | não | Slugs de tag. |
Rascunho vs publicado
Seção intitulada “Rascunho vs publicado”status: "draft"(padrão) — o post é criado, mas não fica visível no site público. Use isso para preparar conteúdo; publique depois pelo admin.status: "published"— o post é criado, categorizado e publicado na mesma chamada. Ele fica no ar imediatamente emhttps://<site-domain>/<slug>.
Categorias e tags
Seção intitulada “Categorias e tags”category_slugs / tag_slugs são armazenados no post e alimentam os arquivos de categoria e de tag do site. Use os slugs configurados para o site (ex.: news, tutorials). Slugs desconhecidos são armazenados como vieram; crie a categoria/tag correspondente no admin para que a página de arquivo exista.
Um exemplo completo
Seção intitulada “Um exemplo completo”Isto publica um artigo completo — título, slug, resumo, categoria e um corpo com blocos variados (parágrafo, título, lista, código):
curl -X POST https://api.canverly.com/v1/posts \ -H "Authorization: Bearer ck_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "title": "Shipping faster with the Canverly API", "slug": "shipping-faster-with-the-canverly-api", "status": "published", "language": "en", "excerpt": "How to automate publishing end to end.", "category_slugs": ["engineering"], "tag_slugs": ["api", "automation"], "blocks_json": { "v": 1, "blocks": [ { "id": "p1", "type": "paragraph", "text": "You can publish from any tool with one authenticated call." }, { "id": "h1", "type": "heading", "attrs": { "level": 2 }, "text": "Why blocks" }, { "id": "l1", "type": "list", "attrs": { "style": "unordered" }, "text": "Portable across themes\nSafe (sanitized)\nEasy to generate" }, { "id": "c1", "type": "code", "attrs": { "language": "bash" }, "text": "curl -X POST https://api.canverly.com/v1/posts" } ] } }'import requests
payload = { "title": "Shipping faster with the Canverly API", "slug": "shipping-faster-with-the-canverly-api", "status": "published", "language": "en", "excerpt": "How to automate publishing end to end.", "category_slugs": ["engineering"], "tag_slugs": ["api", "automation"], "blocks_json": { "v": 1, "blocks": [ {"id": "p1", "type": "paragraph", "text": "You can publish from any tool with one authenticated call."}, {"id": "h1", "type": "heading", "attrs": {"level": 2}, "text": "Why blocks"}, {"id": "l1", "type": "list", "attrs": {"style": "unordered"}, "text": "Portable across themes\nSafe (sanitized)\nEasy to generate"}, {"id": "c1", "type": "code", "attrs": {"language": "bash"}, "text": "curl -X POST https://api.canverly.com/v1/posts"}, ], },}r = requests.post( "https://api.canverly.com/v1/posts", headers={"Authorization": "Bearer ck_live_xxx"}, json=payload, timeout=20,)r.raise_for_status()post = r.json()print(post["id"], post["slug"], post["status"])const payload = { title: "Shipping faster with the Canverly API", slug: "shipping-faster-with-the-canverly-api", status: "published", language: "en", excerpt: "How to automate publishing end to end.", category_slugs: ["engineering"], tag_slugs: ["api", "automation"], blocks_json: { v: 1, blocks: [ { id: "p1", type: "paragraph", text: "You can publish from any tool with one authenticated call." }, { id: "h1", type: "heading", attrs: { level: 2 }, text: "Why blocks" }, { id: "l1", type: "list", attrs: { style: "unordered" }, text: "Portable across themes\nSafe (sanitized)\nEasy to generate" }, { id: "c1", type: "code", attrs: { language: "bash" }, text: "curl -X POST https://api.canverly.com/v1/posts" }, ], },};
const res = await fetch("https://api.canverly.com/v1/posts", { method: "POST", headers: { Authorization: "Bearer ck_live_xxx", "Content-Type": "application/json", }, body: JSON.stringify(payload),});if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);const post = await res.json();console.log(post.id, post.slug, post.status);<?php$payload = [ "title" => "Shipping faster with the Canverly API", "slug" => "shipping-faster-with-the-canverly-api", "status" => "published", "language" => "en", "excerpt" => "How to automate publishing end to end.", "category_slugs" => ["engineering"], "tag_slugs" => ["api", "automation"], "blocks_json" => [ "v" => 1, "blocks" => [ ["id" => "p1", "type" => "paragraph", "text" => "You can publish from any tool with one authenticated call."], ["id" => "h1", "type" => "heading", "attrs" => ["level" => 2], "text" => "Why blocks"], ["id" => "l1", "type" => "list", "attrs" => ["style" => "unordered"], "text" => "Portable across themes\nSafe (sanitized)\nEasy to generate"], ["id" => "c1", "type" => "code", "attrs" => ["language" => "bash"], "text" => "curl -X POST https://api.canverly.com/v1/posts"], ], ],];$ch = curl_init("https://api.canverly.com/v1/posts");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer ck_live_xxx", "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode($payload),]);$res = curl_exec($ch);$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);if ($code >= 400) { throw new Exception("HTTP $code: $res"); }echo $res, "\n";require "net/http"require "json"
payload = { title: "Shipping faster with the Canverly API", slug: "shipping-faster-with-the-canverly-api", status: "published", language: "en", excerpt: "How to automate publishing end to end.", category_slugs: ["engineering"], tag_slugs: ["api", "automation"], blocks_json: { v: 1, blocks: [ { id: "p1", type: "paragraph", text: "You can publish from any tool with one authenticated call." }, { id: "h1", type: "heading", attrs: { level: 2 }, text: "Why blocks" }, { id: "l1", type: "list", attrs: { style: "unordered" }, text: "Portable across themes\nSafe (sanitized)\nEasy to generate" }, { id: "c1", type: "code", attrs: { language: "bash" }, text: "curl -X POST https://api.canverly.com/v1/posts" }, ], },}
uri = URI("https://api.canverly.com/v1/posts")http = Net::HTTP.new(uri.host, uri.port)http.use_ssl = truereq = Net::HTTP::Post.new(uri)req["Authorization"] = "Bearer ck_live_xxx"req["Content-Type"] = "application/json"req.body = payload.to_jsonres = http.request(req)raise "HTTP #{res.code}: #{res.body}" if res.code.to_i >= 400puts res.bodypackage main
import ( "bytes" "encoding/json" "fmt" "io" "net/http")
func main() { payload := map[string]any{ "title": "Shipping faster with the Canverly API", "slug": "shipping-faster-with-the-canverly-api", "status": "published", "language": "en", "excerpt": "How to automate publishing end to end.", "category_slugs": []string{"engineering"}, "tag_slugs": []string{"api", "automation"}, "blocks_json": map[string]any{ "v": 1, "blocks": []map[string]any{ {"id": "p1", "type": "paragraph", "text": "You can publish from any tool with one authenticated call."}, {"id": "h1", "type": "heading", "attrs": map[string]any{"level": 2}, "text": "Why blocks"}, {"id": "l1", "type": "list", "attrs": map[string]any{"style": "unordered"}, "text": "Portable across themes\nSafe (sanitized)\nEasy to generate"}, {"id": "c1", "type": "code", "attrs": map[string]any{"language": "bash"}, "text": "curl -X POST https://api.canverly.com/v1/posts"}, }, }, } b, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", "https://api.canverly.com/v1/posts", bytes.NewReader(b)) req.Header.Set("Authorization", "Bearer ck_live_xxx") req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() out, _ := io.ReadAll(res.Body) if res.StatusCode >= 400 { panic(fmt.Sprintf("HTTP %d: %s", res.StatusCode, out)) } fmt.Println(string(out))}Resposta
Seção intitulada “Resposta”201 Created:
{ "id": "01K8Z9K3F7T8M2QYV5N6B4WJ8R", "slug": "shipping-faster-with-the-canverly-api", "status": "published", "public_url": null}id— o ULID do post (Crockford base32). Guarde-o para correlacionar com o seu sistema de origem.slug— o slug final (pode diferir do que você enviou se tiver sido normalizado).status—"published"ou"draft".
Verifique se está no ar
Seção intitulada “Verifique se está no ar”Um post publicado fica acessível em https://<site-domain>/<slug>. Resolva <site-domain> uma vez por GET /v1/sites/me (primary_domain) e então:
curl -s -o /dev/null -w "%{http_code}\n" https://blog.example.com/shipping-faster-with-the-canverly-api# 200Tipos de post personalizados
Seção intitulada “Tipos de post personalizados”Para publicar em um tipo de post personalizado (CPT), defina post_type com o slug dele. Descubra os slugs válidos antes com GET /v1/post-types. post e page sempre existem.
Evitar duplicatas (Idempotency-Key)
Seção intitulada “Evitar duplicatas (Idempotency-Key)”Autopostadores repetem a requisição em caso de timeout. Para tornar as repetições seguras, envie um cabeçalho Idempotency-Key — um id opaco derivado do seu item de origem (ex.: o id na origem):
curl -X POST https://api.canverly.com/v1/posts \ -H "Authorization: Bearer ck_live_xxx" \ -H "Idempotency-Key: feed-item-2026-0042" \ -H "Content-Type: application/json" \ -d '{ "title": "…", "status": "published", "blocks_json": { "v": 1, "blocks": [] } }'A primeira chamada cria o post; qualquer repetição com a mesma chave e o mesmo corpo devolve o 201 original (com Idempotency-Replayed: true) em vez de criar uma duplicata. A resposta fica em cache por 24 horas, com escopo por chave de API.