# Pages · Management API

> Management API reference for pages: GET /sites/{id}/pages, POST /sites/{id}/pages, PUT /sites/{id}/pages/order, GET /sites/{id}/pages/{pageId}, ….

> Markdown version of https://kanorio.com/en/developers/management-api/pages. 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 pages

`GET /sites/{id}/pages`

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

List the pages of a website (home page first), with slug, visibility, SEO fields, systemManaged, lockedCoreSectionTypes and the revision marker updatedAt. Sections are not included; use pages.get. Per-product pages are not included; product copy, SEO and publish state live on products.*. For the whole page tree in one call use sites.structure. 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. |
| `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[].slug` | string | yes | URL path, "/" for the home page. |
| `data[].path` | string[] | yes | Path segments (e.g. ["services", "haircut"] for "/services/haircut"; [] for "/"). |
| `data[].parentSlug` | string \| null | yes | Immediate parent page slug for generic nested pages (e.g. "/services" for "/services/haircut"; null for "/", top-level pages, and system pages such as blog/shop/product/legal). |
| `data[].isDirectory` | boolean | yes | True when this is a hidden folder page that exists to group subpage URLs. |
| `data[].depth` | integer | yes | Hierarchy depth: 0 for home page, 1 for top-level pages, up to 3. |
| `data[].ancestors` | object[] | yes | Visible ancestor pages from root to immediate parent (only generic pages participate; system pages such as blog, shop, product, and legal always return []). |
| `data[].ancestors[].id` | string | yes | — |
| `data[].ancestors[].slug` | string | yes | — |
| `data[].ancestors[].title` | string | yes | — |
| `data[].breadcrumbs` | object[] | yes | Visible breadcrumb trail shown on the page and in BreadcrumbList JSON-LD (up to 4 items; empty when the page has no visible ancestors or is a system page). |
| `data[].breadcrumbs[].name` | string | yes | — |
| `data[].breadcrumbs[].url` | string | yes | — |
| `data[].breadcrumbSource` | "url" \| "custom" | yes | Whether breadcrumbs follow the URL hierarchy ("url") or a custom breadcrumbParentSlug override ("custom"). |
| `data[].breadcrumbParentSlug` | string \| null | yes | Optional parent page slug override for breadcrumbs and structured data only. |
| `data[].title` | string | yes | — |
| `data[].pageType` | string | yes | — |
| `data[].isHomePage` | boolean | yes | — |
| `data[].isVisible` | boolean | yes | Hidden pages are not published or linked from navigation. |
| `data[].order` | integer | yes | — |
| `data[].globalHeaderId` | string \| null | yes | — |
| `data[].globalFooterId` | string \| null | yes | — |
| `data[].updatedAt` | string | yes | Revision marker. Pass it back as ifUnmodifiedSince when replacing sections. |
| `data[].seo` | object | yes | — |
| `data[].seo.seoTitle` | string \| null | yes | — |
| `data[].seo.seoDescription` | string \| null | yes | — |
| `data[].seo.seoKeywords` | string[] \| null | yes | — |
| `data[].seo.ogTitle` | string \| null | yes | — |
| `data[].seo.ogDescription` | string \| null | yes | — |
| `data[].seo.ogImage` | string \| null | yes | Share image display URL (changes with versions; do not store it). |
| `data[].seo.ogImageAssetId` | string \| null | yes | assetId of the share image when it is in the media library. |
| `data[].seo.ogImageFraming` | object \| null | no | — |
| `data[].seo.canonicalUrl` | string \| null | yes | — |
| `data[].seo.robotsIndex` | string \| null | yes | — |
| `data[].seo.robotsFollow` | string \| null | yes | — |
| `data[].seo.excludeFromSiteSearch` | boolean | yes | When true, this page is excluded from visitor site search (independent of robotsIndex). |
| `data[].systemManaged` | boolean | yes | True when Kanorio owns this page (shop index, blog index, taxonomy templates). Those pages cannot be created or deleted. |
| `data[].lockedCoreSectionTypes` | string[] | yes | Section types locked at index 0. pages.replace_sections must keep exactly one of each, with its locked source. Empty for ordinary pages. |
| `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}/pages" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "data": [
    {
      "id": "pg_example456",
      "slug": "/",
      "path": [],
      "parentSlug": null,
      "isDirectory": false,
      "depth": 0,
      "ancestors": [],
      "breadcrumbs": [],
      "breadcrumbSource": "url",
      "breadcrumbParentSlug": null,
      "title": "Home",
      "pageType": "generic",
      "isHomePage": true,
      "isVisible": true,
      "order": 0,
      "globalHeaderId": "sec_header01",
      "globalFooterId": "sec_footer01",
      "updatedAt": "2026-10-06T08:00:00.000Z",
      "seo": {
        "seoTitle": "Sunny Bakery",
        "seoDescription": "Artisan sourdough bakery in Taipei.",
        "seoKeywords": [
          "sourdough",
          "artisan bakery"
        ],
        "ogTitle": null,
        "ogDescription": null,
        "ogImage": null,
        "ogImageAssetId": null,
        "ogImageFraming": null,
        "canonicalUrl": null,
        "robotsIndex": null,
        "robotsFollow": null,
        "excludeFromSiteSearch": false
      },
      "systemManaged": false,
      "lockedCoreSectionTypes": []
    },
    {
      "id": "pg_example789",
      "slug": "/about",
      "path": [
        "about"
      ],
      "parentSlug": null,
      "isDirectory": false,
      "depth": 1,
      "ancestors": [],
      "breadcrumbs": [],
      "breadcrumbSource": "url",
      "breadcrumbParentSlug": null,
      "title": "About Us",
      "pageType": "generic",
      "isHomePage": false,
      "isVisible": true,
      "order": 1,
      "globalHeaderId": "sec_header01",
      "globalFooterId": "sec_footer01",
      "updatedAt": "2026-10-06T08:00:00.000Z",
      "seo": {
        "seoTitle": "About Us | Sunny Bakery",
        "seoDescription": "Naturally leavened sourdough baked fresh every morning.",
        "seoKeywords": [
          "sourdough",
          "artisan bakery"
        ],
        "ogTitle": null,
        "ogDescription": null,
        "ogImage": null,
        "ogImageAssetId": null,
        "ogImageFraming": null,
        "canonicalUrl": null,
        "robotsIndex": null,
        "robotsFollow": null,
        "excludeFromSiteSearch": false
      },
      "systemManaged": false,
      "lockedCoreSectionTypes": []
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Create a page

`POST /sites/{id}/pages`

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

Create an empty page (up to 3 levels deep). Pass parentSlug (e.g. "/services") plus the last segment in slug (e.g. "/haircut") rather than concatenating multi-segment paths yourself; if the parent page does not exist yet, a hidden folder page is created automatically. Omit slug to have one generated from the title. The home page ("/") already exists and cannot be created; link-in-bio pages have a dedicated flow. The new page uses the home page's header and footer unless you pass globalHeaderId/globalFooterId. It is not added to the navigation automatically: after creating pages, call globals.replace_header with a navItem per visible page (use a short label, not the page title). pageType cannot be a system page (shop, blog, taxonomy templates) or link-in-bio; Kanorio creates those. Passing one returns SYSTEM_PAGE_LOCKED. Fill the page afterwards with pages.replace_sections. Errors: FORBIDDEN, VALIDATION, SLUG_TAKEN, RESERVED_SLUG, RESERVED_SEGMENT, SEGMENT_TOO_LONG, DEPTH_EXCEEDED, PARENT_NOT_ALLOWED, BREADCRUMB_PARENT_INVALID, HOME_NOT_CREATABLE, LINK_IN_BIO_DEDICATED_ENTRY, SYSTEM_PAGE_LOCKED, PAGE_LIMIT_REACHED.

**Parameters**

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

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | yes | Page title; also used for navigation labels. |
| `slug` | string | no | Last URL segment starting with "/" (e.g. "/haircut" when parentSlug is "/services"), or full path (e.g. "/about"). Generated from title when omitted. |
| `parentSlug` | string \| null | no | Optional parent page slug (e.g. "/services"). Pass parentSlug + last segment instead of concatenating "/a/b" yourself. |
| `breadcrumbParentSlug` | string \| null | no | Optional parent page slug override for breadcrumbs and BreadcrumbList JSON-LD only (use only when the typical user trail differs from the URL path; does not change the URL). |
| `pageType` | "generic" \| "article" \| "legal-cookie-policy" \| "legal-privacy-policy" \| "legal-terms-of-service" \| "legal-purchase-terms" \| "legal-return-policy" \| "legal-disclaimer" | no | Defaults to generic. System pages (shop, blog, taxonomy templates) and link-in-bio cannot be created here. |
| `isVisible` | boolean | no | Defaults to true. Hidden pages are not published. |
| `globalHeaderId` | string \| null | no | Header from globalSections. Defaults to the home page's header; pass null for a page without a header. |
| `globalFooterId` | string \| null | no | Footer from globalSections. Defaults to the home page's footer; pass null for a page without a footer. |
| `seoTitle` | string | no | — |
| `seoDescription` | string | no | — |

**Response** (201)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `slug` | string | yes | URL path, "/" for the home page. |
| `path` | string[] | yes | Path segments (e.g. ["services", "haircut"] for "/services/haircut"; [] for "/"). |
| `parentSlug` | string \| null | yes | Immediate parent page slug for generic nested pages (e.g. "/services" for "/services/haircut"; null for "/", top-level pages, and system pages such as blog/shop/product/legal). |
| `isDirectory` | boolean | yes | True when this is a hidden folder page that exists to group subpage URLs. |
| `depth` | integer | yes | Hierarchy depth: 0 for home page, 1 for top-level pages, up to 3. |
| `ancestors` | object[] | yes | Visible ancestor pages from root to immediate parent (only generic pages participate; system pages such as blog, shop, product, and legal always return []). |
| `ancestors[].id` | string | yes | — |
| `ancestors[].slug` | string | yes | — |
| `ancestors[].title` | string | yes | — |
| `breadcrumbs` | object[] | yes | Visible breadcrumb trail shown on the page and in BreadcrumbList JSON-LD (up to 4 items; empty when the page has no visible ancestors or is a system page). |
| `breadcrumbs[].name` | string | yes | — |
| `breadcrumbs[].url` | string | yes | — |
| `breadcrumbSource` | "url" \| "custom" | yes | Whether breadcrumbs follow the URL hierarchy ("url") or a custom breadcrumbParentSlug override ("custom"). |
| `breadcrumbParentSlug` | string \| null | yes | Optional parent page slug override for breadcrumbs and structured data only. |
| `title` | string | yes | — |
| `pageType` | string | yes | — |
| `isHomePage` | boolean | yes | — |
| `isVisible` | boolean | yes | Hidden pages are not published or linked from navigation. |
| `order` | integer | yes | — |
| `globalHeaderId` | string \| null | yes | — |
| `globalFooterId` | string \| null | yes | — |
| `updatedAt` | string | yes | Revision marker. Pass it back as ifUnmodifiedSince when replacing sections. |
| `seo` | object | yes | — |
| `seo.seoTitle` | string \| null | yes | — |
| `seo.seoDescription` | string \| null | yes | — |
| `seo.seoKeywords` | string[] \| null | yes | — |
| `seo.ogTitle` | string \| null | yes | — |
| `seo.ogDescription` | string \| null | yes | — |
| `seo.ogImage` | string \| null | yes | Share image display URL (changes with versions; do not store it). |
| `seo.ogImageAssetId` | string \| null | yes | assetId of the share image when it is in the media library. |
| `seo.ogImageFraming` | object \| null | no | — |
| `seo.ogImageFraming.focus` | object | no | — |
| `seo.ogImageFraming.screens` | object | no | — |
| `seo.ogImageFraming.fitMode` | "contain" \| "cover" | no | — |
| `seo.ogImageFraming.padColor` | string | no | — |
| `seo.canonicalUrl` | string \| null | yes | — |
| `seo.robotsIndex` | string \| null | yes | — |
| `seo.robotsFollow` | string \| null | yes | — |
| `seo.excludeFromSiteSearch` | boolean | yes | When true, this page is excluded from visitor site search (independent of robotsIndex). |
| `systemManaged` | boolean | yes | True when Kanorio owns this page (shop index, blog index, taxonomy templates). Those pages cannot be created or deleted. |
| `lockedCoreSectionTypes` | string[] | yes | Section types locked at index 0. pages.replace_sections must keep exactly one of each, with its locked source. Empty for ordinary pages. |
| `warnings` | string[] | no | Non-fatal warnings from this write (e.g. REDIRECT_SHADOWS_PAGE when a manual redirect intercepts this URL). |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `VALIDATION` | 400 | — |
| `SLUG_TAKEN` | 409 | — |
| `RESERVED_SLUG` | 422 | — |
| `RESERVED_SEGMENT` | 422 | — |
| `SEGMENT_TOO_LONG` | 422 | — |
| `DEPTH_EXCEEDED` | 422 | — |
| `PARENT_NOT_ALLOWED` | 422 | — |
| `BREADCRUMB_PARENT_INVALID` | 422 | — |
| `HOME_NOT_CREATABLE` | 422 | — |
| `LINK_IN_BIO_DEDICATED_ENTRY` | 422 | — |
| `SYSTEM_PAGE_LOCKED` | 422 | — |
| `PAGE_LIMIT_REACHED` | 422 | — |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/pages" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"About Us","slug":"/about","isVisible":true}'
```

