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

# Create a page

> Host an HTML document as a new Page, version 1.

Create a Page owned by the bot your key belongs to. Needs a key with **Pages write** turned on.

<Tip>
  Publishing the same report on a schedule? Use [Create or update by slug](/api-reference/pages/upsert-page-by-slug) instead. It creates on the first run and updates on every run after, so you never store a page id.
</Tip>

## Authorization

<ParamField header="Authorization" type="string" required>
  `Bearer mac_live_…`
</ParamField>

## Body

<ParamField body="content" type="string" required>
  The full HTML document. Up to your plan's per-version cap (512 KB on Free).
</ParamField>

<ParamField body="title" type="string">
  Up to 150 characters. Without it we use the HTML `<title>`, then "Untitled page".
</ParamField>

<ParamField body="description" type="string | null">
  Plain text, up to 300 characters.
</ParamField>

<ParamField body="slug" type="string">
  The URL name: 1 to 63 lowercase letters, digits and dashes. Must be free in your workspace, or you get `409 SLUG_TAKEN`. Without it we make one from the title, adding `-2`, `-3` and so on if needed.
</ParamField>

<ParamField body="visibility" type="string" default="private">
  `private` (workspace members only) or `public` (anyone with the link, no login).
</ParamField>

<ParamField body="conversation_id" type="string">
  Link the Page to a conversation, so it shows up in that conversation's Pages.
</ParamField>

<ParamField body="change_note" type="string">
  Up to 500 characters, shown next to version 1 in the app.
</ParamField>

## Response

<ResponseField name="ok" type="boolean">
  `true` on success.
</ResponseField>

<ResponseField name="page" type="object">
  The new Page. Same fields as [Get a page](/api-reference/pages/get-page).
</ResponseField>

<ResponseField name="version" type="integer">
  Always `1`.
</ResponseField>

<ResponseField name="url" type="string">
  The Page's link: the members-only door for `private`, the Page itself for `public`.
</ResponseField>

<ResponseField name="version_url" type="string">
  The permanent `/v/1` address.
</ResponseField>

<ResponseField name="content_sha256" type="string">
  SHA-256 of the stored content.
</ResponseField>

<RequestExample>
  ```bash curl theme={null}
  curl -X POST "https://app.my-aichatbot.com/api/v1/pages" \
    -H "Authorization: Bearer $MAC_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "title": "Q3 pricing calculator",
      "slug": "q3-pricing",
      "visibility": "public",
      "content": "<!doctype html><html><head><title>Q3 pricing</title></head><body><h1>Q3 pricing</h1></body></html>"
    }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch('https://app.my-aichatbot.com/api/v1/pages', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.MAC_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      title: 'Q3 pricing calculator',
      slug: 'q3-pricing',
      visibility: 'public',
      content: html
    })
  })

  const { page, url } = await res.json()
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      "https://app.my-aichatbot.com/api/v1/pages",
      headers={"Authorization": f"Bearer {os.environ['MAC_KEY']}"},
      json={
          "title": "Q3 pricing calculator",
          "slug": "q3-pricing",
          "visibility": "public",
          "content": html,
      },
  )

  page = res.json()["page"]
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Created theme={null}
  {
    "ok": true,
    "page": {
      "id": "9b3f11c2-…",
      "slug": "q3-pricing",
      "title": "Q3 pricing calculator",
      "description": null,
      "visibility": "public",
      "chatbot_id": "c1a2…",
      "conversation_id": null,
      "current_version": 1,
      "version_count": 1,
      "total_bytes": 2048,
      "disabled_reason": null,
      "created_at": "2026-09-21T03:00:00.000Z",
      "updated_at": "2026-09-21T03:00:00.000Z",
      "url": "https://acme.my-aichatbot-space.com/q3-pricing"
    },
    "version": 1,
    "url": "https://acme.my-aichatbot-space.com/q3-pricing",
    "version_url": "https://acme.my-aichatbot-space.com/q3-pricing/v/1",
    "content_sha256": "3a7bd3e2…"
  }
  ```

  ```json 409 Slug taken theme={null}
  {
    "ok": false,
    "error": { "code": "SLUG_TAKEN", "message": "That slug is invalid or already in use in this workspace." }
  }
  ```
</ResponseExample>

## Errors

| Status | Code                     | Meaning                                                                  |
| ------ | ------------------------ | ------------------------------------------------------------------------ |
| `401`  | `INVALID_API_KEY`        | Missing, broken, or revoked.                                             |
| `402`  | `PAGE_LIMIT_REACHED`     | Your plan's Page count is used up. `limit` says how many.                |
| `402`  | `STORAGE_QUOTA_EXCEEDED` | Workspace storage is full.                                               |
| `403`  | `PAGES_WRITE_DISABLED`   | Turn on **Pages write** for this key.                                    |
| `409`  | `SLUG_TAKEN`             | The slug is in use, retired by a deleted Page, or not valid.             |
| `413`  | `CONTENT_TOO_LARGE`      | Over your plan's per-version cap. `limit_bytes` says how much.           |
| `422`  | `VALIDATION_FAILED`      | Bad body.                                                                |
| `429`  | `RATE_LIMITED`           | Over 120 writes per minute for this key. Wait for `retry_after_seconds`. |
| `500`  | `PERSIST_FAILED`         | We could not store the Page. Safe to retry.                              |
