# Collections · Management API

> Management API reference for collections: GET /sites/{id}/collections, POST /sites/{id}/collections, GET /sites/{id}/collections/{key}, PATCH /sites/{id}/collections/{key}, ….

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

`GET /sites/{id}/collections`

- Stability: beta
- Permission: `collections:view`
- Rate limit: default (120/minute per token)
- MCP tool: `collections_list`

List the site's collections (custom and system) with their field schemas and item counts. Call this before reading or writing items. 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[].key` | string | yes | — |
| `data[].displayName` | string | yes | — |
| `data[].singularName` | string | yes | — |
| `data[].description` | string \| null | yes | — |
| `data[].kind` | "custom" \| "system" | yes | system collections (blog) have reserved fields that cannot be removed or retyped. |
| `data[].systemKey` | string \| null | yes | — |
| `data[].fields` | object[] | yes | — |
| `data[].fields[].id` | string | yes | — |
| `data[].fields[].name` | string | yes | — |
| `data[].fields[].required` | boolean | yes | — |
| `data[].fields[].help` | string | no | — |
| `data[].fields[].aiHint` | string | no | — |
| `data[].fields[].system` | boolean | no | — |
| `data[].fields[].indexed` | boolean | no | — |
| `data[].fields[].unique` | boolean | no | — |
| `data[].fields[].type` | "text" | yes | — |
| `data[].fields[].min` | integer | no | — |
| `data[].fields[].max` | integer | no | — |
| `data[].fields[].pattern` | string | no | — |
| `data[].fields[].default` | string | no | — |
| `data[].fields[].localized` | boolean | no | — |
| `data[].titleFieldId` | string | yes | — |
| `data[].accessPolicy` | object \| null | yes | Field-level read policy for the Content Delivery API: gatedFields are stripped on anonymous requests (returning top-level access summary) and unlocked when a Customer Access Token (knr_cat_) satisfies requires (customer login or an active entitlement featureKey). |
| `data[].accessPolicy.gatedFields` | string[] | yes | — |
| `data[].accessPolicy.requires` | object | yes | — |
| `data[].accessPolicy.itemOverrideField` | string | no | — |
| `data[].search` | object \| null | yes | Site-search settings. null = items are not indexed. fields: text/rich_text fieldIds searched as body (the title field is always indexed). pathTemplate (optional, e.g. /recipes/{slug}) is a presentation setting: when set, search hits carry a url; otherwise url is null and the front end routes by collectionKey + slug or fetches the item from the Delivery API. |
| `data[].search.fields` | string[] | yes | — |
| `data[].search.pathTemplate` | string \| null | no | — |
| `data[].createdBy` | object | yes | — |
| `data[].createdBy.kind` | "agent" \| "user" | yes | — |
| `data[].createdBy.actorId` | string | yes | — |
| `data[].createdBy.clientName` | string \| null | no | — |
| `data[].itemCount` | integer | yes | — |
| `data[].createdAt` | string | yes | — |
| `data[].updatedAt` | string | yes | — |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

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

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

