Skip to content

PATCH /v1/posts/:id

Partial-patch a post that already exists. Only the fields you send change — anything you omit is left exactly as it is.

  • URL: https://api.canverly.com/v1/posts/{id}
  • Method: PATCH
  • Auth: Bearer API key — scope posts:write
  • Content-Type: application/json
  • Rate limit: 60 req/min per key

A key can only edit posts on its own site: an id that belongs to another site returns 404, never 403.

Field Type Notes
title string Ignored when empty or whitespace-only.
blocks_json object A full block document. It replaces the body and goes through the same normalize + allowlist-sanitize pass as create.
excerpt string Also written to the post’s SEO description.
category_slugs string[] Replaces the category list.
tag_slugs string[] Replaces the tag list.
published_at string RFC-3339. Moves or backdates a live post’s publication date, and re-pings IndexNow upstream.

A body with none of these — or one whose only field was an empty title — is a 400 no updatable fields provided, not a silent no-op round trip.

Scope: posts:write. Takes a published post off the air: it moves to archived, the unpublish event is emitted, and the cached page is purged — so the article stops being readable immediately, not when a TTL expires.

reference is the post id or its slug; add ?language= when the same slug exists in more than one language.

Janela do terminal
curl -X POST https://api.canverly.com/v1/posts/como-fazer-x/unpublish -H "Authorization: Bearer ck_live_xxx"
{ "id": "01J8ZQ7X4K2N5M9P0R3T6V8W1Y", "slug": "como-fazer-x", "status": "archived" }

A post that is not published returns 409 — a state conflict, not a server fault. That distinction is not cosmetic: it used to answer 500 internal error, and the platform redacts 5xx bodies, so the one sentence that told you what to do differently was erased on the way out.

Fix a typo in the title and refresh the excerpt:

Janela do terminal
curl -X PATCH https://api.canverly.com/v1/posts/01J8ZQ7X4K2N5M9P0R3T6V8W1Y \
-H "Authorization: Bearer ck_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"title": "How to do X, properly",
"excerpt": "A corrected one-sentence summary."
}'

Replace the body:

Janela do terminal
curl -X PATCH https://api.canverly.com/v1/posts/01J8ZQ7X4K2N5M9P0R3T6V8W1Y \
-H "Authorization: Bearer ck_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"blocks_json": {
"v": 1,
"blocks": [
{ "id": "h1", "type": "heading", "attrs": { "level": 2 }, "text": "Rewritten" },
{ "id": "p1", "type": "paragraph", "text": "New body text." }
]
}
}'

Backdate a post — the campaign case this route exists for:

Janela do terminal
curl -X PATCH https://api.canverly.com/v1/posts/01J8ZQ7X4K2N5M9P0R3T6V8W1Y \
-H "Authorization: Bearer ck_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "published_at": "2026-05-14T09:00:00Z" }'

Retag and re-categorize:

Janela do terminal
curl -X PATCH https://api.canverly.com/v1/posts/01J8ZQ7X4K2N5M9P0R3T6V8W1Y \
-H "Authorization: Bearer ck_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "category_slugs": ["news", "opinion"], "tag_slugs": ["api"] }'

The updated post record, as the content service stores it. This is the internal representation, not the trimmed public shape returned by GET /v1/posts/{reference}:

{
"id": "01J8ZQ7X4K2N5M9P0R3T6V8W1Y",
"site_id": "01KVPWYKT6AJDGG2EJN7DW4VT5",
"org_id": "01KVPWYKT6AJDGG2EJN7DW4VT4",
"type": "post",
"post_type_slug": "post",
"status": "published",
"slug": "how-to-do-x",
"title": "How to do X, properly",
"excerpt": "A corrected one-sentence summary.",
"blocks_json": { "v": 1, "blocks": [] },
"seo_json": { "description": "A corrected one-sentence summary." },
"fields": {},
"author_id": "01KVPWYKT6AJDGG2EJN7DW4VT3",
"persona_id": null,
"featured_image_id": null,
"published_at": "2026-05-14T09:00:00Z",
"canonical_url": "",
"access": "public",
"language": "pt-BR",
"is_primary_translation": true,
"created_at": "2026-05-14T09:00:00Z",
"updated_at": "2026-08-26T11:04:00Z"
}

Read id, slug, status, published_at and updated_at from it; treat the rest as informational. New fields can appear here without notice.

Envelope: { "error": { "code", "message" } }. Full table in Errors & rate limits.

Status error.code When
400 bad_request Empty patch (no updatable fields provided) or malformed JSON.
401 unauthorized Missing/invalid/revoked key.
403 forbidden Key lacks posts:write.
404 not_found No such post on this site.
429 rate_limited Over 60 req/min — honour Retry-After.
502 upstream Transient; retry with backoff.
  • Create a post — POST /v1/posts.
  • Post SEO — title, description, canonical, no_index and friends live behind /v1/posts/{reference}/seo, not here.
  • Bulk import — for many posts at once.