Blog Posts & Categories

Part of the Content API. Endpoints for the site’s blog: posts, categories and the blog’s landing page. Scopes: blog:read / blog:write.

Blog posts

GETPOST/api/v3/blog/posts/
GETPUTPATCHDELETE/api/v3/blog/posts/<id>/
Field Type Notes
id int Read-only.
title string ≤200 Required.
slug slug ≤200 Required, unique.
body HTML Required, sanitized; use <!--read more--> for the summary break.
summary HTML Read-only: the body up to <!--read more-->.
status draft / published Defaults to published — send draft unless you mean it (the AI assistant connector sends draft when no status is given). What you set, not whether the post is live: see state.
state string Read-only, computed from status and publish: draft, published, or scheduled (published with a future publish). This is what decides whether the post is public (below).
publish datetime Defaults to now.
created_at, updated_at datetime Read-only.
allow_comments, nofollow_comments bool Default true.
page_title, meta_description, meta_keywords string ≤400 SEO fields.
facebook_image string / null Image URL for social sharing.
categories list On write: category ids or slugs. On read: [{"id", "name", "slug"}].
path, url string Read-only.

status vs state: status is the field you write; state is read-only and says whether the post is actually live. A post is live (state: "published") only when status is "published" and its publish time has passed, in the site’s time zone. So a status: "published" post with a future publish is state: "scheduled".

Only state: "published" posts are public. Drafts and scheduled posts return 404 at their path/url to visitors and search engines, and stay out of the blog home, categories, month archives, RSS feed and sitemap. Someone logged into the site admin who can edit blog posts still sees them at that URL as a preview, marked noindex. To keep a post private, leave it a draft or give it a future publish; to publish later, PATCH status and/or publish.

List filters: status, state, slug, q (title contains), category (id or slug), ordering (publish, updated_at, created_at, title, id; default -publish), include_body=false to omit body/summary.

curl -X POST \
  https://www.example.com/api/v3/blog/posts/ \
  -H "Authorization: Bearer rg_live_..." \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Spring market update",
  "slug": "spring-market-update",
  "status": "draft",
  "categories": ["market-updates"],
  "body":
    "<p>Up.</p><!--read more--><p>More.</p>"
}'

Posts keep revisions of their tracked fields: GET /blog/posts/<id>/revisions/, with the same undo flow as pages — see Revisions and undo.

From the command line: geekcli posts list, geekcli posts create --body-file post.md, geekcli posts publish <slug>, geekcli posts revisions <slug> with geekcli (see the geekcli guide).

Blog home page

GETPATCH/api/v3/blog/home-page/

The blog’s landing page: title (≤127), meta_description, meta_keywords, content (sanitized HTML), plus read-only path/url. GET is 404 blog_home_page_missing until one exists; PATCH creates it (201) in that case and updates it otherwise.

From the command line: geekcli blog get, geekcli blog update with geekcli (see the geekcli guide).

Blog categories

GETPOST/api/v3/blog/categories/
GETPUTPATCHDELETE/api/v3/blog/categories/<id>/

Fields: id, name (required), slug (required, unique), post_count (read-only), path, url. Filters: slug, q, ordering (name, slug, id).

Deleting a category that posts use returns 409 conflict unless you pass ?force=true, which removes it from those posts.

From the command line: geekcli categories list, geekcli posts update <slug> --add-category <slug> with geekcli (see the geekcli guide).