---
title: "Publishing API"
description: "Three-step publish flow: dry-run builds a manifest and token; publish-confirm kicks off an async deploy; poll publish-status for the result."
url: "https://comstack.ai/docs/api/publish/"
---
## Overview

Publishing is a three-step flow:

1. **`publish`** — dry-run that builds a reviewable diff manifest and a short-lived confirmation token. No data is written.
2. **`publish-confirm`** — presents the token and kicks off the pipeline immediately. Returns `{ publish_id, status: "running" }` within seconds — it does **not** wait for the deploy to finish.
3. **`publish-status`** — poll until the job reaches `succeeded` or `failed`.

**Authentication:** OAuth Bearer token or service API key. The caller must have `manager` role on the project.

## Step 1 — publish (dry-run)

Builds a manifest of pending changes and returns a one-time confirmation token. Nothing is written or deployed in this step.

### Arguments

| Field | Required | Notes |
|---|---|---|
| `project_id` | yes | The project to publish. |
| `project_name` | yes | Must match the stored project name exactly (case-insensitive trim). A mismatch returns an error showing both the expected and supplied values. |

### Response

```json
{
  "manifest": {
    "project_name": "Acme Real Estate",
    "target_site_url": "https://tf-acme.web.app",
    "drafts": [
      { "path": "abc123", "language": "en", "slug": "guides/intro", "title": "Getting started" }
    ],
    "theme_changes": { "colors": { "primary": "#14b8a6" } },
    "translations_planned": 4
  },
  "confirmation_token": "ec1a4b…(64 hex)",
  "expires_at": "2026-05-11T01:23:45.678Z"
}
```

The `confirmation_token` is single-use and expires after **5 minutes**.

## Step 2 — publish-confirm (async kickoff)

Presents the token and enqueues the deployment pipeline. Returns immediately — the promote → translate → deploy chain runs in the background.

### Arguments

| Field | Required | Notes |
|---|---|---|
| `project_id` | yes | Must match the project bound to the token. |
| `confirmation_token` | yes | Returned by `publish`. |

### Response

```json
{
  "publish_id": "auto-generated-id",
  "status": "running",
  "site_url": "https://tf-acme.web.app"
}
```

Save `publish_id` — you need it to poll status and to revert the publish within one hour.

## Step 3 — publish-status (poll until terminal)

Poll until the job status reaches `succeeded` or `failed`. The caller needs `member` role or above.

### Arguments

| Field | Required |
|---|---|
| `project_id` | yes |
| `publish_id` | yes |

### Response by status

| `status` | Additional fields |
|---|---|
| `queued` / `running` | Poll again. |
| `succeeded` | `published_urls: [{ title, language, slug, url }]` — present as clickable links. |
| `failed` | `error` — the failure message. |

## Promotion pipeline

### Validation

Every draft is validated before any writes occur. A validation error aborts the entire batch.

| Rule | Error |
|---|---|
| `metadata.title` missing | `Missing required field 'title'` |
| `metadata.title` not 10–60 chars | `Title must be 10–60 characters` |
| `metadata.description` missing | `Missing required field 'description'` |
| `metadata.description` not 50–160 chars | `Description must be 50–160 characters` |
| `metadata.slug` missing | `Missing required field 'metadata.slug'` |
| `content` empty | `Content cannot be empty` |
| `content` starts with `# ` | `Content contains an h1. Start content with ## instead.` |

### Index requirement

Each published language must have a live or in-batch document with `metadata.slug === "index"`.

### Concurrency

Only one publish job runs at a time per project. A concurrent `publish-confirm` call returns `publish in progress`.

## Translations

After promotion, draft copies of every promoted document are created automatically for all additional locales configured in the project. Translated drafts are validated in a second pass before promotion — a faulty translation cannot reach production directly.

**Skip-on-unchanged:** if a previously-translated variant's source content and translation configuration are identical to the last publish, the translation is reused with no new AI call. This covers both content changes and AI model version bumps — a model upgrade is never silently skipped.

## Reversion

`revert-publish` rolls back within one hour using the `publish_id` from `publish-confirm`.

## Errors

| Phase | Error | Cause |
|---|---|---|
| Authorization | `Insufficient permission: this tool requires 'manager' on project '…'` | Caller has `member` role or no role. |
| Authorization | `project_name does not match project …: expected 'X', got 'Y'` | `project_name` echo failed. |
| Token | `Invalid or already-used confirmation token` | Token expired, already used, or wrong project. |
| Token | `Confirmation token has expired` | 5-minute window elapsed. Run `publish` again. |
| Lock | `publish in progress` | Another publish is already running. Wait and retry. |
| Promote | `Validation failed for one or more drafts` | A validation rule was violated. Details include the draft path and field. |
| Promote | `Missing mandatory index page for locale(s): …` | A configured language lacks an index page. |
| Deploy | `{ "success": false, "error": "…" }` | Build or deployment failure. The `error` field contains the detail. |

## Related

- [MCP Server](/docs/mcp.md) — publish via MCP tools (`publish`, `publish-confirm`, `publish-status`)
- [API Authentication](/docs/api/authentication.md) — OAuth Bearer and API key authentication
- [Translation System](/docs/concepts/translations.md) — how documents are auto-translated at publish time
- [API Overview](/docs/api.md) — full list of endpoints
