> ## Documentation Index
> Fetch the complete documentation index at: https://kardow.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Create or Update CMS Collection

> Create a new CMS collection or replace its schema by slug.

A collection is identified by its `slug`. POSTing the same slug twice
performs an upsert — the schema, name, description, and rules are replaced.

<Warning>
  Changing the schema of a collection that already has items does **not**
  re-validate them. Existing rows keep their stored shape; new writes are
  validated against the new schema.
</Warning>

## Rules object

The optional `rules` field controls collection behavior:

| Key                   | Type                   | Default      | Purpose                                                        |
| --------------------- | ---------------------- | ------------ | -------------------------------------------------------------- |
| `allow_public_submit` | `boolean`              | `false`      | Permit unauthenticated `POST /organization/cms/{slug}/submit`. |
| `moderation`          | `'auto'` \| `'manual'` | `'manual'`   | `auto` = published immediately; `manual` = `pending_review`.   |
| `sort_by`             | `string`               | `created_at` | Default sort field for list responses.                         |
| `sort_dir`            | `'asc'` \| `'desc'`    | `'desc'`     | Default sort direction.                                        |

## Example

```bash theme={null}
curl -X POST https://api.kardow.com/cms \
  -H "x-api-key: $KARDOW_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "slug": "courses",
    "name": "Courses",
    "visibility": "public",
    "schema": [
      { "name": "title",    "type": "text",     "required": true },
      { "name": "summary",  "type": "longtext", "required": true },
      { "name": "cover",    "type": "image" },
      { "name": "level",    "type": "select",   "options": ["beginner","intermediate","advanced"] },
      { "name": "duration", "type": "number" }
    ],
    "rules": { "allow_public_submit": false, "moderation": "manual" }
  }'
```


## OpenAPI

````yaml POST /cms
openapi: 3.1.0
info:
  title: Kardow Public API
  description: Public API for jobs and member automation on Kardow.
  version: 1.0.0
  contact:
    name: Kardow Support
    email: support@kardow.com
    url: https://kardow.com
servers:
  - url: https://api.kardow.com
    description: Production API
security:
  - ApiKeyAuth: []
paths:
  /cms:
    post:
      tags:
        - CMS
      summary: Create or Update CMS Collection
      description: >-
        Upserts a CMS collection by slug. The schema array defines the fields
        each item in the collection must (or may) provide.
      operationId: upsertCmsCollection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CmsCollectionInput'
            examples:
              sponsors:
                summary: A sponsors collection
                value:
                  slug: sponsors
                  name: Sponsors
                  visibility: public
                  schema:
                    - name: name
                      type: text
                      required: true
                    - name: logo
                      type: image
                      required: true
                    - name: url
                      type: url
                    - name: tier
                      type: select
                      options:
                        - bronze
                        - silver
                        - gold
                  rules:
                    allow_public_submit: false
                    moderation: manual
      responses:
        '200':
          description: Created or updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    $ref: '#/components/schemas/CmsCollection'
        '400':
          $ref: '#/components/responses/ValidationError'
        '401':
          $ref: '#/components/responses/UnauthorizedError'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    CmsCollectionInput:
      type: object
      required:
        - slug
        - name
        - schema
      properties:
        slug:
          type: string
          description: URL-safe identifier. `^[a-z][a-z0-9_-]{0,63}$`.
        name:
          type: string
        description:
          type: string
        visibility:
          type: string
          enum:
            - public
            - gated
            - private
          default: public
        schema:
          type: array
          items:
            $ref: '#/components/schemas/CmsField'
          maxItems: 40
        rules:
          type: object
          description: Optional behavior flags.
          properties:
            allow_public_submit:
              type: boolean
              default: false
            moderation:
              type: string
              enum:
                - auto
                - manual
              default: manual
            sort_by:
              type: string
              default: created_at
            sort_dir:
              type: string
              enum:
                - asc
                - desc
              default: desc
        required_entitlement:
          type: string
          nullable: true
    CmsCollection:
      allOf:
        - $ref: '#/components/schemas/CmsCollectionInput'
        - type: object
          properties:
            id:
              type: string
              format: uuid
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
    CmsField:
      type: object
      required:
        - name
        - type
      properties:
        name:
          type: string
          description: Snake-case identifier. Must match `^[a-z][a-z0-9_]{0,63}$`.
        label:
          type: string
          description: Optional human label shown in UIs.
        type:
          type: string
          enum:
            - text
            - longtext
            - number
            - boolean
            - image
            - url
            - select
            - date
        required:
          type: boolean
          default: false
        options:
          type: array
          items:
            type: string
          description: Required when `type=select`.
        max_length:
          type: integer
        min:
          type: number
        max:
          type: number
        default:
          description: Default value when none is provided.
    ErrorEnvelope:
      type: object
      properties:
        error:
          type: object
          properties:
            message:
              type: string
            code:
              type: string
            details: {}
            url:
              type: string
          required:
            - message
            - code
      required:
        - error
  responses:
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    UnauthorizedError:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        API key for authentication. Get yours from Settings > API Keys in the
        Kardow dashboard.

````