{
  "info": {
    "name": "Commune: Build your website with your agent",
    "description": "Your newsletter deserves a home that looks like you wrote it, not like a template chose for you. Describe it to your agent and it writes the pages: plain HTML, CSS and JavaScript you own outright, hosted by Commune on your own domain, with your articles, your archive and your subscribe button already wired in. There is no editor and no builder, which also means there is no undo, so keep the site in a repository of your own.\n\nThe walkthrough: https://usecommune.dev/use-cases/build-your-website\n\nSet the `apiKey` variable to a key from https://usecommune.com/settings/api-keys, then run the requests in order.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.usecommune.com"
    },
    {
      "key": "apiKey",
      "value": ""
    },
    {
      "key": "version",
      "value": "2026-08-26"
    },
    {
      "key": "newsletter",
      "value": "example-letter"
    },
    {
      "key": "page",
      "value": "4d9b1e32-6f70-4c8d-8e9f-1a2b3c4d5e6f"
    },
    {
      "key": "domain",
      "value": "8f1c3a52-7b90-4d1e-9f2a-3b4c5d6e7f80"
    }
  ],
  "item": [
    {
      "name": "1. See what is already there",
      "request": {
        "method": "GET",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/newsletters/{{newsletter}}/website-pages",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "newsletters",
            "{{newsletter}}",
            "website-pages"
          ]
        },
        "description": "The index of the site: every page with its kind, its path, its state and the weight of its document, drafts included. The documents are not in it, because a page holds up to 2MB and a site holds many.\n\nA newsletter with no website answers with an empty list, which is the default rather than an error."
      }
    },
    {
      "name": "2. Write the article list",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/newsletters/{{newsletter}}/website-pages",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "newsletters",
            "{{newsletter}}",
            "website-pages"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"kind\": \"newsletter\",\n  \"path\": \"/\",\n  \"status\": \"published\",\n  \"meta\": {\n    \"title\": \"The Example Letter\",\n    \"description\": \"A weekly letter about how newsletters and communities fit together.\"\n  },\n  \"html\": \"<!doctype html>\\n<html lang=\\\"en\\\">\\n<head>\\n  <meta charset=\\\"utf-8\\\">\\n  <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1\\\">\\n  <link rel=\\\"stylesheet\\\" href=\\\"/styles.css\\\">\\n  <script type=\\\"module\\\" src=\\\"/.commune/commune.js\\\"\\n          data-commune-key=\\\"cmn_pk_7Qa2Lk9xBv3mNd8sTr4wYc6zQh1jFp5K\\\"></script>\\n</head>\\n<body>\\n  <header><h1>The Example Letter</h1></header>\\n  <main>\\n    <commune-article-list></commune-article-list>\\n    <commune-subscribe label=\\\"Get it by email\\\"></commune-subscribe>\\n  </main>\\n</body>\\n</html>\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "description": "`kind` is `newsletter`: the article list, at most one per newsletter, and its path is where a reader who clicks your name or avatar inside Commune arrives. Writing it needs `settings: write` as well as `content: write`, because it moves where Commune sends readers.\n\nThe document is yours, byte for byte. Commune splices the title, the Open Graph tags, the canonical URL, JSON-LD and a preloaded `commune-data` payload in as it serves the page, never as it stores it, so reading the page back returns exactly what you sent.\n\n`<commune-article-list>` is filled from that payload, which means the list renders with no request and no key. The `data-commune-key` attribute is for what the page asks for afterwards: a second page of the archive, another article."
      }
    },
    {
      "name": "3. Write one page that serves every article",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/newsletters/{{newsletter}}/website-pages",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "newsletters",
            "{{newsletter}}",
            "website-pages"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"kind\": \"article\",\n  \"path\": \"/posts/:article\",\n  \"html\": \"<!doctype html>\\n<html lang=\\\"en\\\">\\n<head>\\n  <meta charset=\\\"utf-8\\\">\\n  <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1\\\">\\n  <link rel=\\\"stylesheet\\\" href=\\\"/styles.css\\\">\\n  <script type=\\\"module\\\" src=\\\"/.commune/commune.js\\\"></script>\\n</head>\\n<body>\\n  <main>\\n    <commune-article></commune-article>\\n  </main>\\n  <footer><a href=\\\"/\\\">All issues</a></footer>\\n</body>\\n</html>\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "description": "`kind` is `article` and `path` is a **URL pattern**, not an address. One document serves every issue you have published and every issue you have not written yet.\n\nA token is a whole path segment. `:article` is required; `:year`, `:month`, `:day` and `:id` are the rest, and there is nothing else: a token outside that list answers `422`, and two patterns that could both serve one URL answer `409`. A fixed path always beats a pattern, so `/about` and `/:article` coexist and `/about` is the page.\n\nThis one is written as a `draft`, which has **no public address at all**. Not a 404 page, not a placeholder: nothing is served at its path. The signed `preview_url` in the response opens that one page of that one newsletter and expires after a week."
      }
    },
    {
      "name": "4. Put it live",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/website-pages/{{page}}/publish",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "website-pages",
            "{{page}}",
            "publish"
          ]
        },
        "description": "Live immediately: there is no deploy step and no build. `published_at` is stamped the first time and never moved again, so unpublishing and publishing a page keeps the date it first went up, and `status` is what says whether it is live now.\n\nFrom here on, editing the page is the only publish it needs. A `PATCH` to a published page is live on commit."
      }
    },
    {
      "name": "5. Mint the key your pages read data with",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/newsletters/{{newsletter}}/publishable-keys",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "newsletters",
            "{{newsletter}}",
            "publishable-keys"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"name\": \"Website\",\n  \"allowed_origins\": [\n    \"http://localhost:5173\"\n  ]\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "description": "A **publishable key**, `cmn_pk_`-prefixed because it is meant to be public: it ships inside HTML anybody can read, and the prefix is what makes that recognisable to you, to a reviewer and to a secret scanner.\n\nWhat makes it safe is what it can reach, which is this one newsletter's already-published articles, from this newsletter's own website origins, inside a rate limit. Never an email address, never a draft, never an article restricted to a tag, and never another newsletter.\n\n`allowed_origins` are **extra** origins, for a development machine. Your website's own origins are always accepted, which is why leaving it empty means \"the real website and nowhere else\" rather than \"nowhere\"."
      }
    },
    {
      "name": "6. Point your own hostname at it",
      "request": {
        "method": "PATCH",
        "header": [
          {
            "key": "Commune-Version",
            "value": "{{version}}"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          },
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/domains/{{domain}}",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "domains",
            "{{domain}}"
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"mode\": \"site\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        },
        "description": "A hostname serves one of two Commune origins and `mode` says which. `app` is the Commune newsletter page, which is where every hostname starts and where readers sign in, chat and highlight. `site` is your own pages.\n\n**One host can carry a session or run your JavaScript, never both.** The origin is the only security boundary a browser has: code on an origin that holds a session can act as whoever is reading. That is why this is a mode rather than two features on one hostname, and why switching to `site` makes Commune clear the cookies and storage it left there.\n\nNothing is reprovisioned. The certificate, the ownership records and the target your DNS points at are untouched: what changes is which origin answers."
      }
    }
  ]
}