```json
{
  "data": [
    {
      "id": "coll_example01",
      "key": "teachers",
      "displayName": "Teachers",
      "singularName": "Teacher",
      "description": "Faculty shown on the About page.",
      "kind": "custom",
      "systemKey": null,
      "fields": [
        {
          "id": "name",
          "name": "Name",
          "type": "text",
          "required": true,
          "max": 80,
          "unique": true,
          "aiHint": "Full name as shown on the site."
        },
        {
          "id": "avatar",
          "name": "Avatar",
          "type": "image",
          "required": false,
          "help": "Square portrait."
        },
        {
          "id": "subjects",
          "name": "Subjects",
          "type": "multi_select",
          "required": true,
          "options": [
            {
              "id": "math",
              "label": "Math"
            },
            {
              "id": "english",
              "label": "English"
            },
            {
              "id": "physics",
              "label": "Physics"
            }
          ]
        },
        {
          "id": "bio",
          "name": "Bio",
          "type": "rich_text",
          "required": false,
          "max": 2000
        },
        {
          "id": "website",
          "name": "Personal website",
          "type": "url",
          "required": false
        },
        {
          "id": "course",
          "name": "Main course",
          "type": "reference",
          "required": false,
          "targetCollectionKey": "courses"
        }
      ],
      "titleFieldId": "name",
      "accessPolicy": null,
      "search": {
        "fields": [
          "bio"
        ]
      },
      "createdBy": {
        "kind": "agent",
        "actorId": "user_example01",
        "clientName": "Cursor"
      },
      "itemCount": 3,
      "createdAt": "2026-10-01T02:30:00.000Z",
      "updatedAt": "2026-10-06T08:00:00.000Z"
    },
    {
      "id": "coll_example02",
      "key": "courses",
      "displayName": "Courses",
      "singularName": "Course",
      "description": null,
      "kind": "custom",
      "systemKey": null,
      "fields": [
        {
          "id": "name",
          "name": "Name",
          "type": "text",
          "required": true
        }
      ],
      "titleFieldId": "name",
      "accessPolicy": null,
      "search": null,
      "createdBy": {
        "kind": "agent",
        "actorId": "user_example01",
        "clientName": "Cursor"
      },
      "itemCount": 2,
      "createdAt": "2026-10-01T02:30:00.000Z",
      "updatedAt": "2026-10-06T08:00:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

### Create a collection

`POST /sites/{id}/collections`

- Stability: beta
- Permission: `collections:schema`
- Rate limit: default (120/minute per token)
- MCP tool: `collections_create`

Create a collection (a small database for site content: teachers, projects, locations, FAQ…). Not for app data such as members, orders or comments. Field types: text, rich_text, email, phone, url, video_url, number, boolean, date, datetime, select, multi_select, color, image, gallery, file, reference, multi_reference, json. Every field needs id (snake_case), name, type; required defaults to false. Constraints: text min/max/pattern/unique/default; number integer/min/max/unique; select/multi_select options [{id,label}]; reference/multi_reference targetCollectionKey; gallery/multi_* max. Add aiHint to tell future agents how to fill a field. titleFieldId must point to a text/email/url field; it names items and generates slugs. Optional search: { fields, pathTemplate? } puts published items into site search (GET /api/v1/search returns kind=COLLECTION_ITEM, collectionKey, refId, slug; url only when pathTemplate is set). Returns the Delivery API endpoints and a fetch example. Errors: FORBIDDEN, VALIDATION (details.issues), KEY_TAKEN, COLLECTION_LIMIT_REACHED, SYSTEM_COLLECTION (reserved key).

**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 |
| --- | --- | --- | --- |
| `key` | string | yes | — |
| `displayName` | string | yes | — |
| `singularName` | string | yes | — |
| `description` | string | no | — |
| `fields` | object[] | yes | — |
| `fields[].id` | string | yes | — |
| `fields[].name` | string | yes | — |
| `fields[].required` | boolean | no | — |
| `fields[].help` | string | no | — |
| `fields[].aiHint` | string | no | — |
| `fields[].system` | boolean | no | — |
| `fields[].indexed` | boolean | no | — |
| `fields[].unique` | boolean | no | — |
| `fields[].type` | "text" | yes | — |
| `fields[].min` | integer | no | — |
| `fields[].max` | integer | no | — |
| `fields[].pattern` | string | no | — |
| `fields[].default` | string | no | — |
| `fields[].localized` | boolean | no | — |
| `titleFieldId` | string | yes | — |
| `accessPolicy` | object \| null | no | — |
| `accessPolicy.gatedFields` | string[] | yes | — |
| `accessPolicy.requires` | object | yes | — |
| `accessPolicy.requires.kind` | "customer" | yes | — |
| `accessPolicy.itemOverrideField` | string | no | — |
| `search` | object \| null | no | — |
| `search.fields` | string[] | yes | — |
| `search.pathTemplate` | string \| null | no | — |

**Response** (201)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `collection` | object | yes | — |
| `collection.id` | string | yes | — |
| `collection.key` | string | yes | — |
| `collection.displayName` | string | yes | — |
| `collection.singularName` | string | yes | — |
| `collection.description` | string \| null | yes | — |
| `collection.kind` | "custom" \| "system" | yes | system collections (blog) have reserved fields that cannot be removed or retyped. |
| `collection.systemKey` | string \| null | yes | — |
| `collection.fields` | object[] | yes | — |
| `collection.fields[].id` | string | yes | — |
| `collection.fields[].name` | string | yes | — |
| `collection.fields[].required` | boolean | yes | — |
| `collection.fields[].help` | string | no | — |
| `collection.fields[].aiHint` | string | no | — |
| `collection.fields[].system` | boolean | no | — |
| `collection.fields[].indexed` | boolean | no | — |
| `collection.fields[].unique` | boolean | no | — |
| `collection.fields[].type` | "text" | yes | — |
| `collection.fields[].min` | integer | no | — |
| `collection.fields[].max` | integer | no | — |
| `collection.fields[].pattern` | string | no | — |
| `collection.fields[].default` | string | no | — |
| `collection.fields[].localized` | boolean | no | — |
| `collection.titleFieldId` | string | yes | — |
| `collection.accessPolicy` | object \| null | yes | Field-level read policy for the Content Delivery API: gatedFields are stripped on anonymous requests (returning top-level access summary) and unlocked when a Customer Access Token (knr_cat_) satisfies requires (customer login or an active entitlement featureKey). |
| `collection.accessPolicy.gatedFields` | string[] | yes | — |
| `collection.accessPolicy.requires` | object | yes | — |
| `collection.accessPolicy.itemOverrideField` | string | no | — |
| `collection.search` | object \| null | yes | Site-search settings. null = items are not indexed. fields: text/rich_text fieldIds searched as body (the title field is always indexed). pathTemplate (optional, e.g. /recipes/{slug}) is a presentation setting: when set, search hits carry a url; otherwise url is null and the front end routes by collectionKey + slug or fetches the item from the Delivery API. |
| `collection.search.fields` | string[] | yes | — |
| `collection.search.pathTemplate` | string \| null | no | — |
| `collection.createdBy` | object | yes | — |
| `collection.createdBy.kind` | "agent" \| "user" | yes | — |
| `collection.createdBy.actorId` | string | yes | — |
| `collection.createdBy.clientName` | string \| null | no | — |
| `collection.itemCount` | integer | yes | — |
| `collection.createdAt` | string | yes | — |
| `collection.updatedAt` | string | yes | — |
| `delivery` | object | yes | — |
| `delivery.baseUrl` | string | yes | The site's public origin. |
| `delivery.listUrl` | string | yes | — |
| `delivery.itemUrl` | string | yes | Replace {slug} with an item's slug. |
| `delivery.schemaUrl` | string | yes | — |
| `delivery.fetchExample` | string | yes | Copy-paste JavaScript using fetch(). |
| `delivery.cors` | string | yes | How cross-origin access works and where to allow origins. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `VALIDATION` | 400 | details.issues |
| `KEY_TAKEN` | 409 | — |
| `COLLECTION_LIMIT_REACHED` | 403 | — |
| `SYSTEM_COLLECTION` | 409 | reserved key |

```bash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/collections" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key":"teachers","displayName":"Teachers","singularName":"Teacher","description":"Faculty shown on the About page.","titleFieldId":"name","fields":[{"id":"name","name":"Name","type":"text","required":true,"max":80,"unique":true,"aiHint":"Full name as shown on the site."},{"id":"avatar","name":"Avatar","type":"image","required":false,"help":"Square portrait."},{"id":"subjects","name":"Subjects","type":"multi_select","required":true,"options":[{"id":"math","label":"Math"},{"id":"english","label":"English"},{"id":"physics","label":"Physics"}]},{"id":"bio","name":"Bio","type":"rich_text","required":false,"max":2000},{"id":"website","name":"Personal website","type":"url","required":false},{"id":"course","name":"Main course","type":"reference","required":false,"targetCollectionKey":"courses"}]}'
```

```json
{
  "collection": {
    "id": "coll_example01",
    "key": "teachers",
    "displayName": "Teachers",
    "singularName": "Teacher",
    "description": "Faculty shown on the About page.",
    "kind": "custom",
    "systemKey": null,
    "fields": [
      {
        "id": "name",
        "name": "Name",
        "type": "text",
        "required": true,
        "max": 80,
        "unique": true,
        "aiHint": "Full name as shown on the site."
      },
      {
        "id": "avatar",
        "name": "Avatar",
        "type": "image",
        "required": false,
        "help": "Square portrait."
      },
      {
        "id": "subjects",
        "name": "Subjects",
        "type": "multi_select",
        "required": true,
        "options": [
          {
            "id": "math",
            "label": "Math"
          },
          {
            "id": "english",
            "label": "English"
          },
          {
            "id": "physics",
            "label": "Physics"
          }
        ]
      },
      {
        "id": "bio",
        "name": "Bio",
        "type": "rich_text",
        "required": false,
        "max": 2000
      },
      {
        "id": "website",
        "name": "Personal website",
        "type": "url",
        "required": false
      },
      {
        "id": "course",
        "name": "Main course",
        "type": "reference",
        "required": false,
        "targetCollectionKey": "courses"
      }
    ],
    "titleFieldId": "name",
    "accessPolicy": null,
    "search": {
      "fields": [
        "bio"
      ]
    },
    "createdBy": {
      "kind": "agent",
      "actorId": "user_example01",
      "clientName": "Cursor"
    },
    "itemCount": 0,
    "createdAt": "2026-10-01T02:30:00.000Z",
    "updatedAt": "2026-10-06T08:00:00.000Z"
  },
  "delivery": {
    "baseUrl": "https://sunny-bakery.kanorio.com",
    "listUrl": "https://sunny-bakery.kanorio.com/api/v1/collections/teachers/items",
    "itemUrl": "https://sunny-bakery.kanorio.com/api/v1/collections/teachers/items/{slug}",
    "schemaUrl": "https://sunny-bakery.kanorio.com/api/v1/collections",
    "fetchExample": "const res = await fetch(\"https://sunny-bakery.kanorio.com/api/v1/collections/teachers/items?limit=20&sort=-publishedAt\");\nconst { data, hasMore, nextCursor } = await res.json();",
    "cors": "Same-origin requests need no setup. Other origins must be added with site.update_allowed_origins; localhost is always allowed."
  }
}
```

### Retrieve a collection

`GET /sites/{id}/collections/{key}`

- Stability: beta
- Permission: `collections:view`
- Rate limit: default (120/minute per token)
- MCP tool: `collections_get`

Get one collection's schema (fields, constraints, aiHint) plus its Content Delivery API endpoints. Errors: FORBIDDEN, COLLECTION_NOT_FOUND.

**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 | Collection key (slug). Get it from collections.list. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `collection` | object | yes | — |
| `collection.id` | string | yes | — |
| `collection.key` | string | yes | — |
| `collection.displayName` | string | yes | — |
| `collection.singularName` | string | yes | — |
| `collection.description` | string \| null | yes | — |
| `collection.kind` | "custom" \| "system" | yes | system collections (blog) have reserved fields that cannot be removed or retyped. |
| `collection.systemKey` | string \| null | yes | — |
| `collection.fields` | object[] | yes | — |
| `collection.fields[].id` | string | yes | — |
| `collection.fields[].name` | string | yes | — |
| `collection.fields[].required` | boolean | yes | — |
| `collection.fields[].help` | string | no | — |
| `collection.fields[].aiHint` | string | no | — |
| `collection.fields[].system` | boolean | no | — |
| `collection.fields[].indexed` | boolean | no | — |
| `collection.fields[].unique` | boolean | no | — |
| `collection.fields[].type` | "text" | yes | — |
| `collection.fields[].min` | integer | no | — |
| `collection.fields[].max` | integer | no | — |
| `collection.fields[].pattern` | string | no | — |
| `collection.fields[].default` | string | no | — |
| `collection.fields[].localized` | boolean | no | — |
| `collection.titleFieldId` | string | yes | — |
| `collection.accessPolicy` | object \| null | yes | Field-level read policy for the Content Delivery API: gatedFields are stripped on anonymous requests (returning top-level access summary) and unlocked when a Customer Access Token (knr_cat_) satisfies requires (customer login or an active entitlement featureKey). |
| `collection.accessPolicy.gatedFields` | string[] | yes | — |
| `collection.accessPolicy.requires` | object | yes | — |
| `collection.accessPolicy.itemOverrideField` | string | no | — |
| `collection.search` | object \| null | yes | Site-search settings. null = items are not indexed. fields: text/rich_text fieldIds searched as body (the title field is always indexed). pathTemplate (optional, e.g. /recipes/{slug}) is a presentation setting: when set, search hits carry a url; otherwise url is null and the front end routes by collectionKey + slug or fetches the item from the Delivery API. |
| `collection.search.fields` | string[] | yes | — |
| `collection.search.pathTemplate` | string \| null | no | — |
| `collection.createdBy` | object | yes | — |
| `collection.createdBy.kind` | "agent" \| "user" | yes | — |
| `collection.createdBy.actorId` | string | yes | — |
| `collection.createdBy.clientName` | string \| null | no | — |
| `collection.itemCount` | integer | yes | — |
| `collection.createdAt` | string | yes | — |
| `collection.updatedAt` | string | yes | — |
| `delivery` | object | yes | — |
| `delivery.baseUrl` | string | yes | The site's public origin. |
| `delivery.listUrl` | string | yes | — |
| `delivery.itemUrl` | string | yes | Replace {slug} with an item's slug. |
| `delivery.schemaUrl` | string | yes | — |
| `delivery.fetchExample` | string | yes | Copy-paste JavaScript using fetch(). |
| `delivery.cors` | string | yes | How cross-origin access works and where to allow origins. |

**Errors**

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

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

```json
{
  "collection": {
    "id": "coll_example01",
    "key": "teachers",
    "displayName": "Teachers",
    "singularName": "Teacher",
    "description": "Faculty shown on the About page.",
    "kind": "custom",
    "systemKey": null,
    "fields": [
      {
        "id": "name",
        "name": "Name",
        "type": "text",
        "required": true,
        "max": 80,
        "unique": true,
        "aiHint": "Full name as shown on the site."
      },
      {
        "id": "avatar",
        "name": "Avatar",
        "type": "image",
        "required": false,
        "help": "Square portrait."
      },
      {
        "id": "subjects",
        "name": "Subjects",
        "type": "multi_select",
        "required": true,
        "options": [
          {
            "id": "math",
            "label": "Math"
          },
          {
            "id": "english",
            "label": "English"
          },
          {
            "id": "physics",
            "label": "Physics"
          }
        ]
      },
      {
        "id": "bio",
        "name": "Bio",
        "type": "rich_text",
        "required": false,
        "max": 2000
      },
      {
        "id": "website",
        "name": "Personal website",
        "type": "url",
        "required": false
      },
      {
        "id": "course",
        "name": "Main course",
        "type": "reference",
        "required": false,
        "targetCollectionKey": "courses"
      }
    ],
    "titleFieldId": "name",
    "accessPolicy": null,
    "search": {
      "fields": [
        "bio"
      ]
    },
    "createdBy": {
      "kind": "agent",
      "actorId": "user_example01",
      "clientName": "Cursor"
    },
    "itemCount": 3,
    "createdAt": "2026-10-01T02:30:00.000Z",
    "updatedAt": "2026-10-06T08:00:00.000Z"
  },
  "delivery": {
    "baseUrl": "https://sunny-bakery.kanorio.com",
    "listUrl": "https://sunny-bakery.kanorio.com/api/v1/collections/teachers/items",
    "itemUrl": "https://sunny-bakery.kanorio.com/api/v1/collections/teachers/items/{slug}",
    "schemaUrl": "https://sunny-bakery.kanorio.com/api/v1/collections",
    "fetchExample": "const res = await fetch(\"https://sunny-bakery.kanorio.com/api/v1/collections/teachers/items?limit=20&sort=-publishedAt\");\nconst { data, hasMore, nextCursor } = await res.json();",
    "cors": "Same-origin requests need no setup. Other origins must be added with site.update_allowed_origins; localhost is always allowed."
  }
}
```

### Update a collection

`PATCH /sites/{id}/collections/{key}`

- Stability: beta
- Permission: `collections:schema`
- Rate limit: default (120/minute per token)
- MCP tool: `collections_update`

Change a collection's name, description, titleFieldId, fields, accessPolicy or search. fields replaces the whole ordered list: read it with collections.get, edit, send it back. Allowed immediately: add fields, rename, change help/aiHint, add options, relax constraints, remove custom fields (data is ignored, cleaned on next write). Type changes only along compatible paths (e.g. text→rich_text, select→multi_select, reference→multi_reference, image→gallery); otherwise add a new field, migrate, remove the old one. Tightened constraints are checked against existing items first. accessPolicy gates specific fieldIds (e.g. blog content) behind customer sign-in ({ kind: "customer" }) or an active commercial entitlement ({ kind: "entitlement", featureKey: "premium" }), with optional itemOverrideField (select field with public/customer/paid); pass null to clear. System collections (blog): reserved fields cannot be removed or retyped, but accessPolicy and custom fields can be configured. Errors: FORBIDDEN, COLLECTION_NOT_FOUND, VALIDATION (details.issues), SCHEMA_CHANGE_REJECTED (details.schemaIssues).

**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 | Collection key (slug). Get it from collections.list. |

**Request body**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `displayName` | string | no | — |
| `singularName` | string | no | — |
| `description` | string \| null | no | — |
| `fields` | object[] | no | — |
| `fields[].id` | string | yes | — |
| `fields[].name` | string | yes | — |
| `fields[].required` | boolean | no | — |
| `fields[].help` | string | no | — |
| `fields[].aiHint` | string | no | — |
| `fields[].system` | boolean | no | — |
| `fields[].indexed` | boolean | no | — |
| `fields[].unique` | boolean | no | — |
| `fields[].type` | "text" | yes | — |
| `fields[].min` | integer | no | — |
| `fields[].max` | integer | no | — |
| `fields[].pattern` | string | no | — |
| `fields[].default` | string | no | — |
| `fields[].localized` | boolean | no | — |
| `titleFieldId` | string | no | — |
| `accessPolicy` | object \| null | no | Field-level read policy (gatedFields, requires: customer \| entitlement(featureKey), optional itemOverrideField). Pass null to remove the policy. |
| `accessPolicy.gatedFields` | string[] | yes | — |
| `accessPolicy.requires` | object | yes | — |
| `accessPolicy.requires.kind` | "customer" | yes | — |
| `accessPolicy.itemOverrideField` | string | no | — |
| `search` | object \| null | no | Site-search settings: { fields: [text/rich_text fieldIds], pathTemplate?: '/recipes/{slug}' }. Gated fields cannot be searched. Pass null to remove the collection from site search. Not configurable on system collections. |
| `search.fields` | string[] | yes | — |
| `search.pathTemplate` | string \| null | no | — |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `collection` | object | yes | — |
| `collection.id` | string | yes | — |
| `collection.key` | string | yes | — |
| `collection.displayName` | string | yes | — |
| `collection.singularName` | string | yes | — |
| `collection.description` | string \| null | yes | — |
| `collection.kind` | "custom" \| "system" | yes | system collections (blog) have reserved fields that cannot be removed or retyped. |
| `collection.systemKey` | string \| null | yes | — |
| `collection.fields` | object[] | yes | — |
| `collection.fields[].id` | string | yes | — |
| `collection.fields[].name` | string | yes | — |
| `collection.fields[].required` | boolean | yes | — |
| `collection.fields[].help` | string | no | — |
| `collection.fields[].aiHint` | string | no | — |
| `collection.fields[].system` | boolean | no | — |
| `collection.fields[].indexed` | boolean | no | — |
| `collection.fields[].unique` | boolean | no | — |
| `collection.fields[].type` | "text" | yes | — |
| `collection.fields[].min` | integer | no | — |
| `collection.fields[].max` | integer | no | — |
| `collection.fields[].pattern` | string | no | — |
| `collection.fields[].default` | string | no | — |
| `collection.fields[].localized` | boolean | no | — |
| `collection.titleFieldId` | string | yes | — |
| `collection.accessPolicy` | object \| null | yes | Field-level read policy for the Content Delivery API: gatedFields are stripped on anonymous requests (returning top-level access summary) and unlocked when a Customer Access Token (knr_cat_) satisfies requires (customer login or an active entitlement featureKey). |
| `collection.accessPolicy.gatedFields` | string[] | yes | — |
| `collection.accessPolicy.requires` | object | yes | — |
| `collection.accessPolicy.itemOverrideField` | string | no | — |
| `collection.search` | object \| null | yes | Site-search settings. null = items are not indexed. fields: text/rich_text fieldIds searched as body (the title field is always indexed). pathTemplate (optional, e.g. /recipes/{slug}) is a presentation setting: when set, search hits carry a url; otherwise url is null and the front end routes by collectionKey + slug or fetches the item from the Delivery API. |
| `collection.search.fields` | string[] | yes | — |
| `collection.search.pathTemplate` | string \| null | no | — |
| `collection.createdBy` | object | yes | — |
| `collection.createdBy.kind` | "agent" \| "user" | yes | — |
| `collection.createdBy.actorId` | string | yes | — |
| `collection.createdBy.clientName` | string \| null | no | — |
| `collection.itemCount` | integer | yes | — |
| `collection.createdAt` | string | yes | — |
| `collection.updatedAt` | string | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `COLLECTION_NOT_FOUND` | 404 | — |
| `VALIDATION` | 400 | details.issues |
| `SCHEMA_CHANGE_REJECTED` | 422 | details.schemaIssues |

```bash
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/collections/{key}" \
  -H "Authorization: Bearer $KANORIO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"description":"Faculty and guest lecturers.","fields":[{"id":"name","name":"Name","type":"text","required":true,"max":80,"unique":true,"aiHint":"Full name as shown on the site."},{"id":"avatar","name":"Avatar","type":"image","required":false,"help":"Square portrait."},{"id":"subjects","name":"Subjects","type":"multi_select","required":true,"options":[{"id":"math","label":"Math"},{"id":"english","label":"English"},{"id":"physics","label":"Physics"}]},{"id":"bio","name":"Bio","type":"rich_text","required":false,"max":2000},{"id":"website","name":"Personal website","type":"url","required":false},{"id":"course","name":"Main course","type":"reference","required":false,"targetCollectionKey":"courses"},{"id":"years","name":"Years of experience","type":"number","required":false,"integer":true,"min":0}]}'
