avocado-register CLI, the onboarding agent, and any coding agent workflow all talk to.
How complete this spec is
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:- Access password (editor-facing) — set
ACCESS_PASSWORD_HASHon the orchestrator and the editor will prompt for a password before letting users in.POST /auth/verifyexchanges 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 sendsx-access-token, and the query parameter onEventSource, which cannot send headers. Off by default. - Publish token (integration-facing) — set
PUBLISH_TOKENon the orchestrator andPOST /publishrequests must includex-publish-token: <token>in headers. Off by default; recommended for production.
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:
gateEnabled— should I show a password box? True only whenACCESS_PASSWORD_HASHis set. A host that brings its ownauthhook has no password to collect, so this is false there.mode— will my other requests be accepted? One ofhook(the host passedauthand decides per request),token(ACCESS_PASSWORD_HASHand/orORCHESTRATOR_ACCESS_TOKENis configured),open-dev(neither, outside production, so nothing is refused), orclosed(neither, in production, so every gated route answers 401).
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 script imports the orchestrator’s app, waits for all routes to register, then dumps the@fastify/swagger reflection.
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.