---
title: "Publish the staged draft to the live site"
method: POST
path: "/api/v1/sites/{slug}/publish"
tags: ["Draft & publish"]
---

# Publish the staged draft to the live site

`POST /api/v1/sites/{slug}/publish`

## Request

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

Promotes the draft to the live site and refreshes the serving index. Everything is optional in the body: send `{}` (or nothing) to publish everything that is staged, or `keys` / `routes` to publish only part of it.

Before it goes live the publish checks that the site has a real purpose in its brief (first publish), that no `<dotsy-collection>` card template would render empty, that redirects do not loop, that the site's Rules pass (for agents, a blocking Rule answers 422; a person gets `ruleWarnings` on the 200), and that no import or adapt job is still running. A blocked publish answers an error and changes nothing.

`feedback` and `handoff` are optional. Agents send them so Dotsy learns from the session and the next session inherits the decisions made in this one.

## Path parameters

- `slug` string, required

## Request body

- PublishRequest: Publish options. Every field is optional, and an empty body (or `{}`) publishes everything that is staged.
  - `feedback` object: Optional session feedback (what was hard, what to improve). Agents send it so Dotsy can learn; a routine publish can omit it. When present it must be valid, otherwise the publish answers 400 `invalid_body`.
    - `category` string, required
    - `impactPercent` integer, required
    - `title` string, required
    - `description` string
    - `suggestedImprovement` string
    - `findings` object[]
      - `category` string, required
      - `impactPercent` integer, required
      - `title` string, required
      - `description` string
      - `suggestedImprovement` string
      - `operationId` string
    - `intents` object[]
      - `tool` string, required
      - `context` string, required
      - `at` number
  - `handoff` string: Optional one-line hand-off note (decisions, preferences, open threads) appended to the site brief's hand-off log before the publish. Routine no-information notes such as "Routine dashboard publish." are not appended.
  - `keys` string[]: Selective publish: content keys to promote. Everything else stays staged. Unknown keys are ignored. Omit `keys` and `routes` (or send both empty) to publish everything.
  - `routes` string[]: Selective publish: route paths to promote (see `keys`). Unknown paths are ignored.
  - `auto` boolean: Set by the dashboard's live mode, where every save publishes. Leave it out.
  - `reviewedSet` object: Optional safety check: the `{key, hash}` pairs you displayed from `GET …/draft-status` before this publish. If the staged set moved since, the publish answers 409 `drafts_changed` instead of shipping something you did not review.
    - `content` object[], required
      - `key` string, required
      - `hash` string, required
    - `routes` object[], required
      - `key` string, required
      - `hash` string, required
    - `fingerprint` string

## Response `200`

Published. The site is live.