```json
{
  "id": "pg_example789",
  "slug": "/about",
  "path": [
    "about"
  ],
  "parentSlug": null,
  "isDirectory": false,
  "depth": 1,
  "ancestors": [],
  "breadcrumbs": [],
  "breadcrumbSource": "url",
  "breadcrumbParentSlug": null,
  "title": "About Us",
  "pageType": "generic",
  "isHomePage": false,
  "isVisible": true,
  "order": 1,
  "globalHeaderId": "sec_header01",
  "globalFooterId": "sec_footer01",
  "updatedAt": "2026-10-06T08:00:00.000Z",
  "seo": {
    "seoTitle": "About Us | Sunny Bakery",
    "seoDescription": "Naturally leavened sourdough baked fresh every morning.",
    "seoKeywords": [
      "sourdough",
      "artisan bakery"
    ],
    "ogTitle": null,
    "ogDescription": null,
    "ogImage": null,
    "ogImageAssetId": null,
    "ogImageFraming": null,
    "canonicalUrl": null,
    "robotsIndex": null,
    "robotsFollow": null,
    "excludeFromSiteSearch": false
  },
  "systemManaged": false,
  "lockedCoreSectionTypes": []
}
```

### Reorder pages

`PUT /sites/{id}/pages/order`

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

Set the order of all non-home pages (the home page is always first). Order drives the page list in the console; navigation links come from the header's navItems, which you manage with globals.replace_header. pageIds must list every non-home page from pages.list exactly once; legal pages created with the site count too. Legacy per-product pages are omitted and keep their current position. Errors: FORBIDDEN, FOREIGN_PAGE (an id is not a page of this site), HOME_NOT_SORTABLE (home page included), INCOMPLETE_ORDER (some pages missing or duplicated).

