# Globals · Management API

> Management API reference for globals: GET /sites/{id}/globals, GET /sites/{id}/globals/headers, POST /sites/{id}/globals/headers, DELETE /sites/{id}/globals/headers/{key}, ….

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

### Retrieve a global

`GET /sites/{id}/globals`

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

Read the site-level draft shared by all pages: the primary header and footer (from globalSections), siteSettings (customButtons referenced by sections via ctaIds, socialLinks) and the revision marker contentUpdatedAt. Call it before globals.replace_header / globals.replace_footer / globals.update_site_settings and pass contentUpdatedAt back as ifUnmodifiedSince. Images of the site's media library appear as asset:<assetId>; assets holds them (with display URLs). Errors: FORBIDDEN (also when the website does not exist), WEBSITE_NOT_FOUND.

**Parameters**

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

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `header` | object \| null | yes | Primary header section, or null when the site has none yet. |
| `footer` | object \| null | yes | — |
| `sharedSections` | object[] | yes | Other shared content sections; pages embed them with a global-section-ref item. |
| `sharedSections[].id` | string \| null | yes | — |
| `sharedSections[].type` | string | yes | — |
| `siteSettings` | object \| null | yes | — |
| `assets` | object | yes | The media-library files referenced as asset:<assetId>, by assetId. |
| `contentUpdatedAt` | string \| null | yes | Revision marker of the site-level draft (Website.contentUpdatedAt). Pass it back as ifUnmodifiedSince. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | also when the website does not exist |
| `WEBSITE_NOT_FOUND` | 404 | — |

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

```json
{
  "header": {
    "id": "sec_header01",
    "type": "header",
    "navItems": [
      {
        "id": "nav_home",
        "text": "Home",
        "linkType": "internal",
        "pageSlug": "/"
      },
      {
        "id": "nav_about",
        "text": "About Us",
        "linkType": "internal",
        "pageSlug": "/about"
      }
    ],
    "ctaIds": [
      "btn_order"
    ]
  },
  "footer": {
    "id": "sec_footer01",
    "type": "footer",
    "copyright": "© 2026 Sunny Bakery"
  },
  "sharedSections": [
    {
      "id": "sec_header01",
      "type": "header"
    },
    {
      "id": "sec_footer01",
      "type": "footer"
    }
  ],
  "siteSettings": {
    "customButtons": [
      {
        "id": "btn_order",
        "text": "Order online",
        "url": "/shop",
        "style": "brand"
      }
    ],
    "socialLinks": [
      {
        "platform": "instagram",
        "url": "https://www.instagram.com/sunnybakery.example"
      }
    ]
  },
  "assets": {},
  "contentUpdatedAt": "2026-10-06T08:00:00.000Z"
}
```

### List headers

`GET /sites/{id}/globals/headers`

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

List all headers in globalSections: the primary header plus any secondary headers created for specific pages (ADR-0001). Most websites only need the primary header. All headers are returned in one page. Errors: FORBIDDEN, WEBSITE_NOT_FOUND.