- object
  - `ok` boolean, required
  - `status` 'published', required
  - `publishedAt` number, required: Publish time, Unix ms.
  - `pages` number, required: Keys in the published manifest.
  - `feedbackStored` boolean, required: False when the request carried no `feedback` (or the feedback sink is not configured). The publish succeeds either way.
  - `feedbackQueued` boolean: True when feedback was queued to the outbox because PostgREST was unreachable (0.74 M4).
  - `integrationsPending` integer: Count of unresolved clone integrations (non-blocking; 0.27.1 M3.2).
  - `publicUrl` string, required
  - `liveUrl` string, required
  - `liveReady` true, required
  - `collectionDataPending` string[]: Collections whose data file did not finish building (`partial build`). The publish is live; republish to retry.
  - `collectionSqlPending` string[]: Collections whose SQL index did not finish building. The publish is live; republish to retry.
  - `collectionDiscoveryPending` integer: Pages not yet indexed for the collections they place. The publish is live and every collection built before keeps serving its last data (marked pending); republish shortly to finish. Absent when discovery was complete.
  - `deprecationWarnings` string[]: Non-blocking notices about deprecated markup the publish found.
  - `formWarnings` object[]: Non-blocking problems with forms wired on the published pages.
    - `code` 'dead_form_config' | 'form_recipient_unverified', required
    - `formId` string, required
    - `contentKey` string, required
    - `path` string
    - `message` string, required
  - `fragmentWarnings` string[]: Pages that went live with a `<dotsy-fragment>` that has no `fragments/<name>.html`, so visitors see the raw tag. One line each, starting `fragment_not_found:`, with the pages and a likely name when one is close. Create the fragment or fix the name, then publish again.
  - `seoFileWarnings` string[]: The site's own robots.txt or sitemap.xml that this publish did not use: a robots.txt that blocks the whole site (`Disallow: /` for every crawler) or any static sitemap.xml needs the `ownSeoFiles` site setting; the generated file keeps serving. Also a robots.txt that is used but names a `Sitemap:` on another host (served as written), and each `_headers` line this publish refused (`_headers line N: …`: a header outside the allowlist, a control character, a `Cache-Control` over max-age 3600 or with `immutable`), or the file over its 4 KB / 30-rule caps.
  - `redirectWarnings` object: Non-blocking redirect advisories (chains, targets that are not pages, redirect count).
    - `paths` object[], required
      - `path` string, required
      - `issues` object[], required
        - `code` string, required
    - `site` object[], required
      - `code` string, required
    - `partial` true: The page index scan hit its cap, so some redirect targets may have been misjudged.
  - `materializedDraftJobs` number: Pending import/bulk draft fragments folded into this publish.
  - `templateScanPartial` true: The empty-collection template gate hit its key cap — listing pages beyond the first 1,000 scanned keys were not checked.
  - `postCommitWarnings` string[]: Post-commit enrichment steps that failed. The publish is live; republish to retry them.
  - `ruleWarnings` RuleIssue[]: Rules that failed on checked pages (block first). Present for a person's publish when the brief has Rules; agents get 422 instead of publishing past a blocking Rule.
    - `code` 'rule_failed', required
    - `ruleId` string, required
    - `card` 'intent' | 'design' | 'content' | 'seo', required
    - `level` 'block' | 'warn', required
    - `text` string, required
    - `path` string, required
    - `found` string
  - `rulesCheck` RulesCheck: Present with `ruleWarnings` when the brief has Rules — how much of the draft was checked.
    - `checked` integer, required
    - `total` integer, required
    - `partial` boolean, required
  - `sharedSectionSuggestions` object: Blocks that still repeat across pages and could become shared sections. Never blocks; absent when there are none (or when the site's index can't answer cheaply right after the publish).
    - `count` integer, required: Blocks with an exact copy on 3 or more pages that are not shared or dismissed.
    - `next` 'share_section', required: The next step: MCP `share_section` with no arguments (or GET …/shared-sections/suggestions) lists them.

## Other responses

- `400`: The body is not valid (`invalid_body`), or a gate blocked the publish: `purpose_required`, `empty_collection_template`, `redirect_issues`.
- `401`: Missing or invalid credentials.
- `403`: The key or token cannot publish this site (viewer role, guest token, a key scoped to other sites, or AI access below publish (`ai_access_limited`, with `aiAccess` and a `hint`)).
- `404`: No such site, or it is not visible to this account.
- `409`: Nothing was published: `adapt_active`, `import_in_progress`, `publish_conflict` (a concurrent publish won — retry once), `placement_version_conflict`, `brief_head_corrupt`, or `drafts_changed` (the staged set moved since `reviewedSet`).
- `422`: An experiment blocks the publish, stored page bytes are missing (`content_bytes_missing`), an agent hit a blocking Rule (`rules_failed`), or blocking Rules could not all be checked within the budget (`rules_not_fully_checked` — publish in batches with `keys` / `routes`).
- `500`: `tenant_write_failed`: the publish committed but the serving index could not be updated twice. Publish again.
- `502`: The pre-publish hand-off note could not be saved (`handoff_append_failed`). Nothing was published; retry.
- `503`: The draft index could not be read (`draft_index_unavailable`). Retry the publish.

---

[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)