**Parameters**

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

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pageIds` | string[] | yes | All non-home page ids in the desired order. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pages` | object[] | yes | — |
| `pages[].id` | string | yes | — |
| `pages[].slug` | string | yes | URL path, "/" for the home page. |
| `pages[].path` | string[] | yes | Path segments (e.g. ["services", "haircut"] for "/services/haircut"; [] for "/"). |
| `pages[].parentSlug` | string \| null | yes | Immediate parent page slug for generic nested pages (e.g. "/services" for "/services/haircut"; null for "/", top-level pages, and system pages such as blog/shop/product/legal). |
| `pages[].isDirectory` | boolean | yes | True when this is a hidden folder page that exists to group subpage URLs. |
| `pages[].depth` | integer | yes | Hierarchy depth: 0 for home page, 1 for top-level pages, up to 3. |
| `pages[].ancestors` | object[] | yes | Visible ancestor pages from root to immediate parent (only generic pages participate; system pages such as blog, shop, product, and legal always return []). |
| `pages[].ancestors[].id` | string | yes | — |
| `pages[].ancestors[].slug` | string | yes | — |
| `pages[].ancestors[].title` | string | yes | — |
| `pages[].breadcrumbs` | object[] | yes | Visible breadcrumb trail shown on the page and in BreadcrumbList JSON-LD (up to 4 items; empty when the page has no visible ancestors or is a system page). |
| `pages[].breadcrumbs[].name` | string | yes | — |
| `pages[].breadcrumbs[].url` | string | yes | — |
| `pages[].breadcrumbSource` | "url" \| "custom" | yes | Whether breadcrumbs follow the URL hierarchy ("url") or a custom breadcrumbParentSlug override ("custom"). |
| `pages[].breadcrumbParentSlug` | string \| null | yes | Optional parent page slug override for breadcrumbs and structured data only. |
| `pages[].title` | string | yes | — |
| `pages[].pageType` | string | yes | — |
| `pages[].isHomePage` | boolean | yes | — |
| `pages[].isVisible` | boolean | yes | Hidden pages are not published or linked from navigation. |
| `pages[].order` | integer | yes | — |
| `pages[].globalHeaderId` | string \| null | yes | — |
| `pages[].globalFooterId` | string \| null | yes | — |
| `pages[].updatedAt` | string | yes | Revision marker. Pass it back as ifUnmodifiedSince when replacing sections. |
| `pages[].seo` | object | yes | — |
| `pages[].seo.seoTitle` | string \| null | yes | — |
| `pages[].seo.seoDescription` | string \| null | yes | — |
| `pages[].seo.seoKeywords` | string[] \| null | yes | — |
| `pages[].seo.ogTitle` | string \| null | yes | — |
| `pages[].seo.ogDescription` | string \| null | yes | — |
| `pages[].seo.ogImage` | string \| null | yes | Share image display URL (changes with versions; do not store it). |
| `pages[].seo.ogImageAssetId` | string \| null | yes | assetId of the share image when it is in the media library. |
| `pages[].seo.ogImageFraming` | object \| null | no | — |
| `pages[].seo.canonicalUrl` | string \| null | yes | — |
| `pages[].seo.robotsIndex` | string \| null | yes | — |
| `pages[].seo.robotsFollow` | string \| null | yes | — |
| `pages[].seo.excludeFromSiteSearch` | boolean | yes | When true, this page is excluded from visitor site search (independent of robotsIndex). |
| `pages[].systemManaged` | boolean | yes | True when Kanorio owns this page (shop index, blog index, taxonomy templates). Those pages cannot be created or deleted. |
| `pages[].lockedCoreSectionTypes` | string[] | yes | Section types locked at index 0. pages.replace_sections must keep exactly one of each, with its locked source. Empty for ordinary pages. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `FOREIGN_PAGE` | 422 | an id is not a page of this site |
| `HOME_NOT_SORTABLE` | 422 | home page included |
| `INCOMPLETE_ORDER` | 400 | some pages missing or duplicated |

