Revisions and redirects
Inspect post revision snapshots and resolve old slugs after a post URL changes.
Revisions preserve post snapshots for audit, comparison, and restore workflows. Slug redirects map an old locale-scoped slug to the post's current slug and recommended HTTP status.
Choose a surface
The API, Node SDK, and CLI expose the same revision and redirect records. Compare all Cli Blog tools.
| Action | HTTP API | Node SDK | CLI | Description |
|---|---|---|---|---|
| List revisions | GET /v1/posts/{id}/revisions | blog.posts.revisions.list() or .paginate() | cli-blog posts revisions list | Page through stored snapshots |
| Retrieve revision | GET /v1/posts/{id}/revisions/{revisionId} | blog.posts.revisions.get() | cli-blog posts revisions get | Read one snapshot with Markdown |
| Resolve old slug | GET /v1/posts/slug-redirects/{slug} | blog.posts.slugRedirects.get() | cli-blog posts redirects get | Find the current slug and redirect status |
Revision reads require a private key with editorial post access. Public and private keys with post read access can resolve slug redirects.
Use the REST API
Call the resource from any technology that can send HTTP requests. Use a public key for allowed delivery reads and a private key for content changes.
GET https://api.cli-blog.com/v1/posts/{id}/revisionsChoose an API keyUse the CLI
Use the CLI for local work, continuous integration, and JSON automation. Configure a narrowly scoped key before running content-changing commands.
cli-blog posts revisions list post_id --jsonRead the CLI guideUse the Node SDK
Use the Node SDK in Node.js 20+ servers, scripts, continuous integration, and trusted agent environments. Its methods map to the same resource actions.
await blog.posts.revisions.list(postId)Read the Node SDK guideUse the agent skill
Give the agent the Cli Blog skill and a result-focused instruction. The skill helps it choose the API, CLI, or SDK and keeps publishing behind an approval gate.
List revisions for the selected post and summarize what changed. Do not restore a version without approval.Install the agent skillList post revisions
Cursor pagination is the preferred list mode. Set limit up to 100, then pass next_cursor as after. Use numbered pagination only when a revision browser requires exact totals. See Pagination.
curl --get \
"https://api.cli-blog.com/v1/posts/post_2f51de/revisions" \
--header "x-api-key: $CLI_BLOG_PRIVATE_API_KEY" \
--data-urlencode "locale=en-US" \
--data-urlencode "limit=20"The list returns snapshot metadata without the full Markdown body:
{
"object": "list",
"data": [{
"id": "rev_4b11ac",
"object": "post_revision",
"parent_post_id": "post_2f51de",
"title": "How we ship release notes",
"version": 1,
"created_at": "2026-07-11T17:20:00.000Z",
"updated_at": "2026-07-11T17:20:00.000Z"
}],
"has_more": false,
"next_cursor": null
}The SDK and CLI support the same cursor controls:
import { CliBlog } from "@cli-blog/node";
const blog = new CliBlog({
apiKey: process.env.CLI_BLOG_PRIVATE_API_KEY!,
apiUrl: "https://api.cli-blog.com",
});
const revisions = await blog.posts.revisions.list("post_2f51de", {
locale: "en-US",
limit: 20,
});cli-blog posts revisions list post_2f51de \
--locale en-US \
--limit 20 \
--jsonRetrieve and restore a revision
Retrieve one snapshot when the workflow needs its Markdown body:
curl --get \
"https://api.cli-blog.com/v1/posts/post_2f51de/revisions/rev_4b11ac" \
--header "x-api-key: $CLI_BLOG_PRIVATE_API_KEY" \
--data-urlencode "locale=en-US"{
"id": "rev_4b11ac",
"object": "post_revision",
"parent_post_id": "post_2f51de",
"title": "How we ship release notes",
"version": 1,
"body_markdown": "## A repeatable release workflow\n\nDraft, review, then publish.",
"created_at": "2026-07-11T17:20:00.000Z",
"updated_at": "2026-07-11T17:20:00.000Z"
}Cli Blog does not expose a separate restore endpoint. Read the snapshot, review the diff, then send the selected fields through the normal post update action.
const revision = await blog.posts.revisions.get(
"post_2f51de",
"rev_4b11ac",
{ locale: "en-US" },
);
const current = await blog.posts.get("post_2f51de", {
locale: "en-US",
fields: ["workflow"],
});
await blog.posts.update("post_2f51de", {
title: revision.title,
body_markdown: revision.body_markdown,
expected_version: current.version,
});cli-blog posts revisions get post_2f51de rev_4b11ac --locale en-US --jsonResolve an old slug
Resolve the slug before rendering a missing-post page. Send the returned HTTP status and redirect to the current slug on your own domain.
curl --get \
"https://api.cli-blog.com/v1/posts/slug-redirects/old-release-workflow" \
--header "x-api-key: $CLI_BLOG_PUBLIC_API_KEY" \
--data-urlencode "locale=en-US"{
"object": "slug_redirect",
"content_type": "blog_post",
"locale": "en-US",
"from_slug": "old-release-workflow",
"to_slug": "how-we-ship-release-notes",
"post_id": "post_2f51de",
"status_code": 301
}const redirect = await blog.posts.slugRedirects.get(
"old-release-workflow",
{ locale: "en-US" },
);cli-blog posts redirects get old-release-workflow --locale en-US --jsonRevision and redirect fields
| Resource | Fields and description |
|---|---|
| Revision summary | id, object, parent_post_id, title, version, created_at, and updated_at identify a snapshot |
| Revision detail | Adds body_markdown for comparison or a reviewed restore update |
| Slug redirect | locale, from_slug, to_slug, post_id, and status_code describe the redirect target |
Common errors
| Status | Cause | Resolution |
|---|---|---|
400 | Unsupported locale or mixed pagination modes | Select an enabled locale and use one pagination mode |
401 | Missing or invalid API key | Send the organization key in x-api-key |
403 | Public key used for revisions or key lacks post permission | Use a private key for revisions and scoped read permission |
404 | Post, revision, old slug, or localized variant does not exist | Confirm identifiers, organization, and locale |
Complete API operations
The operation reference lists every path and query parameter, revision schema, redirect schema, authentication rule, and error response. Use Try it to open the request runner.
List revisions
Lists stored revision snapshots for a localized post entry. Cursor pagination is the default; page and per_page opt into exact numbered pagination and cannot be combined with after or limit.
Authorization
ApiKeyAuth Organization API key. Each key selects exactly one organization; do not send an organization ID separately. Public keys use the cli_blog_pk_ prefix and are intended for published-content delivery reads. Private keys use the cli_blog_sk_ prefix, belong only in trusted environments, and are required for write, publish, delete, and editorial-state workflows when their scopes allow it.
In: header
Path Parameters
Post ID or locale-scoped post slug.
Query Parameters
Opaque cursor returned by the previous revision list page.
Maximum post revisions to return. Defaults to 20. The maximum accepted value is 100.
One-based exact page number. Supplying page selects numbered pagination and cannot be combined with after or limit.
Items per numbered page. Requires page, defaults to 20, and cannot be combined with after or limit.
Post locale for slug lookup. Omit it to use the organization primary/default locale.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/posts/string/revisions"{ "object": "list", "has_more": true, "next_cursor": "string", "page": 0, "per_page": 0, "total_items": 0, "total_pages": 0, "data": [ { "id": "string", "object": "post_revision", "parent_post_id": "string", "title": "string", "version": 0, "created_at": "string", "updated_at": "string" } ]}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}Retrieve a revision
Retrieves one revision snapshot for Markdown diff and restore workflows.
Authorization
ApiKeyAuth Organization API key. Each key selects exactly one organization; do not send an organization ID separately. Public keys use the cli_blog_pk_ prefix and are intended for published-content delivery reads. Private keys use the cli_blog_sk_ prefix, belong only in trusted environments, and are required for write, publish, delete, and editorial-state workflows when their scopes allow it.
In: header
Path Parameters
Post ID or locale-scoped post slug.
Stored post revision ID.
Query Parameters
Post locale for slug lookup. Omit it to use the organization primary/default locale.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/posts/string/revisions/string"{ "id": "string", "object": "post_revision", "parent_post_id": "string", "title": "string", "version": 0, "created_at": "string", "updated_at": "string", "body_markdown": "string"}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}Resolve an old slug
Resolve an old locale-scoped post slug after a slug change. Customer sites can use the returned to_slug and status_code to issue their own redirect on the public blog domain.
Authorization
ApiKeyAuth Organization API key. Each key selects exactly one organization; do not send an organization ID separately. Public keys use the cli_blog_pk_ prefix and are intended for published-content delivery reads. Private keys use the cli_blog_sk_ prefix, belong only in trusted environments, and are required for write, publish, delete, and editorial-state workflows when their scopes allow it.
In: header
Path Parameters
Previous locale-scoped post slug.
Query Parameters
Locale where the old slug was valid. Omit it to use the organization primary/default locale.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/posts/slug-redirects/string"{ "object": "slug_redirect", "content_type": "blog_post", "locale": "string", "from_slug": "string", "to_slug": "string", "post_id": "string", "status_code": 0}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}