Posts API
Posts are the product. Reading them is what your website does with a project key; writing them is a dashboard action performed by a person or by an agent token acting for one.
Last updated
Endpoints
| Method | Path | What it does | Credential |
|---|---|---|---|
| GET | /v1/blog/posts | List posts | Key or session |
| GET | /v1/blog/posts/{id} | One post by id | Key or session |
| GET | /v1/blog/posts/slug/{slug} | One post by slug | Key or session |
| POST | /v1/blog/posts | Create a post | Session or agent |
| PATCH | /v1/blog/posts/{id} | Update a post | Session or agent |
| DELETE | /v1/blog/posts/{id} | Delete a post | Session or agent |
What a post is
| Field | Type | Notes |
|---|---|---|
id | string | post_ + 32 hex |
title | string | Required, 1–300 characters, trimmed |
slug | string | Generated from the title if you do not send one; unique per project |
excerpt | string | null | Up to 1,000 characters |
body | string | Up to 1,000,000 characters |
bodyFormat | markdown | html | plain | Defaults to markdown; we store it, we do not render it |
status | draft | scheduled | published | archived | Defaults to draft |
publishedAt | ISO 8601 | null | Set automatically when you publish without one |
scheduledAt | ISO 8601 | null | Required when status is scheduled |
author | object | null | Expanded on reads, set with authorId on writes |
category | object | null | Expanded on reads, set with categoryId on writes |
tags | array | Expanded on reads; on writes, names or ids — unknown names are created |
featuredMediaId | string | null | A media id |
seo | object | title, description, canonicalUrl, ogImage, noIndex, keywords |
createdAt / updatedAt | ISO 8601 | Set by the platform |
Listing and filtering
| Parameter | Example | What it does |
|---|---|---|
limit | 50 | 1–100, default 25 |
cursor | eyJ2Ijox… | From the previous response |
status | published | Ignored for a public key, which only ever sees published |
category | logistics | By slug |
categoryId | cat_0193… | By id |
tag | cold-chain | By slug |
authorId | auth_0193… | By id |
q | congestion | Substring match on the listing; see Search for real full-text |
publishedAfter | 2026-01-01T00:00:00Z | Inclusive |
publishedBefore | 2026-12-31T23:59:59Z | Inclusive |
sort | -publishedAt | See Pagination for the closed list |
curl -G 'https://api.cmskite.com/v1/blog/posts' \
-H "authorization: Bearer $KEY" \
--data-urlencode 'category=logistics' \
--data-urlencode 'tag=cold-chain' \
--data-urlencode 'sort=-publishedAt' \
--data-urlencode 'limit=50'Creating and updating
curl -X POST https://api.cmskite.com/v1/blog/posts \
-H "authorization: Bearer $ACCESS_TOKEN" \
-H "x-tenant-id: $WORKSPACE_ID" \
-H "x-project-id: $PROJECT_ID" \
-H 'content-type: application/json' \
-H "idempotency-key: $(uuidgen)" \
-d '{
"title": "Six weeks of port congestion, in one chart",
"body": "# Rotterdam, week by week\n\nThe queue peaked on a Tuesday.",
"status": "published",
"categoryId": "cat_0193...",
"tags": ["ports", "data"],
"seo": { "description": "Six weeks of Rotterdam queue data." }
}'A PATCH takes the same fields and changes only what you send. Sending null clears a nullable field; leaving it out leaves it alone.
- Fields that decide ownership —
tenantId,projectId,id— are not part of the request surface at all. Sending one is a 400, not a silently ignored key. - Two people creating the same title at the same moment both succeed, with different slugs. Nobody gets a 500.
- Tags are created on demand. Sending a tag name nobody has used yet makes it; sending one that exists reuses it.
Publishing and scheduling
- Set
status: "published"to publish now. If you send nopublishedAt, it is set to now — a published post with no timestamp would sit at the top of a newest-first listing forever. - Set
status: "scheduled"with ascheduledAtin the future. It is refused without one. - A scheduled post becomes published on its own. Until then it is invisible to a project key, exactly like a draft.
- Set
status: "archived"to take it off the site without deleting it.
Deleting
Deletes are soft. The row is retained so support can restore it, and it stops being reachable immediately — a project key, the dashboard and a listing all stop seeing it at once.
The slug frees straight away, so delete-and-recreate gives you the same URL back rather than my-post-2 forever.