Skip to main content

Post mutation endpoints

Five endpoints for modifying posts you've already published. None of them change a post's URL — only its content, publication state, visibility, or tags.

All four accept either API-key or session auth and all fall under the general 120 req/min rate bucket.

tip

Updates to existing posts don't consume a document slot, so these endpoints all work on Free even when the workspace is at 10/10. Only POST /api/v1/publish enforces the cap.

:postId can be either the post UUID or the post slug. Slug lookups support ?workspaceId=ws_123 for disambiguation; the CLI sends the active workspace id by default on commands that work with slugs.

POST /api/v1/posts/:postId/update

Update the current content of a published post.

Request body

{
"markdown": "# Revised title\n\nUpdated body.",
"title": "Revised title",
"visibility": "workspace",
"workspaceId": "ws_123",
"tags": ["api", "v2"],
"sourcePath": "revised-doc.md",
"confirmPublic": false
}

Field semantics are identical to POST /api/v1/publish, except update does not apply publish defaults to omitted metadata. markdown and workspaceId are required. Omit visibility to keep the current visibility, and omit tags to keep the current tags. Passing tags: [] intentionally clears all tags. confirmPublic: true is still required if the new visibility is public.

Success response

{
"postId": "post_123",
"versionId": "ver_456",
"versionNumber": 2,
"slug": "checkout-api-reference",
"title": "Revised title",
"visibility": "workspace",
"tags": ["api", "v2"]
}

Every successful update bumps versionNumber. The slug — and therefore the URL — does not change.

Errors

StatuserrorWhen
400invalid_requestBody failed validation (empty title+markdown, bad visibility, etc.)
400update_failedBusiness-rule failure from the service layer
404not_foundPost ID doesn't exist or isn't owned by the caller

POST /api/v1/posts/:postId/unpublish

Unpublish a post. The underlying markdown and version history are kept, but the post is no longer reachable at its public URL, and its slot is freed on Free plans.

Success response

{
"postId": "post_123",
"unpublished": true
}

Errors

StatuserrorWhen
400unpublish_failedPost was already unpublished, or couldn't be unpublished
404not_foundPost ID doesn't exist or isn't owned by the caller

Re-publishing an unpublished post goes through a fresh POST /api/v1/publish, which means it goes through the document-limit check again if the workspace is on Free. update only works on currently published posts.

POST /api/v1/posts/:postId/visibility

Change post visibility without uploading new Markdown.

Request body

{
"visibility": "public",
"confirmPublic": true
}

visibility must be one of private, workspace, or public. confirmPublic: true is required when switching to public.

Success response

{
"postId": "post_123",
"visibility": "public"
}

Errors

StatuserrorWhen
400invalid_visibilityvisibility wasn't one of the four allowed values
400visibility_change_failedMost commonly: moving to public without confirmPublic: true
404not_foundPost ID doesn't exist or isn't owned by the caller

PATCH /api/v1/posts/:postId/tags

Patch tags incrementally without resending the full list.

Request body

{
"add": ["reference"],
"remove": ["draft"]
}

At least one of add or remove must be non-empty. Both accept arrays of strings; tags are normalized to lowercase and capped at 20 total per post.

Success response

{
"postId": "post_123",
"tags": ["api", "reference"]
}

The response is the final tag list after the patch was applied.

Errors

StatuserrorWhen
400invalid_requestBoth add and remove empty, or bad types
400tag_update_failedService-layer failure (e.g., exceeding the 20-tag cap)
404not_foundPost ID doesn't exist or isn't owned by the caller

DELETE /api/v1/posts/:postId

Permanently delete a post and all versions/tags. This is not reversible. It also frees the post slug and, if the post was published, frees a Free-plan document slot.

Success response

{
"postId": "post_123",
"deleted": true
}

Errors

StatuserrorWhen
400delete_failedTransactional delete failed
404not_foundPost ID doesn't exist or isn't owned by the caller