---
title: "Create an empty site"
method: POST
path: "/api/v1/sites"
tags: ["Sites"]
---

# Create an empty site

`POST /api/v1/sites`

## Request

- Base URL: `https://api.dotsy.ai`
- URL: `https://api.dotsy.ai/api/v1/sites`
- Auth: HTTP bearer

Creates an empty site in the active workspace and seeds its brief. Nothing is published until you publish. By default a slug that another account holds is auto-suffixed (`{slug}-{adjective}-{noun}`); the response says so with `slugAdjusted: true`, so always use the returned `slug` afterwards.

Free-site capacity is `base + perPaid × paidSites` in the workspace (defaults 10 + 10 per paid site; fleet and per-workspace overrides exist). Only free-tier sites count toward usage; past the effective max the request answers 403 `free_site_limit`. A key limited to specific sites, and a guest token, can't create sites.

## Headers

- `X-Dotsy-Workspace` string

## Request body

- CreateSiteRequest
  - `slug` string, required: The address you want: `{slug}.dotsy.site`. Lowercase letters, numbers and hyphens, 1 to 63 characters. A slug that looks like a site id (a UUID) or is reserved for the platform is refused.
  - `title` string: The site's display name, up to 200 characters.
  - `keyConvention` 'flat' | 'directory': How content keys map to URLs. `flat` (default): `about.html` serves at `/about`. `directory`: `about/index.html` serves at `/about`.
  - `purpose` string: One sentence on what the site is for, 5 to 280 characters. It seeds the site brief. Left out, it is inferred from the title or slug.
  - `onSlugTaken` 'fail' | 'auto-suffix' | 'merge': What to do when another account already has the slug. `auto-suffix` (default) picks `{slug}-{adjective}-{noun}` and answers `slugAdjusted: true`. `fail` answers 409 `slug_taken` instead. `merge` reuses a slug you already own, where the other policies answer 409 `slug_taken_self`.
  - `editingMode` 'live' | 'staged': How the editor's Save behaves. `live` (default) publishes each save right away. `staged` keeps saves as drafts until you publish. Writes made through the API always stage, whatever this says.

## Response `201`

The site exists. It is empty and unpublished.

- CreateSiteResponse
  - `slug` string, required: The slug the site was created with. It differs from `requestedSlug` when `slugAdjusted` is true.
  - `requestedSlug` string, required: The slug you asked for.
  - `slugAdjusted` boolean, required: True when the slug was taken and a variant was picked.
  - `title` string, nullable, required: The title you sent, or null.
  - `keyConvention` 'flat' | 'directory', required
  - `editingMode` 'live' | 'staged', required
  - `createdAt` number, required: Creation time, Unix ms.
  - `created` true, required
  - `publicUrl` string, required
  - `liveUrl` string, required: Same as `publicUrl`.
  - `liveReady` boolean, required: False: nothing is published yet.
  - `guidance` GuidanceHint: Which rules & guidelines sections are still empty and the MCP skill to fill them (`get_skill('guidance')`).
    - `missing` string[], required
    - `next` 'Before your first change: load get_skill('guidance') and write the missing sections.', required

## Other responses

- `400`: The body is not valid (`invalid_body`), or the slug is malformed (`invalid_slug`) or reserved (`reserved_slug`).
- `401`: Missing or invalid credentials.
- `403`: `free_site_limit`: the workspace is at its free-site cap. `forbidden_site_scope`: the key is limited to specific sites. `guest_only`: this account may keep only the site it already claimed. `guest_requires_account`: a guest token can't create sites.
- `409`: `slug_taken`: another account has it and `onSlugTaken` is `fail` (or no variant was free). `slug_taken_self`: you already own it, or it is a former address of one of your sites.
- `500`: `create_failed`: the site could not be created. Try again.

---

[API](https://skmtc.dev/dotsy/apis/dotsy-api.md) · [All operations](https://skmtc.dev/dotsy/apis/dotsy-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dotsy/apis/dotsy-api/revisions/4272ed3b85d7?raw)