```

```json
{
  "collection": {
    "id": "coll_example01",
    "key": "teachers",
    "displayName": "Teachers",
    "singularName": "Teacher",
    "description": "Faculty and guest lecturers.",
    "kind": "custom",
    "systemKey": null,
    "fields": [
      {
        "id": "name",
        "name": "Name",
        "type": "text",
        "required": true,
        "max": 80,
        "unique": true,
        "aiHint": "Full name as shown on the site."
      },
      {
        "id": "avatar",
        "name": "Avatar",
        "type": "image",
        "required": false,
        "help": "Square portrait."
      },
      {
        "id": "subjects",
        "name": "Subjects",
        "type": "multi_select",
        "required": true,
        "options": [
          {
            "id": "math",
            "label": "Math"
          },
          {
            "id": "english",
            "label": "English"
          },
          {
            "id": "physics",
            "label": "Physics"
          }
        ]
      },
      {
        "id": "bio",
        "name": "Bio",
        "type": "rich_text",
        "required": false,
        "max": 2000
      },
      {
        "id": "website",
        "name": "Personal website",
        "type": "url",
        "required": false
      },
      {
        "id": "course",
        "name": "Main course",
        "type": "reference",
        "required": false,
        "targetCollectionKey": "courses"
      }
    ],
    "titleFieldId": "name",
    "accessPolicy": null,
    "search": {
      "fields": [
        "bio"
      ]
    },
    "createdBy": {
      "kind": "agent",
      "actorId": "user_example01",
      "clientName": "Cursor"
    },
    "itemCount": 3,
    "createdAt": "2026-10-01T02:30:00.000Z",
    "updatedAt": "2026-10-06T08:00:00.000Z"
  }
}
```

### Delete a collection

`DELETE /sites/{id}/collections/{key}`

- Stability: beta
- Permission: `collections:schema`
- Rate limit: default (120/minute per token)
- MCP tool: `collections_delete`

Delete a custom collection and ALL its items. Irreversible. confirmKey must equal key. Refused while another collection references it, and for system collections. Not included in any OAuth scope preset; a PAT must grant collections:manage explicitly. Errors: FORBIDDEN, COLLECTION_NOT_FOUND, CONFIRM_REQUIRED, REFERENCED (details.referencedBy), SYSTEM_COLLECTION.

**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 | Collection key (slug). Get it from collections.list. |
| `confirmKey` | query | string | yes | Must equal key. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deletedItems` | integer | yes | — |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `FORBIDDEN` | 403 | — |
| `COLLECTION_NOT_FOUND` | 404 | — |
| `CONFIRM_REQUIRED` | 400 | — |
| `REFERENCED` | 409 | details.referencedBy |
| `SYSTEM_COLLECTION` | 409 | — |

```bash
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/collections/{key}?confirmKey=teachers" \
  -H "Authorization: Bearer $KANORIO_TOKEN"
```

```json
{
  "deletedItems": 3
}
```
