# Revisions · Management API

> Management API reference for revisions: GET /sites/{id}/revisions, GET /sites/{id}/revisions/{revisionId}, POST /sites/{id}/revisions/{revisionId}/restore.

> Markdown version of https://kanorio.com/en/developers/management-api/revisions. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Base URL: `https://app.kanorio.com/api/v1` · [Errors](https://kanorio.com/en/developers/basics/errors) · [Pagination](https://kanorio.com/en/developers/basics/pagination)

### List revisions

`GET /sites/{id}/revisions`

- Stability: beta
- Permission: `editor:enter`
- Rate limit: default (120/minute per token)
- MCP tool: `revisions_list`

List content revisions for a website draft (subject: WEBSITE, subjectId omitted or equal to websiteId), a blog post (subject: BLOG_POST, subjectId: postId), or a custom collection item (subject: CONTENT_ITEM, subjectId: itemId). Revisions are retained for 30 days and coalesced within 10-minute autosave windows. Optionally pass pageId when listing WEBSITE revisions to filter to edits that touched that page. Newest first. Errors: FORBIDDEN, INVALID_CURSOR.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `subject` | query | "WEBSITE" \| "BLOG_POST" \| "CONTENT_ITEM" | no | Defaults to WEBSITE. |
| `subjectId` | query | string | no | Required unless subject is WEBSITE (pass postId for BLOG_POST, itemId for CONTENT_ITEM); defaults to websiteId for WEBSITE. |
| `pageId` | query | string | no | Optional filter for WEBSITE revisions: only revisions that modified this page or whole-site checkpoints. |
| `limit` | query | integer | no | Maximum number of items to return, 1–100. Defaults to 20. |
| `cursor` | query | string | no | Opaque cursor from the previous page's nextCursor. Omit for the first page. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].id` | string | yes | — |
| `data[].websiteId` | string | yes | — |
| `data[].subject` | "WEBSITE" \| "BLOG_POST" \| "CONTENT_ITEM" | yes | Subject type: WEBSITE for site-wide draft and page sections, BLOG_POST for a blog article, CONTENT_ITEM for a custom collection item. |
| `data[].subjectId` | string | yes | — |
| `data[].kind` | "AUTO" \| "PUBLISH" \| "PRE_RESTORE" \| "PRE_AI" \| "MANUAL" | yes | Why the revision was recorded. |
| `data[].schemaVersion` | integer | yes | — |
| `data[].contentHash` | string | yes | — |
| `data[].summary` | object \| null | yes | — |
| `data[].summary.title` | string | no | — |
| `data[].summary.editedPageIds` | string[] | no | — |
| `data[].summary.editedPageTitles` | object | no | — |
| `data[].summary.editedScopes` | "sections" \| "header" \| "footer" \| "siteSettings" \| "theme" \| "pages"[] | no | — |
| `data[].summary.collectionKey` | string | no | CONTENT_ITEM only: the collection this item belongs to. |
| `data[].summary.status` | "draft" \| "published" \| "changed" | no | CONTENT_ITEM only. |
| `data[].summary.backup` | "beforeDelete" | no | Set when the system recorded this revision automatically (e.g. right before the item was moved to the trash). |
| `data[].label` | string \| null | yes | — |
| `data[].actor` | object | yes | — |
| `data[].actor.id` | string \| null | yes | — |
| `data[].actor.name` | string \| null | yes | — |
| `data[].actor.email` | string \| null | yes | — |
| `data[].actor.image` | string \| null | yes | — |
| `data[].actor.isAgent` | boolean | yes | True when the revision was written through an API token or MCP client. |
| `data[].actor.tokenName` | string \| null | no | — |
| `data[].createdAt` | string | yes | — |
| `data[].updatedAt` | string | yes | — |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `INVALID_CURSOR` | 400 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/revisions?subject=WEBSITE&limit=20" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "id": "rev_example01",
      "websiteId": "ws_example123",
      "subject": "WEBSITE",
      "subjectId": "ws_example123",
      "kind": "AUTO",
      "schemaVersion": 1,
      "contentHash": "a1b2c3d4:1240",
      "summary": {
        "editedPageIds": [
          "pg_example456"
        ],
        "editedPageTitles": {
          "pg_example456": "Home"
        },
        "editedScopes": [
          "sections"
        ]
      },
      "label": null,
      "actor": {
        "id": "usr_example01",
        "name": "Sunny Lin",
        "email": "sunny@example.com",
        "image": null,
        "isAgent": true,
        "tokenName": "Cursor MCP"
      },
      "createdAt": "2026-10-01T02:30:00.000Z",
      "updatedAt": "2026-10-06T08:00:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Retrieve a revision

`GET /sites/{id}/revisions/{revisionId}`

- Stability: beta
- Permission: `editor:enter`
- Rate limit: default (120/minute per token)
- MCP tool: `revisions_get`