```bash
curl -X PUT "https://app.kanorio.com/api/v1/sites/{id}/pages/order" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pageIds":["pg_example789"]}'
```

```json
{
  "pages": [
    {
      "id": "pg_example456",
      "slug": "/",
      "path": [],
      "parentSlug": null,
      "isDirectory": false,
      "depth": 0,
      "ancestors": [],
      "breadcrumbs": [],
      "breadcrumbSource": "url",
      "breadcrumbParentSlug": null,
      "title": "Home",
      "pageType": "generic",
      "isHomePage": true,
      "isVisible": true,
      "order": 0,
      "globalHeaderId": "sec_header01",
      "globalFooterId": "sec_footer01",
      "updatedAt": "2026-10-06T08:00:00.000Z",
      "seo": {
        "seoTitle": "Sunny Bakery",
        "seoDescription": "Artisan sourdough bakery in Taipei.",
        "seoKeywords": [
          "sourdough",
          "artisan bakery"
        ],
        "ogTitle": null,
        "ogDescription": null,
        "ogImage": null,
        "ogImageAssetId": null,
        "ogImageFraming": null,
        "canonicalUrl": null,
        "robotsIndex": null,
        "robotsFollow": null,
        "excludeFromSiteSearch": false
      },
      "systemManaged": false,
      "lockedCoreSectionTypes": []
    },
    {
      "id": "pg_example789",
      "slug": "/about",
      "path": [
        "about"
      ],
      "parentSlug": null,
      "isDirectory": false,
      "depth": 1,
      "ancestors": [],
      "breadcrumbs": [],
      "breadcrumbSource": "url",
      "breadcrumbParentSlug": null,
      "title": "About Us",
      "pageType": "generic",
      "isHomePage": false,
      "isVisible": true,
      "order": 1,
      "globalHeaderId": "sec_header01",
      "globalFooterId": "sec_footer01",
      "updatedAt": "2026-10-06T08:00:00.000Z",
      "seo": {
        "seoTitle": "About Us | Sunny Bakery",
        "seoDescription": "Naturally leavened sourdough baked fresh every morning.",
        "seoKeywords": [
          "sourdough",
          "artisan bakery"
        ],
        "ogTitle": null,
        "ogDescription": null,
        "ogImage": null,
        "ogImageAssetId": null,
        "ogImageFraming": null,
        "canonicalUrl": null,
        "robotsIndex": null,
        "robotsFollow": null,
        "excludeFromSiteSearch": false
      },
      "systemManaged": false,
      "lockedCoreSectionTypes": []
    }
  ]
}
```

