Kanorio

API 参考

Posts

Management API: GET /sites/{id}/posts, POST /sites/{id}/posts, GET /sites/{id}/posts/{postId}, …

本页只有英文版。API 参考、Webhook 事件和更新日志保持英文,与代码中的字段名称、错误码一致。
Base URL https://app.kanorio.com/api/v1ErrorsPagination

List posts

GET/sites/{id}/posts
betaposts:viewdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • status"DRAFT" | "PUBLISHED" · query
  • searchstring · query

    Case-insensitive match on title or slug.

  • categoryIdstring · query
  • tagIdstring · query
  • pendingReviewboolean · query

    True 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" · query

    Defaults to edited_desc.

  • limitinteger · query

    Maximum number of items to return, 1–100. Defaults to 20.

  • cursorstring · query

    Opaque cursor from the previous page's nextCursor. Omit for the first page.

Response 200

  • dataobject[]required
  • data[].idstringrequired
  • data[].slugstringrequired
  • data[].titlestringrequired
  • data[].excerptstring | nullrequired
  • data[].status"DRAFT" | "PUBLISHED"required
  • data[].publishedAtstring | nullrequired
  • data[].updatedAtstringrequired
  • data[].contentUpdatedAtstring | nullrequired
  • data[].readingTimeMinnumber | nullrequired
  • data[].hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • data[].primaryCategoryobject | nullrequired
  • data[].primaryCategory.idstringrequired
  • data[].primaryCategory.slugstringrequired
  • data[].primaryCategory.namestringrequired
  • data[].categoriesobject[]required
  • data[].categories[].idstringrequired
  • data[].categories[].slugstringrequired
  • data[].categories[].namestringrequired
  • data[].tagsobject[]required
  • data[].tags[].idstringrequired
  • data[].tags[].slugstringrequired
  • data[].tags[].namestringrequired
  • data[].authorsobject[]required
  • data[].authors[].idstringrequired
  • data[].authors[].slugstringrequired
  • data[].authors[].namestringrequired
  • data[].coverImagestring | nullrequired

    ogImage, else auto-generated OG image, else first image in content.

  • data[].pageviewsinteger
  • data[].reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • data[].reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • data[].reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • hasMorebooleanrequired

    True when another page exists; pass nextCursor to fetch it.

  • nextCursorstring | nullrequired

    Cursor for the next page, or null on the last page.

Errors

  • FORBIDDEN 403
  • INVALID_CURSOR 400

MCP tool posts_list

curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/posts?status=DRAFT" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
Response
{
  "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 post

POST/sites/{id}/posts
betablog:managedefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

Request body

  • titlestringrequired
  • slugstring

    URL slug without leading slash; generated from title when omitted and de-duplicated per website.

  • excerptstring
  • contentMarkdownstring

    Markdown 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 ![alt](asset:<assetId>).

  • authorIdsstring[]

    Ids from this website's blog authors.

  • primaryCategoryIdstring | null

    Must also be in categoryIds.

  • categoryIdsstring[]

    Ids from blog_categories.list.

  • tagIdsstring[]

    Ids from blog_tags.list.

  • showTocboolean
  • seoTitlestring
  • seoDescriptionstring
  • seoKeywordsstring[]
  • ogImageAltstring
  • ogImageFramingobject | null
  • ogImageFraming.focusobject
  • ogImageFraming.focus.xnumberrequired
  • ogImageFraming.focus.ynumberrequired
  • ogImageFraming.screensobject
  • ogImageFraming.screens.desktopobject
  • ogImageFraming.screens.tabletobject
  • ogImageFraming.screens.mobileobject
  • ogImageFraming.fitMode"contain" | "cover"
  • ogImageFraming.padColorstring
  • ogTitlestring
  • ogDescriptionstring
  • canonicalUrlstring
  • robotsIndex"index" | "noindex"
  • robotsFollow"follow" | "nofollow"
  • twitterCard"summary" | "summary_large_image"
  • autoOgEnabledboolean
  • excludeFromSiteSearchboolean

    Exclude this post from visitor site search (/api/v1/search and the /search page); does not affect search engine indexing (robotsIndex).

  • ogImageAssetIdstring | null

    Share image: assetId of an image in the media library. null removes it; without a share image one is generated automatically.

Response 201

  • idstringrequired
  • slugstringrequired
  • titlestringrequired
  • excerptstring | nullrequired
  • status"DRAFT" | "PUBLISHED"required
  • publishedAtstring | nullrequired
  • updatedAtstringrequired
  • contentUpdatedAtstring | nullrequired
  • readingTimeMinnumber | nullrequired
  • hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • primaryCategoryobject | nullrequired
  • primaryCategory.idstringrequired
  • primaryCategory.slugstringrequired
  • primaryCategory.namestringrequired
  • categoriesobject[]required
  • categories[].idstringrequired
  • categories[].slugstringrequired
  • categories[].namestringrequired
  • tagsobject[]required
  • tags[].idstringrequired
  • tags[].slugstringrequired
  • tags[].namestringrequired
  • authorsobject[]required
  • authors[].idstringrequired
  • authors[].slugstringrequired
  • authors[].namestringrequired
  • reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • contentMarkdownstringrequired
  • segmentsobject[]required

    Structured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.

  • tocobject[]required

    Table of contents headings (H2/H3) with anchor ids.

  • toc[].idstringrequired
  • toc[].labelstringrequired
  • toc[].level2 | 3required
  • showTocbooleanrequired
  • seoobjectrequired
  • seo.seoTitlestring | nullrequired
  • seo.seoDescriptionstring | nullrequired
  • seo.seoKeywordsstring[] | nullrequired
  • seo.ogImagestring | nullrequired

    Share image display URL (changes with versions; do not store it).

  • seo.ogImageAssetIdstring | nullrequired

    assetId of the share image when it is in the media library.

  • seo.ogImageAltstring | nullrequired
  • seo.ogImageFramingobject | null
  • seo.ogImageFraming.focusobject
  • seo.ogImageFraming.screensobject
  • seo.ogImageFraming.fitMode"contain" | "cover"
  • seo.ogImageFraming.padColorstring
  • seo.ogTitlestring | nullrequired
  • seo.ogDescriptionstring | nullrequired
  • seo.canonicalUrlstring | nullrequired
  • seo.robotsIndexstringrequired
  • seo.robotsFollowstringrequired
  • seo.twitterCardstringrequired
  • seo.autoOgEnabledbooleanrequired
  • seo.autoOgImagestring | nullrequired
  • seo.excludeFromSiteSearchbooleanrequired

    When true, this post is excluded from visitor site search (independent of robotsIndex).

  • assetsobjectrequired

    The media-library files contentMarkdown and the share image use, by assetId.

  • createdAtstringrequired
  • formatNotesstring[]

    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

  • FORBIDDEN 403
  • VALIDATION 400
  • SLUG_TAKEN 409
  • ASSET_NOT_FOUND 404 · details.assetIds: not in this site's media library; the share image also must not be in the trash
  • ASSET_NOT_ALLOWED 422 · 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"]}'
Response
{
  "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![Starter jar on the counter](asset:asset_example01)\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![Starter jar on the counter](https://media.kanorio.com/example/sunny-bakery-hero.jpg)\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"
}

Retrieve a post

GET/sites/{id}/posts/{postId}
betaposts:viewdefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • postIdstring · pathrequired

    Blog post id. Get it from posts.list.

Response 200

  • idstringrequired
  • slugstringrequired
  • titlestringrequired
  • excerptstring | nullrequired
  • status"DRAFT" | "PUBLISHED"required
  • publishedAtstring | nullrequired
  • updatedAtstringrequired
  • contentUpdatedAtstring | nullrequired
  • readingTimeMinnumber | nullrequired
  • hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • primaryCategoryobject | nullrequired
  • primaryCategory.idstringrequired
  • primaryCategory.slugstringrequired
  • primaryCategory.namestringrequired
  • categoriesobject[]required
  • categories[].idstringrequired
  • categories[].slugstringrequired
  • categories[].namestringrequired
  • tagsobject[]required
  • tags[].idstringrequired
  • tags[].slugstringrequired
  • tags[].namestringrequired
  • authorsobject[]required
  • authors[].idstringrequired
  • authors[].slugstringrequired
  • authors[].namestringrequired
  • reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • contentMarkdownstringrequired
  • segmentsobject[]required

    Structured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.

  • tocobject[]required

    Table of contents headings (H2/H3) with anchor ids.

  • toc[].idstringrequired
  • toc[].labelstringrequired
  • toc[].level2 | 3required
  • showTocbooleanrequired
  • seoobjectrequired
  • seo.seoTitlestring | nullrequired
  • seo.seoDescriptionstring | nullrequired
  • seo.seoKeywordsstring[] | nullrequired
  • seo.ogImagestring | nullrequired

    Share image display URL (changes with versions; do not store it).

  • seo.ogImageAssetIdstring | nullrequired

    assetId of the share image when it is in the media library.

  • seo.ogImageAltstring | nullrequired
  • seo.ogImageFramingobject | null
  • seo.ogImageFraming.focusobject
  • seo.ogImageFraming.screensobject
  • seo.ogImageFraming.fitMode"contain" | "cover"
  • seo.ogImageFraming.padColorstring
  • seo.ogTitlestring | nullrequired
  • seo.ogDescriptionstring | nullrequired
  • seo.canonicalUrlstring | nullrequired
  • seo.robotsIndexstringrequired
  • seo.robotsFollowstringrequired
  • seo.twitterCardstringrequired
  • seo.autoOgEnabledbooleanrequired
  • seo.autoOgImagestring | nullrequired
  • seo.excludeFromSiteSearchbooleanrequired

    When true, this post is excluded from visitor site search (independent of robotsIndex).

  • assetsobjectrequired

    The media-library files contentMarkdown and the share image use, by assetId.

  • createdAtstringrequired
  • formatNotesstring[]

    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

  • FORBIDDEN 403
  • POST_NOT_FOUND 404

MCP tool posts_get

curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/posts/{postId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
Response
{
  "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![Starter jar on the counter](asset:asset_example01)\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![Starter jar on the counter](https://media.kanorio.com/example/sunny-bakery-hero.jpg)\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"
}

Update a post

PATCH/sites/{id}/posts/{postId}
betablog:managedefault (120/minute per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • postIdstring · pathrequired

    Blog post id. Get it from posts.list.

Request body

  • titlestring
  • slugstring

    URL slug without leading slash; generated from title when omitted and de-duplicated per website.

  • excerptstring
  • contentMarkdownstring

    Markdown 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 ![alt](asset:<assetId>).

  • authorIdsstring[]

    Ids from this website's blog authors.

  • primaryCategoryIdstring | null

    Must also be in categoryIds.

  • categoryIdsstring[]

    Ids from blog_categories.list.

  • tagIdsstring[]

    Ids from blog_tags.list.

  • showTocboolean
  • seoTitlestring
  • seoDescriptionstring
  • seoKeywordsstring[]
  • ogImageAltstring
  • ogImageFramingobject | null
  • ogImageFraming.focusobject
  • ogImageFraming.focus.xnumberrequired
  • ogImageFraming.focus.ynumberrequired
  • ogImageFraming.screensobject
  • ogImageFraming.screens.desktopobject
  • ogImageFraming.screens.tabletobject
  • ogImageFraming.screens.mobileobject
  • ogImageFraming.fitMode"contain" | "cover"
  • ogImageFraming.padColorstring
  • ogTitlestring
  • ogDescriptionstring
  • canonicalUrlstring
  • robotsIndex"index" | "noindex"
  • robotsFollow"follow" | "nofollow"
  • twitterCard"summary" | "summary_large_image"
  • autoOgEnabledboolean
  • excludeFromSiteSearchboolean

    Exclude this post from visitor site search (/api/v1/search and the /search page); does not affect search engine indexing (robotsIndex).

  • ogImageAssetIdstring | null

    Share image: assetId of an image in the media library. null removes it; without a share image one is generated automatically.

  • ifUnmodifiedSincestring

    Optimistic lock: the post's updatedAt you last read. If it no longer matches, the update is rejected with CONFLICT instead of overwriting.

Response 200

  • idstringrequired
  • slugstringrequired
  • titlestringrequired
  • excerptstring | nullrequired
  • status"DRAFT" | "PUBLISHED"required
  • publishedAtstring | nullrequired
  • updatedAtstringrequired
  • contentUpdatedAtstring | nullrequired
  • readingTimeMinnumber | nullrequired
  • hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • primaryCategoryobject | nullrequired
  • primaryCategory.idstringrequired
  • primaryCategory.slugstringrequired
  • primaryCategory.namestringrequired
  • categoriesobject[]required
  • categories[].idstringrequired
  • categories[].slugstringrequired
  • categories[].namestringrequired
  • tagsobject[]required
  • tags[].idstringrequired
  • tags[].slugstringrequired
  • tags[].namestringrequired
  • authorsobject[]required
  • authors[].idstringrequired
  • authors[].slugstringrequired
  • authors[].namestringrequired
  • reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • contentMarkdownstringrequired
  • segmentsobject[]required

    Structured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.

  • tocobject[]required

    Table of contents headings (H2/H3) with anchor ids.

  • toc[].idstringrequired
  • toc[].labelstringrequired
  • toc[].level2 | 3required
  • showTocbooleanrequired
  • seoobjectrequired
  • seo.seoTitlestring | nullrequired
  • seo.seoDescriptionstring | nullrequired
  • seo.seoKeywordsstring[] | nullrequired
  • seo.ogImagestring | nullrequired

    Share image display URL (changes with versions; do not store it).

  • seo.ogImageAssetIdstring | nullrequired

    assetId of the share image when it is in the media library.

  • seo.ogImageAltstring | nullrequired
  • seo.ogImageFramingobject | null
  • seo.ogImageFraming.focusobject
  • seo.ogImageFraming.screensobject
  • seo.ogImageFraming.fitMode"contain" | "cover"
  • seo.ogImageFraming.padColorstring
  • seo.ogTitlestring | nullrequired
  • seo.ogDescriptionstring | nullrequired
  • seo.canonicalUrlstring | nullrequired
  • seo.robotsIndexstringrequired
  • seo.robotsFollowstringrequired
  • seo.twitterCardstringrequired
  • seo.autoOgEnabledbooleanrequired
  • seo.autoOgImagestring | nullrequired
  • seo.excludeFromSiteSearchbooleanrequired

    When true, this post is excluded from visitor site search (independent of robotsIndex).

  • assetsobjectrequired

    The media-library files contentMarkdown and the share image use, by assetId.

  • createdAtstringrequired
  • formatNotesstring[]

    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

  • FORBIDDEN 403
  • POST_NOT_FOUND 404
  • CONFLICT 409 · re-read with posts.get and retry
  • VALIDATION 400
  • SLUG_TAKEN 409
  • ASSET_NOT_FOUND 404 · details.assetIds: not in this site's media library; the share image also must not be in the trash
  • ASSET_NOT_ALLOWED 422 · 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."}'
Response
{
  "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![Starter jar on the counter](asset:asset_example01)\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![Starter jar on the counter](https://media.kanorio.com/example/sunny-bakery-hero.jpg)\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 post

POST/sites/{id}/posts/{postId}/publish
betablog:publishpublish (30/hour per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • postIdstring · pathrequired

    Blog post id. Get it from posts.list.

Request body

  • scheduledAtstring

    Future ISO timestamp. When set, schedules instead of publishing immediately.

Response 200

  • idstringrequired
  • slugstringrequired
  • titlestringrequired
  • excerptstring | nullrequired
  • status"DRAFT" | "PUBLISHED"required
  • publishedAtstring | nullrequired
  • updatedAtstringrequired
  • contentUpdatedAtstring | nullrequired
  • readingTimeMinnumber | nullrequired
  • hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • primaryCategoryobject | nullrequired
  • primaryCategory.idstringrequired
  • primaryCategory.slugstringrequired
  • primaryCategory.namestringrequired
  • categoriesobject[]required
  • categories[].idstringrequired
  • categories[].slugstringrequired
  • categories[].namestringrequired
  • tagsobject[]required
  • tags[].idstringrequired
  • tags[].slugstringrequired
  • tags[].namestringrequired
  • authorsobject[]required
  • authors[].idstringrequired
  • authors[].slugstringrequired
  • authors[].namestringrequired
  • reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • contentMarkdownstringrequired
  • segmentsobject[]required

    Structured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.

  • tocobject[]required

    Table of contents headings (H2/H3) with anchor ids.

  • toc[].idstringrequired
  • toc[].labelstringrequired
  • toc[].level2 | 3required
  • showTocbooleanrequired
  • seoobjectrequired
  • seo.seoTitlestring | nullrequired
  • seo.seoDescriptionstring | nullrequired
  • seo.seoKeywordsstring[] | nullrequired
  • seo.ogImagestring | nullrequired

    Share image display URL (changes with versions; do not store it).

  • seo.ogImageAssetIdstring | nullrequired

    assetId of the share image when it is in the media library.

  • seo.ogImageAltstring | nullrequired
  • seo.ogImageFramingobject | null
  • seo.ogImageFraming.focusobject
  • seo.ogImageFraming.screensobject
  • seo.ogImageFraming.fitMode"contain" | "cover"
  • seo.ogImageFraming.padColorstring
  • seo.ogTitlestring | nullrequired
  • seo.ogDescriptionstring | nullrequired
  • seo.canonicalUrlstring | nullrequired
  • seo.robotsIndexstringrequired
  • seo.robotsFollowstringrequired
  • seo.twitterCardstringrequired
  • seo.autoOgEnabledbooleanrequired
  • seo.autoOgImagestring | nullrequired
  • seo.excludeFromSiteSearchbooleanrequired

    When true, this post is excluded from visitor site search (independent of robotsIndex).

  • assetsobjectrequired

    The media-library files contentMarkdown and the share image use, by assetId.

  • createdAtstringrequired
  • formatNotesstring[]

    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 | nullrequired

    Set 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

  • FORBIDDEN 403
  • POST_NOT_FOUND 404
  • SITE_NOT_PUBLISHED 422
  • SLUG_TAKEN 409
  • VALIDATION 400 · 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 '{}'
Response
{
  "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![Starter jar on the counter](asset:asset_example01)\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![Starter jar on the counter](https://media.kanorio.com/example/sunny-bakery-hero.jpg)\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
}

Unpublish a post

POST/sites/{id}/posts/{postId}/unpublish
betablog:publishpublish (30/hour per token)

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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • postIdstring · pathrequired

    Blog post id. Get it from posts.list.

Response 200

  • idstringrequired
  • slugstringrequired
  • titlestringrequired
  • excerptstring | nullrequired
  • status"DRAFT" | "PUBLISHED"required
  • publishedAtstring | nullrequired
  • updatedAtstringrequired
  • contentUpdatedAtstring | nullrequired
  • readingTimeMinnumber | nullrequired
  • hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • primaryCategoryobject | nullrequired
  • primaryCategory.idstringrequired
  • primaryCategory.slugstringrequired
  • primaryCategory.namestringrequired
  • categoriesobject[]required
  • categories[].idstringrequired
  • categories[].slugstringrequired
  • categories[].namestringrequired
  • tagsobject[]required
  • tags[].idstringrequired
  • tags[].slugstringrequired
  • tags[].namestringrequired
  • authorsobject[]required
  • authors[].idstringrequired
  • authors[].slugstringrequired
  • authors[].namestringrequired
  • reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • contentMarkdownstringrequired
  • segmentsobject[]required

    Structured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.

  • tocobject[]required

    Table of contents headings (H2/H3) with anchor ids.

  • toc[].idstringrequired
  • toc[].labelstringrequired
  • toc[].level2 | 3required
  • showTocbooleanrequired
  • seoobjectrequired
  • seo.seoTitlestring | nullrequired
  • seo.seoDescriptionstring | nullrequired
  • seo.seoKeywordsstring[] | nullrequired
  • seo.ogImagestring | nullrequired

    Share image display URL (changes with versions; do not store it).

  • seo.ogImageAssetIdstring | nullrequired

    assetId of the share image when it is in the media library.

  • seo.ogImageAltstring | nullrequired
  • seo.ogImageFramingobject | null
  • seo.ogImageFraming.focusobject
  • seo.ogImageFraming.screensobject
  • seo.ogImageFraming.fitMode"contain" | "cover"
  • seo.ogImageFraming.padColorstring
  • seo.ogTitlestring | nullrequired
  • seo.ogDescriptionstring | nullrequired
  • seo.canonicalUrlstring | nullrequired
  • seo.robotsIndexstringrequired
  • seo.robotsFollowstringrequired
  • seo.twitterCardstringrequired
  • seo.autoOgEnabledbooleanrequired
  • seo.autoOgImagestring | nullrequired
  • seo.excludeFromSiteSearchbooleanrequired

    When true, this post is excluded from visitor site search (independent of robotsIndex).

  • assetsobjectrequired

    The media-library files contentMarkdown and the share image use, by assetId.

  • createdAtstringrequired
  • formatNotesstring[]

    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

  • FORBIDDEN 403
  • POST_NOT_FOUND 404

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 '{}'
Response
{
  "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![Starter jar on the counter](asset:asset_example01)\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![Starter jar on the counter](https://media.kanorio.com/example/sunny-bakery-hero.jpg)\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

POST/sites/{id}/posts/{postId}/request-review
betablog:managedefault (120/minute per token)

Ask 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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • postIdstring · pathrequired

    Blog post id. Get it from posts.list.

Response 200

  • idstringrequired
  • slugstringrequired
  • titlestringrequired
  • excerptstring | nullrequired
  • status"DRAFT" | "PUBLISHED"required
  • publishedAtstring | nullrequired
  • updatedAtstringrequired
  • contentUpdatedAtstring | nullrequired
  • readingTimeMinnumber | nullrequired
  • hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • primaryCategoryobject | nullrequired
  • primaryCategory.idstringrequired
  • primaryCategory.slugstringrequired
  • primaryCategory.namestringrequired
  • categoriesobject[]required
  • categories[].idstringrequired
  • categories[].slugstringrequired
  • categories[].namestringrequired
  • tagsobject[]required
  • tags[].idstringrequired
  • tags[].slugstringrequired
  • tags[].namestringrequired
  • authorsobject[]required
  • authors[].idstringrequired
  • authors[].slugstringrequired
  • authors[].namestringrequired
  • reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • contentMarkdownstringrequired
  • segmentsobject[]required

    Structured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.

  • tocobject[]required

    Table of contents headings (H2/H3) with anchor ids.

  • toc[].idstringrequired
  • toc[].labelstringrequired
  • toc[].level2 | 3required
  • showTocbooleanrequired
  • seoobjectrequired
  • seo.seoTitlestring | nullrequired
  • seo.seoDescriptionstring | nullrequired
  • seo.seoKeywordsstring[] | nullrequired
  • seo.ogImagestring | nullrequired

    Share image display URL (changes with versions; do not store it).

  • seo.ogImageAssetIdstring | nullrequired

    assetId of the share image when it is in the media library.

  • seo.ogImageAltstring | nullrequired
  • seo.ogImageFramingobject | null
  • seo.ogImageFraming.focusobject
  • seo.ogImageFraming.screensobject
  • seo.ogImageFraming.fitMode"contain" | "cover"
  • seo.ogImageFraming.padColorstring
  • seo.ogTitlestring | nullrequired
  • seo.ogDescriptionstring | nullrequired
  • seo.canonicalUrlstring | nullrequired
  • seo.robotsIndexstringrequired
  • seo.robotsFollowstringrequired
  • seo.twitterCardstringrequired
  • seo.autoOgEnabledbooleanrequired
  • seo.autoOgImagestring | nullrequired
  • seo.excludeFromSiteSearchbooleanrequired

    When true, this post is excluded from visitor site search (independent of robotsIndex).

  • assetsobjectrequired

    The media-library files contentMarkdown and the share image use, by assetId.

  • createdAtstringrequired
  • formatNotesstring[]

    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

  • FORBIDDEN 403
  • POST_NOT_FOUND 404

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 '{}'
Response
{
  "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![Starter jar on the counter](asset:asset_example01)\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![Starter jar on the counter](https://media.kanorio.com/example/sunny-bakery-hero.jpg)\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

POST/sites/{id}/posts/{postId}/reject-review
betablog:publishdefault (120/minute per token)

Send 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 · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • postIdstring · pathrequired

    Blog post id. Get it from posts.list.

Request body

  • notestring | null

Response 200

  • idstringrequired
  • slugstringrequired
  • titlestringrequired
  • excerptstring | nullrequired
  • status"DRAFT" | "PUBLISHED"required
  • publishedAtstring | nullrequired
  • updatedAtstringrequired
  • contentUpdatedAtstring | nullrequired
  • readingTimeMinnumber | nullrequired
  • hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • primaryCategoryobject | nullrequired
  • primaryCategory.idstringrequired
  • primaryCategory.slugstringrequired
  • primaryCategory.namestringrequired
  • categoriesobject[]required
  • categories[].idstringrequired
  • categories[].slugstringrequired
  • categories[].namestringrequired
  • tagsobject[]required
  • tags[].idstringrequired
  • tags[].slugstringrequired
  • tags[].namestringrequired
  • authorsobject[]required
  • authors[].idstringrequired
  • authors[].slugstringrequired
  • authors[].namestringrequired
  • reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • contentMarkdownstringrequired
  • segmentsobject[]required

    Structured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.

  • tocobject[]required

    Table of contents headings (H2/H3) with anchor ids.

  • toc[].idstringrequired
  • toc[].labelstringrequired
  • toc[].level2 | 3required
  • showTocbooleanrequired
  • seoobjectrequired
  • seo.seoTitlestring | nullrequired
  • seo.seoDescriptionstring | nullrequired
  • seo.seoKeywordsstring[] | nullrequired
  • seo.ogImagestring | nullrequired

    Share image display URL (changes with versions; do not store it).

  • seo.ogImageAssetIdstring | nullrequired

    assetId of the share image when it is in the media library.

  • seo.ogImageAltstring | nullrequired
  • seo.ogImageFramingobject | null
  • seo.ogImageFraming.focusobject
  • seo.ogImageFraming.screensobject
  • seo.ogImageFraming.fitMode"contain" | "cover"
  • seo.ogImageFraming.padColorstring
  • seo.ogTitlestring | nullrequired
  • seo.ogDescriptionstring | nullrequired
  • seo.canonicalUrlstring | nullrequired
  • seo.robotsIndexstringrequired
  • seo.robotsFollowstringrequired
  • seo.twitterCardstringrequired
  • seo.autoOgEnabledbooleanrequired
  • seo.autoOgImagestring | nullrequired
  • seo.excludeFromSiteSearchbooleanrequired

    When true, this post is excluded from visitor site search (independent of robotsIndex).

  • assetsobjectrequired

    The media-library files contentMarkdown and the share image use, by assetId.

  • createdAtstringrequired
  • formatNotesstring[]

    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

  • FORBIDDEN 403
  • POST_NOT_FOUND 404
  • VALIDATION 400 · 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."}'
Response
{
  "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![Starter jar on the counter](asset:asset_example01)\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![Starter jar on the counter](https://media.kanorio.com/example/sunny-bakery-hero.jpg)\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

POST/sites/{id}/posts/{postId}/unschedule
betablog:publishdefault (120/minute per token)

Cancel a scheduled publish (posts.publish with scheduledAt). Leaves the draft and published states untouched. Errors: FORBIDDEN, POST_NOT_FOUND.

Parameters

  • idstring · pathrequired

    Website id. Get it from sites.list or from sites.create.

  • postIdstring · pathrequired

    Blog post id. Get it from posts.list.

Response 200

  • idstringrequired
  • slugstringrequired
  • titlestringrequired
  • excerptstring | nullrequired
  • status"DRAFT" | "PUBLISHED"required
  • publishedAtstring | nullrequired
  • updatedAtstringrequired
  • contentUpdatedAtstring | nullrequired
  • readingTimeMinnumber | nullrequired
  • hasDraftChangesbooleanrequired

    Published post has edits not yet re-published.

  • primaryCategoryobject | nullrequired
  • primaryCategory.idstringrequired
  • primaryCategory.slugstringrequired
  • primaryCategory.namestringrequired
  • categoriesobject[]required
  • categories[].idstringrequired
  • categories[].slugstringrequired
  • categories[].namestringrequired
  • tagsobject[]required
  • tags[].idstringrequired
  • tags[].slugstringrequired
  • tags[].namestringrequired
  • authorsobject[]required
  • authors[].idstringrequired
  • authors[].slugstringrequired
  • authors[].namestringrequired
  • reviewRequestedAtstring | nullrequired

    Set by posts.request_review; non-null means it is waiting for someone with blog:publish. Cleared on posts.publish/posts.reject_review.

  • reviewRequestedByUserIdstring | nullrequired

    Who last requested review; kept after a rejection so posts.request_review can be called again by the same person.

  • reviewNotestring | nullrequired

    Reason from the last posts.reject_review call; cleared on the next request or successful publish.

  • contentMarkdownstringrequired
  • segmentsobject[]required

    Structured content blocks (markdown, cta, faq, gallery, toggle, embed, audio, pdf, file) parsed from contentMarkdown.

  • tocobject[]required

    Table of contents headings (H2/H3) with anchor ids.

  • toc[].idstringrequired
  • toc[].labelstringrequired
  • toc[].level2 | 3required
  • showTocbooleanrequired
  • seoobjectrequired
  • seo.seoTitlestring | nullrequired
  • seo.seoDescriptionstring | nullrequired
  • seo.seoKeywordsstring[] | nullrequired
  • seo.ogImagestring | nullrequired

    Share image display URL (changes with versions; do not store it).

  • seo.ogImageAssetIdstring | nullrequired

    assetId of the share image when it is in the media library.

  • seo.ogImageAltstring | nullrequired
  • seo.ogImageFramingobject | null
  • seo.ogImageFraming.focusobject
  • seo.ogImageFraming.screensobject
  • seo.ogImageFraming.fitMode"contain" | "cover"
  • seo.ogImageFraming.padColorstring
  • seo.ogTitlestring | nullrequired
  • seo.ogDescriptionstring | nullrequired
  • seo.canonicalUrlstring | nullrequired
  • seo.robotsIndexstringrequired
  • seo.robotsFollowstringrequired
  • seo.twitterCardstringrequired
  • seo.autoOgEnabledbooleanrequired
  • seo.autoOgImagestring | nullrequired
  • seo.excludeFromSiteSearchbooleanrequired

    When true, this post is excluded from visitor site search (independent of robotsIndex).

  • assetsobjectrequired

    The media-library files contentMarkdown and the share image use, by assetId.

  • createdAtstringrequired
  • formatNotesstring[]

    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

  • FORBIDDEN 403
  • POST_NOT_FOUND 404

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 '{}'
Response
{
  "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![Starter jar on the counter](asset:asset_example01)\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![Starter jar on the counter](https://media.kanorio.com/example/sunny-bakery-hero.jpg)\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"
}