Get the full unpacked snapshot of a content revision by revisionId. Media references belonging to this site's library are presented as asset:<assetId> with display metadata in assets. Errors: FORBIDDEN, REVISION_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `revisionId` | path | string | yes | Revision identifier from revisions.list. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `revision` | object | yes | — |
| `revision.id` | string | yes | — |
| `revision.websiteId` | string | yes | — |
| `revision.subject` | "WEBSITE" \| "BLOG_POST" \| "CONTENT_ITEM" | yes | Subject type: WEBSITE for site-wide draft and page sections, BLOG_POST for a blog article, CONTENT_ITEM for a custom collection item. |
| `revision.subjectId` | string | yes | — |
| `revision.kind` | "AUTO" \| "PUBLISH" \| "PRE_RESTORE" \| "PRE_AI" \| "MANUAL" | yes | Why the revision was recorded. |
| `revision.schemaVersion` | integer | yes | — |
| `revision.contentHash` | string | yes | — |
| `revision.summary` | object \| null | yes | — |
| `revision.summary.title` | string | no | — |
| `revision.summary.editedPageIds` | string[] | no | — |
| `revision.summary.editedPageTitles` | object | no | — |
| `revision.summary.editedScopes` | "sections" \| "header" \| "footer" \| "siteSettings" \| "theme" \| "pages"[] | no | — |
| `revision.summary.collectionKey` | string | no | CONTENT_ITEM only: the collection this item belongs to. |
| `revision.summary.status` | "draft" \| "published" \| "changed" | no | CONTENT_ITEM only. |
| `revision.summary.backup` | "beforeDelete" | no | Set when the system recorded this revision automatically (e.g. right before the item was moved to the trash). |
| `revision.label` | string \| null | yes | — |
| `revision.actor` | object | yes | — |
| `revision.actor.id` | string \| null | yes | — |
| `revision.actor.name` | string \| null | yes | — |
| `revision.actor.email` | string \| null | yes | — |
| `revision.actor.image` | string \| null | yes | — |
| `revision.actor.isAgent` | boolean | yes | True when the revision was written through an API token or MCP client. |
| `revision.actor.tokenName` | string \| null | no | — |
| `revision.createdAt` | string | yes | — |
| `revision.updatedAt` | string | yes | — |
| `snapshot` | object | yes | — |
| `assets` | object | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `REVISION_NOT_FOUND` | 404 | — |

```bash
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/revisions/{revisionId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "revision": {
    "id": "rev_example01",
    "websiteId": "ws_example123",
    "subject": "WEBSITE",
    "subjectId": "ws_example123",
    "kind": "AUTO",
    "schemaVersion": 1,
    "contentHash": "a1b2c3d4:1240",
    "summary": {
      "editedPageIds": [
        "pg_example456"
      ],
      "editedPageTitles": {
        "pg_example456": "Home"
      },
      "editedScopes": [
        "sections"
      ]
    },
    "label": null,
    "actor": {
      "id": "usr_example01",
      "name": "Sunny Lin",
      "email": "sunny@example.com",
      "image": null,
      "isAgent": true,
      "tokenName": "Cursor MCP"
    },
    "createdAt": "2026-10-01T02:30:00.000Z",
    "updatedAt": "2026-10-06T08:00:00.000Z"
  },
  "snapshot": {
    "siteSettings": {},
    "globalSections": [],
    "themeSettings": {
      "sectionDensity": "standard"
    },
    "pages": [
      {
        "id": "pg_example456",
        "slug": "/",
        "title": "Home",
        "isHomePage": true,
        "sections": []
      }
    ]
  },
  "assets": {}
}
```

### Restore a revision

`POST /sites/{id}/revisions/{revisionId}/restore`

- Stability: beta
- Permission: `content:edit`
- Rate limit: default (120/minute per token)
- MCP tool: `revisions_restore`

Restore a website draft, blog post working copy, or custom collection item to a previous revision. Before writing, the current draft is automatically backed up as a PRE_RESTORE revision so the restore itself can be undone. Restoring writes only to the draft/working copy and never alters the live published site directly; call site.publish, posts.publish or items.publish to publish. For WEBSITE revisions, pass mode: "page" with pageId to restore only a single page's sections, or mode: "full" (default) to restore the entire site draft and theme settings. A CONTENT_ITEM that is in the trash cannot be restored to a revision (SUBJECT_NOT_FOUND): call items.restore first. Errors: FORBIDDEN, REVISION_NOT_FOUND, SUBJECT_NOT_FOUND, PAGE_NOT_FOUND_IN_REVISION, PAGE_NOT_FOUND_IN_SITE, VALIDATION_FAILED, WRITE_FROZEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `revisionId` | path | string | yes | Revision identifier from revisions.list. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `mode` | "full" \| "page" | no | Defaults to "full". Use "page" with pageId to restore only one page of a WEBSITE revision. |
| `pageId` | string | no | Required when mode is "page". |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `restored` | true | yes | — |
| `subject` | "WEBSITE" \| "BLOG_POST" \| "CONTENT_ITEM" | yes | Subject type: WEBSITE for site-wide draft and page sections, BLOG_POST for a blog article, CONTENT_ITEM for a custom collection item. |
| `subjectId` | string | yes | — |
| `revisionId` | string | yes | — |
| `preRestoreRevisionId` | string \| null | yes | — |
| `kind` | "AUTO" \| "PUBLISH" \| "PRE_RESTORE" \| "PRE_AI" \| "MANUAL" | yes | Why the revision was recorded. |
| `mode` | "full" \| "page" | yes | — |
| `pageId` | string | no | Resolved page id when mode is page. |
| `collectionKey` | string | no | CONTENT_ITEM only: the collection this item belongs to. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `REVISION_NOT_FOUND` | 404 | — |
| `SUBJECT_NOT_FOUND` | 404 | — |
| `PAGE_NOT_FOUND_IN_REVISION` | 422 | — |
| `PAGE_NOT_FOUND_IN_SITE` | 422 | — |
| `VALIDATION_FAILED` | 422 | — |
| `WRITE_FROZEN` | 503 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/revisions/{revisionId}/restore" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"page","pageId":"pg_example456"}'
```

```json
{
  "restored": true,
  "subject": "WEBSITE",
  "subjectId": "ws_example123",
  "revisionId": "rev_example01",
  "preRestoreRevisionId": "rev_backup01",
  "kind": "AUTO",
  "mode": "page"
}
```