### Retrieve a page

`GET /sites/{id}/pages/{pageId}`

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

Get one page including its current draft sections, hierarchy metadata (path, parentSlug, isDirectory, depth, ancestors, breadcrumbs, breadcrumbSource), systemManaged, lockedCoreSectionTypes and updatedAt. Always read the page right before pages.replace_sections and pass updatedAt back as ifUnmodifiedSince. Images and files of the site's media library appear as asset:<assetId> in sections; assets holds them (with display URLs). Per-product pages return PAGE_NOT_FOUND; use products.get. On a system page, keep the locked core section at index 0 and do not change its source. Errors: FORBIDDEN, PAGE_NOT_FOUND.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `pageId` | path | string | yes | Page id. Get it from pages.list. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `slug` | string | yes | URL path, "/" for the home page. |
| `path` | string[] | yes | Path segments (e.g. ["services", "haircut"] for "/services/haircut"; [] for "/"). |
| `parentSlug` | string \| null | yes | Immediate parent page slug for generic nested pages (e.g. "/services" for "/services/haircut"; null for "/", top-level pages, and system pages such as blog/shop/product/legal). |
| `isDirectory` | boolean | yes | True when this is a hidden folder page that exists to group subpage URLs. |
| `depth` | integer | yes | Hierarchy depth: 0 for home page, 1 for top-level pages, up to 3. |
| `ancestors` | object[] | yes | Visible ancestor pages from root to immediate parent (only generic pages participate; system pages such as blog, shop, product, and legal always return []). |
| `ancestors[].id` | string | yes | — |
| `ancestors[].slug` | string | yes | — |
| `ancestors[].title` | string | yes | — |
| `breadcrumbs` | object[] | yes | Visible breadcrumb trail shown on the page and in BreadcrumbList JSON-LD (up to 4 items; empty when the page has no visible ancestors or is a system page). |
| `breadcrumbs[].name` | string | yes | — |
| `breadcrumbs[].url` | string | yes | — |
| `breadcrumbSource` | "url" \| "custom" | yes | Whether breadcrumbs follow the URL hierarchy ("url") or a custom breadcrumbParentSlug override ("custom"). |
| `breadcrumbParentSlug` | string \| null | yes | Optional parent page slug override for breadcrumbs and structured data only. |
| `title` | string | yes | — |
| `pageType` | string | yes | — |
| `isHomePage` | boolean | yes | — |
| `isVisible` | boolean | yes | Hidden pages are not published or linked from navigation. |
| `order` | integer | yes | — |
| `globalHeaderId` | string \| null | yes | — |
| `globalFooterId` | string \| null | yes | — |
| `updatedAt` | string | yes | Revision marker. Pass it back as ifUnmodifiedSince when replacing sections. |
| `seo` | object | yes | — |
| `seo.seoTitle` | string \| null | yes | — |
| `seo.seoDescription` | string \| null | yes | — |
| `seo.seoKeywords` | string[] \| null | yes | — |
| `seo.ogTitle` | string \| null | yes | — |
| `seo.ogDescription` | string \| null | yes | — |
| `seo.ogImage` | string \| null | yes | Share image display URL (changes with versions; do not store it). |
| `seo.ogImageAssetId` | string \| null | yes | assetId of the share image when it is in the media library. |
| `seo.ogImageFraming` | object \| null | no | — |
| `seo.ogImageFraming.focus` | object | no | — |
| `seo.ogImageFraming.screens` | object | no | — |
| `seo.ogImageFraming.fitMode` | "contain" \| "cover" | no | — |
| `seo.ogImageFraming.padColor` | string | no | — |
| `seo.canonicalUrl` | string \| null | yes | — |
| `seo.robotsIndex` | string \| null | yes | — |
| `seo.robotsFollow` | string \| null | yes | — |
| `seo.excludeFromSiteSearch` | boolean | yes | When true, this page is excluded from visitor site search (independent of robotsIndex). |
| `sections` | object[] | yes | Current draft sections (what the editor shows), not the published copy. Media-library files appear as asset:<assetId>. |
| `assets` | object | yes | The media-library files the sections use, by assetId. |
| `systemManaged` | boolean | yes | True when Kanorio owns this page (shop index, blog index, taxonomy templates). Those pages cannot be created or deleted. |
| `lockedCoreSectionTypes` | string[] | yes | Section types locked at index 0. pages.replace_sections must keep exactly one of each, with its locked source. Empty for ordinary pages. |

**Errors**

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

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

```json
{
  "id": "pg_example456",
  "slug": "/",
  "path": [],
  "parentSlug": null,
  "isDirectory": false,
  "depth": 0,
  "ancestors": [],
  "breadcrumbs": [],
  "breadcrumbSource": "url",
  "breadcrumbParentSlug": null,
  "title": "Home",
  "pageType": "generic",
  "isHomePage": true,
  "isVisible": true,
  "order": 0,
  "globalHeaderId": "sec_header01",
  "globalFooterId": "sec_footer01",
  "updatedAt": "2026-10-06T08:00:00.000Z",
  "seo": {
    "seoTitle": "Sunny Bakery",
    "seoDescription": "Artisan sourdough bakery in Taipei.",
    "seoKeywords": [
      "sourdough",
      "artisan bakery"
    ],
    "ogTitle": null,
    "ogDescription": null,
    "ogImage": null,
    "ogImageAssetId": null,
    "ogImageFraming": null,
    "canonicalUrl": null,
    "robotsIndex": null,
    "robotsFollow": null,
    "excludeFromSiteSearch": false
  },
  "systemManaged": false,
  "lockedCoreSectionTypes": [],
  "sections": [
    {
      "id": "sec_hero01",
      "type": "hero",
      "title": "Freshly baked sourdough every morning",
      "subtitle": "Natural starter, local grain. Doors open at 7 a.m.",
      "image": "asset:asset_example01",
      "ctaIds": [
        "btn_order"
      ]
    },
    {
      "id": "sec_faq01",
      "type": "faq",
      "title": "Frequently Asked Questions",
      "items": [
        {
          "question": "Can I pre-order loaves?",
          "answer": "Yes, place pre-orders by 5 p.m. the day before."
        }
      ]
    }
  ],
  "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"
    }
  }
}
```

