# MOSS-Image API spec (node4) HTTP image-generation API for the MOSS-Image TTNN bundle. Diffusion model — **no tokens, no KV cache, no streaming**; requests are image jobs. ## Base URL - From node4 itself: `http://127.0.0.1:8000` - From other boxes on the LAN: `http://192.168.50.97:8000` (node4), or `http://node4:8000` if your `/etc/hosts`/DNS resolves it. - The server binds `0.0.0.0:$PORT` (default **8000**). Open that port in any firewall between boxes. No auth (intended for a trusted LAN); put it behind a proxy if you need auth. > Status note: the API shape below is identical whether it is served by the device bundle > (TTNN on the Blackhole card) or the CPU reference — same endpoints, same schemas. Only speed > differs. ## Endpoints ### `GET /health` Liveness + which model/mesh is loaded, and the capability contract. ```json { "status": "ok", "model": "OpenMOSS-Team/MOSS-Image-Base", "mesh": "P150", "mesh_shape": "1x1", "vae_device": true, "pipeline_loaded": true, "kind": "tt-dit-server", "progress_reporting": true, "job_persistence": true, "private_ephemeral_jobs": true, "private_result_ttl_s": 900 } ``` - `vae_device` — `true` when the VAE decodes on the device (pure `ttnn`, the default; validated vs host fp32 to PCC ≥0.9999 at all resolutions). Force host with `MOSS_VAE_DEVICE=0`. - `mesh_shape` — DiT topology (`MOSS_MESH`): `1x1` single-chip (default), `1x2`/`1x4` mesh. - `private_ephemeral_jobs` / `private_result_ttl_s` — see **Private jobs** below. ### Private jobs (default for new `POST /generate`) Send an `X-Moss-Job-Token` header on `POST /generate` (or let the server mint one and return it as `job_token`). The job is then token-scoped: **not** listed in `GET /jobs`, **never** written to disk (result held in memory only), and reaped after a **900 s** TTL. The same token is required on `GET /jobs/{id}`, `GET /images/{id}.png`, and `DELETE /jobs/{id}`; a missing/wrong token returns **404**. Limits: max 8 private jobs, 64 MiB encoded result each. Legacy disk-backed jobs (created without a token) remain listable and survive restarts. ### `GET /v1/models` OpenAI-style model list. ```json { "object": "list", "data": [ { "id": "OpenMOSS-Team/MOSS-Image-Base", "object": "model", "owned_by": "openmoss" } ] } ``` ### `POST /generate` → submit an async job (recommended) Request body (JSON): | field | type | default | notes | | --- | --- | --- | --- | | `prompt` | string | — | **required** | | `height` | int | 1024 | 256–2048, multiple of 16 | | `width` | int | 1024 | 256–2048, multiple of 16 | | `num_inference_steps` | int | 50 | 1–200; ≥28 for quality | | `guidance_scale` | float | 3.0 | 0–20; CFG scale (1.0 disables CFG) | | `negative_prompt` | string | `""` | used when `guidance_scale != 1` | | `seed` | int\|null | null | reproducibility (CPU RNG) | | `max_sequence_length` | int | 4096 | 1–4096; max prompt tokens | | `shift` | float\|null | null→5.0 | 1–10; flow-match scheduler shift | | `uncond_skip_layers` | list[int] | `[22]` | DiT layers skipped on the CFG negative branch; `[]` disables | | `slg_layers` | list[int]\|null | null | skip-layer-guidance layers; null/`[]` disables the extra forward | | `slg_scale` | float | 0.0 | 0–20; `prediction += slg_scale*(cond − cond_skipped)` | | `slg_start` | float | 0.0 | 0–1; SLG/skip window start (fraction of steps) | | `slg_stop` | float | 1.0 | 0–1; SLG/skip window stop (fraction of steps) | The advanced knobs mirror `reference/pipeline_moss_image.py __call__`. Call **`GET /v1/parameters`** (alias `GET /config`) for this schema with ranges at runtime. Response (every new job is private by default — keep the `job_token`): ```json { "job_id": "ff70ee2b28e4", "status": "queued", "poll": "/jobs/ff70ee2b28e4", "job_token": "<43–128 url-safe chars>", "storage": "memory", "private": true, "result_ttl_s": 900 } ``` Jobs are serialized through one worker (one model / one device); submitting many just queues them. ### `GET /jobs/{job_id}` → poll status ```json { "job_id": "ff70ee2b28e4", "status": "done", "request": { ... }, "image_url": "/images/ff70ee2b28e4.png" } ``` `status` ∈ `queued` | `running` | `done` | `error`. On `error`, an `error` field carries the message. ### `GET /images/{job_id}.png` → fetch the result Returns `image/png` once the job is `done` (404 until then). Finished images are saved to disk (`$MOSS_OUTPUT_DIR`, default `./moss_outputs`) and freed from memory, so this is served from the saved file after the job completes. ### `GET /jobs` → list jobs ```json { "jobs": [ { "job_id": "ff70ee2b28e4", "status": "done", "file_path": "/.../moss_outputs/ff70ee2b28e4.png", "prompt": "a red fox in snow" } ] } ``` ### `DELETE /jobs/{job_id}` → delete a job + its saved image Drops the job from memory and removes its PNG from disk. ```json { "deleted": true, "job_id": "ff70ee2b28e4", "file_removed": true } ``` ### `POST /generate/sync` → blocking, returns PNG directly Same body as `/generate`; responds with `image/png`. Convenient but holds the connection for the full generation (minutes at 1024²/50) — prefer the async flow for anything non-trivial. ### `POST /v1/images/generations` → OpenAI-compatible (blocking) Request (superset of the OpenAI schema — the full control set is accepted here too): ```json { "prompt": "a red fox in snow", "n": 1, "size": "1024x1024", "seed": 42, "width": 1024, "height": 1024, "num_inference_steps": 50, "guidance_scale": 3.0, "negative_prompt": "", "shift": 5.0, "slg_layers": [24], "slg_scale": 3.0 } ``` `size` is `WIDTHxHEIGHT`; explicit `width`+`height` override it. All `/generate` advanced knobs (`max_sequence_length`, `shift`, `uncond_skip_layers`, `slg_*`) are accepted. Response (base64 PNGs, OpenAI Images shape): ```json { "created": 1760000000, "data": [ { "b64_json": "" } ] } ``` ### `GET /v1/parameters` (alias `GET /config`) → controllable-parameter schema A UI reads this to render generation controls. Each entry has `name`, `type`, `default`, and `min`/`max`/`multiple_of`/`max_length` where applicable. ```json { "model": "OpenMOSS-Team/MOSS-Image-Base", "endpoints": ["/generate", "/v1/images/generations"], "parameters": [ { "name": "num_inference_steps", "type": "int", "default": 50, "min": 1, "max": 200 }, { "name": "guidance_scale", "type": "float", "default": 3.0, "min": 0.0, "max": 20.0 }, { "name": "shift", "type": "float|null", "default": 5.0, "min": 1.0, "max": 10.0 }, { "name": "width", "type": "int", "default": 1024, "min": 256, "max": 2048, "multiple_of": 16 } /* …height, seed, negative_prompt, max_sequence_length, uncond_skip_layers, slg_* … */ ] } ``` ## Example (async) ```bash HOST=http://192.168.50.97:8000 JID=$(curl -s -X POST $HOST/generate -H 'content-type: application/json' \ -d '{"prompt":"a cozy bookshop, warm light, a cat asleep on a chair","height":1024,"width":1024,"num_inference_steps":50,"seed":7}' \ | python3 -c 'import sys,json;print(json.load(sys.stdin)["job_id"])') until curl -s $HOST/jobs/$JID | grep -q '"status":"done"'; do sleep 5; done curl -s $HOST/images/$JID.png -o out.png ``` ## Notes for client/agent builders - **Latency** (single p150, device backend): ~12–25 s for 256²/4-step warm; ~235 s for 1024²/50-step. Set generous client timeouts; prefer `/generate` + poll over `/generate/sync`. - **Concurrency:** requests queue (single device). Don't fan out expecting parallelism. - **Resolution:** `height`/`width` must be multiples of 16. - **Determinism:** pass `seed` for reproducible images. - **Errors:** poll `/jobs/{id}` and check `status == "error"` + the `error` string; HTTP 4xx for bad input (e.g. malformed `size`), 404 for an unknown job or an image not yet ready.