CPU usage
—
Average server load
Moonli
—
Average server load
—
—
—
—
—
Server uptime
No production requests in this period.
The request has not been built yet.
The preview updates immediately; the change is saved only when you select Apply.
The logical ZIP excludes Google, client and updater keys, browser sessions, and .env. A pre-restore snapshot is created automatically before restore.
No snapshot selected.
Download an operator snapshot before installation. The local host updater uses a Unix socket and enforces checksums, an immutable image digest, server backup, health checks, and rollback. One-click updates are safe for image-only releases; Compose topology changes require an explicit repair or install.
Updater status has not been requested.
A device is registered on its first production request with a td-######## or aa-######## identifier. Device type does not restrict pipeline selection.
The device list has not been loaded.
Current tab only, up to 100 entries. Secrets are redacted.
Up to 200 recent events; the server enforces limits of 10,000 records, 30 days, and 64 MiB.
Audit has not been loaded.
OPERATIONS · API · CLIENTS
This handbook describes the deployed service, its production API, the operator console, artifacts, recovery, and the changes required in Android and TouchDesigner clients.
Moonli owns three tagged generation profiles. pipeline-1 and pipeline-2 complete the processed image flow in one POST /v1/generate request. pipeline-3 mirrors the production TouchDesigner flow: audio is transcribed and normalized into concise Russian by a multipart POST /v1/generate; the returned text is sent to the same endpoint as JSON, translated into an English image prompt, and used for three independent JPEG variants. The client type never selects or restricts the pipeline.
Operator authentication, client authentication, device identity, and pipeline routing are separate concerns:
X-Moonli-Device-Id identifies a persistent client installation but is not a secret.pipeline is the only selector for the processing profile.Mock providers can complete every test stage without Google, including through the authenticated Test Calls surface in a production deployment. Production client pipelines never use those mock selections.
Every request requires a Bearer credential, a stable device identifier, and an idempotency key. Use a fresh UUID for each logical operation and reuse it only when retrying that exact operation. All three pipelines use /v1/generate: pipeline 3 sends audio first and then sends the returned normalized text with a new operation UUID.
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, without people"
}
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=<binary audio file>
Do not send palette, provider, stage, or table-selection fields. Unknown pipeline tags—including misspellings such as pipelien-2—are rejected. The server validates declared and streamed audio size before provider work begins.
IDEMPOTENCY_CONFLICT.Before its first production request, a client creates an identifier and stores it in installation-local persistent storage:
| Client | Format | Example |
|---|---|---|
| TouchDesigner | td- + 8 random digits | td-02941846 |
| Android app | aa- + 8 random digits | aa-26093758 |
The server registers a valid identifier on first use and counts every production API attempt. The Devices page shows its type, registration time, lifetime request count, and status. Blocking a device rejects future requests with DEVICE_BLOCKED before the pipeline starts; unblocking restores access. Either prefix may call any pipeline. TouchDesigner stores one device identifier in .moonli/device_id.txt and reuses it for both pipeline-3 operations.
Deleting application data may generate a new identity, so client storage should survive ordinary restarts and updates. The identifier does not replace the Bearer credential and must not be treated as authentication.
| Stage | Purpose | Input → output |
|---|---|---|
| 1 · Transcription | Optional for audio requests. | Audio → source text |
| 2 · Normalization | Removes greetings, filler, and test commentary; Pipelines 1–2 produce English while Pipeline 3 produces Russian. | Source text → normalized phrase |
| 3 · Prompt builder | Applies the pipeline template, palette contract, canvas, and exclusions. | Normalized phrase → technical prompt |
| 4 · Generation | Requests an image from the configured provider and converts the response to PNG. | Prompt → provider PNG |
| 5 · Quantization | Maps every visible pixel to the versioned palette in CIE Lab and optionally removes isolated speckles. | Provider PNG → strict-palette PNG |
| 6 · Validation | Requires exact palette colors and the expected alpha contract with zero tolerance. | PNG → validation report |
| 7 · Vectorization | Builds palette-safe vector paths where required by the output profile. | PNG → SVG |
| 8 · Segmentation | Creates the ordered fixed-slot layer package for layered output. | SVG/PNG → layer ZIP |
pipeline-1 publishes the validated raster PNG. pipeline-2 continues through vectorization and segmentation and publishes a twelve-slot layered package. pipeline-3 intentionally stops after generation: its first operation performs transcription and Russian normalization, while its second operation translates the prompt into English and launches three independent image generations without quantization, palette validation, vectorization, or segmentation. The Test Calls page exposes the processed stages independently and lets an operator copy or download its output and pass it into the next stage.
pipeline-1 returns Content-Type: image/png with the filename moonli.png. pipeline-2 returns application/vnd.moonli.layers+zip with the filename moonli-layers.zip. pipeline-3 normalization returns only the normalized Russian UTF-8 text. pipeline-3 generation translates that text into an English prompt and returns application/zip containing exactly image_1.jpg, image_2.jpg, and image_3.jpg; every member is a 1024×1024 RGB JPEG.
Successful pipeline-1 and pipeline-2 responses include X-Moonli-Run-Id, X-Moonli-Result-SHA256, X-Moonli-Device-Id, and X-Idempotent-Replay. Clients must stream the body to a staging file, verify the SHA-256 response digest, validate the expected media type, and atomically publish the completed file. The pipeline-3 TouchDesigner integration validates the in-memory ZIP, JPEG dimensions, names, and uniqueness before atomically replacing the three files.
A pipeline-2 package contains:
manifest.json
composite.png
layers/00.png
...
layers/11.png
Each layer is a full-canvas RGBA PNG. Transparent pixels are exactly (0,0,0,0); visible pixels are fully opaque and use exactly the assigned slot color. Unused slots remain present as transparent images with used:false. The manifest records contract version, run ID, canvas, palette order, member paths, usage flags, and SHA-256 values. The backend verifies ZIP safety, member checksums, dimensions, colors, alpha, ordering, and pixel-perfect recomposition before publishing.
The operator full-run download is a diagnostic archive rather than the production response. Pipelines 1 and 2 contain the applicable original text or audio, normalized text, technical prompt, generated PNG, quantized PNG, validation report, vector output, and layer artifacts. Pipeline 3 contains the original text or audio, optional transcription, normalized Russian text, translated English prompt, three JPEG variants, the exact production image ZIP, and an execution trace; skipped stages are explicitly marked not_applicable. Every archive records the selected pipeline and input type.
Each pipeline has its own persistent Google API key on the Production page. Keys are verified against Google, written atomically to pipeline-specific files in the dedicated secrets volume, and never placed in .env, localStorage, logs, API responses, logical backups, or release files. The browser receives only configured status. Deleting a key disables Google-backed calls for that pipeline until a replacement is saved.
If Google rejects the server region, Configuration → Routing can send every Google provider request—including Pipeline 3 prompt translation—through the internal Xray sidecar. Paste a VLESS Reality/TCP/Vision connection, enable the switch, and save. The secret is stored only in moonli_secrets; the browser can see configured status but cannot retrieve the value. Disabling routing restores direct Google connections and retains the private VLESS value.
The key entered from Test Calls is intentionally temporary: it exists only in the current tab's memory and is used by individual stages and Full Run for all three pipelines. Test endpoints never fall back to a Production key or Production pipeline configuration. Refreshing or signing out clears it.
Prompt normalization is a separate provider step. Pipelines 1 and 2 normalize to concise English before Prompt Builder applies their technical template. Pipeline 3 normalizes to concise Russian for the TouchDesigner response, then its second operation translates that phrase into English without adding details. Available placeholders are listed beside the template editor; both processed-pipeline templates must be non-empty before saving.
Changing the operator Access Key requires the current key, enforces the minimum length, updates the persistent verifier, and revokes every active browser session. It does not rotate client or updater credentials.
A logical snapshot is a ZIP using format moonli-logical-backup, schema version 2, with a manifest and SHA-256 for every member. It includes runs, usage statistics, registered devices and block state, non-secret settings, the operator verifier, retained audit events, and referenced completed artifacts.
It deliberately excludes Google keys, optional VLESS routing state, client and updater credentials, cookies, sessions, .env, staging files, and release files. Back up the secrets volume separately using an encrypted host-level disaster-recovery procedure.
Before mutation, restore checks the 128 MiB compressed and 256 MiB uncompressed limits, compression ratios, member count, duplicate names, paths, links, schema, and all digests. It creates a pre-restore snapshot and reapplies it if restore fails. The service keeps the five newest restore points. A successful restore revokes browser sessions, so sign in again afterward.
Retention baselines are 10,000 audit events / 30 days / 64 MiB estimated payload, up to 1,000,000 usage rows / 365 days, and three rotated container log files of 10 MiB each.
.env to the server.vless-proxy service has no host port. Configure it from Configuration → Routing only when the server's direct region cannot reach Google.Do not expose application ports 8000 or 18000. Port 18000 is loopback-only for readiness and local updater operations. Normal domain TLS is the intended boundary; VPN and mTLS are not required. Nginx allows the UI, health endpoint, client generation endpoint, and allow-listed operator routes. Unknown hosts receive 421 and concealed routes return 404.
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
Generation can take several minutes. Nginx uses a 300-second upstream timeout, so Android and TouchDesigner clients must use a call/read timeout of at least 300 seconds and keep TLS certificate verification enabled.
The root-owned host updater is based on psewdon1m-exocortex/updater at the pinned reviewed commit. The web application receives only its Unix socket and never receives the Docker socket. Moonli may request a semantic release version but cannot provide arbitrary commands, repositories, image names, URLs, or services.
A valid release is named moonli-vX.Y.Z and includes moonli-release.json, a checksummed Compose bundle, and an immutable image digest. Before mutation the application creates a logical backup. The updater validates the release, pulls the image, updates version state atomically, and runs loopback and public health checks. Failure restores the previous image/version and invokes backup restore.
API failures use a stable envelope and never expose a traceback or partial artifact:
{
"error": {
"code": "PALETTE_VALIDATION_FAILED",
"message": "Unable to generate an image that matches the allowed palette."
}
}
| Code | Action |
|---|---|
INVALID_DEVICE_ID | Generate and persist the required prefix plus exactly eight digits. |
DEVICE_BLOCKED | Unblock the installation on Devices or investigate why it was disabled. |
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 | Check the Bearer client credential; the device ID is not a credential. |
GOOGLE_KEY_NOT_CONFIGURED | Save the persistent key on Production. |
PROMPT_NORMALIZATION_FAILED | Verify the normalization provider, model access, endpoint, key, and timeout. |
PROMPT_TRANSLATION_FAILED | Verify the Pipeline 3 translation instruction, model access, endpoint, key, and timeout. |
GENERATION_IN_PROGRESS | Wait for Retry-After, then retry the identical request and idempotency key. |
IDEMPOTENCY_CONFLICT | Use a new key for a changed input or pipeline. |
RATE_LIMITED | Honor HTTP 429 and Retry-After. |
PALETTE_QUANTIZATION_FAILED / PALETTE_VALIDATION_FAILED | Inspect the generated and quantized PNGs in a full-run archive; validation requires exact colors and alpha. |
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 the run ID to correlate the client response with the redacted server audit.
The backend repository does not modify either client project. Apply these integration changes in their own codebases:
Player.toe.td-######## identifier in installation-local configuration./v1/generate, then send the returned normalized text from op('answer') as JSON to the same endpoint.image_1.jpg through image_3.jpg, and reload all three matching Movie File In TOPs.aa-######## identifier with DataStore.Both client types may request any pipeline. Separate per-device credentials remain recommended even though the pipeline and device prefix do not impose authorization roles.