Posts
Create, deliver, relate, update, publish, schedule, translate, and delete Markdown posts.
Posts are localized Markdown entries with workflow, author, taxonomy, media, search engine optimization (SEO), and custom metadata fields. Use public keys for published delivery reads. Keep private keys on trusted servers, continuous integration (CI) jobs, command-line interfaces, and agent runtimes.
Choose a surface
Every surface uses the same /v1 resource model. Compare the API, Node SDK, CLI, and agent skill before choosing one.
| Action | HTTP API | Node SDK | CLI | Description |
|---|---|---|---|---|
| List | GET /v1/posts | blog.posts.list() or .paginate() | cli-blog posts list | Filter and page through posts |
| Create | POST /v1/posts | blog.posts.create() | cli-blog posts create | Create a draft, scheduled, or published post |
| Retrieve | GET /v1/posts/{id} | blog.posts.get() | cli-blog posts get | Get a post by ID or locale-scoped slug |
| Related | GET /v1/posts/{id}/related | blog.posts.related() | cli-blog posts related | Get ranked related posts with a recent-post fallback |
| List revisions | GET /v1/posts/{id}/revisions | blog.posts.revisions.list() | cli-blog posts revisions list | Page through stored post snapshots |
| Retrieve revision | GET /v1/posts/{id}/revisions/{revisionId} | blog.posts.revisions.get() | cli-blog posts revisions get | Read one post snapshot with Markdown |
| Resolve old slug | GET /v1/posts/slug-redirects/{slug} | blog.posts.slugRedirects.get() | cli-blog posts redirects get | Map an old localized slug to its current slug |
| Update | POST /v1/posts/{id} | blog.posts.update() | cli-blog posts update | Change content, relations, SEO, or status |
| Publish | POST /v1/posts/{id} | blog.posts.publish() | cli-blog posts publish | Set status to published |
| Schedule | POST /v1/posts/{id} | blog.posts.schedule() | cli-blog posts schedule | Set status and scheduled_at |
| Delete | DELETE /v1/posts/{id} | blog.posts.delete() | cli-blog posts delete | Delete a post by ID or slug |
Status is the canonical workflow control. Omit status when creating a draft. Set status: "published" to publish, or pair status: "scheduled" with the same request's scheduled_at. The SDK publish and schedule methods and their CLI counterparts are convenience helpers over the same update endpoint. A scheduled time at or before now is accepted and publishes on the next scheduler run.
See Revisions and redirects for snapshot retrieval, reviewed restore workflows, and old-slug resolution.
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 or POST https://api.cli-blog.com/v1/postsChoose 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 list --status draft --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.list({ status: "draft" })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 draft posts, create a complete draft from the approved notes, and wait for review before publishing.Install the agent skillList posts with cursor pagination
Cursor pagination is the default and preferred mode. Set limit up to 100, then pass next_cursor as after. Cursor queries avoid the exact count required by numbered pages. Use page and per_page only when a numbered interface needs total_items and total_pages; never mix the two modes. See Pagination.
This public-key request returns a lean published list with author and tag objects:
curl --get "https://api.cli-blog.com/v1/posts" \
--header "x-api-key: $CLI_BLOG_PUBLIC_API_KEY" \
--data-urlencode "status=published" \
--data-urlencode "locale=en-US" \
--data-urlencode "limit=20" \
--data-urlencode "fields=summary,seo" \
--data-urlencode "include=authors,tags"A cursor response identifies the next request without calculating a total count:
{
"object": "list",
"data": [{
"id": "post_2f51de",
"object": "post",
"content_type": "blog_post",
"locale": "en-US",
"title": "How we ship release notes",
"slug": "how-we-ship-release-notes",
"authors": [{ "id": "author_8af12c", "public_name": "Maya Chen" }],
"tags": [{ "id": "term_tag_3c09f1", "name": "Release notes" }]
}],
"has_more": true,
"next_cursor": "cursor_next_page"
}Filter relations and search
Post filters combine author, category, and tag dimensions with AND logic. Within one dimension, matching defaults to any; set author_match, category_match, or tag_match to all when every selected relation must match. Prefix a relation filter with exclude_ for none-of matching.
| Parameter | Description |
|---|---|
status, locale, is_featured | Limit results by workflow state, locale, or featured state |
search | Search post content; pair with sort=relevance when relevance order is required |
sort, direction | Sort by publish, create, update, or relevance order |
author_id, author_slug | Match one or more authors |
category_id, category_slug | Match one or more categories |
tag_id, tag_slug | Match one or more tags |
*_match | Select any or all within one relation dimension |
exclude_* | Exclude posts assigned to any selected relation |
Select field groups
Identity fields id, object, content_type, organization_id, and locale always return. Lists default to summary; single-post retrieval defaults to every group. Add only the groups required by the current view.
| Field group | Description |
|---|---|
summary | title, slug, excerpt, is_featured, featured and attached media IDs, publish time, and timestamps |
content | body_markdown and generated table_of_contents entries from H1/H2 headings |
seo | Search, robots, Open Graph, X/Twitter, canonical, keyword, and schema fields |
workflow | status, scheduled_at, and version |
metadata | Integration-defined metadata object |
Include related resources
Includes embed related objects in the same response. They reduce follow-up requests without making every post response large.
| Include | Description |
|---|---|
authors | Author profiles in byline order |
categories | Assigned category terms |
tags | Assigned tag terms |
media | Referenced featured, social, and attachment media assets |
translations | Linked localized post summaries |
Get related posts
Use the related-post endpoint to fill an article's “Read next” or “Related posts” section. It returns ordinary post objects in the standard cursor-list response, so fields and include work the same way they do on GET /v1/posts.
curl --get "https://api.cli-blog.com/v1/posts/how-we-ship-release-notes/related" \
--header "x-api-key: $CLI_BLOG_PUBLIC_API_KEY" \
--data-urlencode "locale=en-US" \
--data-urlencode "limit=4" \
--data-urlencode "fields=summary" \
--data-urlencode "include=authors,media"The V1 ranking works as follows:
- Published posts that share tags, categories, or authors with the source post rank first. Tag overlap has the most weight, followed by category overlap and author overlap.
- More recent publication time breaks equal scores.
- If there are not enough relation matches to satisfy
limit, the newest published posts in the same locale fill the remaining positions.
The source post and posts in its translation group are excluded. Public keys can use only a published source post. A private key can preview recommendations from a draft or scheduled source, but the returned recommendations are still published posts. There is no implicit locale fallback.
{
"object": "list",
"data": [
{
"id": "post_7f21ab",
"object": "post",
"organization_id": "org_123",
"content_type": "blog_post",
"locale": "en-US",
"title": "A better release-note workflow",
"slug": "better-release-note-workflow",
"excerpt": "Plan, review, and publish product updates.",
"published_at": "2026-07-15T16:00:00.000Z",
"authors": [{ "id": "author_8af12c", "public_name": "Maya Chen" }]
}
],
"has_more": false,
"next_cursor": null
}Every item in data is a normal post response. The API does not return internal ranking scores or label which items came from the recent fallback. Include authors, categories, or tags when the frontend needs that context. The default limit is 4, the maximum is 12, and next_cursor can be sent as after when more results are needed.
The Node SDK and CLI expose the same operation:
const related = await blog.posts.related("how-we-ship-release-notes", {
locale: "en-US",
limit: 4,
fields: ["summary"],
include: ["authors", "media"],
});cli-blog posts related how-we-ship-release-notes \
--locale en-US \
--limit 4 \
--fields summary \
--include authors,media \
--jsonCreate and publish a post
Create authors, categories, tags, and media before assigning their IDs. This API request creates a reviewable draft with Markdown content and ordered relations:
curl "https://api.cli-blog.com/v1/posts" \
--header "x-api-key: $CLI_BLOG_PRIVATE_API_KEY" \
--header "content-type: application/json" \
--data '{
"title": "How we ship release notes",
"locale": "en-US",
"body_markdown": "## A repeatable release workflow\n\nDraft, review, then publish.",
"author_profile_ids": ["author_8af12c"],
"category_ids": ["term_category_917bd2"],
"tag_ids": ["term_tag_3c09f1"]
}'The Node SDK follows the same lifecycle:
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 draft = await blog.posts.create({
title: "How we ship release notes",
body_markdown: "## A repeatable release workflow\n\nDraft, review, then publish.",
locale: "en-US",
author_profile_ids: ["author_8af12c"],
category_ids: ["term_category_917bd2"],
tag_ids: ["term_tag_3c09f1"],
});
await blog.posts.publish(draft.id, { expected_version: draft.version });The CLI can create the same draft and publish it after review:
cli-blog posts create \
--title "How we ship release notes" \
--body-markdown ./release-notes.md \
--author-ids author_8af12c \
--category-ids term_category_917bd2 \
--tag-ids term_tag_3c09f1 \
--json
cli-blog posts publish post_2f51de --expected-version 1 --jsonA write response returns the selected post fields and current version:
{
"id": "post_2f51de",
"object": "post",
"content_type": "blog_post",
"locale": "en-US",
"status": "published",
"title": "How we ship release notes",
"slug": "how-we-ship-release-notes",
"version": 2,
"published_at": "2026-07-11T17:30:00.000Z"
}Retrieve, update, schedule, and translate
Retrieve by stable ID or locale-scoped slug. Pass locale when a slug may exist in several locales. Use expected_version on trusted updates to reject writes based on an older version.
const post = await blog.posts.get("how-we-ship-release-notes", {
locale: "en-US",
fields: ["summary", "content", "workflow"],
include: ["authors", "categories", "tags", "media"],
});
await blog.posts.schedule(
post.id,
"2026-07-18T16:00:00.000Z",
{ expected_version: post.version },
);scheduled_at must travel with status: "scheduled" in the same request. If you set a due time that is already in the past, Cli Blog accepts it and publishes the post on the next scheduler run.
Create a linked translation with translation_of_id. The API inherits authors, taxonomy assignments, and shared media unless the request replaces them.
await blog.posts.create({
title: "Cómo publicamos notas de lanzamiento",
locale: "es-MX",
translation_of_id: "post_2f51de",
body_markdown: "## Un flujo repetible\n\nRedacta, revisa y publica.",
});Delete a post
Delete calls require a private key. Add a human approval step before an agent or CI workflow deletes content.
cli-blog posts delete post_2f51de --yesPost fields
Create and update bodies accept the following field families. The generated operations below mark required fields, nullability, defaults, and validation limits.
| Field family | Fields |
|---|---|
| Identity and locale | title, slug, locale, translation_of_id |
| Content and workflow | body_markdown, excerpt, status, is_featured, published_at, scheduled_at, expected_version |
| Relations and media | author_profile_ids, category_ids, tag_ids, featured_media_asset_id, media_asset_ids |
| Search and social | seo_title, seo_description, canonical_url, focus_keyphrase, seo_keywords, robots fields, Open Graph fields, X/Twitter fields, schema_type |
| Integration data | metadata |
Common errors
| Status | Cause | Resolution |
|---|---|---|
400 | Invalid locale, status, timestamp, relation, or pagination combination | Check the named param and use one pagination mode |
401 | Missing or invalid x-api-key | Send the organization key in the request header |
403 | Public key used for editorial data or a private key lacks permission | Use a scoped private key for writes and editorial reads |
404 | ID, slug, locale, relation, or post does not exist | Confirm organization, locale, and resource IDs |
409 | Slug conflict or stale expected_version | Choose a unique slug or reload before updating |
Complete API operations
The operation reference lists every path, query, body, response, and error schema. Use Try it to open the request runner.
List posts
Discover localized blog posts with efficient cursor pagination by default, or opt into exact numbered pagination with page and per_page. Numbered pagination cannot be combined with after or limit. Archive filters, featured state, full-text search, field groups, and opt-in related objects are supported. Locale requests do not fall back in v1; omitted locale uses the organization primary/default locale.
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
Query Parameters
Opaque cursor returned by a previous post list using the same filters and sort.
Maximum posts 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.
BCP 47 post locale to list, for example en-US or es-MX. Category/tag slug filters resolve in this locale. Omitted locale uses the organization's primary/default locale.
Private-key filter for editorial states. Public API keys always see published posts only.
Filter by the simple featured-post flag.
Comma-separated shared category concept IDs assigned to matching posts.
Comma-separated category slugs in the requested locale.
Whether the positive values in this relation group must match any value or all values.
Comma-separated category IDs that matching posts must not have.
Comma-separated category slugs that matching posts must not have.
Comma-separated shared tag concept IDs assigned to matching posts.
Comma-separated tag slugs in the requested locale.
Whether the positive values in this relation group must match any value or all values.
Comma-separated tag IDs that matching posts must not have.
Comma-separated tag slugs that matching posts must not have.
Postgres full-text search over post title, excerpt, and Markdown body.
Post ordering field. Public archives default to published_at; search defaults to relevance.
Sort direction. Defaults to desc.
Post field groups to return. Identity fields id, object, content_type, organization_id, and locale are always returned. Lists default to summary; post retrieval defaults to every group.
summary: title, slug, excerpt, is_featured, featured_media_asset_id, media_asset_ids, published_at, created_at, updated_atcontent: body_markdown and table_of_contents derived from H1/H2 headingsseo: seo_title, seo_description, canonical_url, focus_keyphrase, seo_keywords, robots_index, robots_follow, Open Graph fields, X/Twitter fields, schema_typeworkflow: status, scheduled_at, versionmetadata: metadata
Related objects to expand separately from fields.
authors: byline author profile objectscategories: assigned categories localized to the post localetags: assigned tags localized to the post localemedia: media assets referenced by the post, including featured and social preview assetstranslations: linked localized post availability
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/posts"{ "object": "list", "has_more": true, "next_cursor": "string", "page": 0, "per_page": 0, "total_items": 0, "total_pages": 0, "data": [ { "id": "string", "object": "post", "organization_id": "string", "content_type": "blog_post", "locale": "string", "status": "string", "title": "string", "slug": "string", "is_featured": true, "excerpt": "string", "body_markdown": "string", "table_of_contents": [ { "id": "string", "depth": 1, "text": "string" } ], "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "featured_media_asset_id": "string", "media_asset_ids": [ "string" ], "published_at": "string", "scheduled_at": "string", "version": 0, "metadata": null, "created_at": "string", "updated_at": "string", "authors": [ { "id": "string", "object": "author", "organization_id": "string", "public_name": "string", "slug": "string", "bio": "string", "avatar_media_id": "string", "avatar_url": "string", "website_url": "string", "metadata": null, "created_at": "string", "updated_at": "string" } ], "categories": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "tags": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "media": [ { "id": "string", "object": "media_asset", "organization_id": "string", "url": "string", "original_filename": "string", "alt_text": "string", "caption": "string", "mime_type": "string", "width": 0, "height": 0, "size_bytes": 0, "metadata": null, "created_at": "string", "updated_at": "string" } ], "translations": [ { "id": "string", "locale": "string", "slug": "string", "status": "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" }}Create a post
Send a JSON body to create a localized blog post entry. Pair status "scheduled" with the same request's scheduled_at; timestamps at or before now are accepted and publish on the next scheduler run.
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
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/posts" \ -H "Content-Type: application/json" \ -d '{ "title": "string" }'{ "id": "string", "object": "post", "organization_id": "string", "content_type": "blog_post", "locale": "string", "status": "string", "title": "string", "slug": "string", "is_featured": true, "excerpt": "string", "body_markdown": "string", "table_of_contents": [ { "id": "string", "depth": 1, "text": "string" } ], "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "featured_media_asset_id": "string", "media_asset_ids": [ "string" ], "published_at": "string", "scheduled_at": "string", "version": 0, "metadata": null, "created_at": "string", "updated_at": "string", "authors": [ { "id": "string", "object": "author", "organization_id": "string", "public_name": "string", "slug": "string", "bio": "string", "avatar_media_id": "string", "avatar_url": "string", "website_url": "string", "metadata": null, "created_at": "string", "updated_at": "string" } ], "categories": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "tags": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "media": [ { "id": "string", "object": "media_asset", "organization_id": "string", "url": "string", "original_filename": "string", "alt_text": "string", "caption": "string", "mime_type": "string", "width": 0, "height": 0, "size_bytes": 0, "metadata": null, "created_at": "string", "updated_at": "string" } ], "translations": [ { "id": "string", "locale": "string", "slug": "string", "status": "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" }}List related posts
Returns published posts in the source post's locale. Posts that share tags, categories, or authors rank first; the newest published posts fill any remaining positions. Tag overlap carries the most weight, followed by category and author overlap. Internal scores are not returned. The source post and its translations are excluded.
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
Source post ID or locale-scoped post slug.
Query Parameters
Opaque cursor returned by the previous related-post page.
Maximum related posts to return. Defaults to 4. The maximum accepted value is 12.
Source post locale for slug lookup. Omit it to use the organization primary/default locale. Results always use the source post's locale.
Post field groups to return. Identity fields id, object, content_type, organization_id, and locale are always returned. Lists default to summary; post retrieval defaults to every group.
summary: title, slug, excerpt, is_featured, featured_media_asset_id, media_asset_ids, published_at, created_at, updated_atcontent: body_markdown and table_of_contents derived from H1/H2 headingsseo: seo_title, seo_description, canonical_url, focus_keyphrase, seo_keywords, robots_index, robots_follow, Open Graph fields, X/Twitter fields, schema_typeworkflow: status, scheduled_at, versionmetadata: metadata
Related objects to expand separately from fields.
authors: byline author profile objectscategories: assigned categories localized to the post localetags: assigned tags localized to the post localemedia: media assets referenced by the post, including featured and social preview assetstranslations: linked localized post availability
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/related"{ "object": "list", "has_more": true, "next_cursor": "string", "page": 0, "per_page": 0, "total_items": 0, "total_pages": 0, "data": [ { "id": "string", "object": "post", "organization_id": "string", "content_type": "blog_post", "locale": "string", "status": "string", "title": "string", "slug": "string", "is_featured": true, "excerpt": "string", "body_markdown": "string", "table_of_contents": [ { "id": "string", "depth": 1, "text": "string" } ], "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "featured_media_asset_id": "string", "media_asset_ids": [ "string" ], "published_at": "string", "scheduled_at": "string", "version": 0, "metadata": null, "created_at": "string", "updated_at": "string", "authors": [ { "id": "string", "object": "author", "organization_id": "string", "public_name": "string", "slug": "string", "bio": "string", "avatar_media_id": "string", "avatar_url": "string", "website_url": "string", "metadata": null, "created_at": "string", "updated_at": "string" } ], "categories": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "tags": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "media": [ { "id": "string", "object": "media_asset", "organization_id": "string", "url": "string", "original_filename": "string", "alt_text": "string", "caption": "string", "mime_type": "string", "width": 0, "height": 0, "size_bytes": 0, "metadata": null, "created_at": "string", "updated_at": "string" } ], "translations": [ { "id": "string", "locale": "string", "slug": "string", "status": "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" }}List post 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 post 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 post 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" }}Retrieve a post
Retrieve a localized post by ID or locale-scoped slug. Use fields for field groups and include for related authors, categories, tags, or media.
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
Post locale for slug lookup. Omitted locale uses the organization's primary/default locale; v1 has no fallback.
Post field groups to return. Identity fields id, object, content_type, organization_id, and locale are always returned. Lists default to summary; post retrieval defaults to every group.
summary: title, slug, excerpt, is_featured, featured_media_asset_id, media_asset_ids, published_at, created_at, updated_atcontent: body_markdown and table_of_contents derived from H1/H2 headingsseo: seo_title, seo_description, canonical_url, focus_keyphrase, seo_keywords, robots_index, robots_follow, Open Graph fields, X/Twitter fields, schema_typeworkflow: status, scheduled_at, versionmetadata: metadata
Related objects to expand separately from fields.
authors: byline author profile objectscategories: assigned categories localized to the post localetags: assigned tags localized to the post localemedia: media assets referenced by the post, including featured and social preview assetstranslations: linked localized post availability
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/v1/posts/string"{ "id": "string", "object": "post", "organization_id": "string", "content_type": "blog_post", "locale": "string", "status": "string", "title": "string", "slug": "string", "is_featured": true, "excerpt": "string", "body_markdown": "string", "table_of_contents": [ { "id": "string", "depth": 1, "text": "string" } ], "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "featured_media_asset_id": "string", "media_asset_ids": [ "string" ], "published_at": "string", "scheduled_at": "string", "version": 0, "metadata": null, "created_at": "string", "updated_at": "string", "authors": [ { "id": "string", "object": "author", "organization_id": "string", "public_name": "string", "slug": "string", "bio": "string", "avatar_media_id": "string", "avatar_url": "string", "website_url": "string", "metadata": null, "created_at": "string", "updated_at": "string" } ], "categories": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "tags": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "media": [ { "id": "string", "object": "media_asset", "organization_id": "string", "url": "string", "original_filename": "string", "alt_text": "string", "caption": "string", "mime_type": "string", "width": 0, "height": 0, "size_bytes": 0, "metadata": null, "created_at": "string", "updated_at": "string" } ], "translations": [ { "id": "string", "locale": "string", "slug": "string", "status": "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" }}Update a post
Send a JSON body to update a post by ID or locale-scoped slug. Pair status "scheduled" with the same request's scheduled_at; timestamps at or before now are accepted and publish on the next scheduler run. Use expected_version when you need optimistic conflict protection.
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
Query Parameters
Post locale for slug lookup. Omit it to use the organization primary/default locale.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/posts/string" \ -H "Content-Type: application/json" \ -d '{}'{ "id": "string", "object": "post", "organization_id": "string", "content_type": "blog_post", "locale": "string", "status": "string", "title": "string", "slug": "string", "is_featured": true, "excerpt": "string", "body_markdown": "string", "table_of_contents": [ { "id": "string", "depth": 1, "text": "string" } ], "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "featured_media_asset_id": "string", "media_asset_ids": [ "string" ], "published_at": "string", "scheduled_at": "string", "version": 0, "metadata": null, "created_at": "string", "updated_at": "string", "authors": [ { "id": "string", "object": "author", "organization_id": "string", "public_name": "string", "slug": "string", "bio": "string", "avatar_media_id": "string", "avatar_url": "string", "website_url": "string", "metadata": null, "created_at": "string", "updated_at": "string" } ], "categories": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "tags": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "media": [ { "id": "string", "object": "media_asset", "organization_id": "string", "url": "string", "original_filename": "string", "alt_text": "string", "caption": "string", "mime_type": "string", "width": 0, "height": 0, "size_bytes": 0, "metadata": null, "created_at": "string", "updated_at": "string" } ], "translations": [ { "id": "string", "locale": "string", "slug": "string", "status": "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" }}{ "error": { "code": "invalid_request", "message": "string", "param": "string" }}Delete a post
Delete a post by ID or locale-scoped slug.
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
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 DELETE "https://example.com/v1/posts/string"{ "id": "string", "object": "post", "organization_id": "string", "content_type": "blog_post", "locale": "string", "status": "string", "title": "string", "slug": "string", "is_featured": true, "excerpt": "string", "body_markdown": "string", "table_of_contents": [ { "id": "string", "depth": 1, "text": "string" } ], "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "featured_media_asset_id": "string", "media_asset_ids": [ "string" ], "published_at": "string", "scheduled_at": "string", "version": 0, "metadata": null, "created_at": "string", "updated_at": "string", "authors": [ { "id": "string", "object": "author", "organization_id": "string", "public_name": "string", "slug": "string", "bio": "string", "avatar_media_id": "string", "avatar_url": "string", "website_url": "string", "metadata": null, "created_at": "string", "updated_at": "string" } ], "categories": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "tags": [ { "id": "string", "object": "taxonomy_term", "organization_id": "string", "taxonomy_type": "string", "parent_taxonomy_term_id": "string", "locale": "string", "name": "string", "slug": "string", "description": "string", "seo_title": "string", "seo_description": "string", "canonical_url": "string", "focus_keyphrase": "string", "seo_keywords": [ "string" ], "robots_index": true, "robots_follow": true, "open_graph_title": "string", "open_graph_description": "string", "open_graph_media_asset_id": "string", "twitter_title": "string", "twitter_description": "string", "twitter_media_asset_id": "string", "schema_type": "string", "metadata": null, "created_at": "string", "updated_at": "string", "translations": [ { "id": "string", "locale": "string", "name": "string", "slug": "string" } ] } ], "media": [ { "id": "string", "object": "media_asset", "organization_id": "string", "url": "string", "original_filename": "string", "alt_text": "string", "caption": "string", "mime_type": "string", "width": 0, "height": 0, "size_bytes": 0, "metadata": null, "created_at": "string", "updated_at": "string" } ], "translations": [ { "id": "string", "locale": "string", "slug": "string", "status": "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" }}