Skip to main content
This tab documents the Orchestrator HTTP API — 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, and any coding agent workflow all talk to.

How complete this spec is

The endpoint list is real; the payload shapes are partial. The OpenAPI document under Orchestrator endpoints in the sidebar is generated from the orchestrator’s live Fastify route table via @fastify/swagger. That route table is the source of truth, so the URLs and HTTP methods are exactly what the running service answers.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 endpoint list and HTTP methods are accurate — reflected from the live route table.
  • 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.
  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.
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.

What the orchestrator exposes (high level)

The public surface groups into the tags the spec declares:
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.

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 for the broader hosting story.
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.

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:
  • gateEnabledshould 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.
  • modewill 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

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.
The script imports the orchestrator’s app, waits for all routes to register, then dumps the @fastify/swagger reflection.
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.