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

# Publish the draft to the live site target

> Computes the diff against the currently-published state first, then hands the selected pages to the registered publish target and records a publish-log row either way. Requires the publish token when one is configured; a `siteOrigin` that is not an allowed URL is refused with 400.

The site-contract target POSTs the pages to `{siteOrigin}/api/editor/publish`, forwarding this orchestrator's `PUBLISH_TOKEN` as `x-publish-token`. That endpoint has guards of its own — it refuses a publish that would remove every page, and refuses to run at all under `NODE_ENV=production` with no token configured — and whatever it refuses arrives here as a 400 carrying its explanation.



## OpenAPI

````yaml /api-reference/orchestrator.openapi.json post /publish
openapi: 3.0.3
info:
  title: Avocado Studio Orchestrator API
  description: >-
    HTTP API exposed by the orchestrator. The brain that runs editor sessions,
    calls the LLMs, and serves draft state to integrated sites.


    The route list is the one the standalone Fastify server registers
    (`apps/orchestrator/src`); the handlers behind most of them live in
    `packages/orchestrator-core/src/http`, so library mode —
    `createOrchestrator()` from `@avocadostudio-ai/site-sdk` — serves the same
    shapes from the same code. Where the two differ, the route says so.


    **Note**: most routes carry no Fastify response schema, so responses are
    described in prose rather than typed here. Request shapes are taken from the
    handler source. See [the API Reference index](/api-reference) for context.


    **Auth**: in the standalone server only the agent surface enforces the
    access gate — `/chat` and `/ops` remain open. `createOrchestrator()` gates
    every route instead. Under that gate `GET /auth/status`, `POST
    /auth/verify`, `GET /health` and `GET /generated-images/*` are public — the
    first two are how a caller obtains a credential, a probe that needs one is
    not a probe, and an image tag on the rendered page cannot send a header —
    while every other route wants the token as `x-access-token`, `Authorization:
    Bearer`, or `?accessToken=` on `EventSource`. A refusal is `401` with
    `{"error":"unauthorized"}`. On a `closed` mount — `NODE_ENV=production` with
    no credential configured and no `auth` hook — that body also carries
    `reason`, the sentence naming the variable that would open it; `error` keeps
    its exact value either way, because that is the field the editor matches on
    to re-prompt.
  version: 0.11.1
servers:
  - url: http://localhost:4200
    description: Local development orchestrator (default port)
security: []
tags:
  - name: Sites
    description: Site registration and listing
  - name: Chat
    description: AI chat / planning endpoints
  - name: Sites Agent
    description: Site onboarding agent (migrate / integrate / create)
  - name: Draft
    description: Draft content read endpoints called by integrated sites
  - name: Publish
    description: Publishing and content snapshot endpoints
  - name: History
    description: Undo / redo / version log
  - name: Auth
    description: Optional access password gate
  - name: Media
    description: Image upload and generation
  - name: Health
    description: Service health and readiness
  - name: Operations
    description: Applying typed content operations directly
  - name: Checks
    description: Site-health check runs and findings
  - name: Restore
    description: Publish snapshots and rolling a draft back to one
  - name: Sessions
    description: Session introspection and admin
  - name: Preview
    description: Preview utilities
  - name: Telemetry
    description: Chat pipeline traces and feedback
  - name: Agent
    description: Open-ended agent loops — gated, and off in production by default
  - name: Jira
    description: Jira as a second input channel
paths:
  /publish:
    post:
      tags:
        - Publish
      summary: Publish the draft to the live site target
      description: >-
        Computes the diff against the currently-published state first, then
        hands the selected pages to the registered publish target and records a
        publish-log row either way. Requires the publish token when one is
        configured; a `siteOrigin` that is not an allowed URL is refused with
        400.


        The site-contract target POSTs the pages to
        `{siteOrigin}/api/editor/publish`, forwarding this orchestrator's
        `PUBLISH_TOKEN` as `x-publish-token`. That endpoint has guards of its
        own — it refuses a publish that would remove every page, and refuses to
        run at all under `NODE_ENV=production` with no token configured — and
        whatever it refuses arrives here as a 400 carrying its explanation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                session:
                  type: string
                siteId:
                  type: string
                siteOrigin:
                  type: string
                slugs:
                  type: array
                  items:
                    type: string
                  description: >-
                    Publish only these pages and leave every other page as the
                    live site already has it. Absent or empty means the whole
                    draft. The non-destructive merge lives in
                    `publish-selection.ts`; a selection is refused with 400 when
                    no trustworthy published baseline can be loaded to merge
                    against.
                includeSiteConfig:
                  type: boolean
                  description: >-
                    Whether the site header/navigation configuration ships with
                    this publish. Defaults to true.
      responses:
        '200':
          description: >-
            `{ status, session, slugs, ... }` — what the target reported, plus
            whatever that target can say about where the content went
            (`commitSha`, `message`, `inspectUrl`, `deploymentId`). `ready`
            means the write has already happened; `triggered` means a deployment
            is running and a later `GET /publish/status` is the only thing that
            learns how it ended. Read `status` rather than the HTTP code alone:
            a deploy-hook target whose hook rejected the call still answers 200,
            with `status: "failed"`.
        '400':
          description: >-
            A body carrying `error` when the request is refused before anything
            publishes — `siteOrigin` is not an allowed URL, or a page selection
            arrived with no trustworthy published baseline to merge it against.
            `{ status: "failed", session, slugs, reason }` when the target ran
            and the publish failed. `reason` is written for the person who
            pressed Publish: a site implementing the publish contract puts a
            machine-readable verdict in `error` and the sentence to act on in
            `reason`, and this route forwards the sentence, not the verdict. So
            a site that refuses because the publish would remove every one of
            its pages answers with the sentence naming `allowDelete`, and a site
            running in production with no publish token configured answers with
            the one naming `PUBLISH_TOKEN`.
        '401':
          description: >-
            `{ error: "invalid publish token" }` — this orchestrator has
            `PUBLISH_TOKEN` set and the request did not carry it as
            `x-publish-token`. Standalone server only.
        '500':
          description: >-
            `{ error: "no publish target registered" }` — nothing can carry this
            publish. Standalone server only.
        '502':
          description: >-
            The target could not be reached at all: the site's
            `/api/editor/publish` refused the connection, or the deploy hook
            threw. The site-contract target answers `{ status: "failed",
            session, slugs, reason }` naming the URL it tried.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.