Skip to content

Publishing posts

POST /v1/posts is the one call that creates content. With status: "published" it also publishes, so a single request takes a post from nothing to live.

  • URL: POST https://api.canverly.com/v1/posts
  • Scope: posts:write
  • Content-Type: application/json
  • Max body size: 4 MiB (larger → 413)
Field Type Required Notes
title string yes The post title.
blocks_json object yes The body as a block document: { "v": 1, "blocks": [...] }.
status string no "draft" (default) or "published". "publish"/"public"/"live" are accepted aliases for published.
slug string no URL slug. Auto-derived from title when omitted.
excerpt string no Short summary; stored as the SEO description.
language string no BCP-47 tag. Defaults to "pt-BR".
post_type string no "post" (default), "page", or a custom post-type slug from GET /v1/post-types.
category_slugs string[] no Category slugs. Drives the site’s category archives.
tag_slugs string[] no Tag slugs.
  • status: "draft" (default) — the post is created but not visible on the public site. Use this to stage content; publish it later from the admin.
  • status: "published" — the post is created, categorized, and published in the same call. It is live immediately at https://<site-domain>/<slug>.

category_slugs / tag_slugs are stored on the post and power the site’s category and tag archives. Use the slugs configured for the site (e.g. news, tutorials). Unknown slugs are stored as-is; create the matching category/tag in the admin so the archive page exists.

This publishes a fully-formed article — title, slug, excerpt, category, and a mixed block body (paragraph, heading, list, code):

Janela do terminal
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" }
]
}
}'

201 Created:

{
"id": "01K8Z9K3F7T8M2QYV5N6B4WJ8R",
"slug": "shipping-faster-with-the-canverly-api",
"status": "published",
"public_url": null
}
  • id — the post’s ULID (Crockford base32). Keep it to correlate with your source system.
  • slug — the final slug (may differ from your input if it was normalized).
  • status — "published" or "draft".

A published post is reachable at https://<site-domain>/<slug>. Resolve <site-domain> once via GET /v1/sites/me (primary_domain), then:

Janela do terminal
curl -s -o /dev/null -w "%{http_code}\n" https://blog.example.com/shipping-faster-with-the-canverly-api
# 200

To publish into a custom post type (CPT), set post_type to its slug. Discover valid slugs first with GET /v1/post-types. post and page always exist.

Autoposters retry on timeouts. To make retries safe, send an Idempotency-Key header — an opaque id derived from your source item (e.g. the source id):

Janela do terminal
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": [] } }'

The first call creates the post; any replay with the same key and body returns the original 201 (with Idempotency-Replayed: true) instead of creating a duplicate. The response is cached for 24 hours, scoped per api key.