OPERATIONS · API · CLIENTS
Moonli Operations Guide
A standalone reference for operating, integrating, deploying, backing up, and updating the Moonli generation service.
Return to MoonliContents
01 Architecture and access
Moonli runs three tagged generation profiles behind Nginx. Pipelines 1 and 2 complete through one synchronous POST /v1/generate. Pipeline 3 mirrors the production TouchDesigner interaction with two operations: audio normalization followed by three-image generation. The pipeline tag is part of the request; Android and TouchDesigner are not separate API pipelines.
- Public: Nginx on HTTPS exposes the web UI,
/health,/v1/generate,/v1/normalize, and an allow-list of authenticated operator routes. - Loopback only: readiness, updater catalog, and rollback restore use
127.0.0.1:18000. - Concealed: metrics, framework docs, databases, storage paths, legacy endpoints, Docker, and updater internals are not exposed through Nginx.
Operator authentication uses an Access Key of at least 24 characters. A successful login creates a short-lived HttpOnly SameSite session; state-changing operator requests also require CSRF. Rotating the Access Key revokes all browser sessions. Client requests use independent Bearer credentials plus persistent device identities. No VPN or mTLS is required.
02 First production startup
- Configure the public hostname, allowed hosts, trusted TLS files, and independent random service credentials.
- Choose Google for the required providers and enter exact model names in Configuration. Production rejects mock providers, blank model names, development credentials, wildcard hosts, and a Google key placed in
.env. - Start the stack, sign in, and configure the required pipeline-specific Google API keys and models on Production. Starting without these keys is allowed so they can be provisioned through the browser; Google-backed calls for a pipeline remain unavailable until its key exists.
- Run a text and audio test for each required pipeline, download the artifacts, and verify a logical backup before connecting real clients.
Each persistent pipeline Google key is verified through Google's models API and written atomically to a pipeline-specific file in the dedicated secrets volume. The backend never returns the value or a mask. Keys are excluded from .env, browser storage, audit, backups, and release artifacts.
If direct Google access is region-blocked, open Configuration → Routing, paste a VLESS Reality/TCP/Vision connection, enable the switch, and save. The connection is stored only in the secrets volume and is never returned. The internal Xray sidecar reloads it automatically and has no public host port. Transcription, normalization, Pipeline 3 prompt translation, and image generation all follow the selected route. Disabling the switch restores direct Google routing.
03 Production client API
Text request
POST /v1/generate HTTP/1.1
Host: moonli.example.com
Authorization: Bearer <client-api-key>
X-Moonli-Device-Id: td-02941846
Idempotency-Key: 4c4058a6-fd85-4cf3-bf18-7fa475dd0ceb
Content-Type: application/json
{"type":"text","pipeline":"pipeline-1","text":"A red tree on a small island"}
Audio request
POST /v1/generate HTTP/1.1
Host: moonli.example.com
Authorization: Bearer <client-api-key>
X-Moonli-Device-Id: aa-26093758
Idempotency-Key: f78a3993-04c5-40d0-bc46-60dbc154106e
Content-Type: multipart/form-data; boundary=...
type=audio
pipeline=pipeline-2
audio=<audio file>
Each request selects exactly one server-owned profile: pipeline-1, pipeline-2, or pipeline-3. Do not send provider, palette, stage, or table fields. The server rejects missing, unknown, or misspelled tags. Audio type and size are validated before provider work begins. Pipeline 3 sends both operations to /v1/generate: audio as multipart, then the returned normalized text as JSON using a fresh operation UUID.
IDEMPOTENCY_CONFLICT; a concurrent duplicate produces GENERATION_IN_PROGRESS and Retry-After.04 Device identity
On first start, TouchDesigner creates td- plus eight cryptographically random decimal digits; Android creates aa- plus eight such digits. The client persists that value in installation-local storage and sends it on every generation request as X-Moonli-Device-Id.
The server automatically registers the identifier and counts every production attempt. The Devices page displays type, registration time, lifetime request count, and status. Blocking stops the device before the pipeline starts; unblocking restores its access. The prefix is descriptive only: either device type may call any pipeline. Pipeline-3 TouchDesigner scripts share the same persisted device ID across normalization and generation. The device ID is not a credential and never replaces Bearer authentication.
05 Processing pipeline
| Stage | Input | Output and rule |
|---|---|---|
| 1 · Transcription | Audio | Source text; omitted for text input. |
| 2 · Normalization | Source text | Concise English visual intent with filler and test commentary removed. |
| 3 · Prompt builder | Normalized phrase | Pipeline-specific technical prompt with canvas, palette, and style constraints. |
| 4 · Generation | Technical prompt | Provider result converted to PNG. |
| 5 · Quantization | Provider PNG | Exact versioned palette using nearest CIE Lab color and optional 3×3 cleanup. |
| 6 · Validation | Quantized PNG | Strict zero-tolerance color and alpha report. |
| 7 · Vectorization | Strict PNG | Palette-safe SVG where applicable. |
| 8 · Segmentation | Vector/raster data | Stable ordered layer package for pipeline-2. |
Normalization is intentionally independent from Prompt Builder. A spoken request can become a phrase such as cute penguin icon; the builder then adds the technical instructions. This prevents greetings, hesitations, and test narration from becoming part of the image subject.
Pipeline 3 normalizes every request into concise Russian. During the second client operation, its Prompt Building stage translates that Russian phrase into concise English without adding details, then sends the English prompt to three independent Google image calls under the configured system instruction. English input at the translation boundary passes through unchanged. It skips quantization, validation, vectorization, and segmentation.
06 Artifact contracts
pipeline-1 returns image/png as moonli.png. pipeline-2 returns application/vnd.moonli.layers+zip as moonli-layers.zip. Those processed responses include run, digest, device, and replay headers. Pipeline-3 normalization returns only normalized text; pipeline-3 generation returns a ZIP containing only three 1024×1024 RGB JPEGs named image_1.jpg, image_2.jpg, and image_3.jpg.
manifest.json
composite.png
layers/00.png
...
layers/11.png
The layered contract always contains twelve full-canvas RGBA slots. Unused slots are transparent PNGs with used:false. Every visible pixel is fully opaque and equals its declared palette color; transparent pixels are exactly zero RGBA. The server validates safe relative names, duplicate members, checksums, dimensions, slot ordering, exact colors, alpha, ZIP integrity, and pixel-perfect layer recomposition.
The Test Calls full-run ZIP is diagnostic and contains every applicable intermediate plus pipeline and input-type metadata. For pipeline 3 this means source text or audio, optional transcription, normalized Russian text, the translated English prompt, three 1024×1024 JPEG variants, the nested image ZIP, and an execution trace; only the post-processing stages are marked not_applicable.
07 Operator console
- Overview: CPU, RAM, disk, uptime, total and 24-hour production request/token usage, and usage history.
- Test Calls: isolated providers, models, temporary Google key, prompt templates, full diagnostic runs for all three pipelines, and eight independently callable stages. Authenticated operators can use either mock or Google test providers in production.
- Configuration: optional private VLESS routing for Google calls, appearance, Access Key rotation, backup, and updates.
- Production: three independent pipeline sections with a persistent Google key, model selection, provider configuration, instructions, and request-contract previews.
- Devices: registered installation identities, counters, registration time, blocking, and unblocking.
- Activity: redacted tab-local calls and retained server audit, with ZIP export.
The test-stage key exists only in the current browser tab and is cleared on refresh or sign-out. It is separate from the three persistent production keys.
08 Backup and recovery
The logical ZIP uses format moonli-logical-backup, schema version 2, and a SHA-256 digest for every member. It includes runs, usage, registered devices and block state, non-secret settings, operator verifier, retained audit, and referenced completed artifacts. It excludes all Google/client/updater keys, cookies, sessions, .env, staging files, and releases.
Restore validates a 128 MiB compressed limit, a 256 MiB uncompressed limit, compression ratio, member count, path safety, link rejection, duplicate names, schema, and every digest before mutating state. It first creates a pre-restore snapshot and automatically reapplies it on failure. The five newest restore points are kept. A successful restore revokes browser sessions.
Audit retention is bounded by 10,000 events, 30 days, and 64 MiB estimated payload. Usage retention is bounded by 1,000,000 rows and 365 days. Container stdout rotates at three files of 10 MiB. Protect the secrets volume, including Google keys and optional VLESS routing state, with a separate encrypted host-level backup because it is intentionally absent from logical snapshots.
09 Domain deployment
- Point the Moonli hostname to the server and allow inbound TCP 80/443.
- Install the trusted certificate and private key in the configured Nginx TLS paths.
- Use independent strong operator, client, catalog, restore, and updater credentials.
- Configure exact providers and Google models, then validate the production Compose configuration.
- Start the stack, verify public
/healthand loopback/readyz, then save the required pipeline Google keys in the UI.
docker compose --env-file /opt/moonli/.env \
-f docker-compose.yml -f compose.production.yml config
docker compose --env-file /opt/moonli/.env \
-f docker-compose.yml -f compose.production.yml up -d --wait
curl --fail https://moonli.example.com/health
Never expose 8000 or 18000 externally and never copy a workstation .env to the server. Unknown hosts return 421. Generation may take several minutes, so Nginx and clients use timeouts of at least 300 seconds. Keep normal TLS certificate verification enabled.
10 Updates and rollback
The host updater is based on psewdon1m-exocortex/updater at the pinned reviewed commit and communicates with Moonli through a Unix socket. The application never receives the Docker socket. It can request only a semantic Moonli version and cannot inject commands, image names, repositories, URLs, or service names.
Stable releases use moonli-vX.Y.Z, a signed-off moonli-release.json, a checksummed Compose bundle, and an immutable container image digest. Before mutation, Moonli creates a backup. The updater validates the release, changes image/version atomically, and checks loopback and public health. Failure restores the previous version and backup.
11 Errors and troubleshooting
{"error":{"code":"PALETTE_VALIDATION_FAILED","message":"Unable to generate an image that matches the allowed palette."}}
| Code | Meaning and action |
|---|---|
INVALID_DEVICE_ID | Use td-######## or aa-######## and persist it. |
DEVICE_BLOCKED | The operator blocked this installation; review it on Devices. |
NO_VISUAL_SUBJECT | Speech was recognized, but it did not say what should be drawn. Ask for a concrete visual subject; do not retry automatically. |
UNAUTHORIZED | The Bearer credential is missing or wrong. |
GOOGLE_KEY_NOT_CONFIGURED | Save the persistent key on Production. |
PROMPT_NORMALIZATION_FAILED | Check normalization model access, endpoint, key, and timeout. |
PROMPT_TRANSLATION_FAILED | Check the Pipeline 3 translation instruction, model access, endpoint, key, and timeout. |
GENERATION_IN_PROGRESS | Honor Retry-After and retry the same payload/key. |
IDEMPOTENCY_CONFLICT | A changed request reused a key; allocate a new UUID. |
RATE_LIMITED | Honor HTTP 429 and Retry-After. |
PALETTE_QUANTIZATION_FAILED / PALETTE_VALIDATION_FAILED | Inspect generated and quantized PNG artifacts from a full run. |
Other stable codes include INVALID_INPUT, AUDIO_TOO_LARGE, TRANSCRIPTION_FAILED, PROMPT_BUILD_FAILED, IMAGE_GENERATION_FAILED, OUTPUT_VALIDATION_FAILED, RESULT_EXPIRED, and INTERNAL_ERROR. Use X-Moonli-Run-Id to correlate a response with the redacted server audit.
12 Android and TouchDesigner integration
TouchDesigner
- Add an external, diffable API module beside the canonical
Player.toe; keep credentials outside the project file and Git. - Persist one
td-########identity in.moonli/device_id.txtand reuse it for both pipeline-3 operations. - Send audio as multipart to
/v1/generate, place the returned text inop('answer'), then send that text as JSON to the same endpoint. Allocate a fresh UUID per operation and reuse it only for an automatic retry of that operation. - Keep the generation ZIP in memory, require exactly three distinct 1024×1024 JPEG members, atomically save
generated/image_1.jpgthroughimage_3.jpg, and reloadmoviefilein1throughmoviefilein3. - Preserve the existing index increment, both sound pulses, and both water-queue commands around the generation trigger.
Android
- Add Internet permission, a pinned streaming HTTP client, an HTTPS-only base domain, Android Keystore-backed credentials, and DataStore-backed
aa-########. - Implement JSON text and multipart audio through the one production endpoint with cancellation and idempotent retry.
- Validate layer packages for safe paths, sizes, counts, CRC, manifest contract, SHA-256, dimensions, colors, and alpha before atomic storage.
- Render the twelve downloaded layers in manifest order; do not poll backend stages.
Android and TouchDesigner source projects are intentionally not edited by the backend repository. The ready production generation replacement is stored at integrations/touchdesigner/pipeline3_generation.py; apply it in the TouchDesigner project and provision an independent credential per installation where practical.