### Update a page

`PATCH /sites/{id}/pages/{pageId}`

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

Update a page's properties: title, slug, parentSlug, createRedirect, breadcrumbParentSlug, visibility, header/footer reference, SEO fields (seoTitle, seoDescription, seoKeywords, og*, canonicalUrl, robots*) and excludeFromSiteSearch. Only the fields you send change; sections are untouched (use pages.replace_sections). Nullable fields (parentSlug, breadcrumbParentSlug, globalHeaderId, globalFooterId, ogImageAssetId) can be cleared by passing null; all other fields can only be replaced, not cleared. Slug, parentSlug and visibility changes take effect on the published site immediately; renaming automatically updates descendant subpages and creates 301 redirects for previously published pages unless createRedirect is false. Errors: FORBIDDEN, PAGE_NOT_FOUND, PARENT_NOT_FOUND, PARENT_NOT_ALLOWED, DEPTH_EXCEEDED, SEGMENT_TOO_LONG, RESERVED_SLUG, RESERVED_SEGMENT, BREADCRUMB_PARENT_INVALID, REDIRECT_CONFLICT, REDIRECT_LIMIT, VALIDATION, SLUG_TAKEN, HOME_SLUG_IMMUTABLE, HOME_NOT_HIDEABLE, ASSET_NOT_FOUND, ASSET_NOT_ALLOWED.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `pageId` | path | string | yes | Page id. Get it from pages.list. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | no | Page title; also the default navigation label. |
| `slug` | string | no | New URL path (or last segment when parentSlug is provided), e.g. "/about". Renaming a page also renames all its descendant subpages and creates 301 redirects for previously published pages. The home page slug cannot change. |
| `parentSlug` | string \| null | no | Move this page (and its subpages) under a new parent page slug (e.g. "/services"), or pass null to move it to the top level. Pass parentSlug + last segment rather than concatenating "/a/b" yourself. |
| `createRedirect` | boolean | no | Whether to create 301 permanent redirects for previously published pages when renaming or moving (defaults to true). |
| `breadcrumbParentSlug` | string \| null | no | Override the parent page shown in breadcrumbs and BreadcrumbList JSON-LD (pass null to reset to URL hierarchy). Use only when the page's logical category differs from its URL; does not change the URL or create redirects. Renaming or deleting the target page updates or clears this override automatically. |
| `isVisible` | boolean | no | Hidden pages are not published. The home page cannot be hidden. |
| `globalHeaderId` | string \| null | no | Header from globalSections to show on this page; null removes it. |
| `globalFooterId` | string \| null | no | — |
| `seoTitle` | string | no | — |
| `seoDescription` | string | no | — |
| `seoKeywords` | string[] | no | — |
| `ogTitle` | string | no | — |
| `ogDescription` | string | no | — |
| `ogImageAssetId` | string \| null | no | Share image: assetId of an image in the media library (media.list, media.upload). null removes it; without a share image a title card is generated automatically. |
| `ogImageFraming` | object \| null | no | — |
| `ogImageFraming.focus` | object | no | — |
| `ogImageFraming.focus.x` | number | yes | — |
| `ogImageFraming.focus.y` | number | yes | — |
| `ogImageFraming.screens` | object | no | — |
| `ogImageFraming.screens.desktop` | object | no | — |
| `ogImageFraming.screens.tablet` | object | no | — |
| `ogImageFraming.screens.mobile` | object | no | — |
| `ogImageFraming.fitMode` | "contain" \| "cover" | no | — |
| `ogImageFraming.padColor` | string | no | — |
| `canonicalUrl` | string | no | — |
| `robotsIndex` | "index" \| "noindex" | no | — |
| `robotsFollow` | "follow" \| "nofollow" | no | — |
| `excludeFromSiteSearch` | boolean | no | Exclude this page from visitor site search (/api/v1/search and the /search page); does not affect search engine indexing (robotsIndex). |
| `ifUnmodifiedSince` | string | no | Optional optimistic-concurrency marker: the page's updatedAt from pages.get. If the page changed since, the call fails with CONFLICT (details.conflict.updatedAt) instead of silently overwriting. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `slug` | string | yes | URL path, "/" for the home page. |
| `path` | string[] | yes | Path segments (e.g. ["services", "haircut"] for "/services/haircut"; [] for "/"). |
| `parentSlug` | string \| null | yes | Immediate parent page slug for generic nested pages (e.g. "/services" for "/services/haircut"; null for "/", top-level pages, and system pages such as blog/shop/product/legal). |
| `isDirectory` | boolean | yes | True when this is a hidden folder page that exists to group subpage URLs. |
| `depth` | integer | yes | Hierarchy depth: 0 for home page, 1 for top-level pages, up to 3. |
| `ancestors` | object[] | yes | Visible ancestor pages from root to immediate parent (only generic pages participate; system pages such as blog, shop, product, and legal always return []). |
| `ancestors[].id` | string | yes | — |
| `ancestors[].slug` | string | yes | — |
| `ancestors[].title` | string | yes | — |
| `breadcrumbs` | object[] | yes | Visible breadcrumb trail shown on the page and in BreadcrumbList JSON-LD (up to 4 items; empty when the page has no visible ancestors or is a system page). |
| `breadcrumbs[].name` | string | yes | — |
| `breadcrumbs[].url` | string | yes | — |
| `breadcrumbSource` | "url" \| "custom" | yes | Whether breadcrumbs follow the URL hierarchy ("url") or a custom breadcrumbParentSlug override ("custom"). |
| `breadcrumbParentSlug` | string \| null | yes | Optional parent page slug override for breadcrumbs and structured data only. |
| `title` | string | yes | — |
| `pageType` | string | yes | — |
| `isHomePage` | boolean | yes | — |
| `isVisible` | boolean | yes | Hidden pages are not published or linked from navigation. |
| `order` | integer | yes | — |
| `globalHeaderId` | string \| null | yes | — |
| `globalFooterId` | string \| null | yes | — |
| `updatedAt` | string | yes | Revision marker. Pass it back as ifUnmodifiedSince when replacing sections. |
| `seo` | object | yes | — |
| `seo.seoTitle` | string \| null | yes | — |
| `seo.seoDescription` | string \| null | yes | — |
| `seo.seoKeywords` | string[] \| null | yes | — |
| `seo.ogTitle` | string \| null | yes | — |
| `seo.ogDescription` | string \| null | yes | — |
| `seo.ogImage` | string \| null | yes | Share image display URL (changes with versions; do not store it). |
| `seo.ogImageAssetId` | string \| null | yes | assetId of the share image when it is in the media library. |
| `seo.ogImageFraming` | object \| null | no | — |
| `seo.ogImageFraming.focus` | object | no | — |
| `seo.ogImageFraming.screens` | object | no | — |
| `seo.ogImageFraming.fitMode` | "contain" \| "cover" | no | — |
| `seo.ogImageFraming.padColor` | string | no | — |
| `seo.canonicalUrl` | string \| null | yes | — |
| `seo.robotsIndex` | string \| null | yes | — |
| `seo.robotsFollow` | string \| null | yes | — |
| `seo.excludeFromSiteSearch` | boolean | yes | When true, this page is excluded from visitor site search (independent of robotsIndex). |
| `systemManaged` | boolean | yes | True when Kanorio owns this page (shop index, blog index, taxonomy templates). Those pages cannot be created or deleted. |
| `lockedCoreSectionTypes` | string[] | yes | Section types locked at index 0. pages.replace_sections must keep exactly one of each, with its locked source. Empty for ordinary pages. |
| `warnings` | string[] | no | Non-fatal warnings from this write (e.g. REDIRECT_SHADOWS_PAGE when a manual redirect intercepts this URL). |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `PAGE_NOT_FOUND` | 404 | — |
| `PARENT_NOT_FOUND` | 404 | — |
| `PARENT_NOT_ALLOWED` | 422 | — |
| `DEPTH_EXCEEDED` | 422 | — |
| `SEGMENT_TOO_LONG` | 422 | — |
| `RESERVED_SLUG` | 422 | — |
| `RESERVED_SEGMENT` | 422 | — |
| `BREADCRUMB_PARENT_INVALID` | 422 | — |
| `REDIRECT_CONFLICT` | 409 | — |
| `REDIRECT_LIMIT` | 403 | — |
| `VALIDATION` | 400 | — |
| `SLUG_TAKEN` | 409 | — |
| `HOME_SLUG_IMMUTABLE` | 422 | — |
| `HOME_NOT_HIDEABLE` | 422 | — |
| `ASSET_NOT_FOUND` | 404 | — |
| `ASSET_NOT_ALLOWED` | 422 | — |

