{
  "openapi": "3.1.0",
  "info": {
    "title": "kiapi ERNIE-Image API",
    "description": "Image generation, editing, and LoRA training (ERNIE-Image via mflux).\n\nThree operations on Baidu's ERNIE-Image models: `/generate` (text-to-image),\n`/edit` (single-image image-to-image), and `/train` (LoRA finetune).\n\n## Upstream docs\n- [mflux — ERNIE-Image](https://github.com/filipstrand/mflux/blob/main/src/mflux/models/ernie_image/README.md) — the MLX engine kiapi runs\n- [baidu/ERNIE-Image-Turbo](https://huggingface.co/baidu/ERNIE-Image-Turbo) — `turbo` weights\n- [baidu/ERNIE-Image](https://huggingface.co/baidu/ERNIE-Image) — `base` weights\n\n## Models\n- **turbo** (default) — `baidu/ERNIE-Image-Turbo`. Fast, few steps (default 8),\n  guidance 1.0. The everyday choice.\n- **base** — `baidu/ERNIE-Image`. Higher quality but slower: more steps\n  (default 50) and stronger guidance (default 4.0).\n\nPick the variant with `model` (aliases like `ernie-image-turbo` are accepted);\ndiscover variants at `GET /v1/image/ernie/models`. Variant-dependent defaults\n(`steps` / `guidance` / `quantize`) are filled in server-side when omitted, and\nthe resolved values are recorded in the result `params`.\n\n## Resident vs transient runs\nThe warmed, resident model uses the server-default quantization. A request that\neither supplies `loras` or sets a `quantize` differing from the resident model\nbuilds a **one-off transient model** for that call — weights load fresh, so it is\nslower and not reused. For the fast path, omit `loras` and leave `quantize` unset.\n\n## LoRA workflow\nTrain an adapter from a captioned image set via `/train` (always async), then\napply it on `/generate` or `/edit` by passing its `adapter_file_id` in `loras`\n(up to 4, each `{file, scale}`). Dataset = a ZIP uploaded to the Files API with a\nsame-stem `.txt` caption next to each image (`cat.png` + `cat.txt`); images may\nsit at the top level or in a single subfolder, and `preview*` images are exempt\nfrom the caption requirement.\n\n```\ndataset.zip\n├── cat_01.png      # image\n├── cat_01.txt      # same-stem caption: \"a photo of a tabby cat\"\n├── cat_02.jpg\n├── cat_02.txt\n└── preview.png     # optional preview, no caption needed\n```\n\nA single wrapping subfolder is also accepted (the images just need to live\ntogether under one directory):\n\n```\ndataset.zip\n└── cats/\n    ├── cat_01.png\n    ├── cat_01.txt\n    └── ...\n```\n\n## TIPS\n- For a quick image, call `sync` without `Accept: application/json` to get the\n  raw bytes straight back (`curl -o out.png`).\n- `width`/`height` must be multiples of 16 and at most 2048x2048. `edit`\n  additionally requires square sizes by default — set\n  `KIAPI_ERNIE_EDIT_REQUIRE_SQUARE=0` to lift the guard (mflux 0.18.0 can fail on\n  some non-square ERNIE img2img sizes).\n- First use downloads the ERNIE-Image weights; run `kiapi activate` ahead of time\n  to avoid a cold-start download on the first request.\n\n## Performance\n- **turbo**: q8 512x512 generation around 15s after cache load, peak RSS ~5-6 GiB.\n- **base**: slower and uses more steps; prefer turbo unless you need its quality.\n- Transient runs (any `loras` or a `quantize` override) rebuild the model each\n  call and are slower than the resident path.\n",
    "version": "0.1.0"
  },
  "paths": {
    "/v1/image/ernie/generate": {
      "post": {
        "summary": "Generate Ernie Endpoint",
        "description": "Generate an image from a text prompt (ERNIE-Image, text-to-image).\n\nNo input image — use `/v1/image/ernie/edit` for image-to-image. The same\nendpoint serves both `sync` and `async` via `mode`. Variant defaults differ:\n`turbo` (default) is fast and uses few steps; `base` is higher quality and\nslower.\n\nSync content negotiation: a single image is produced, so unless the client\nasks for JSON the raw image bytes are returned with `X-Kiapi-File-Id` /\n`X-Kiapi-Job-Id` headers — `curl -o out.png` just works. With\n`Accept: application/json` (or async) the Job JSON is returned, whose\n`result` follows ImageResponse.\n\nAny `loras` or a `quantize` differing from the resident model runs a one-off\ntransient model (slower, not reused). Async returns 202 immediately; poll\nGET /v1/jobs/{job_id} and fetch the artifact via GET /v1/files/{file_id}.",
        "operationId": "generate_ernie_endpoint_v1_image_ernie_generate_post",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Response media type preference. application/json returns the Job JSON; otherwise sync requests with one artifact return raw bytes when possible.",
              "examples": [
                "application/json",
                "image/png",
                "audio/wav",
                "video/mp4"
              ],
              "title": "Accept"
            },
            "description": "Response media type preference. application/json returns the Job JSON; otherwise sync requests with one artifact return raw bytes when possible."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sync result. Returns Job JSON with Accept: application/json; single-artifact jobs may return raw bytes otherwise.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobImageResponse"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "X-Kiapi-File-Id": {
                "description": "Produced artifact file_id when raw bytes are returned.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Kiapi-Job-Id": {
                "description": "Job id when raw bytes are returned.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "Async job accepted. Poll GET /v1/jobs/{job_id}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AsyncJobResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request for the selected model or file reference."
          },
          "422": {
            "description": "Request schema or validation error."
          },
          "503": {
            "description": "Model setup or memory budget error."
          },
          "504": {
            "description": "Sync request exceeded the configured timeout."
          }
        }
      }
    },
    "/v1/image/ernie/edit": {
      "post": {
        "summary": "Edit Ernie Endpoint",
        "description": "Edit a single input image with a prompt (ERNIE-Image, image-to-image).\n\nTakes one `image` (FileRef) plus a `prompt`; `image_strength` controls how\nmuch of the input is preserved (lower = closer to input). Output must be\nsquare by default (a guard around an mflux 0.18.0 non-square img2img issue),\noverrideable via `KIAPI_ERNIE_EDIT_REQUIRE_SQUARE=0`. The same endpoint\nserves both `sync` and `async` via `mode`.\n\nSync content negotiation, transient-model behavior (`loras` / `quantize`\noverride), and the ImageResponse `result` shape match\n`/v1/image/ernie/generate`.",
        "operationId": "edit_ernie_endpoint_v1_image_ernie_edit_post",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Response media type preference. application/json returns the Job JSON; otherwise sync requests with one artifact return raw bytes when possible.",
              "examples": [
                "application/json",
                "image/png",
                "audio/wav",
                "video/mp4"
              ],
              "title": "Accept"
            },
            "description": "Response media type preference. application/json returns the Job JSON; otherwise sync requests with one artifact return raw bytes when possible."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EditRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sync result. Returns Job JSON with Accept: application/json; single-artifact jobs may return raw bytes otherwise.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobImageResponse"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/webp": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "X-Kiapi-File-Id": {
                "description": "Produced artifact file_id when raw bytes are returned.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Kiapi-Job-Id": {
                "description": "Job id when raw bytes are returned.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "Async job accepted. Poll GET /v1/jobs/{job_id}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AsyncJobResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request for the selected model or file reference."
          },
          "422": {
            "description": "Request schema or validation error."
          },
          "503": {
            "description": "Model setup or memory budget error."
          },
          "504": {
            "description": "Sync request exceeded the configured timeout."
          }
        }
      }
    },
    "/v1/image/ernie/train": {
      "post": {
        "summary": "Train Ernie Endpoint",
        "description": "Train a LoRA adapter on a captioned image set (ERNIE-Image, always async).\n\nLong-running, so this endpoint is always async: it returns 202 with a\njob_id — poll GET /v1/jobs/{job_id}. The `dataset` is a Files-API ZIP whose\nimages each have a same-stem `.txt` caption. On success the job's `result`\nholds `adapter_file_id` (the trained `.safetensors`, also the artifact),\n`adapter_bytes`, `num_images`, the resolved `config`, `lora_targets`, and\n`timings`; the adapter can then be passed back via `loras` on generate/edit.",
        "operationId": "train_ernie_endpoint_v1_image_ernie_train_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrainRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "202": {
            "description": "Training job accepted. Poll GET /v1/jobs/{job_id}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AsyncJobResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid model or dataset FileRef."
          },
          "422": {
            "description": "Request schema or validation error."
          },
          "503": {
            "description": "Model setup or memory budget error."
          }
        }
      }
    },
    "/v1/image/ernie/models": {
      "get": {
        "summary": "List Models",
        "description": "List the servable models for this capability.\n\nReturns the public catalog of every variant selectable via the ``model``\nfield on this capability's endpoints.",
        "operationId": "list_models_v1_image_ernie_models_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/CapabilityModelSpec"
                  },
                  "type": "array",
                  "title": "Response List Models V1 Image Ernie Models Get"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AsyncJobResponse": {
        "properties": {
          "job_id": {
            "type": "string",
            "title": "Job Id",
            "description": "In-memory job id. Poll GET /v1/jobs/{job_id} to inspect status, progress, result, and artifacts.",
            "examples": [
              "job_0123456789abcdef"
            ]
          },
          "type": {
            "type": "string",
            "title": "Type",
            "description": "Job type. Generation APIs use values such as zimage, flux2-edit, or acestep-extract.",
            "examples": [
              "zimage"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus",
            "description": "Initial job status. Async responses are normally queued unless the worker starts immediately.",
            "examples": [
              "queued"
            ]
          }
        },
        "type": "object",
        "required": [
          "job_id",
          "type",
          "status"
        ],
        "title": "AsyncJobResponse"
      },
      "CapabilityModelSpec": {
        "properties": {
          "name": {
            "type": "string",
            "title": "Name",
            "description": "Model variant name to pass in the request model field.",
            "examples": [
              "turbo"
            ]
          },
          "family": {
            "type": "string",
            "title": "Family",
            "description": "Capability family that resolves this model variant.",
            "examples": [
              "zimage"
            ]
          },
          "domain": {
            "type": "string",
            "title": "Domain",
            "description": "Capability domain used for grouping model lists.",
            "examples": [
              "image"
            ]
          },
          "aliases": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Aliases",
            "description": "Alternative names that also resolve to this model.",
            "examples": [
              [
                "omni",
                "qwen3-omni-30b"
              ]
            ]
          },
          "default": {
            "type": "boolean",
            "title": "Default",
            "description": "Whether this is the default model when the request omits model.",
            "default": false,
            "examples": [
              true
            ]
          },
          "features": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Features",
            "description": "Handler-declared modalities and features supported by this model.",
            "examples": [
              [
                "text",
                "image"
              ]
            ]
          }
        },
        "type": "object",
        "required": [
          "name",
          "family",
          "domain"
        ],
        "title": "CapabilityModelSpec",
        "description": "Public model discovery entry for capability-specific model lists."
      },
      "EditRequest": {
        "properties": {
          "model": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Model",
            "description": "Model variant (see GET /v1/image/ernie/models). Omit for the default `turbo`; `base` is higher-quality but slower. Aliases such as `ernie-image-turbo` / `ernie-image` are accepted."
          },
          "mode": {
            "type": "string",
            "enum": [
              "sync",
              "async"
            ],
            "title": "Mode",
            "description": "`sync` waits for the image (504 on timeout); `async` returns 202 with a job_id immediately — poll GET /v1/jobs/{job_id}.",
            "default": "sync"
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "title": "Prompt",
            "description": "Text prompt describing the desired edit (required)."
          },
          "negative_prompt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Negative Prompt",
            "description": "Optional text describing what to avoid in the result."
          },
          "image": {
            "$ref": "#/components/schemas/FileRef",
            "description": "Input image to edit, as a Files-API file id, http(s) URL, or data URL."
          },
          "image_strength": {
            "type": "number",
            "title": "Image Strength",
            "description": "How much the input image is preserved, in 0..1. Lower keeps more of the input; higher follows the prompt more freely.",
            "default": 0.4
          },
          "width": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Width",
            "description": "Output width in pixels. Omit for the server default (1024). Must be a multiple of 16 and at most 2048. By default must equal height (square)."
          },
          "height": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Height",
            "description": "Output height in pixels. Omit for the server default (1024). Must be a multiple of 16 and at most 2048. By default must equal width (square)."
          },
          "steps": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Steps",
            "description": "Number of denoising steps (1..100). Omit for the variant default (turbo 8, base 50)."
          },
          "guidance": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Guidance",
            "description": "Classifier-free guidance scale. Omit for the variant default (turbo 1.0, base 4.0); higher follows the prompt more strictly."
          },
          "seed": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Seed",
            "description": "Random seed for reproducibility. Omit for a random seed (the resolved seed is recorded in the result `params`)."
          },
          "quantize": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Quantize",
            "description": "Quantization bits, one of {3, 4, 5, 6, 8}. Omit to use the resident model's quantization (default 8). A differing value builds a one-off transient model (slower, not reused)."
          },
          "scheduler": {
            "type": "string",
            "title": "Scheduler",
            "description": "Noise scheduler. `linear` is the default and only tested value.",
            "default": "linear"
          },
          "format": {
            "type": "string",
            "enum": [
              "png",
              "jpeg",
              "webp"
            ],
            "title": "Format",
            "description": "Output image encoding for the produced file.",
            "default": "png"
          },
          "quality": {
            "type": "integer",
            "maximum": 100.0,
            "minimum": 1.0,
            "title": "Quality",
            "description": "Encoder quality 1..100 (used for jpeg/webp; ignored for png).",
            "default": 90
          },
          "loras": {
            "items": {
              "$ref": "#/components/schemas/LoraRef"
            },
            "type": "array",
            "title": "Loras",
            "description": "Up to 4 LoRA adapters [{file, scale}] referencing Files-API ids. Any lora forces a one-off transient model (slower, not reused)."
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "prompt",
          "image"
        ],
        "title": "EditRequest"
      },
      "FileDataURLRef": {
        "properties": {
          "type": {
            "type": "string",
            "const": "data_url",
            "title": "Type",
            "default": "data_url"
          },
          "data_url": {
            "type": "string",
            "minLength": 1,
            "title": "Data Url"
          }
        },
        "type": "object",
        "required": [
          "data_url"
        ],
        "title": "FileDataURLRef",
        "examples": [
          {
            "data_url": "data:image/png;base64,iVBORw0KGgo...",
            "type": "data_url"
          }
        ]
      },
      "FileID": {
        "type": "string"
      },
      "FileIDRef": {
        "properties": {
          "type": {
            "type": "string",
            "const": "file_id",
            "title": "Type",
            "default": "file_id"
          },
          "file_id": {
            "type": "string",
            "minLength": 1,
            "title": "File Id"
          }
        },
        "type": "object",
        "required": [
          "file_id"
        ],
        "title": "FileIDRef",
        "examples": [
          {
            "file_id": "file_0123456789abcdef",
            "type": "file_id"
          }
        ]
      },
      "FileRef": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/FileIDRef"
          },
          {
            "$ref": "#/components/schemas/FileURLRef"
          },
          {
            "$ref": "#/components/schemas/FileDataURLRef"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "data_url": "#/components/schemas/FileDataURLRef",
            "file_id": "#/components/schemas/FileIDRef",
            "url": "#/components/schemas/FileURLRef"
          }
        }
      },
      "FileURLRef": {
        "properties": {
          "type": {
            "type": "string",
            "const": "url",
            "title": "Type",
            "default": "url"
          },
          "url": {
            "type": "string",
            "minLength": 1,
            "title": "Url"
          }
        },
        "type": "object",
        "required": [
          "url"
        ],
        "title": "FileURLRef",
        "examples": [
          {
            "type": "url",
            "url": "https://example.com/input.png"
          }
        ]
      },
      "GenerateRequest": {
        "properties": {
          "model": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Model",
            "description": "Model variant (see GET /v1/image/ernie/models). Omit for the default `turbo`; `base` is the higher-quality, slower variant. Aliases such as `ernie-image-turbo` / `ernie-image` are accepted."
          },
          "mode": {
            "type": "string",
            "enum": [
              "sync",
              "async"
            ],
            "title": "Mode",
            "description": "`sync` waits for the image (504 on timeout); `async` returns 202 with a job_id immediately — poll GET /v1/jobs/{job_id}.",
            "default": "sync"
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "title": "Prompt",
            "description": "Text prompt describing the image to generate (required)."
          },
          "negative_prompt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Negative Prompt",
            "description": "Optional text describing what to avoid in the image."
          },
          "width": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Width",
            "description": "Output width in pixels. Omit for the server default (1024). Must be a multiple of 16 and at most 2048."
          },
          "height": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Height",
            "description": "Output height in pixels. Omit for the server default (1024). Must be a multiple of 16 and at most 2048."
          },
          "steps": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Steps",
            "description": "Number of denoising steps (1..100). Omit for the variant default (turbo 8, base 50). More steps = slower, sometimes higher quality."
          },
          "guidance": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Guidance",
            "description": "Classifier-free guidance scale. Omit for the variant default (turbo 1.0, base 4.0); higher follows the prompt more strictly."
          },
          "seed": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Seed",
            "description": "Random seed for reproducibility. Omit for a random seed (the resolved seed is recorded in the result `params`)."
          },
          "quantize": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Quantize",
            "description": "Quantization bits, one of {3, 4, 5, 6, 8}. Omit to use the resident model's quantization (default 8). A differing value builds a one-off transient model (slower, not reused)."
          },
          "scheduler": {
            "type": "string",
            "title": "Scheduler",
            "description": "Noise scheduler. `linear` is the default and only tested value.",
            "default": "linear"
          },
          "format": {
            "type": "string",
            "enum": [
              "png",
              "jpeg",
              "webp"
            ],
            "title": "Format",
            "description": "Output image encoding for the produced file.",
            "default": "png"
          },
          "quality": {
            "type": "integer",
            "maximum": 100.0,
            "minimum": 1.0,
            "title": "Quality",
            "description": "Encoder quality 1..100 (used for jpeg/webp; ignored for png).",
            "default": 90
          },
          "loras": {
            "items": {
              "$ref": "#/components/schemas/LoraRef"
            },
            "type": "array",
            "title": "Loras",
            "description": "Up to 4 LoRA adapters [{file, scale}] referencing Files-API ids. Any lora forces a one-off transient model (slower, not reused)."
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "prompt"
        ],
        "title": "GenerateRequest"
      },
      "ImageResponse": {
        "properties": {
          "model": {
            "type": "string",
            "title": "Model",
            "description": "Resolved model variant that produced the image (turbo | base)."
          },
          "prompt": {
            "type": "string",
            "title": "Prompt",
            "description": "The prompt used for the run."
          },
          "file_id": {
            "type": "string",
            "title": "File Id",
            "description": "Files-API id of the produced image. Fetch metadata at GET /v1/files/{id} or bytes at /download. This is also the artifact returned as raw bytes by a single-artifact sync call."
          },
          "image_bytes": {
            "type": "integer",
            "title": "Image Bytes",
            "description": "Size of the produced image in bytes."
          },
          "width": {
            "type": "integer",
            "title": "Width",
            "description": "Width in pixels of the produced image."
          },
          "height": {
            "type": "integer",
            "title": "Height",
            "description": "Height in pixels of the produced image."
          },
          "params": {
            "additionalProperties": true,
            "type": "object",
            "title": "Params",
            "description": "Resolved parameters actually used for the run (model, seed, steps, guidance, quantize, scheduler, format, quality, loras, …), so the result is reproducible."
          },
          "timings": {
            "$ref": "#/components/schemas/_Timings",
            "description": "kiapi extension: server-side timing."
          }
        },
        "type": "object",
        "required": [
          "model",
          "prompt",
          "file_id",
          "image_bytes",
          "width",
          "height",
          "params",
          "timings"
        ],
        "title": "ImageResponse",
        "description": "Capability-specific ``result`` for a succeeded ernie generate/edit job."
      },
      "JobID": {
        "type": "string"
      },
      "JobImageResponse": {
        "properties": {
          "type": {
            "$ref": "#/components/schemas/JobType",
            "description": "Job type. Use this to interpret the capability-specific result payload.",
            "examples": [
              "zimage"
            ]
          },
          "params": {
            "additionalProperties": true,
            "type": "object",
            "title": "Params",
            "description": "Request parameters captured for inspection and reproducibility. Secret or large media payloads may be omitted or redacted by endpoints."
          },
          "id": {
            "$ref": "#/components/schemas/JobID",
            "description": "In-memory job id. Jobs are cleared when the kiapi process restarts.",
            "examples": [
              "job_0123456789abcdef"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus",
            "description": "Job lifecycle state: queued, running, succeeded, failed, or canceled.",
            "default": "queued",
            "examples": [
              "succeeded"
            ]
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ImageResponse"
              },
              {
                "type": "null"
              }
            ]
          },
          "artifacts": {
            "items": {
              "$ref": "#/components/schemas/FileID"
            },
            "type": "array",
            "title": "Artifacts",
            "description": "File ids produced by the job. Use GET /v1/files/{file_id} for metadata or /download for bytes.",
            "examples": [
              [
                "file_0123456789abcdef"
              ]
            ]
          },
          "error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error",
            "description": "Error message when status is failed; otherwise null.",
            "examples": [
              "model 'turbo' is not activated"
            ]
          },
          "created_at": {
            "type": "number",
            "title": "Created At",
            "description": "Unix timestamp when the job was created.",
            "examples": [
              1766200000.0
            ]
          },
          "started_at": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Started At",
            "description": "Unix timestamp when the worker started the job, or null while queued.",
            "examples": [
              1766200001.0
            ]
          },
          "finished_at": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Finished At",
            "description": "Unix timestamp when the job reached a terminal state, or null while queued/running.",
            "examples": [
              1766200030.0
            ]
          },
          "progress": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 1.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Progress",
            "description": "Best-effort completion fraction in [0.0, 1.0]. Null means the job has not reported progress.",
            "examples": [
              0.42
            ]
          },
          "progress_label": {
            "type": "string",
            "title": "Progress Label",
            "description": "Short human-readable phase label such as queued, running, denoising, saving, or done.",
            "default": "queued",
            "examples": [
              "denoising"
            ]
          }
        },
        "type": "object",
        "required": [
          "type"
        ],
        "title": "JobImageResponse"
      },
      "JobStatus": {
        "type": "string",
        "enum": [
          "queued",
          "running",
          "succeeded",
          "failed",
          "canceled"
        ],
        "title": "JobStatus"
      },
      "JobType": {
        "type": "string"
      },
      "LoraRef": {
        "properties": {
          "file": {
            "$ref": "#/components/schemas/FileRef",
            "description": "The LoRA adapter (`.safetensors`) to apply, as a Files-API file id, http(s) URL, or data URL."
          },
          "scale": {
            "type": "number",
            "title": "Scale",
            "description": "Blend strength for this adapter. 1.0 applies it at full weight; lower values weaken its effect, higher values exaggerate it.",
            "default": 1.0
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "LoraRef",
        "description": "One LoRA adapter to apply, by Files-API reference and blend scale.\n\nAny lora forces a one-off transient model (slower, not reused). Up to 4 may\nbe passed in a request's ``loras`` list."
      },
      "TrainRequest": {
        "properties": {
          "model": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Model",
            "description": "Model variant to finetune (see GET /v1/image/ernie/models). Omit for the default `turbo`."
          },
          "dataset": {
            "$ref": "#/components/schemas/FileRef",
            "description": "Training dataset as a Files-API reference to a ZIP. Images may sit at the top level or in a single subfolder; each image needs a same-stem `.txt` caption (e.g. `cat.png` + `cat.txt`). `preview*` images are exempt from the caption requirement."
          },
          "seed": {
            "type": "integer",
            "title": "Seed",
            "description": "Random seed for the training run.",
            "default": 42
          },
          "steps": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Steps",
            "description": "Inference/denoising steps used during training. Omit for the variant default (turbo 8, base 50)."
          },
          "guidance": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Guidance",
            "description": "Guidance scale. Omit for the variant default (turbo 1.0, base 4.0)."
          },
          "quantize": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Quantize",
            "description": "Quantization bits during training, one of {3, 4, 5, 6, 8}. Omit for the variant default (8)."
          },
          "max_resolution": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Max Resolution",
            "description": "Images are resized so their long side is at most this many pixels.",
            "default": 512
          },
          "low_ram": {
            "type": "boolean",
            "title": "Low Ram",
            "description": "Trade speed for lower peak memory during training.",
            "default": true
          },
          "num_epochs": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Num Epochs",
            "description": "Number of passes over the dataset.",
            "default": 10
          },
          "batch_size": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Batch Size",
            "description": "Training batch size.",
            "default": 1
          },
          "timestep_low": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Timestep Low",
            "description": "Lower diffusion-timestep bound to sample. Omit for the default (1)."
          },
          "timestep_high": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Timestep High",
            "description": "Upper diffusion-timestep bound to sample. Omit to use `steps`."
          },
          "learning_rate": {
            "type": "number",
            "title": "Learning Rate",
            "description": "AdamW learning rate.",
            "default": 0.0001
          },
          "save_frequency": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Save Frequency",
            "description": "Checkpoint every N steps. Omit to only keep the final checkpoint."
          },
          "lora_rank": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Lora Rank",
            "description": "Rank of the trained LoRA adapter.",
            "default": 16
          },
          "lora_include_ff": {
            "type": "boolean",
            "title": "Lora Include Ff",
            "description": "Also train feed-forward / time-embedding layers (larger adapter), not just attention projections.",
            "default": false
          },
          "lora_layers": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Lora Layers",
            "description": "Advanced: explicit mflux LoRA target spec. Omit to use the built-in default targets derived from `lora_rank` / `lora_include_ff`."
          }
        },
        "additionalProperties": true,
        "type": "object",
        "required": [
          "dataset"
        ],
        "title": "TrainRequest"
      },
      "_Timings": {
        "properties": {
          "total_s": {
            "type": "number",
            "title": "Total S",
            "description": "Wall-clock generation time in seconds (model run only)."
          }
        },
        "type": "object",
        "required": [
          "total_s"
        ],
        "title": "_Timings"
      }
    }
  },
  "x-kiapi-capability": "ernie",
  "x-kiapi-domain": "image",
  "x-kiapi-root-openapi": "/openapi.json"
}