**Parameters**

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

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].id` | string | yes | Pass this id to pages.update globalHeaderId to assign the header to a page. |
| `data[].key` | string | yes | — |
| `data[].isPrimary` | boolean | yes | — |
| `data[].header` | object | 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. |
| `assets` | object | yes | The media-library files referenced as asset:<assetId>, by assetId. |
| `contentUpdatedAt` | string \| null | yes | Revision marker of the site-level draft (Website.contentUpdatedAt). Pass it back as ifUnmodifiedSince. |

**Errors**

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

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

```json
{
  "data": [
    {
      "id": "sec_header01",
      "key": "primary",
      "isPrimary": true,
      "header": {
        "id": "sec_header01",
        "type": "header",
        "navItems": [
          {
            "id": "nav_home",
            "text": "Home",
            "linkType": "internal",
            "pageSlug": "/"
          },
          {
            "id": "nav_about",
            "text": "About Us",
            "linkType": "internal",
            "pageSlug": "/about"
          }
        ],
        "ctaIds": [
          "btn_order"
        ]
      }
    }
  ],
  "assets": {},
  "contentUpdatedAt": "2026-10-06T08:00:00.000Z",
  "hasMore": false,
  "nextCursor": null
}
```

### Create header

`POST /sites/{id}/globals/headers`

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

Create a secondary header in globalSections with a human-readable key. Only use this when specific pages need a different navigation bar (for example a sub-brand or campaign microsite); most websites only need the primary header. After creating the header, assign its returned id to a page with pages.update({ globalHeaderId }). Saved as draft only; call site.publish to go live. Errors: FORBIDDEN, WEBSITE_NOT_FOUND, VALIDATION, KEY_TAKEN, CONFLICT, WRITE_FROZEN, 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. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ifUnmodifiedSince` | string | yes | contentUpdatedAt from globals.get. Required: the write is rejected with CONFLICT when the site-level draft changed since (details.contentUpdatedAt holds the current value). |
| `key` | string | yes | Unique header key (a-z, 0-9, hyphen, underscore; 1-40 chars). 'primary' refers to the main site header. |
| `header` | object | yes | Complete header object. navItems is the navigation: one { id, text, linkType: "internal", pageSlug } per visible page (the header never lists pages automatically); keep text short (1-3 words) since it has to fit a single desktop row — it is not the page title. Use linkType: "group" to nest related pages under one dropdown instead of adding many top-level items (desktop headers work best with 7 or fewer top-level items). Use ctaIds (up to 2) to show buttons from siteSettings.customButtons. layout picks the desktop arrangement (top-spread, top-left, top-center, top-stacked, side-left) — top-stacked puts navigation on its own full-width row below the logo/CTA row and suits sites with many nav items; surface sets the background tone; showBrandLogo / showSiteName / logoSize control the brand slot and showSocialLinks (boolean or per device) plus hiddenSocialLinkUrls choose which social links appear; utilities orders and toggles the feature buttons (search, cart) after the CTA buttons. The logo comes from the website's brand settings unless logoOverride is set. |
| `header.type` | "header" | yes | — |
| `header.id` | string | yes | Stable id; pages reference it via globalHeaderId. |
| `header.key` | string | no | Human-readable key for this header (e.g. 'primary', 'campaign', 'docs'). |
| `header.cta` | object | no | — |
| `header.cta.id` | string | no | — |
| `header.cta.icon` | "briefcase" \| "star" \| "award" \| "crown" \| "flame" \| "zap" \| "target" \| "rocket" \| "flag" \| "gem" \| "diamond" \| "scroll" \| "fingerprint" \| "smile" \| "laugh" \| "clock" \| "phone" \| "mail" \| "bell" \| "settings" \| "check-circle" \| "info" \| "help-circle" \| "headset" \| "megaphone" \| "newspaper" \| "hourglass" \| "stamp" \| "milestone" \| "quote" \| "table" \| "list-ordered" \| "list-checks" \| "arrow-right" \| "message-circle" \| "external-link" \| "download" \| "play" \| "pause" \| "bookmark" \| "search" \| "filter" \| "thumbs-up" \| "share-2" \| "eye" \| "phone-call" \| "send" \| "lock" \| "rotate-ccw" \| "clipboard-check" \| "alarm-clock" \| "utensils" \| "utensils-crossed" \| "chef-hat" \| "coffee" \| "pizza" \| "soup" \| "ice-cream-cone" \| "sandwich" \| "beef" \| "fish" \| "salad" \| "cookie" \| "cake" \| "cake-slice" \| "dessert" \| "cup-soda" \| "wine" \| "beer" \| "martini" \| "milk" \| "wheat" \| "egg" \| "croissant" \| "popcorn" \| "candy" \| "apple" \| "cherry" \| "banana" \| "citrus" \| "grape" \| "carrot" \| "drumstick" \| "cooking-pot" \| "shrimp" \| "donut" \| "bean" \| "vegan" \| "store" \| "shopping-bag" \| "shopping-cart" \| "shopping-basket" \| "package" \| "package-check" \| "boxes" \| "truck" \| "motorbike" \| "warehouse" \| "tag" \| "tags" \| "ticket-percent" \| "barcode" \| "qr-code" \| "scan-qr-code" \| "credit-card" \| "smartphone-nfc" \| "wallet" \| "banknote" \| "coins" \| "dollar-sign" \| "percent" \| "badge-percent" \| "receipt" \| "shirt" \| "heart" \| "heart-pulse" \| "stethoscope" \| "hospital" \| "syringe" \| "pill" \| "pill-bottle" \| "bandage" \| "cross" \| "thermometer" \| "briefcase-medical" \| "scissors" \| "sparkles" \| "scan-face" \| "hand" \| "bath" \| "droplet" \| "droplets" \| "flower" \| "flower-2" \| "sun" \| "moon" \| "book-open" \| "book-text" \| "book-copy" \| "book-marked" \| "book-check" \| "graduation-cap" \| "school" \| "library" \| "monitor-play" \| "backpack" \| "pencil" \| "pen-tool" \| "file-text" \| "scroll-text" \| "notebook-pen" \| "languages" \| "calculator" \| "presentation" \| "lightbulb" \| "brain" \| "puzzle" \| "laptop" \| "monitor" \| "smartphone" \| "tablet" \| "code" \| "code-2" \| "terminal" \| "server" \| "database" \| "cloud" \| "cpu" \| "globe" \| "network" \| "circuit-board" \| "brain-circuit" \| "bot" \| "blocks" \| "palette" \| "brush" \| "paint-bucket" \| "pen-line" \| "image" \| "image-plus" \| "camera" \| "video" \| "film" \| "clapperboard" \| "music" \| "music-2" \| "music-4" \| "mic" \| "mic-2" \| "headphones" \| "speaker" \| "radio" \| "podcast" \| "piano" \| "plane" \| "map-pin" \| "map-pinned" \| "navigation" \| "route" \| "compass" \| "mountain" \| "tree-pine" \| "trees" \| "tent" \| "umbrella" \| "anchor" \| "ship" \| "car" \| "bike" \| "bus" \| "train" \| "luggage" \| "hotel" \| "bed-double" \| "concierge-bell" \| "wifi" \| "parking-square" \| "air-vent" \| "washing-machine" \| "cigarette-off" \| "accessibility" \| "dumbbell" \| "biceps-flexed" \| "footprints" \| "timer" \| "watch" \| "medal" \| "trophy" \| "swords" \| "badge-check" \| "activity" \| "building" \| "building-2" \| "factory" \| "house" \| "house-plus" \| "map-pin-house" \| "door-open" \| "door-closed" \| "key" \| "key-round" \| "fence" \| "sofa" \| "ruler" \| "hard-hat" \| "hammer" \| "wrench" \| "paint-roller" \| "brush-cleaning" \| "cat" \| "dog" \| "bird" \| "rabbit" \| "squirrel" \| "paw-print" \| "bone" \| "leaf" \| "sprout" \| "clover" \| "tractor" \| "cloud-sun" \| "cloud-rain" \| "snowflake" \| "leafy-green" \| "tree-deciduous" \| "wind" \| "waves" \| "recycle" \| "landmark" \| "vault" \| "scale" \| "gavel" \| "shield" \| "shield-check" \| "badge-dollar-sign" \| "hand-coins" \| "trending-up" \| "trending-down" \| "bar-chart" \| "bar-chart-2" \| "bar-chart-3" \| "pie-chart" \| "line-chart" \| "chart-no-axes-combined" \| "users" \| "users-round" \| "user-circle" \| "user-plus" \| "hand-heart" \| "hand-helping" \| "heart-handshake" \| "handshake" \| "baby" \| "church" \| "party-popper" \| "sparkle" \| "gift" \| "ticket" \| "calendar" \| "calendar-days" \| "calendar-check" \| "calendar-clock" | no | — |
| `header.cta.text` | string | no | — |
| `header.cta.url` | string | no | Optional link: absolute http(s)/tel:/mailto: URL, site-relative path (/about) or anchor (#id). |
| `header.cta.style` | "brand" \| "default" \| "ghost" | no | — |
| `header.ctaId` | string | no | Legacy single button id from siteSettings.customButtons. Prefer ctaIds; when both are set ctaIds wins. |
| `header.ctaIds` | string[] | no | Up to 2 ids of buttons defined in siteSettings.customButtons, shown at the right of the header (and at the bottom of the side-left sidebar / mobile menu). |
| `header.ctaStyleById` | object | no | Per-button style override keyed by customButton id: brand (filled brand color), default (neutral), or ghost (text with arrow). |
| `header.layout` | "top-spread" \| "top-left" \| "top-center" \| "top-stacked" \| "side-left" | no | Desktop header layout: top-spread (default), top-left (nav next to logo), top-center (centered nav), top-stacked (nav on its own full-width row below the logo/CTA row; best for many nav items or long labels), or side-left (fixed left sidebar). |
| `header.surface` | "auto" \| "default" \| "muted" \| "tint" \| "brand" \| "contrast" | no | Header background tone: default (light bar with blur), muted, tint, brand or contrast; text colors adapt automatically. auto behaves like default. |
| `header.showBrandLogo` | boolean | no | Show the brand logo (default true). When false and showSiteName is false, nothing is rendered in the brand slot. |
| `header.showSiteName` | boolean | no | Show the website name next to (or instead of) the logo (default false; the name is still shown when no logo exists). |
| `header.logoSize` | "sm" \| "md" \| "lg" | no | Logo size in the header (default md). |
| `header.showSocialLinks` | boolean \| object | no | Show siteSettings.socialLinks in the header, either for every device (boolean) or per device. Defaults: desktop only for the side-left layout, tablet and mobile menu on. |
| `header.showSocialLinks.desktop` | boolean | no | — |
| `header.showSocialLinks.tablet` | boolean | no | — |
| `header.showSocialLinks.mobile` | boolean | no | — |
| `header.hiddenSocialLinkUrls` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `header.hiddenSocialLinkUrlsByDevice` | object | no | Per-device blacklist of social link urls; a device without its own list falls back to hiddenSocialLinkUrls. |
| `header.hiddenSocialLinkUrlsByDevice.desktop` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `header.hiddenSocialLinkUrlsByDevice.tablet` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `header.hiddenSocialLinkUrlsByDevice.mobile` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `header.variant` | "solid" \| "transparent" \| "glass" | no | Deprecated and ignored: the header background is controlled by surface. Accepted for backward compatibility. |
| `header.shrinkOnScroll` | boolean | no | Compact the header after scrolling (default true). |
| `header.showProgressBar` | boolean | no | — |
| `header.showAccentLine` | boolean | no | Show a thin brand-gradient line at the bottom edge (default false). |
| `header.isVisible` | boolean | no | — |
| `header.navItems` | object[] | no | Navigation links. The header never lists pages automatically; add one internal item per visible page. |
| `header.navItems[].id` | string | yes | — |
| `header.navItems[].text` | string | yes | — |
| `header.navItems[].linkType` | "internal" \| "external" \| "group" | yes | — |
| `header.navItems[].pageSlug` | string | no | — |
| `header.navItems[].anchorId` | string | no | — |
| `header.navItems[].externalUrl` | string | no | Optional link: absolute http(s)/tel:/mailto: URL, site-relative path (/about) or anchor (#id). |
| `header.navItems[].openInNewTab` | boolean | no | — |
| `header.navItems[].children` | object[] | no | — |
| `header.navItems[].link` | object | no | — |
| `header.utilities` | object[] | no | Header utility buttons (site features shown at the right of the header, after the CTA buttons), in display order; each id at most once. enabled shows or hides it; an omitted id keeps its default (search: off, cart: on, language: on, account: on; newly created sites default all to on). A utility only renders when the site has that feature: search needs a published site with searchable content; cart needs the shopping cart turned on in store settings and at least one active product; language needs at least two published site languages; account needs the customer sign-in entry turned on under Settings › Customer accounts (signed-in customers always see it). On phones only the first 2 visible utilities stay next to the menu button; the rest move to the top of the mobile menu. |
| `header.utilities[].id` | "search" \| "cart" \| "language" \| "account" | yes | search: site search button; cart: shopping cart button; language: language switcher; account: customer sign-in / account panel. |
| `header.utilities[].enabled` | boolean | yes | — |
| `header.isGlobal` | boolean | no | — |
| `header.logoOverride` | any | no | Header-specific logo (e.g. a wide wordmark). Omit to use the website's brand logo. |

**Response** (201)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `key` | string | yes | — |
| `isPrimary` | false | yes | — |
| `header` | object | yes | — |
| `assets` | object | yes | The media-library files referenced as asset:<assetId>, by assetId. |
| `contentUpdatedAt` | string \| null | yes | Revision marker of the site-level draft (Website.contentUpdatedAt). Pass it back as ifUnmodifiedSince. |
| `warnings` | object[] | yes | Content-quality warnings about navItems: long labels or too many top-level items. These are fixed content rules, not a prediction of whether the header will overflow at some width — the renderer always collapses items that do not fit into a "more" menu, so the site never breaks either way. Empty array when there is nothing to flag. |
| `warnings[].code` | "NAV_LABEL_TOO_LONG" \| "NAV_TOO_MANY_ITEMS" | yes | — |
| `warnings[].message` | string | yes | — |
| `warnings[].navItemIds` | string[] | no | ids of the navItems the warning is about, when applicable. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBSITE_NOT_FOUND` | 404 | — |
| `VALIDATION` | 400 | — |
| `KEY_TAKEN` | 409 | — |
| `CONFLICT` | 409 | — |
| `WRITE_FROZEN` | 503 | — |
| `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 POST "https://app.kanorio.com/api/v1/sites/{id}/globals/headers" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ifUnmodifiedSince":"2026-10-06T08:00:00.000Z","key":"campaign","header":{"id":"hdr_campaign_01","type":"header","navItems":[{"id":"nav_home","text":"Home","linkType":"internal","pageSlug":"/"},{"id":"nav_about","text":"About Us","linkType":"internal","pageSlug":"/about"}],"ctaIds":["btn_order"],"key":"campaign"}}'
```

```json
{
  "key": "campaign",
  "isPrimary": false,
  "header": {
    "id": "hdr_campaign_01",
    "type": "header",
    "navItems": [
      {
        "id": "nav_home",
        "text": "Home",
        "linkType": "internal",
        "pageSlug": "/"
      },
      {
        "id": "nav_about",
        "text": "About Us",
        "linkType": "internal",
        "pageSlug": "/about"
      }
    ],
    "ctaIds": [
      "btn_order"
    ],
    "key": "campaign"
  },
  "assets": {},
  "contentUpdatedAt": "2026-10-06T08:00:00.000Z",
  "warnings": []
}
```

### Delete header

`DELETE /sites/{id}/globals/headers/{key}`

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

Delete a secondary header by key and automatically reassign any pages using it back to the primary header. The primary header cannot be deleted. Saved as draft only; call site.publish to go live. Errors: FORBIDDEN, WEBSITE_NOT_FOUND, HEADER_NOT_FOUND, PRIMARY_HEADER_PROTECTED, CONFLICT, WRITE_FROZEN.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Website id. Get it from sites.list or from sites.create. |
| `key` | path | string | yes | Unique header key (a-z, 0-9, hyphen, underscore; 1-40 chars). 'primary' refers to the main site header. |
| `ifUnmodifiedSince` | query | string | yes | contentUpdatedAt from globals.get. Required: the write is rejected with CONFLICT when the site-level draft changed since (details.contentUpdatedAt holds the current value). |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deletedKey` | string | yes | — |
| `reassignedPages` | integer | yes | — |
| `contentUpdatedAt` | string \| null | yes | Revision marker of the site-level draft (Website.contentUpdatedAt). Pass it back as ifUnmodifiedSince. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBSITE_NOT_FOUND` | 404 | — |
| `HEADER_NOT_FOUND` | 404 | — |
| `PRIMARY_HEADER_PROTECTED` | 409 | — |
| `CONFLICT` | 409 | — |
| `WRITE_FROZEN` | 503 | — |

```bash
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/globals/headers/{key}?ifUnmodifiedSince=2026-10-06T08%3A00%3A00.000Z" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "deletedKey": "campaign",
  "reassignedPages": 1,
  "contentUpdatedAt": "2026-10-06T08:00:00.000Z"
}
```

### Update site settings

`PATCH /sites/{id}/globals/site-settings`

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

Partially update siteSettings (only the keys you send are replaced; optimistic concurrency). customButtons is the single list of CTA buttons for the whole site: define them here (text, url, optional icon and style), then reference their ids from sections, the header and the footer via ctaIds (up to 2 per place). Buttons without an id get one; read it back from the response. Replaces the whole list when sent. socialLinks: [{ platform, url }] rendered in the footer and social sections. Contact fields (address/phone/email/hours) are business facts and are not editable here. Saved as draft only; call site.publish to go live. Errors: FORBIDDEN, WEBSITE_NOT_FOUND, VALIDATION, CONFLICT, WRITE_FROZEN, 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. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ifUnmodifiedSince` | string | yes | contentUpdatedAt from globals.get. Required: the write is rejected with CONFLICT when the site-level draft changed since (details.contentUpdatedAt holds the current value). |
| `siteSettings` | object | yes | Keys to replace: customButtons (max 2 are shown per section; the list itself can be longer), socialLinks, socialIconColor, showBreadcrumbs (boolean, defaults to true; controls site-wide breadcrumb navigation and BreadcrumbList JSON-LD). |
| `siteSettings.socialLinks` | object[] | no | — |
| `siteSettings.socialLinks[].platform` | "website" \| "facebook" \| "instagram" \| "line_oa" \| "threads" \| "tiktok" \| "douyin" \| "youtube" \| "twitter" \| "whatsapp" \| "linkedin" \| "xhs" \| "pinterest" \| "behance" \| "bluesky" \| "discord" \| "messenger" \| "dribbble" \| "github" \| "mastodon" \| "medium" \| "reddit" \| "snapchat" \| "substack" \| "patreon" \| "ko_fi" \| "buymeacoffee" \| "linktree" \| "tumblr" \| "quora" \| "nextdoor" \| "soundcloud" \| "bandcamp" \| "tidal" \| "notion" \| "figma" \| "canva" \| "signal" \| "slack" \| "zoom" \| "bilibili" \| "weibo" \| "wechat" \| "zhihu" \| "douban" \| "strava" \| "letterboxd" \| "goodreads" \| "yelp" \| "foursquare" \| "tripadvisor" \| "stackoverflow" \| "shopify" \| "skype" \| "viber" \| "wordpress" \| "deezer" \| "mixcloud" \| "dailymotion" \| "gitlab" \| "bitbucket" \| "trello" \| "dropbox" \| "codepen" \| "artstation" \| "audible" \| "gojek" \| "bukalapak" \| "zalo" \| "kakaotalk" \| "naver" \| "aliexpress" \| "amazon" \| "ebay" \| "etsy" \| "paypal" \| "stripe" \| "alipay" \| "jkopay" \| "line_pay" \| "taiwan_pay" \| "pxpay" \| "ipass_money" \| "grab" \| "meituan" \| "dianping" \| "plurk" \| "dcard" \| "bahamut" \| "shopee" \| "rakuten" \| "rakuten_tickets" \| "taobao" \| "pinkoi" \| "carousell" \| "momo" \| "pchome" \| "yahoo_shopping" \| "yahoo_mall" \| "ruten" \| "coupang" \| "books_com_tw" \| "etmall" \| "friday" \| "spotify" \| "vocus" \| "telegram" \| "twitch" \| "vimeo" \| "surveycake" \| "apple_podcast" \| "apple_music" \| "firstory" \| "kkbox" \| "soundon" \| "uber" \| "uber_eats" \| "foodpanda" \| "keeta" \| "deliveroo" \| "nidin" \| "ichef" \| "dudoo" \| "hahow" \| "pressplay" \| "udemy" \| "google_maps" \| "google_forms" \| "google_reviews" \| "typeform" \| "openrice" \| "inline" \| "funnow" \| "accupass" \| "kktix" \| "eztable" \| "104_jobs" \| "1111_jobs" \| "518_jobs" \| "yes123" \| "yourator" \| "meet_jobs" \| "cake" \| "calendly" \| "booking_com" \| "agoda" \| "airbnb" \| "asiayo" \| "kkday" \| "klook" \| "house591" \| "youbike" \| "irent" \| "hktvmall" \| "payme" \| "octopus" \| "alipay_hk" \| "jobsdb" \| "lihkg" \| "price_com_hk" \| "cityline" \| "joox" \| "ctgoodjobs" \| "discuss_com_hk" \| "hse28" \| "spacious" \| "urbtix" \| "jd_com" \| "yesstyle" \| "ztore" \| "moov" \| "flickr" \| "meetup" \| "kickstarter" \| "product_hunt" \| "lastfm" \| "scribd" \| "pocket" \| "steam" \| "vk" \| "xing" \| "orcid" \| "researchgate" | yes | — |
| `siteSettings.socialLinks[].url` | string | yes | — |
| `siteSettings.customButtons` | object[] | no | — |
| `siteSettings.customButtons[].id` | string | no | — |
| `siteSettings.customButtons[].iconType` | "icon" \| "platform" | no | — |
| `siteSettings.customButtons[].icon` | "briefcase" \| "star" \| "award" \| "crown" \| "flame" \| "zap" \| "target" \| "rocket" \| "flag" \| "gem" \| "diamond" \| "scroll" \| "fingerprint" \| "smile" \| "laugh" \| "clock" \| "phone" \| "mail" \| "bell" \| "settings" \| "check-circle" \| "info" \| "help-circle" \| "headset" \| "megaphone" \| "newspaper" \| "hourglass" \| "stamp" \| "milestone" \| "quote" \| "table" \| "list-ordered" \| "list-checks" \| "arrow-right" \| "message-circle" \| "external-link" \| "download" \| "play" \| "pause" \| "bookmark" \| "search" \| "filter" \| "thumbs-up" \| "share-2" \| "eye" \| "phone-call" \| "send" \| "lock" \| "rotate-ccw" \| "clipboard-check" \| "alarm-clock" \| "utensils" \| "utensils-crossed" \| "chef-hat" \| "coffee" \| "pizza" \| "soup" \| "ice-cream-cone" \| "sandwich" \| "beef" \| "fish" \| "salad" \| "cookie" \| "cake" \| "cake-slice" \| "dessert" \| "cup-soda" \| "wine" \| "beer" \| "martini" \| "milk" \| "wheat" \| "egg" \| "croissant" \| "popcorn" \| "candy" \| "apple" \| "cherry" \| "banana" \| "citrus" \| "grape" \| "carrot" \| "drumstick" \| "cooking-pot" \| "shrimp" \| "donut" \| "bean" \| "vegan" \| "store" \| "shopping-bag" \| "shopping-cart" \| "shopping-basket" \| "package" \| "package-check" \| "boxes" \| "truck" \| "motorbike" \| "warehouse" \| "tag" \| "tags" \| "ticket-percent" \| "barcode" \| "qr-code" \| "scan-qr-code" \| "credit-card" \| "smartphone-nfc" \| "wallet" \| "banknote" \| "coins" \| "dollar-sign" \| "percent" \| "badge-percent" \| "receipt" \| "shirt" \| "heart" \| "heart-pulse" \| "stethoscope" \| "hospital" \| "syringe" \| "pill" \| "pill-bottle" \| "bandage" \| "cross" \| "thermometer" \| "briefcase-medical" \| "scissors" \| "sparkles" \| "scan-face" \| "hand" \| "bath" \| "droplet" \| "droplets" \| "flower" \| "flower-2" \| "sun" \| "moon" \| "book-open" \| "book-text" \| "book-copy" \| "book-marked" \| "book-check" \| "graduation-cap" \| "school" \| "library" \| "monitor-play" \| "backpack" \| "pencil" \| "pen-tool" \| "file-text" \| "scroll-text" \| "notebook-pen" \| "languages" \| "calculator" \| "presentation" \| "lightbulb" \| "brain" \| "puzzle" \| "laptop" \| "monitor" \| "smartphone" \| "tablet" \| "code" \| "code-2" \| "terminal" \| "server" \| "database" \| "cloud" \| "cpu" \| "globe" \| "network" \| "circuit-board" \| "brain-circuit" \| "bot" \| "blocks" \| "palette" \| "brush" \| "paint-bucket" \| "pen-line" \| "image" \| "image-plus" \| "camera" \| "video" \| "film" \| "clapperboard" \| "music" \| "music-2" \| "music-4" \| "mic" \| "mic-2" \| "headphones" \| "speaker" \| "radio" \| "podcast" \| "piano" \| "plane" \| "map-pin" \| "map-pinned" \| "navigation" \| "route" \| "compass" \| "mountain" \| "tree-pine" \| "trees" \| "tent" \| "umbrella" \| "anchor" \| "ship" \| "car" \| "bike" \| "bus" \| "train" \| "luggage" \| "hotel" \| "bed-double" \| "concierge-bell" \| "wifi" \| "parking-square" \| "air-vent" \| "washing-machine" \| "cigarette-off" \| "accessibility" \| "dumbbell" \| "biceps-flexed" \| "footprints" \| "timer" \| "watch" \| "medal" \| "trophy" \| "swords" \| "badge-check" \| "activity" \| "building" \| "building-2" \| "factory" \| "house" \| "house-plus" \| "map-pin-house" \| "door-open" \| "door-closed" \| "key" \| "key-round" \| "fence" \| "sofa" \| "ruler" \| "hard-hat" \| "hammer" \| "wrench" \| "paint-roller" \| "brush-cleaning" \| "cat" \| "dog" \| "bird" \| "rabbit" \| "squirrel" \| "paw-print" \| "bone" \| "leaf" \| "sprout" \| "clover" \| "tractor" \| "cloud-sun" \| "cloud-rain" \| "snowflake" \| "leafy-green" \| "tree-deciduous" \| "wind" \| "waves" \| "recycle" \| "landmark" \| "vault" \| "scale" \| "gavel" \| "shield" \| "shield-check" \| "badge-dollar-sign" \| "hand-coins" \| "trending-up" \| "trending-down" \| "bar-chart" \| "bar-chart-2" \| "bar-chart-3" \| "pie-chart" \| "line-chart" \| "chart-no-axes-combined" \| "users" \| "users-round" \| "user-circle" \| "user-plus" \| "hand-heart" \| "hand-helping" \| "heart-handshake" \| "handshake" \| "baby" \| "church" \| "party-popper" \| "sparkle" \| "gift" \| "ticket" \| "calendar" \| "calendar-days" \| "calendar-check" \| "calendar-clock" | no | — |
| `siteSettings.customButtons[].platform` | "website" \| "facebook" \| "instagram" \| "line_oa" \| "threads" \| "tiktok" \| "douyin" \| "youtube" \| "twitter" \| "whatsapp" \| "linkedin" \| "xhs" \| "pinterest" \| "behance" \| "bluesky" \| "discord" \| "messenger" \| "dribbble" \| "github" \| "mastodon" \| "medium" \| "reddit" \| "snapchat" \| "substack" \| "patreon" \| "ko_fi" \| "buymeacoffee" \| "linktree" \| "tumblr" \| "quora" \| "nextdoor" \| "soundcloud" \| "bandcamp" \| "tidal" \| "notion" \| "figma" \| "canva" \| "signal" \| "slack" \| "zoom" \| "bilibili" \| "weibo" \| "wechat" \| "zhihu" \| "douban" \| "strava" \| "letterboxd" \| "goodreads" \| "yelp" \| "foursquare" \| "tripadvisor" \| "stackoverflow" \| "shopify" \| "skype" \| "viber" \| "wordpress" \| "deezer" \| "mixcloud" \| "dailymotion" \| "gitlab" \| "bitbucket" \| "trello" \| "dropbox" \| "codepen" \| "artstation" \| "audible" \| "gojek" \| "bukalapak" \| "zalo" \| "kakaotalk" \| "naver" \| "aliexpress" \| "amazon" \| "ebay" \| "etsy" \| "paypal" \| "stripe" \| "alipay" \| "jkopay" \| "line_pay" \| "taiwan_pay" \| "pxpay" \| "ipass_money" \| "grab" \| "meituan" \| "dianping" \| "plurk" \| "dcard" \| "bahamut" \| "shopee" \| "rakuten" \| "rakuten_tickets" \| "taobao" \| "pinkoi" \| "carousell" \| "momo" \| "pchome" \| "yahoo_shopping" \| "yahoo_mall" \| "ruten" \| "coupang" \| "books_com_tw" \| "etmall" \| "friday" \| "spotify" \| "vocus" \| "telegram" \| "twitch" \| "vimeo" \| "surveycake" \| "apple_podcast" \| "apple_music" \| "firstory" \| "kkbox" \| "soundon" \| "uber" \| "uber_eats" \| "foodpanda" \| "keeta" \| "deliveroo" \| "nidin" \| "ichef" \| "dudoo" \| "hahow" \| "pressplay" \| "udemy" \| "google_maps" \| "google_forms" \| "google_reviews" \| "typeform" \| "openrice" \| "inline" \| "funnow" \| "accupass" \| "kktix" \| "eztable" \| "104_jobs" \| "1111_jobs" \| "518_jobs" \| "yes123" \| "yourator" \| "meet_jobs" \| "cake" \| "calendly" \| "booking_com" \| "agoda" \| "airbnb" \| "asiayo" \| "kkday" \| "klook" \| "house591" \| "youbike" \| "irent" \| "hktvmall" \| "payme" \| "octopus" \| "alipay_hk" \| "jobsdb" \| "lihkg" \| "price_com_hk" \| "cityline" \| "joox" \| "ctgoodjobs" \| "discuss_com_hk" \| "hse28" \| "spacious" \| "urbtix" \| "jd_com" \| "yesstyle" \| "ztore" \| "moov" \| "flickr" \| "meetup" \| "kickstarter" \| "product_hunt" \| "lastfm" \| "scribd" \| "pocket" \| "steam" \| "vk" \| "xing" \| "orcid" \| "researchgate" | no | — |
| `siteSettings.customButtons[].text` | string | no | — |
| `siteSettings.customButtons[].url` | string | no | Optional link: absolute http(s)/tel:/mailto: URL, site-relative path (/about) or anchor (#id). |
| `siteSettings.customButtons[].link` | object | no | — |
| `siteSettings.customButtons[].openInNewTab` | boolean | no | — |
| `siteSettings.customButtons[].style` | "brand" \| "default" \| "ghost" | no | — |
| `siteSettings.customButtons[].iconPosition` | "left" \| "right" | no | — |
| `siteSettings.socialIconColor` | object | no | — |
| `siteSettings.socialIconColor.light` | string | no | — |
| `siteSettings.socialIconColor.dark` | string | no | — |
| `siteSettings.showBreadcrumbs` | boolean | no | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `siteSettings` | object | yes | siteSettings as stored; customButtons[].id are the ids to use in ctaIds. |
| `assets` | object | yes | The media-library files referenced as asset:<assetId>, by assetId. |
| `contentUpdatedAt` | string \| null | yes | Revision marker of the site-level draft (Website.contentUpdatedAt). Pass it back as ifUnmodifiedSince. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBSITE_NOT_FOUND` | 404 | — |
| `VALIDATION` | 400 | — |
| `CONFLICT` | 409 | — |
| `WRITE_FROZEN` | 503 | — |
| `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 PATCH "https://app.kanorio.com/api/v1/sites/{id}/globals/site-settings" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ifUnmodifiedSince":"2026-10-06T08:00:00.000Z","siteSettings":{"customButtons":[{"id":"btn_order","text":"Order online","url":"/shop","style":"brand"}]}}'
```

```json
{
  "siteSettings": {
    "customButtons": [
      {
        "id": "btn_order",
        "text": "Order online",
        "url": "/shop",
        "style": "brand"
      }
    ],
    "socialLinks": [
      {
        "platform": "instagram",
        "url": "https://www.instagram.com/sunnybakery.example"
      }
    ]
  },
  "assets": {},
  "contentUpdatedAt": "2026-10-06T08:00:00.000Z"
}
```

### Replace header

`PUT /sites/{id}/globals/header`

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

Replace the primary header or a secondary header identified by key (whole object, optimistic concurrency). The header's id stays the one pages already reference; when the site has no header yet, the given id is used and attached to every page. Typical use after creating pages: rebuild navItems so each visible page has an internal link. Saved as draft only; call site.publish to go live. Reference a logoOverride image as asset:<assetId>. Errors: FORBIDDEN, WEBSITE_NOT_FOUND, HEADER_NOT_FOUND, VALIDATION (details lists the failing field), CONFLICT, WRITE_FROZEN, 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. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ifUnmodifiedSince` | string | yes | contentUpdatedAt from globals.get. Required: the write is rejected with CONFLICT when the site-level draft changed since (details.contentUpdatedAt holds the current value). |
| `key` | string | no | Header key to replace (default 'primary'; pass a secondary header key from globals.list_headers to replace that header). |
| `header` | object | yes | Complete header object. navItems is the navigation: one { id, text, linkType: "internal", pageSlug } per visible page (the header never lists pages automatically); keep text short (1-3 words) since it has to fit a single desktop row — it is not the page title. Use linkType: "group" to nest related pages under one dropdown instead of adding many top-level items (desktop headers work best with 7 or fewer top-level items). Use ctaIds (up to 2) to show buttons from siteSettings.customButtons. layout picks the desktop arrangement (top-spread, top-left, top-center, top-stacked, side-left) — top-stacked puts navigation on its own full-width row below the logo/CTA row and suits sites with many nav items; surface sets the background tone; showBrandLogo / showSiteName / logoSize control the brand slot and showSocialLinks (boolean or per device) plus hiddenSocialLinkUrls choose which social links appear; utilities orders and toggles the feature buttons (search, cart) after the CTA buttons. The logo comes from the website's brand settings unless logoOverride is set. |
| `header.type` | "header" | yes | — |
| `header.id` | string | yes | Stable id; pages reference it via globalHeaderId. |
| `header.key` | string | no | Human-readable key for this header (e.g. 'primary', 'campaign', 'docs'). |
| `header.cta` | object | no | — |
| `header.cta.id` | string | no | — |
| `header.cta.icon` | "briefcase" \| "star" \| "award" \| "crown" \| "flame" \| "zap" \| "target" \| "rocket" \| "flag" \| "gem" \| "diamond" \| "scroll" \| "fingerprint" \| "smile" \| "laugh" \| "clock" \| "phone" \| "mail" \| "bell" \| "settings" \| "check-circle" \| "info" \| "help-circle" \| "headset" \| "megaphone" \| "newspaper" \| "hourglass" \| "stamp" \| "milestone" \| "quote" \| "table" \| "list-ordered" \| "list-checks" \| "arrow-right" \| "message-circle" \| "external-link" \| "download" \| "play" \| "pause" \| "bookmark" \| "search" \| "filter" \| "thumbs-up" \| "share-2" \| "eye" \| "phone-call" \| "send" \| "lock" \| "rotate-ccw" \| "clipboard-check" \| "alarm-clock" \| "utensils" \| "utensils-crossed" \| "chef-hat" \| "coffee" \| "pizza" \| "soup" \| "ice-cream-cone" \| "sandwich" \| "beef" \| "fish" \| "salad" \| "cookie" \| "cake" \| "cake-slice" \| "dessert" \| "cup-soda" \| "wine" \| "beer" \| "martini" \| "milk" \| "wheat" \| "egg" \| "croissant" \| "popcorn" \| "candy" \| "apple" \| "cherry" \| "banana" \| "citrus" \| "grape" \| "carrot" \| "drumstick" \| "cooking-pot" \| "shrimp" \| "donut" \| "bean" \| "vegan" \| "store" \| "shopping-bag" \| "shopping-cart" \| "shopping-basket" \| "package" \| "package-check" \| "boxes" \| "truck" \| "motorbike" \| "warehouse" \| "tag" \| "tags" \| "ticket-percent" \| "barcode" \| "qr-code" \| "scan-qr-code" \| "credit-card" \| "smartphone-nfc" \| "wallet" \| "banknote" \| "coins" \| "dollar-sign" \| "percent" \| "badge-percent" \| "receipt" \| "shirt" \| "heart" \| "heart-pulse" \| "stethoscope" \| "hospital" \| "syringe" \| "pill" \| "pill-bottle" \| "bandage" \| "cross" \| "thermometer" \| "briefcase-medical" \| "scissors" \| "sparkles" \| "scan-face" \| "hand" \| "bath" \| "droplet" \| "droplets" \| "flower" \| "flower-2" \| "sun" \| "moon" \| "book-open" \| "book-text" \| "book-copy" \| "book-marked" \| "book-check" \| "graduation-cap" \| "school" \| "library" \| "monitor-play" \| "backpack" \| "pencil" \| "pen-tool" \| "file-text" \| "scroll-text" \| "notebook-pen" \| "languages" \| "calculator" \| "presentation" \| "lightbulb" \| "brain" \| "puzzle" \| "laptop" \| "monitor" \| "smartphone" \| "tablet" \| "code" \| "code-2" \| "terminal" \| "server" \| "database" \| "cloud" \| "cpu" \| "globe" \| "network" \| "circuit-board" \| "brain-circuit" \| "bot" \| "blocks" \| "palette" \| "brush" \| "paint-bucket" \| "pen-line" \| "image" \| "image-plus" \| "camera" \| "video" \| "film" \| "clapperboard" \| "music" \| "music-2" \| "music-4" \| "mic" \| "mic-2" \| "headphones" \| "speaker" \| "radio" \| "podcast" \| "piano" \| "plane" \| "map-pin" \| "map-pinned" \| "navigation" \| "route" \| "compass" \| "mountain" \| "tree-pine" \| "trees" \| "tent" \| "umbrella" \| "anchor" \| "ship" \| "car" \| "bike" \| "bus" \| "train" \| "luggage" \| "hotel" \| "bed-double" \| "concierge-bell" \| "wifi" \| "parking-square" \| "air-vent" \| "washing-machine" \| "cigarette-off" \| "accessibility" \| "dumbbell" \| "biceps-flexed" \| "footprints" \| "timer" \| "watch" \| "medal" \| "trophy" \| "swords" \| "badge-check" \| "activity" \| "building" \| "building-2" \| "factory" \| "house" \| "house-plus" \| "map-pin-house" \| "door-open" \| "door-closed" \| "key" \| "key-round" \| "fence" \| "sofa" \| "ruler" \| "hard-hat" \| "hammer" \| "wrench" \| "paint-roller" \| "brush-cleaning" \| "cat" \| "dog" \| "bird" \| "rabbit" \| "squirrel" \| "paw-print" \| "bone" \| "leaf" \| "sprout" \| "clover" \| "tractor" \| "cloud-sun" \| "cloud-rain" \| "snowflake" \| "leafy-green" \| "tree-deciduous" \| "wind" \| "waves" \| "recycle" \| "landmark" \| "vault" \| "scale" \| "gavel" \| "shield" \| "shield-check" \| "badge-dollar-sign" \| "hand-coins" \| "trending-up" \| "trending-down" \| "bar-chart" \| "bar-chart-2" \| "bar-chart-3" \| "pie-chart" \| "line-chart" \| "chart-no-axes-combined" \| "users" \| "users-round" \| "user-circle" \| "user-plus" \| "hand-heart" \| "hand-helping" \| "heart-handshake" \| "handshake" \| "baby" \| "church" \| "party-popper" \| "sparkle" \| "gift" \| "ticket" \| "calendar" \| "calendar-days" \| "calendar-check" \| "calendar-clock" | no | — |
| `header.cta.text` | string | no | — |
| `header.cta.url` | string | no | Optional link: absolute http(s)/tel:/mailto: URL, site-relative path (/about) or anchor (#id). |
| `header.cta.style` | "brand" \| "default" \| "ghost" | no | — |
| `header.ctaId` | string | no | Legacy single button id from siteSettings.customButtons. Prefer ctaIds; when both are set ctaIds wins. |
| `header.ctaIds` | string[] | no | Up to 2 ids of buttons defined in siteSettings.customButtons, shown at the right of the header (and at the bottom of the side-left sidebar / mobile menu). |
| `header.ctaStyleById` | object | no | Per-button style override keyed by customButton id: brand (filled brand color), default (neutral), or ghost (text with arrow). |
| `header.layout` | "top-spread" \| "top-left" \| "top-center" \| "top-stacked" \| "side-left" | no | Desktop header layout: top-spread (default), top-left (nav next to logo), top-center (centered nav), top-stacked (nav on its own full-width row below the logo/CTA row; best for many nav items or long labels), or side-left (fixed left sidebar). |
| `header.surface` | "auto" \| "default" \| "muted" \| "tint" \| "brand" \| "contrast" | no | Header background tone: default (light bar with blur), muted, tint, brand or contrast; text colors adapt automatically. auto behaves like default. |
| `header.showBrandLogo` | boolean | no | Show the brand logo (default true). When false and showSiteName is false, nothing is rendered in the brand slot. |
| `header.showSiteName` | boolean | no | Show the website name next to (or instead of) the logo (default false; the name is still shown when no logo exists). |
| `header.logoSize` | "sm" \| "md" \| "lg" | no | Logo size in the header (default md). |
| `header.showSocialLinks` | boolean \| object | no | Show siteSettings.socialLinks in the header, either for every device (boolean) or per device. Defaults: desktop only for the side-left layout, tablet and mobile menu on. |
| `header.showSocialLinks.desktop` | boolean | no | — |
| `header.showSocialLinks.tablet` | boolean | no | — |
| `header.showSocialLinks.mobile` | boolean | no | — |
| `header.hiddenSocialLinkUrls` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `header.hiddenSocialLinkUrlsByDevice` | object | no | Per-device blacklist of social link urls; a device without its own list falls back to hiddenSocialLinkUrls. |
| `header.hiddenSocialLinkUrlsByDevice.desktop` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `header.hiddenSocialLinkUrlsByDevice.tablet` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `header.hiddenSocialLinkUrlsByDevice.mobile` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `header.variant` | "solid" \| "transparent" \| "glass" | no | Deprecated and ignored: the header background is controlled by surface. Accepted for backward compatibility. |
| `header.shrinkOnScroll` | boolean | no | Compact the header after scrolling (default true). |
| `header.showProgressBar` | boolean | no | — |
| `header.showAccentLine` | boolean | no | Show a thin brand-gradient line at the bottom edge (default false). |
| `header.isVisible` | boolean | no | — |
| `header.navItems` | object[] | no | Navigation links. The header never lists pages automatically; add one internal item per visible page. |
| `header.navItems[].id` | string | yes | — |
| `header.navItems[].text` | string | yes | — |
| `header.navItems[].linkType` | "internal" \| "external" \| "group" | yes | — |
| `header.navItems[].pageSlug` | string | no | — |
| `header.navItems[].anchorId` | string | no | — |
| `header.navItems[].externalUrl` | string | no | Optional link: absolute http(s)/tel:/mailto: URL, site-relative path (/about) or anchor (#id). |
| `header.navItems[].openInNewTab` | boolean | no | — |
| `header.navItems[].children` | object[] | no | — |
| `header.navItems[].link` | object | no | — |
| `header.utilities` | object[] | no | Header utility buttons (site features shown at the right of the header, after the CTA buttons), in display order; each id at most once. enabled shows or hides it; an omitted id keeps its default (search: off, cart: on, language: on, account: on; newly created sites default all to on). A utility only renders when the site has that feature: search needs a published site with searchable content; cart needs the shopping cart turned on in store settings and at least one active product; language needs at least two published site languages; account needs the customer sign-in entry turned on under Settings › Customer accounts (signed-in customers always see it). On phones only the first 2 visible utilities stay next to the menu button; the rest move to the top of the mobile menu. |
| `header.utilities[].id` | "search" \| "cart" \| "language" \| "account" | yes | search: site search button; cart: shopping cart button; language: language switcher; account: customer sign-in / account panel. |
| `header.utilities[].enabled` | boolean | yes | — |
| `header.isGlobal` | boolean | no | — |
| `header.logoOverride` | any | no | Header-specific logo (e.g. a wide wordmark). Omit to use the website's brand logo. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `header` | object | yes | Header as stored (id may differ from the one sent when the site already had one). |
| `assets` | object | yes | The media-library files referenced as asset:<assetId>, by assetId. |
| `contentUpdatedAt` | string \| null | yes | Revision marker of the site-level draft (Website.contentUpdatedAt). Pass it back as ifUnmodifiedSince. |
| `warnings` | object[] | yes | Content-quality warnings about navItems: long labels or too many top-level items. These are fixed content rules, not a prediction of whether the header will overflow at some width — the renderer always collapses items that do not fit into a "more" menu, so the site never breaks either way. Empty array when there is nothing to flag. |
| `warnings[].code` | "NAV_LABEL_TOO_LONG" \| "NAV_TOO_MANY_ITEMS" | yes | — |
| `warnings[].message` | string | yes | — |
| `warnings[].navItemIds` | string[] | no | ids of the navItems the warning is about, when applicable. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBSITE_NOT_FOUND` | 404 | — |
| `HEADER_NOT_FOUND` | 404 | — |
| `VALIDATION` | 400 | details lists the failing field |
| `CONFLICT` | 409 | — |
| `WRITE_FROZEN` | 503 | — |
| `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}/globals/header" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ifUnmodifiedSince":"2026-10-06T08:00:00.000Z","header":{"id":"sec_header01","type":"header","navItems":[{"id":"nav_home","text":"Home","linkType":"internal","pageSlug":"/"},{"id":"nav_about","text":"About Us","linkType":"internal","pageSlug":"/about"}],"ctaIds":["btn_order"]}}'
```

```json
{
  "header": {
    "id": "sec_header01",
    "type": "header",
    "navItems": [
      {
        "id": "nav_home",
        "text": "Home",
        "linkType": "internal",
        "pageSlug": "/"
      },
      {
        "id": "nav_about",
        "text": "About Us",
        "linkType": "internal",
        "pageSlug": "/about"
      }
    ],
    "ctaIds": [
      "btn_order"
    ]
  },
  "assets": {},
  "contentUpdatedAt": "2026-10-06T08:00:00.000Z",
  "warnings": []
}
```

### Replace footer

`PUT /sites/{id}/globals/footer`

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

Replace the primary footer (whole object, optimistic concurrency). The footer's id stays the one pages already reference; when the site has no footer yet, the given id is used and attached to every page. Saved as draft only; call site.publish to go live. Errors: FORBIDDEN, WEBSITE_NOT_FOUND, VALIDATION, CONFLICT, WRITE_FROZEN, 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. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ifUnmodifiedSince` | string | yes | contentUpdatedAt from globals.get. Required: the write is rejected with CONFLICT when the site-level draft changed since (details.contentUpdatedAt holds the current value). |
| `footer` | object | yes | Complete footer object: copyright, optional description, navItems (a group item renders as its own column), showBrandLogo / showSiteName / showSocialLinks / hiddenSocialLinkUrls, layout and surface, optional ctaIds (up to 2), and enabledTerms to link the legal pages. |
| `footer.type` | "footer" | yes | — |
| `footer.isVisible` | boolean | no | — |
| `footer.layout` | "columns" \| "centered" \| "minimal" | no | Footer layout preset. |
| `footer.surface` | "auto" \| "default" \| "muted" \| "tint" \| "brand" \| "contrast" | no | Footer background tone: muted (default), default, tint, brand or contrast; text and icon colors adapt automatically. |
| `footer.logoSize` | "sm" \| "md" \| "lg" | no | Logo size in the footer (default md). |
| `footer.logoFramed` | boolean | no | Wrap the logo in a white rounded frame (default false: logo rendered as is). |
| `footer.showBrandLogo` | boolean | no | — |
| `footer.showSocialLinks` | boolean | no | — |
| `footer.hiddenSocialLinkUrls` | string[] | no | Urls from siteSettings.socialLinks to hide in this section (blacklist; omitted or empty shows all). |
| `footer.ctaIds` | string[] | no | Up to 2 ids of buttons defined in siteSettings.customButtons, shown under the brand description. |
| `footer.ctaStyleById` | object | no | Per-button style override keyed by customButton id: brand (filled brand color), default (neutral), or ghost (text with arrow). |
| `footer.showAccentLine` | boolean | no | Show a thin brand-gradient line at the top edge (default false). |
| `footer.copyright` | string | no | — |
| `footer.description` | string | no | Body text (optional). Multi-line description. You may use paragraphs, lists (- or 1.), quotes (>), fenced code, bold **text**, italic *text*, underline ++text++, strikethrough ~~text~~, and links [text](https://…). Do not use headings (#), tables, images, or HTML; headings are converted to bold paragraphs. Up to 3000 visible characters. |
| `footer.descriptionCollapsible` | 3 \| 5 \| 10 \| 15 \| "scroll" \| null | no | Clamp long text: null shows everything, a number clamps to that many lines, "scroll" keeps a fixed height with scrolling. |
| `footer.showSiteName` | boolean | no | — |
| `footer.navItems` | object[] | no | Footer links. An item with linkType group becomes its own column titled with the item text; its children are the links of that column. |
| `footer.navItems[].id` | string | yes | — |
| `footer.navItems[].text` | string | yes | — |
| `footer.navItems[].linkType` | "internal" \| "external" \| "group" | yes | — |
| `footer.navItems[].pageSlug` | string | no | — |
| `footer.navItems[].anchorId` | string | no | — |
| `footer.navItems[].externalUrl` | string | no | Optional link: absolute http(s)/tel:/mailto: URL, site-relative path (/about) or anchor (#id). |
| `footer.navItems[].openInNewTab` | boolean | no | — |
| `footer.navItems[].children` | object[] | no | — |
| `footer.navItems[].link` | object | no | — |
| `footer.enabledTerms` | "cookie-settings" \| "cookie-policy" \| "privacy-policy" \| "terms-of-service" \| "purchase-terms" \| "return-policy" \| "disclaimer"[] | no | Legal links to render at the bottom; each refers to one of the hidden legal pages created with the site. |
| `footer.id` | string | yes | Stable id; pages reference it via globalFooterId. |
| `footer.isGlobal` | boolean | no | — |
| `footer.showAddress` | boolean | no | — |
| `footer.showPhone` | boolean | no | — |
| `footer.showEmail` | boolean | no | — |
| `footer.showBusinessHours` | boolean | no | — |
| `footer.navItemsTitle` | string | no | — |
| `footer.contactInfoTitle` | string | no | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `footer` | object | yes | — |
| `assets` | object | yes | The media-library files referenced as asset:<assetId>, by assetId. |
| `contentUpdatedAt` | string \| null | yes | Revision marker of the site-level draft (Website.contentUpdatedAt). Pass it back as ifUnmodifiedSince. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `WEBSITE_NOT_FOUND` | 404 | — |
| `VALIDATION` | 400 | — |
| `CONFLICT` | 409 | — |
| `WRITE_FROZEN` | 503 | — |
| `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}/globals/footer" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ifUnmodifiedSince":"2026-10-06T08:00:00.000Z","footer":{"id":"sec_footer01","type":"footer","copyright":"© 2026 Sunny Bakery"}}'
```

```json
{
  "footer": {
    "id": "sec_footer01",
    "type": "footer",
    "copyright": "© 2026 Sunny Bakery"
  },
  "assets": {},
  "contentUpdatedAt": "2026-10-06T08:00:00.000Z"
}
```
