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.
Request body
Section titled “Request body”| 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.
POST /v1/posts/{reference}/unpublish
Section titled “POST /v1/posts/{reference}/unpublish”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.
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.
Examples
Section titled “Examples”Fix a typo in the title and refresh the excerpt:
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:
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:
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:
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"] }'Response
Section titled “Response”200 OK
Section titled “200 OK”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.
Errors
Section titled “Errors”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. |
See also
Section titled “See also”- Create a post —
POST /v1/posts. - Post SEO — title, description, canonical,
no_indexand friends live behind/v1/posts/{reference}/seo, not here. - Bulk import — for many posts at once.