> ## 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.

# API Reference

> OpenAPI 3.0.3 reference for the orchestrator's HTTP API. The spec began as the Fastify route table and is now hand-maintained; it trails the running server, and several routes still carry placeholder bodies.

This tab is for the developer calling the **Orchestrator HTTP API** directly — the brain that runs editor sessions, calls the LLMs, and serves draft state to integrated sites. The same API is what the editor app, the `avocado-register` CLI, the onboarding agent, the [MCP server](/integration/mcp-server) and any [coding agent workflow](/sites/coding-agent) all talk to. A library-mode mount (`createOrchestrator()`) serves the same routes under its own base path, `/api/avocado` by default.

## How complete this spec is

<Warning>
  **The endpoint list trails the server; the payload shapes are partial.** The OpenAPI document under *Orchestrator endpoints* in the sidebar began as the output of the orchestrator's live Fastify route table via [`@fastify/swagger`](https://github.com/fastify/fastify-swagger), and has been edited by hand since (see [regenerating this spec](#regenerating-this-spec)). Every route it lists exists on the running server, but routes added since it was last updated are missing — among them `GET /editor/credentials`, `POST /draft/schema-drift`, `POST /draft/pull-page`, `POST /draft/variation-preview` and `GET /suggestions`. The table below names them.

  The payloads are a different story. The generator already runs `fastify-type-provider-zod`'s `jsonSchemaTransform`, so any route that attaches a Zod schema is reflected into the document as real JSON Schema — but only a handful of routes attach one today. `POST /chat` and `POST /ops` are the fullest; most of the rest carry a placeholder body and no machine-readable response schema at all.

  **What this means in practice:**

  * The **endpoints listed** exist, with the right HTTP methods. The list is not complete.
  * **Request bodies** are documented for the few routes that declare a schema, and empty for the rest.
  * **Response shapes** are described in prose rather than typed as JSON Schema the panel could render. Most operations carry that prose for their success body and for each refusal they can return — the `Auth` and `Publish` operations are the fullest, down to which status code each refusal uses.
  * The **"Try it" panel** on each operation page sends real requests, but will not validate or autocomplete a payload it has no schema for.
  * **Internal routes** are filtered out of the public spec: `/telemetry/*`, `/gdrive/*`, `/jira/*`, `/audio/*`, `/restore/*`, `/agent/*`, `/published/*`, `/status/*`, `/generated-images/*`. Those exist on the running orchestrator and are deliberately not part of the surface external clients should depend on.

  **If you need to call one of these endpoints today**, the most reliable references are:

  1. The route source files in `apps/orchestrator/src/routes/*.ts` — every route is named and its request/response types are inline. Most handlers live in `packages/orchestrator-core/src/http/`, which is what library mode runs too.
  2. The editor's own network calls — open browser devtools, perform an action, and inspect what the editor sends.
  3. The `avocado-register` CLI, which is a complete worked example of `POST /sites/register`. See the [Next.js walkthrough](/integration/nextjs-integration#walkthrough).

  Filling in the remaining schemas is route-by-route work — attach a Zod schema to a handler and it appears in the spec, with runtime validation as a side benefit. If a specific route is blocking you, get in touch and say which one; that is what decides the order.
</Warning>

## What the orchestrator exposes (high level)

The public surface groups into the tags the spec declares:

| Tag | What it covers | Used by |
| - | - | - |
| **Sites** | Site registration and listing — `POST /sites/register`, `GET /sites`, `DELETE /sites` | The editor on mount, `avocado-register` CLI |
| **Chat** | AI chat / planning — `POST /chat`, `POST /chat/start`, `GET /chat/stream`, `POST /chat/cancel`, `POST /chat/prefetch`, `POST /chat/variations` | The AI chat editor's per-edit panel |
| **Operations** | Direct typed edits, bypassing the planner — `POST /ops`, with `dryRun: true` to validate without applying. The vocabulary is in [the block system](/integration/block-system#operations). On the standalone server an op on a site's own block type needs the site's manifest in the body as `componentsManifest` (its `GET /api/editor/blocks`); that process only knows the built-in blocks, so without it the op fails with `Unknown block type`. A library-mode mount falls back to its own manifest when the body carries none | Integration tests, agents, anything building operations itself |
| **Sites Agent** | Onboarding agent (migrate / integrate / create modes) — `POST /sites-agent/start`, `GET /sites-agent/stream`, `POST /sites-agent/respond`, `POST /sites-agent/cancel` | The editor's onboarding agent |
| **Draft** | Draft content — `GET /draft/pages`, `GET /draft/slugs`, `GET /draft/site-config`, `POST /draft/bootstrap` (takes `blockTypes`, returns `stalePages` and `rejectedPages`), `POST /draft/schema-drift` (draft pages holding block types the site no longer declares), `POST /draft/pull-page` (replace one draft page with the site's copy; `dryRun` returns the diff), `POST /draft/variation-preview`, `GET /suggestions` | The integrated site (called from `fetchEditorPage` / `createSitePage`), and the editor |
| **Blocks** | `GET /blocks/manifest` — the block manifest as this process sees it. On a library-mode mount that is the site's catalogue; on the standalone server, the built-ins | The MCP server's discovery tools |
| **Editor** | `GET /editor/credentials` — `{ siteDraftSecret?, publishToken? }` from the orchestrator's own `DRAFT_MODE_SECRET` and `PUBLISH_TOKEN`, `cache-control: no-store`. Gated in library mode; on the standalone server it checks the access token itself and answers 403 in production when no credential is configured | The editor, after sign-in |
| **Publish** | Publishing endpoints — `POST /publish`, `GET /publish/status`, `GET /publish/content`, `GET /publish/diff`, `GET /publish/log` | The editor's publish flow |
| **History** | Undo / redo / version log — `GET /history/log`, `GET /history/status`, `POST /history/undo`, `POST /history/redo`, `POST /history/restore`, `POST /history/discard`. In library mode these scope by the mount's site id and ignore a `siteId` in the request | The editor's history panel |
| **Auth** | The password exchange and the state of the gate — `POST /auth/verify`, `GET /auth/status` | The editor's login screen, and any client asking whether its requests will be accepted |
| **Media** | Image upload, generation, search — `POST /image/upload`, `POST /image/generate`, `POST /image/generate/chat`, `POST /image/interpret`, `GET /unsplash/search` | The editor's asset manager |
| **Health** | Service health and readiness — `GET /health`, `GET /docs/json` | Container health checks, this docs site |

The server also mounts routes for the pre-alpha site ops agents (`/agents/*`, `/assistant/*`, `/routines`, `/inbox/*`, `/memory/facts`, `/connections/google/*`). They are inactive unless `SITE_OPS_AGENTS=1`, which is off by default, and are not documented here.

<Note>
  Generated images are served from `GET /generated-images/:fileName`, but that prefix is on the internal filter list, so it does not appear in the spec. It is a static file route, not part of the API contract — the URL you need comes back in the `POST /image/generate` response.
</Note>

## Auth model

Most routes on the **standalone** orchestrator are unauthenticated today; only the agent surface enforces the access gate. The standalone server is designed to be reachable from your editor and from your integrated sites — both of which run inside your trust boundary. There are two optional gates:

1. **Access password** (editor-facing) — set `ACCESS_PASSWORD_HASH` on the orchestrator and the editor will prompt for a password before letting users in. `POST /auth/verify` exchanges the password for a bearer token. The orchestrator reads that token from any of three transports — `x-access-token`, `Authorization: Bearer` (the conventional spelling, for scripts), and `?accessToken=` on a URL; the editor itself sends `x-access-token`, and the query parameter on `EventSource`, which cannot send headers. Off by default.
2. **Publish token** (integration-facing) — set `PUBLISH_TOKEN` on the orchestrator and `POST /publish` requests must include `x-publish-token: <token>` in headers. Off by default; recommended for production.

There's no per-user authentication or session-bound API key today. If you expose the standalone orchestrator to the public internet without these gates, anyone who knows the URL can use it. See [Docker Deployment](/operations/docker-deployment#critical-hosting-constraints) for the broader hosting story.

<Note>
  **Library mode is gated by default.** When you mount the orchestrator inside your own site with `createOrchestrator()` rather than running the standalone server, every non-public route is gated. Public means `GET /auth/status`, `POST /auth/verify`, `GET /health` and `GET /generated-images/*`: the first two are how a caller obtains a credential, a health probe that needs one is not a probe, and an `<img>` tag on the rendered page cannot send a header. Everything else needs the token.

  With neither `ACCESS_PASSWORD_HASH` nor `ORCHESTRATOR_ACCESS_TOKEN` set and no `auth` hook passed, a mount under `NODE_ENV=production` resolves to mode `closed` and refuses every gated request with a `401` — failing closed rather than shipping an open mount that can edit and publish your site. To run open in production on purpose, say so: `auth: () => true`. See [CMS adapters](/integration/cms-adapters#wiring-it-up).
</Note>

### Asking the gate what it is doing

`GET /auth/status` is public precisely so a client can ask before it holds a credential. It answers two questions that are not the same question:

* **`gateEnabled`** — *should I show a password box?* True only when `ACCESS_PASSWORD_HASH` is set. A host that brings its own `auth` hook has no password to collect, so this is false there.
* **`mode`** — *will my other requests be accepted?* One of `hook` (the host passed `auth` and decides per request), `token` (`ACCESS_PASSWORD_HASH` and/or `ORCHESTRATOR_ACCESS_TOKEN` is configured), `open-dev` (neither, outside production, so nothing is refused), or `closed` (neither, in production, so every gated route answers 401).

The two come apart in exactly one state, which is why `mode` exists at all. A `closed` mount has no password gate, so `gateEnabled` is false, and a client reading that as "open" renders itself around a site whose every other route refuses it — with no password box and no way to reach one. So a closed mount also reports `reason`: the sentence naming the variables that would open it. It repeats that same sentence in the `reason` field of every `401` it returns, while `error` stays exactly `"unauthorized"` — the value the editor's fetch shim matches on to re-prompt.

`POST /auth/verify` is the password exchange and nothing else, so a closed mount has nothing to exchange. It answers `503` with `{ ok: false, error: "unavailable", reason }` instead of minting a token. A token minted there would open nothing — every route behind it still answers 401 — and would leave the operator holding a credential and a wall of refusals with no way to connect the two. Where the deployment is open by choice, or gated by something other than a password, `POST /auth/verify` still returns a token, so the client's code path is the same either way.

## Regenerating this spec

<Warning>
  **The checked-in spec is hand-maintained, and `export-openapi` overwrites it wholesale.**

  `docs-site/api-reference/orchestrator.openapi.json` began as that script's output and has been edited by hand since. It now carries response descriptions, request bodies and prose the orchestrator's route schemas do not contain — including every status code documented on this page. The script writes `JSON.stringify(app.swagger())` over the whole file, so running it deletes all of that and resets `info.version` to `0.0.1`.

  Run it to see what the route schemas currently reflect. Do not commit the result without re-applying the hand-written material, or moving it into the route schemas first so the generator can produce it.
</Warning>

The script imports the orchestrator's app, waits for all routes to register, then dumps the `@fastify/swagger` reflection.

```bash theme={null}
pnpm --filter @ai-site-editor/orchestrator export-openapi
```

It runs with `NODE_ENV=test` so the orchestrator does not actually start listening on port 4200 — it only registers route plugins, which is enough for `@fastify/swagger` to introspect them. It counts the operations exported.

The same divergence is why the internal-route note above describes the filter's intent rather than the file's contents: `INTERNAL_ROUTE_PREFIXES` does hide those prefixes from the generator's output, but the committed spec predates that filter in places and still lists some of them.

## When the spec is wrong

If an operation here is missing, mislabeled, or you need a request/response shape that isn't documented:

* For **a missing schema** (the placeholder situation above): get in touch and name the route and what you are building. Which routes get schemas first is decided by which ones people are actually blocked on.
* For **an endpoint listed here that doesn't exist on the running orchestrator**: the spec is stale — either the orchestrator changed without re-running `export-openapi`, or the route filter dropped something it shouldn't. Tell us the path and the method.
* For **internal routes that shouldn't be public**: the route filter at `apps/orchestrator/src/index.ts` (`INTERNAL_ROUTE_PREFIXES`) decides what's filtered. Add your prefix there and re-run the export.


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