```bash
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/pages/{pageId}" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"seoTitle":"About Us | Sunny Bakery","seoDescription":"Naturally leavened sourdough baked fresh every morning.","ogImageAssetId":"asset_example01"}'
```

```json
{
  "id": "pg_example789",
  "slug": "/about",
  "path": [
    "about"
  ],
  "parentSlug": null,
  "isDirectory": false,
  "depth": 1,
  "ancestors": [],
  "breadcrumbs": [],
  "breadcrumbSource": "url",
  "breadcrumbParentSlug": null,
  "title": "About Us",
  "pageType": "generic",
  "isHomePage": false,
  "isVisible": true,
  "order": 1,
  "globalHeaderId": "sec_header01",
  "globalFooterId": "sec_footer01",
  "updatedAt": "2026-10-06T08:00:00.000Z",
  "seo": {
    "seoTitle": "About Us | Sunny Bakery",
    "seoDescription": "Naturally leavened sourdough baked fresh every morning.",
    "seoKeywords": [
      "sourdough",
      "artisan bakery"
    ],
    "ogTitle": null,
    "ogDescription": null,
    "ogImage": null,
    "ogImageAssetId": null,
    "ogImageFraming": null,
    "canonicalUrl": null,
    "robotsIndex": null,
    "robotsFollow": null,
    "excludeFromSiteSearch": false
  },
  "systemManaged": false,
  "lockedCoreSectionTypes": []
}
```

### Delete a page

