API 参考
Posts
Management API: GET /sites/{id}/posts, POST /sites/{id}/posts, GET /sites/{id}/posts/{postId}, …
List blog posts (drafts and published) with status, taxonomy and revision timestamps; contentMarkdown is not included, use posts.get. Filter by status, category, tag, search or pendingReview; sort by edited/published/views. Errors: FORBIDDEN, INVALID_CURSOR.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
status"DRAFT" | "PUBLISHED" · querysearchstring · queryCase-insensitive match on title or slug.
categoryIdstring · querytagIdstring · querypendingReviewboolean · queryTrue returns only posts submitted for review (posts.request_review) and not yet published or rejected.
sort"edited_desc" | "edited_asc" | "published_desc" | "published_asc" | "views_desc" | "views_asc" · queryDefaults to edited_desc.
limitinteger · queryMaximum number of items to return, 1–100. Defaults to 20.
cursorstring · queryOpaque cursor from the previous page's nextCursor. Omit for the first page.
Response 200
dataobject[]requireddata[].idstringrequireddata[].slugstringrequireddata[].titlestringrequireddata[].excerptstring | nullrequireddata[].status"DRAFT" | "PUBLISHED"requireddata[].publishedAtstring | nullrequireddata[].updatedAtstringrequireddata[].contentUpdatedAtstring | nullrequireddata[].readingTimeMinnumber | nullrequireddata[].hasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
data[].primaryCategoryobject | nullrequireddata[].primaryCategory.idstringrequireddata[].primaryCategory.slugstringrequireddata[].primaryCategory.namestringrequireddata[].categoriesobject[]requireddata[].categories[].idstringrequireddata[].categories[].slugstringrequireddata[].categories[].namestringrequireddata[].tagsobject[]requireddata[].tags[].idstringrequireddata[].tags[].slugstringrequireddata[].tags[].namestringrequireddata[].authorsobject[]requireddata[].authors[].idstringrequireddata[].authors[].slugstringrequireddata[].authors[].namestringrequireddata[].coverImagestring | nullrequiredogImage, else auto-generated OG image, else first image in content.
data[].pageviewsintegerdata[].reviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
data[].reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
data[].reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
hasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
Errors
FORBIDDEN403INVALID_CURSOR400
MCP tool posts_list
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/posts?status=DRAFT" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "DRAFT",
"publishedAt": null,
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": true,
"reviewRequestedAt": null,
"reviewRequestedByUserId": null,
"reviewNote": null,
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"coverImage": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"pageviews": 0
}
],
"hasMore": false,
"nextCursor": null
}Create a blog post as a DRAFT. Drafts are not visible on the public site until posts.publish. Taxonomy ids (categoryIds, tagIds, authorIds) must belong to this website; use blog_categories.list / blog_tags.list first. Errors: FORBIDDEN, VALIDATION, SLUG_TAKEN, ASSET_NOT_FOUND (details.assetIds: not in this site's media library; the share image also must not be in the trash), ASSET_NOT_ALLOWED (product files, or a non-image as the share image).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
titlestringrequiredslugstringURL slug without leading slash; generated from title when omitted and de-duplicated per website.
excerptstringcontentMarkdownstringMarkdown body. Long-form Markdown. You may use headings (## and ###; a single # is lowered to ##), paragraphs, lists, quotes, tables, images, code, bold, italic, underline ++text++, strikethrough, links, and ::: blocks (faq, cta, toggle, gallery, and similar). No HTML. Reference media-library images as .
authorIdsstring[]Ids from this website's blog authors.
primaryCategoryIdstring | nullMust also be in categoryIds.
categoryIdsstring[]Ids from blog_categories.list.
tagIdsstring[]Ids from blog_tags.list.
showTocbooleanseoTitlestringseoDescriptionstringseoKeywordsstring[]ogImageAltstringogImageFramingobject | nullogImageFraming.focusobjectogImageFraming.focus.xnumberrequiredogImageFraming.focus.ynumberrequiredogImageFraming.screensobjectogImageFraming.screens.desktopobjectogImageFraming.screens.tabletobjectogImageFraming.screens.mobileobjectogImageFraming.fitMode"contain" | "cover"ogImageFraming.padColorstringogTitlestringogDescriptionstringcanonicalUrlstringrobotsIndex"index" | "noindex"robotsFollow"follow" | "nofollow"twitterCard"summary" | "summary_large_image"autoOgEnabledbooleanexcludeFromSiteSearchbooleanExclude this post from visitor site search (/api/v1/search and the /search page); does not affect search engine indexing (robotsIndex).
ogImageAssetIdstring | nullShare image: assetId of an image in the media library. null removes it; without a share image one is generated automatically.
Response 201
idstringrequiredslugstringrequiredtitlestringrequiredexcerptstring | nullrequiredstatus"DRAFT" | "PUBLISHED"requiredpublishedAtstring | nullrequiredupdatedAtstringrequiredcontentUpdatedAtstring | nullrequiredreadingTimeMinnumber | nullrequiredhasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
primaryCategoryobject | nullrequiredprimaryCategory.idstringrequiredprimaryCategory.slugstringrequiredprimaryCategory.namestringrequiredcategoriesobject[]requiredcategories[].idstringrequiredcategories[].slugstringrequiredcategories[].namestringrequiredtagsobject[]requiredtags[].idstringrequiredtags[].slugstringrequiredtags[].namestringrequiredauthorsobject[]requiredauthors[].idstringrequiredauthors[].slugstringrequiredauthors[].namestringrequiredreviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
contentMarkdownstringrequiredsegmentsobject[]requiredStructured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.
tocobject[]requiredTable of contents headings (H2/H3) with anchor ids.
toc[].idstringrequiredtoc[].labelstringrequiredtoc[].level2 | 3requiredshowTocbooleanrequiredseoobjectrequiredseo.seoTitlestring | nullrequiredseo.seoDescriptionstring | nullrequiredseo.seoKeywordsstring[] | nullrequiredseo.ogImagestring | nullrequiredShare image display URL (changes with versions; do not store it).
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.ogImageAltstring | nullrequiredseo.ogImageFramingobject | nullseo.ogImageFraming.focusobjectseo.ogImageFraming.screensobjectseo.ogImageFraming.fitMode"contain" | "cover"seo.ogImageFraming.padColorstringseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.canonicalUrlstring | nullrequiredseo.robotsIndexstringrequiredseo.robotsFollowstringrequiredseo.twitterCardstringrequiredseo.autoOgEnabledbooleanrequiredseo.autoOgImagestring | nullrequiredseo.excludeFromSiteSearchbooleanrequiredWhen true, this post is excluded from visitor site search (independent of robotsIndex).
assetsobjectrequiredThe media-library files contentMarkdown and the share image use, by assetId.
createdAtstringrequiredformatNotesstring[]Present on create/update when contentMarkdown was reformatted to the article profile's supported syntax (e.g. a level-1 heading became level 2).
Errors
FORBIDDEN403VALIDATION400SLUG_TAKEN409ASSET_NOT_FOUND404 · details.assetIds: not in this site's media library; the share image also must not be in the trashASSET_NOT_ALLOWED422 · product files, or a non-image as the share image
MCP tool posts_create
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/posts" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"How we feed our sourdough starter","contentMarkdown":"## 5 a.m. in the bakehouse\n\nDiscard half the starter, then fold in fresh flour and warm water...","categoryIds":["cat_example01"],"tagIds":["tag_example01"]}'{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "DRAFT",
"publishedAt": null,
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": true,
"reviewRequestedAt": null,
"reviewRequestedByUserId": null,
"reviewNote": null,
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"contentMarkdown": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water...",
"segments": [
{
"type": "markdown",
"value": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water..."
}
],
"toc": [
{
"id": "5-am-in-the-bakehouse",
"label": "5 a.m. in the bakehouse",
"level": 2
}
],
"showToc": true,
"seo": {
"seoTitle": null,
"seoDescription": null,
"seoKeywords": null,
"ogImage": null,
"ogImageAssetId": null,
"ogImageAlt": null,
"ogImageFraming": null,
"ogTitle": null,
"ogDescription": null,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow",
"twitterCard": "summary_large_image",
"autoOgEnabled": true,
"autoOgImage": null,
"excludeFromSiteSearch": false
},
"assets": {
"asset_example01": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
},
"createdAt": "2026-10-01T02:30:00.000Z"
}Get one blog post including the working-copy contentMarkdown (what the editor shows, not necessarily the published copy) and SEO fields. Errors: FORBIDDEN, POST_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
postIdstring · pathrequiredBlog post id. Get it from posts.list.
Response 200
idstringrequiredslugstringrequiredtitlestringrequiredexcerptstring | nullrequiredstatus"DRAFT" | "PUBLISHED"requiredpublishedAtstring | nullrequiredupdatedAtstringrequiredcontentUpdatedAtstring | nullrequiredreadingTimeMinnumber | nullrequiredhasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
primaryCategoryobject | nullrequiredprimaryCategory.idstringrequiredprimaryCategory.slugstringrequiredprimaryCategory.namestringrequiredcategoriesobject[]requiredcategories[].idstringrequiredcategories[].slugstringrequiredcategories[].namestringrequiredtagsobject[]requiredtags[].idstringrequiredtags[].slugstringrequiredtags[].namestringrequiredauthorsobject[]requiredauthors[].idstringrequiredauthors[].slugstringrequiredauthors[].namestringrequiredreviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
contentMarkdownstringrequiredsegmentsobject[]requiredStructured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.
tocobject[]requiredTable of contents headings (H2/H3) with anchor ids.
toc[].idstringrequiredtoc[].labelstringrequiredtoc[].level2 | 3requiredshowTocbooleanrequiredseoobjectrequiredseo.seoTitlestring | nullrequiredseo.seoDescriptionstring | nullrequiredseo.seoKeywordsstring[] | nullrequiredseo.ogImagestring | nullrequiredShare image display URL (changes with versions; do not store it).
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.ogImageAltstring | nullrequiredseo.ogImageFramingobject | nullseo.ogImageFraming.focusobjectseo.ogImageFraming.screensobjectseo.ogImageFraming.fitMode"contain" | "cover"seo.ogImageFraming.padColorstringseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.canonicalUrlstring | nullrequiredseo.robotsIndexstringrequiredseo.robotsFollowstringrequiredseo.twitterCardstringrequiredseo.autoOgEnabledbooleanrequiredseo.autoOgImagestring | nullrequiredseo.excludeFromSiteSearchbooleanrequiredWhen true, this post is excluded from visitor site search (independent of robotsIndex).
assetsobjectrequiredThe media-library files contentMarkdown and the share image use, by assetId.
createdAtstringrequiredformatNotesstring[]Present on create/update when contentMarkdown was reformatted to the article profile's supported syntax (e.g. a level-1 heading became level 2).
Errors
FORBIDDEN403POST_NOT_FOUND404
MCP tool posts_get
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/posts/{postId}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "DRAFT",
"publishedAt": null,
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": true,
"reviewRequestedAt": null,
"reviewRequestedByUserId": null,
"reviewNote": null,
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"contentMarkdown": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water...",
"segments": [
{
"type": "markdown",
"value": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water..."
}
],
"toc": [
{
"id": "5-am-in-the-bakehouse",
"label": "5 a.m. in the bakehouse",
"level": 2
}
],
"showToc": true,
"seo": {
"seoTitle": null,
"seoDescription": null,
"seoKeywords": null,
"ogImage": null,
"ogImageAssetId": null,
"ogImageAlt": null,
"ogImageFraming": null,
"ogTitle": null,
"ogDescription": null,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow",
"twitterCard": "summary_large_image",
"autoOgEnabled": true,
"autoOgImage": null,
"excludeFromSiteSearch": false
},
"assets": {
"asset_example01": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
},
"createdAt": "2026-10-01T02:30:00.000Z"
}Partially update a blog post's working copy (only provided fields change; categoryIds/tagIds/authorIds replace the whole list when provided). Editing a PUBLISHED post does not change the public copy until posts.publish is called again (hasDraftChanges becomes true). Pass ifUnmodifiedSince (the updatedAt from posts.get/posts.list) to avoid silently overwriting a change made by someone else (or another agent run) since you last read it. Errors: FORBIDDEN, POST_NOT_FOUND, CONFLICT (re-read with posts.get and retry), VALIDATION, SLUG_TAKEN, ASSET_NOT_FOUND (details.assetIds: not in this site's media library; the share image also must not be in the trash), ASSET_NOT_ALLOWED (product files, or a non-image as the share image).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
postIdstring · pathrequiredBlog post id. Get it from posts.list.
Request body
titlestringslugstringURL slug without leading slash; generated from title when omitted and de-duplicated per website.
excerptstringcontentMarkdownstringMarkdown body. Long-form Markdown. You may use headings (## and ###; a single # is lowered to ##), paragraphs, lists, quotes, tables, images, code, bold, italic, underline ++text++, strikethrough, links, and ::: blocks (faq, cta, toggle, gallery, and similar). No HTML. Reference media-library images as .
authorIdsstring[]Ids from this website's blog authors.
primaryCategoryIdstring | nullMust also be in categoryIds.
categoryIdsstring[]Ids from blog_categories.list.
tagIdsstring[]Ids from blog_tags.list.
showTocbooleanseoTitlestringseoDescriptionstringseoKeywordsstring[]ogImageAltstringogImageFramingobject | nullogImageFraming.focusobjectogImageFraming.focus.xnumberrequiredogImageFraming.focus.ynumberrequiredogImageFraming.screensobjectogImageFraming.screens.desktopobjectogImageFraming.screens.tabletobjectogImageFraming.screens.mobileobjectogImageFraming.fitMode"contain" | "cover"ogImageFraming.padColorstringogTitlestringogDescriptionstringcanonicalUrlstringrobotsIndex"index" | "noindex"robotsFollow"follow" | "nofollow"twitterCard"summary" | "summary_large_image"autoOgEnabledbooleanexcludeFromSiteSearchbooleanExclude this post from visitor site search (/api/v1/search and the /search page); does not affect search engine indexing (robotsIndex).
ogImageAssetIdstring | nullShare image: assetId of an image in the media library. null removes it; without a share image one is generated automatically.
ifUnmodifiedSincestringOptimistic lock: the post's updatedAt you last read. If it no longer matches, the update is rejected with CONFLICT instead of overwriting.
Response 200
idstringrequiredslugstringrequiredtitlestringrequiredexcerptstring | nullrequiredstatus"DRAFT" | "PUBLISHED"requiredpublishedAtstring | nullrequiredupdatedAtstringrequiredcontentUpdatedAtstring | nullrequiredreadingTimeMinnumber | nullrequiredhasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
primaryCategoryobject | nullrequiredprimaryCategory.idstringrequiredprimaryCategory.slugstringrequiredprimaryCategory.namestringrequiredcategoriesobject[]requiredcategories[].idstringrequiredcategories[].slugstringrequiredcategories[].namestringrequiredtagsobject[]requiredtags[].idstringrequiredtags[].slugstringrequiredtags[].namestringrequiredauthorsobject[]requiredauthors[].idstringrequiredauthors[].slugstringrequiredauthors[].namestringrequiredreviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
contentMarkdownstringrequiredsegmentsobject[]requiredStructured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.
tocobject[]requiredTable of contents headings (H2/H3) with anchor ids.
toc[].idstringrequiredtoc[].labelstringrequiredtoc[].level2 | 3requiredshowTocbooleanrequiredseoobjectrequiredseo.seoTitlestring | nullrequiredseo.seoDescriptionstring | nullrequiredseo.seoKeywordsstring[] | nullrequiredseo.ogImagestring | nullrequiredShare image display URL (changes with versions; do not store it).
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.ogImageAltstring | nullrequiredseo.ogImageFramingobject | nullseo.ogImageFraming.focusobjectseo.ogImageFraming.screensobjectseo.ogImageFraming.fitMode"contain" | "cover"seo.ogImageFraming.padColorstringseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.canonicalUrlstring | nullrequiredseo.robotsIndexstringrequiredseo.robotsFollowstringrequiredseo.twitterCardstringrequiredseo.autoOgEnabledbooleanrequiredseo.autoOgImagestring | nullrequiredseo.excludeFromSiteSearchbooleanrequiredWhen true, this post is excluded from visitor site search (independent of robotsIndex).
assetsobjectrequiredThe media-library files contentMarkdown and the share image use, by assetId.
createdAtstringrequiredformatNotesstring[]Present on create/update when contentMarkdown was reformatted to the article profile's supported syntax (e.g. a level-1 heading became level 2).
Errors
FORBIDDEN403POST_NOT_FOUND404CONFLICT409 · re-read with posts.get and retryVALIDATION400SLUG_TAKEN409ASSET_NOT_FOUND404 · details.assetIds: not in this site's media library; the share image also must not be in the trashASSET_NOT_ALLOWED422 · product files, or a non-image as the share image
MCP tool posts_update
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/posts/{postId}" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"excerpt":"The first thing we do at 5 a.m. every morning."}'{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "DRAFT",
"publishedAt": null,
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": true,
"reviewRequestedAt": null,
"reviewRequestedByUserId": null,
"reviewNote": null,
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"contentMarkdown": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water...",
"segments": [
{
"type": "markdown",
"value": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water..."
}
],
"toc": [
{
"id": "5-am-in-the-bakehouse",
"label": "5 a.m. in the bakehouse",
"level": 2
}
],
"showToc": true,
"seo": {
"seoTitle": null,
"seoDescription": null,
"seoKeywords": null,
"ogImage": null,
"ogImageAssetId": null,
"ogImageAlt": null,
"ogImageFraming": null,
"ogTitle": null,
"ogDescription": null,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow",
"twitterCard": "summary_large_image",
"autoOgEnabled": true,
"autoOgImage": null,
"excludeFromSiteSearch": false
},
"assets": {
"asset_example01": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
},
"createdAt": "2026-10-01T02:30:00.000Z"
}Publish a blog post (or re-publish a published post's pending edits). Snapshots the working copy to the public site immediately; the website itself must already be published (site.publish) or use frontendMode HEADLESS (a custom frontend, where publishing the hosted site is not needed). Pass scheduledAt (a future ISO timestamp) to schedule it instead: freezes title, excerpt, contentMarkdown and showToc as a revision and publishes exactly that snapshot when the time comes; categories, tags, authors, slug and SEO/OG fields are NOT frozen and use whatever they are set to at publish time, so further edits to those fields before the schedule fires do still take effect. Cancel with posts.unschedule. Publishing immediately (no scheduledAt) cancels any pending schedule, so it will not publish again later. Requires blog:publish (separate from blog:manage). Errors: FORBIDDEN, POST_NOT_FOUND, SITE_NOT_PUBLISHED, SLUG_TAKEN, VALIDATION (scheduledAt must be a future timestamp, or the post is already scheduled: call posts.unschedule first to change the time).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
postIdstring · pathrequiredBlog post id. Get it from posts.list.
Request body
scheduledAtstringFuture ISO timestamp. When set, schedules instead of publishing immediately.
Response 200
idstringrequiredslugstringrequiredtitlestringrequiredexcerptstring | nullrequiredstatus"DRAFT" | "PUBLISHED"requiredpublishedAtstring | nullrequiredupdatedAtstringrequiredcontentUpdatedAtstring | nullrequiredreadingTimeMinnumber | nullrequiredhasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
primaryCategoryobject | nullrequiredprimaryCategory.idstringrequiredprimaryCategory.slugstringrequiredprimaryCategory.namestringrequiredcategoriesobject[]requiredcategories[].idstringrequiredcategories[].slugstringrequiredcategories[].namestringrequiredtagsobject[]requiredtags[].idstringrequiredtags[].slugstringrequiredtags[].namestringrequiredauthorsobject[]requiredauthors[].idstringrequiredauthors[].slugstringrequiredauthors[].namestringrequiredreviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
contentMarkdownstringrequiredsegmentsobject[]requiredStructured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.
tocobject[]requiredTable of contents headings (H2/H3) with anchor ids.
toc[].idstringrequiredtoc[].labelstringrequiredtoc[].level2 | 3requiredshowTocbooleanrequiredseoobjectrequiredseo.seoTitlestring | nullrequiredseo.seoDescriptionstring | nullrequiredseo.seoKeywordsstring[] | nullrequiredseo.ogImagestring | nullrequiredShare image display URL (changes with versions; do not store it).
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.ogImageAltstring | nullrequiredseo.ogImageFramingobject | nullseo.ogImageFraming.focusobjectseo.ogImageFraming.screensobjectseo.ogImageFraming.fitMode"contain" | "cover"seo.ogImageFraming.padColorstringseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.canonicalUrlstring | nullrequiredseo.robotsIndexstringrequiredseo.robotsFollowstringrequiredseo.twitterCardstringrequiredseo.autoOgEnabledbooleanrequiredseo.autoOgImagestring | nullrequiredseo.excludeFromSiteSearchbooleanrequiredWhen true, this post is excluded from visitor site search (independent of robotsIndex).
assetsobjectrequiredThe media-library files contentMarkdown and the share image use, by assetId.
createdAtstringrequiredformatNotesstring[]Present on create/update when contentMarkdown was reformatted to the article profile's supported syntax (e.g. a level-1 heading became level 2).
warningstring | nullrequiredSet when the post went live but the automatic OG image could not be generated, or REVIEW_CONTENT_CHANGED when it was submitted for review (posts.request_review) and edited since (publish still proceeds). Always null when scheduling (scheduledAt set).
Errors
FORBIDDEN403POST_NOT_FOUND404SITE_NOT_PUBLISHED422SLUG_TAKEN409VALIDATION400 · scheduledAt must be a future timestamp, or the post is already scheduled: call posts.unschedule first to change the time
MCP tool posts_publish
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/posts/{postId}/publish" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "PUBLISHED",
"publishedAt": "2026-10-06T08:00:00.000Z",
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": false,
"reviewRequestedAt": null,
"reviewRequestedByUserId": null,
"reviewNote": null,
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"contentMarkdown": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water...",
"segments": [
{
"type": "markdown",
"value": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water..."
}
],
"toc": [
{
"id": "5-am-in-the-bakehouse",
"label": "5 a.m. in the bakehouse",
"level": 2
}
],
"showToc": true,
"seo": {
"seoTitle": null,
"seoDescription": null,
"seoKeywords": null,
"ogImage": null,
"ogImageAssetId": null,
"ogImageAlt": null,
"ogImageFraming": null,
"ogTitle": null,
"ogDescription": null,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow",
"twitterCard": "summary_large_image",
"autoOgEnabled": true,
"autoOgImage": null,
"excludeFromSiteSearch": false
},
"assets": {
"asset_example01": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
},
"createdAt": "2026-10-01T02:30:00.000Z",
"warning": null
}Revert a published post to DRAFT so it disappears from the public site. The content is kept; publishedAt is preserved as the first-publish date. Requires blog:publish (separate from blog:manage). Errors: FORBIDDEN, POST_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
postIdstring · pathrequiredBlog post id. Get it from posts.list.
Response 200
idstringrequiredslugstringrequiredtitlestringrequiredexcerptstring | nullrequiredstatus"DRAFT" | "PUBLISHED"requiredpublishedAtstring | nullrequiredupdatedAtstringrequiredcontentUpdatedAtstring | nullrequiredreadingTimeMinnumber | nullrequiredhasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
primaryCategoryobject | nullrequiredprimaryCategory.idstringrequiredprimaryCategory.slugstringrequiredprimaryCategory.namestringrequiredcategoriesobject[]requiredcategories[].idstringrequiredcategories[].slugstringrequiredcategories[].namestringrequiredtagsobject[]requiredtags[].idstringrequiredtags[].slugstringrequiredtags[].namestringrequiredauthorsobject[]requiredauthors[].idstringrequiredauthors[].slugstringrequiredauthors[].namestringrequiredreviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
contentMarkdownstringrequiredsegmentsobject[]requiredStructured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.
tocobject[]requiredTable of contents headings (H2/H3) with anchor ids.
toc[].idstringrequiredtoc[].labelstringrequiredtoc[].level2 | 3requiredshowTocbooleanrequiredseoobjectrequiredseo.seoTitlestring | nullrequiredseo.seoDescriptionstring | nullrequiredseo.seoKeywordsstring[] | nullrequiredseo.ogImagestring | nullrequiredShare image display URL (changes with versions; do not store it).
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.ogImageAltstring | nullrequiredseo.ogImageFramingobject | nullseo.ogImageFraming.focusobjectseo.ogImageFraming.screensobjectseo.ogImageFraming.fitMode"contain" | "cover"seo.ogImageFraming.padColorstringseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.canonicalUrlstring | nullrequiredseo.robotsIndexstringrequiredseo.robotsFollowstringrequiredseo.twitterCardstringrequiredseo.autoOgEnabledbooleanrequiredseo.autoOgImagestring | nullrequiredseo.excludeFromSiteSearchbooleanrequiredWhen true, this post is excluded from visitor site search (independent of robotsIndex).
assetsobjectrequiredThe media-library files contentMarkdown and the share image use, by assetId.
createdAtstringrequiredformatNotesstring[]Present on create/update when contentMarkdown was reformatted to the article profile's supported syntax (e.g. a level-1 heading became level 2).
Errors
FORBIDDEN403POST_NOT_FOUND404
MCP tool posts_unpublish
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/posts/{postId}/unpublish" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "DRAFT",
"publishedAt": null,
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": true,
"reviewRequestedAt": null,
"reviewRequestedByUserId": null,
"reviewNote": null,
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"contentMarkdown": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water...",
"segments": [
{
"type": "markdown",
"value": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water..."
}
],
"toc": [
{
"id": "5-am-in-the-bakehouse",
"label": "5 a.m. in the bakehouse",
"level": 2
}
],
"showToc": true,
"seo": {
"seoTitle": null,
"seoDescription": null,
"seoKeywords": null,
"ogImage": null,
"ogImageAssetId": null,
"ogImageAlt": null,
"ogImageFraming": null,
"ogTitle": null,
"ogDescription": null,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow",
"twitterCard": "summary_large_image",
"autoOgEnabled": true,
"autoOgImage": null,
"excludeFromSiteSearch": false
},
"assets": {
"asset_example01": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
},
"createdAt": "2026-10-01T02:30:00.000Z"
}Request review
/sites/{id}/posts/{postId}/request-reviewAsk someone with blog:publish to review and publish a post (for a token/role that has blog:manage but not blog:publish). Records the current content hash so posts.publish can flag if it changed since review. Errors: FORBIDDEN, POST_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
postIdstring · pathrequiredBlog post id. Get it from posts.list.
Response 200
idstringrequiredslugstringrequiredtitlestringrequiredexcerptstring | nullrequiredstatus"DRAFT" | "PUBLISHED"requiredpublishedAtstring | nullrequiredupdatedAtstringrequiredcontentUpdatedAtstring | nullrequiredreadingTimeMinnumber | nullrequiredhasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
primaryCategoryobject | nullrequiredprimaryCategory.idstringrequiredprimaryCategory.slugstringrequiredprimaryCategory.namestringrequiredcategoriesobject[]requiredcategories[].idstringrequiredcategories[].slugstringrequiredcategories[].namestringrequiredtagsobject[]requiredtags[].idstringrequiredtags[].slugstringrequiredtags[].namestringrequiredauthorsobject[]requiredauthors[].idstringrequiredauthors[].slugstringrequiredauthors[].namestringrequiredreviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
contentMarkdownstringrequiredsegmentsobject[]requiredStructured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.
tocobject[]requiredTable of contents headings (H2/H3) with anchor ids.
toc[].idstringrequiredtoc[].labelstringrequiredtoc[].level2 | 3requiredshowTocbooleanrequiredseoobjectrequiredseo.seoTitlestring | nullrequiredseo.seoDescriptionstring | nullrequiredseo.seoKeywordsstring[] | nullrequiredseo.ogImagestring | nullrequiredShare image display URL (changes with versions; do not store it).
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.ogImageAltstring | nullrequiredseo.ogImageFramingobject | nullseo.ogImageFraming.focusobjectseo.ogImageFraming.screensobjectseo.ogImageFraming.fitMode"contain" | "cover"seo.ogImageFraming.padColorstringseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.canonicalUrlstring | nullrequiredseo.robotsIndexstringrequiredseo.robotsFollowstringrequiredseo.twitterCardstringrequiredseo.autoOgEnabledbooleanrequiredseo.autoOgImagestring | nullrequiredseo.excludeFromSiteSearchbooleanrequiredWhen true, this post is excluded from visitor site search (independent of robotsIndex).
assetsobjectrequiredThe media-library files contentMarkdown and the share image use, by assetId.
createdAtstringrequiredformatNotesstring[]Present on create/update when contentMarkdown was reformatted to the article profile's supported syntax (e.g. a level-1 heading became level 2).
Errors
FORBIDDEN403POST_NOT_FOUND404
MCP tool posts_request_review
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/posts/{postId}/request-review" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "DRAFT",
"publishedAt": null,
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": true,
"reviewRequestedAt": "2026-10-06T08:00:00.000Z",
"reviewRequestedByUserId": "usr_example01",
"reviewNote": null,
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"contentMarkdown": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water...",
"segments": [
{
"type": "markdown",
"value": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water..."
}
],
"toc": [
{
"id": "5-am-in-the-bakehouse",
"label": "5 a.m. in the bakehouse",
"level": 2
}
],
"showToc": true,
"seo": {
"seoTitle": null,
"seoDescription": null,
"seoKeywords": null,
"ogImage": null,
"ogImageAssetId": null,
"ogImageAlt": null,
"ogImageFraming": null,
"ogTitle": null,
"ogDescription": null,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow",
"twitterCard": "summary_large_image",
"autoOgEnabled": true,
"autoOgImage": null,
"excludeFromSiteSearch": false
},
"assets": {
"asset_example01": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
},
"createdAt": "2026-10-01T02:30:00.000Z"
}Reject review
/sites/{id}/posts/{postId}/reject-reviewSend a reviewed post back for changes instead of publishing it, optionally with a note explaining why. Requires blog:publish (separate from blog:manage). Errors: FORBIDDEN, POST_NOT_FOUND, VALIDATION (not pending review).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
postIdstring · pathrequiredBlog post id. Get it from posts.list.
Request body
notestring | null
Response 200
idstringrequiredslugstringrequiredtitlestringrequiredexcerptstring | nullrequiredstatus"DRAFT" | "PUBLISHED"requiredpublishedAtstring | nullrequiredupdatedAtstringrequiredcontentUpdatedAtstring | nullrequiredreadingTimeMinnumber | nullrequiredhasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
primaryCategoryobject | nullrequiredprimaryCategory.idstringrequiredprimaryCategory.slugstringrequiredprimaryCategory.namestringrequiredcategoriesobject[]requiredcategories[].idstringrequiredcategories[].slugstringrequiredcategories[].namestringrequiredtagsobject[]requiredtags[].idstringrequiredtags[].slugstringrequiredtags[].namestringrequiredauthorsobject[]requiredauthors[].idstringrequiredauthors[].slugstringrequiredauthors[].namestringrequiredreviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
contentMarkdownstringrequiredsegmentsobject[]requiredStructured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.
tocobject[]requiredTable of contents headings (H2/H3) with anchor ids.
toc[].idstringrequiredtoc[].labelstringrequiredtoc[].level2 | 3requiredshowTocbooleanrequiredseoobjectrequiredseo.seoTitlestring | nullrequiredseo.seoDescriptionstring | nullrequiredseo.seoKeywordsstring[] | nullrequiredseo.ogImagestring | nullrequiredShare image display URL (changes with versions; do not store it).
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.ogImageAltstring | nullrequiredseo.ogImageFramingobject | nullseo.ogImageFraming.focusobjectseo.ogImageFraming.screensobjectseo.ogImageFraming.fitMode"contain" | "cover"seo.ogImageFraming.padColorstringseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.canonicalUrlstring | nullrequiredseo.robotsIndexstringrequiredseo.robotsFollowstringrequiredseo.twitterCardstringrequiredseo.autoOgEnabledbooleanrequiredseo.autoOgImagestring | nullrequiredseo.excludeFromSiteSearchbooleanrequiredWhen true, this post is excluded from visitor site search (independent of robotsIndex).
assetsobjectrequiredThe media-library files contentMarkdown and the share image use, by assetId.
createdAtstringrequiredformatNotesstring[]Present on create/update when contentMarkdown was reformatted to the article profile's supported syntax (e.g. a level-1 heading became level 2).
Errors
FORBIDDEN403POST_NOT_FOUND404VALIDATION400 · not pending review
MCP tool posts_reject_review
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/posts/{postId}/reject-review" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note":"Please double-check the sourdough timing before we publish."}'{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "DRAFT",
"publishedAt": null,
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": true,
"reviewRequestedAt": null,
"reviewRequestedByUserId": "usr_example01",
"reviewNote": "Please double-check the sourdough timing before we publish.",
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"contentMarkdown": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water...",
"segments": [
{
"type": "markdown",
"value": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water..."
}
],
"toc": [
{
"id": "5-am-in-the-bakehouse",
"label": "5 a.m. in the bakehouse",
"level": 2
}
],
"showToc": true,
"seo": {
"seoTitle": null,
"seoDescription": null,
"seoKeywords": null,
"ogImage": null,
"ogImageAssetId": null,
"ogImageAlt": null,
"ogImageFraming": null,
"ogTitle": null,
"ogDescription": null,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow",
"twitterCard": "summary_large_image",
"autoOgEnabled": true,
"autoOgImage": null,
"excludeFromSiteSearch": false
},
"assets": {
"asset_example01": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
},
"createdAt": "2026-10-01T02:30:00.000Z"
}Unschedule a post
/sites/{id}/posts/{postId}/unscheduleCancel a scheduled publish (posts.publish with scheduledAt). Leaves the draft and published states untouched. Errors: FORBIDDEN, POST_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
postIdstring · pathrequiredBlog post id. Get it from posts.list.
Response 200
idstringrequiredslugstringrequiredtitlestringrequiredexcerptstring | nullrequiredstatus"DRAFT" | "PUBLISHED"requiredpublishedAtstring | nullrequiredupdatedAtstringrequiredcontentUpdatedAtstring | nullrequiredreadingTimeMinnumber | nullrequiredhasDraftChangesbooleanrequiredPublished post has edits not yet re-published.
primaryCategoryobject | nullrequiredprimaryCategory.idstringrequiredprimaryCategory.slugstringrequiredprimaryCategory.namestringrequiredcategoriesobject[]requiredcategories[].idstringrequiredcategories[].slugstringrequiredcategories[].namestringrequiredtagsobject[]requiredtags[].idstringrequiredtags[].slugstringrequiredtags[].namestringrequiredauthorsobject[]requiredauthors[].idstringrequiredauthors[].slugstringrequiredauthors[].namestringrequiredreviewRequestedAtstring | nullrequiredSet by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.
reviewRequestedByUserIdstring | nullrequiredWho last requested review; kept after a rejection so posts.request_review can be called again by the same person.
reviewNotestring | nullrequiredReason from the last posts.reject_review call; cleared on the next request or successful publish.
contentMarkdownstringrequiredsegmentsobject[]requiredStructured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.
tocobject[]requiredTable of contents headings (H2/H3) with anchor ids.
toc[].idstringrequiredtoc[].labelstringrequiredtoc[].level2 | 3requiredshowTocbooleanrequiredseoobjectrequiredseo.seoTitlestring | nullrequiredseo.seoDescriptionstring | nullrequiredseo.seoKeywordsstring[] | nullrequiredseo.ogImagestring | nullrequiredShare image display URL (changes with versions; do not store it).
seo.ogImageAssetIdstring | nullrequiredassetId of the share image when it is in the media library.
seo.ogImageAltstring | nullrequiredseo.ogImageFramingobject | nullseo.ogImageFraming.focusobjectseo.ogImageFraming.screensobjectseo.ogImageFraming.fitMode"contain" | "cover"seo.ogImageFraming.padColorstringseo.ogTitlestring | nullrequiredseo.ogDescriptionstring | nullrequiredseo.canonicalUrlstring | nullrequiredseo.robotsIndexstringrequiredseo.robotsFollowstringrequiredseo.twitterCardstringrequiredseo.autoOgEnabledbooleanrequiredseo.autoOgImagestring | nullrequiredseo.excludeFromSiteSearchbooleanrequiredWhen true, this post is excluded from visitor site search (independent of robotsIndex).
assetsobjectrequiredThe media-library files contentMarkdown and the share image use, by assetId.
createdAtstringrequiredformatNotesstring[]Present on create/update when contentMarkdown was reformatted to the article profile's supported syntax (e.g. a level-1 heading became level 2).
Errors
FORBIDDEN403POST_NOT_FOUND404
MCP tool posts_unschedule
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/posts/{postId}/unschedule" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "post_example01",
"slug": "how-we-feed-our-starter",
"title": "How we feed our sourdough starter",
"excerpt": "The first thing we do at 5 a.m. every morning.",
"status": "DRAFT",
"publishedAt": null,
"updatedAt": "2026-10-06T08:00:00.000Z",
"contentUpdatedAt": "2026-10-06T08:00:00.000Z",
"readingTimeMin": 4,
"hasDraftChanges": true,
"reviewRequestedAt": null,
"reviewRequestedByUserId": null,
"reviewNote": null,
"primaryCategory": {
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
},
"categories": [
{
"id": "cat_example01",
"slug": "bread-notes",
"name": "Bread Notes"
}
],
"tags": [
{
"id": "tag_example01",
"slug": "sourdough",
"name": "Sourdough"
}
],
"authors": [
{
"id": "author_example01",
"slug": "sunny",
"name": "Sunny"
}
],
"contentMarkdown": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water...",
"segments": [
{
"type": "markdown",
"value": "## 5 a.m. in the bakehouse\n\n\n\nDiscard half the starter, then fold in fresh flour and warm water..."
}
],
"toc": [
{
"id": "5-am-in-the-bakehouse",
"label": "5 a.m. in the bakehouse",
"level": 2
}
],
"showToc": true,
"seo": {
"seoTitle": null,
"seoDescription": null,
"seoKeywords": null,
"ogImage": null,
"ogImageAssetId": null,
"ogImageAlt": null,
"ogImageFraming": null,
"ogTitle": null,
"ogDescription": null,
"canonicalUrl": null,
"robotsIndex": "index",
"robotsFollow": "follow",
"twitterCard": "summary_large_image",
"autoOgEnabled": true,
"autoOgImage": null,
"excludeFromSiteSearch": false
},
"assets": {
"asset_example01": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
},
"createdAt": "2026-10-01T02:30:00.000Z"
}