`DELETE /sites/{id}/pages/{pageId}`

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

Delete a page and its draft content. The home page and system pages (shop, blog, taxonomy templates) cannot be deleted; system pages return SYSTEM_PAGE_LOCKED. When the page has subpages, you must pass mode: "subtree" (delete the page and all its subpages) or "lift-children" (move subpages up one level with 301 redirects before deleting the parent); omitting mode returns HAS_CHILDREN with details.children. Errors: FORBIDDEN, PAGE_NOT_FOUND, HOME_NOT_DELETABLE, SYSTEM_PAGE_LOCKED, HAS_CHILDREN, SLUG_TAKEN, REDIRECT_CONFLICT, REDIRECT_LIMIT.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `pageId` | path | string | yes | Page id. Get it from pages.list. |
| `mode` | query | "subtree" \| "lift-children" | no | Required when deleting a parent page that has subpages: "subtree" deletes all descendants; "lift-children" moves subpages up one level first. |
| `createRedirect` | query | boolean | no | With mode "lift-children": whether to create 301 redirects from the old subpage URLs for previously published subpages (defaults to true). Ignored otherwise. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `deleted` | true | yes | — |
| `deletedCount` | integer | no | — |
| `liftedCount` | integer | no | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `PAGE_NOT_FOUND` | 404 | — |
| `HOME_NOT_DELETABLE` | 422 | — |
| `SYSTEM_PAGE_LOCKED` | 422 | — |
| `HAS_CHILDREN` | 409 | — |
| `SLUG_TAKEN` | 409 | — |
| `REDIRECT_CONFLICT` | 409 | — |
| `REDIRECT_LIMIT` | 403 | — |

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

```json
{
  "id": "pg_example789",
  "deleted": true,
  "deletedCount": 1
}
```

### Replace sections

`PUT /sites/{id}/pages/{pageId}/sections`

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

Replace the whole draft section list of a page (optimistic concurrency). Get the shapes from sections.describe; read the page with pages.get and pass its updatedAt as ifUnmodifiedSince. If someone else (another agent or a person in the editor) changed the page since, the call fails with CONFLICT and details.updatedAt holds the current revision: re-read the page, merge, retry. Changes are saved as draft only; call site.publish to go live. Reference images and files as asset:<assetId> (from media.list, media.upload, media.import_stock, media.upload_link_status); values you read with pages.get can be sent back unchanged. Text fields are normalized to the format that field supports (see sections.describe conventions.textFormat), not rejected; check `formatNotes` in the response for fields that were reformatted. On a system page the locked core section stays at index 0 with its locked source; removing it, duplicating it, or changing that source returns CORE_SECTION_VIOLATION. Errors: FORBIDDEN, PAGE_NOT_FOUND, VALIDATION (details lists the failing section index and field), CONFLICT, CORE_SECTION_VIOLATION, ASSET_NOT_FOUND (details.assetIds: not in this site's media library), ASSET_NOT_ALLOWED (product files cannot be shown on pages).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `pageId` | path | string | yes | Page id. Get it from pages.list. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ifUnmodifiedSince` | string | yes | The page's updatedAt from pages.get/pages.list. Required: the write is rejected with CONFLICT when it no longer matches. |
| `sections` | object[] | yes | Complete ordered list of section objects, each with `type` and a unique `id`. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | — |
| `sections` | object[] | yes | Sections as stored after sanitization (media as asset:<assetId>). |
| `assets` | object | yes | The media-library files the sections use, by assetId. |
| `updatedAt` | string | yes | New revision marker; use it for the next replace. |
| `formatNotes` | string[] | no | Field paths whose text was reformatted to the field's supported Markdown syntax (e.g. a heading became bold text). |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `PAGE_NOT_FOUND` | 404 | — |
| `VALIDATION` | 400 | details lists the failing section index and field |
| `CONFLICT` | 409 | — |
| `CORE_SECTION_VIOLATION` | 422 | — |
| `ASSET_NOT_FOUND` | 404 | details.assetIds: not in this site's media library |
| `ASSET_NOT_ALLOWED` | 422 | product files cannot be shown on pages |

```bash
curl -X PUT "https://app.kanorio.com/api/v1/sites/{id}/pages/{pageId}/sections" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ifUnmodifiedSince":"2026-10-06T08:00:00.000Z","sections":[{"id":"sec_hero01","type":"hero","title":"Freshly baked sourdough every morning","subtitle":"Natural starter, local grain. Doors open at 7 a.m.","image":"asset:asset_example01","ctaIds":["btn_order"]},{"id":"sec_faq01","type":"faq","title":"Frequently Asked Questions","items":[{"question":"Can I pre-order loaves?","answer":"Yes, place pre-orders by 5 p.m. the day before."}]}]}'
```

```json
{
  "id": "pg_example456",
  "sections": [
    {
      "id": "sec_hero01",
      "type": "hero",
      "title": "Freshly baked sourdough every morning",
      "subtitle": "Natural starter, local grain. Doors open at 7 a.m.",
      "image": "asset:asset_example01",
      "ctaIds": [
        "btn_order"
      ]
    },
    {
      "id": "sec_faq01",
      "type": "faq",
      "title": "Frequently Asked Questions",
      "items": [
        {
          "question": "Can I pre-order loaves?",
          "answer": "Yes, place pre-orders by 5 p.m. the day before."
        }
      ]
    }
  ],
  "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"
    }
  },
  "updatedAt": "2026-10-06T08:01:30.000Z"
}
```
