pi-lemonade-link 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/openai.md CHANGED
@@ -1,46 +1,79 @@
1
1
  # OpenAI-Compatible API
2
2
 
3
- This spec defines Lemonade's implementation of the [OpenAI API](https://developers.openai.com/api/docs).
3
+ This page documents Lemonade's implementation of the [OpenAI API](https://developers.openai.com/api/docs).
4
4
 
5
+ <!-- BEGIN GENERATED: openai.summary -->
5
6
  | Method | Endpoint | Description | Modality |
6
7
  |--------|----------|-------------|----------|
7
- | `POST` | [`/v1/chat/completions`](#post-v1chatcompletions) | Chat Completions | messages -> completion |
8
- | `POST` | [`/v1/completions`](#post-v1completions) | Text Completions | prompt -> completion |
9
- | `POST` | [`/v1/embeddings`](#post-v1embeddings) | Embeddings | text -> vector representations |
10
- | `POST` | [`/v1/responses`](#post-v1responses) | Responses API | prompt/messages -> event |
11
- | `POST` | [`/v1/audio/transcriptions`](#post-v1audiotranscriptions) | Audio Transcription | audio file -> text |
12
- | `POST` | [`/v1/audio/speech`](#post-v1audiospeech) | Text to speech | text -> audio |
8
+ | `POST` | [`/v1/chat/completions`](#post-v1chatcompletions) | Generate the next message in a conversation | messages -> completion |
9
+ | `POST` | [`/v1/completions`](#post-v1completions) | Continue a text prompt | prompt -> completion |
10
+ | `POST` | [`/v1/embeddings`](#post-v1embeddings) | Embed text as vectors | text -> vector representations |
11
+ | `POST` | [`/v1/responses`](#post-v1responses) | Generate a response in OpenAI's Responses format | prompt/messages -> event |
12
+ | `POST` | [`/v1/audio/transcriptions`](#post-v1audiotranscriptions) | Transcribe an audio file to text | audio file -> text |
13
+ | `POST` | [`/v1/images/generations`](#post-v1imagesgenerations) | Generate an image from a text prompt | prompt -> image |
14
+ | `POST` | [`/v1/images/edits`](#post-v1imagesedits) | Edit images as a prompt describes | image + prompt -> edited image |
15
+ | `POST` | [`/v1/images/variations`](#post-v1imagesvariations) | Generate a variation of an image | image -> varied image |
16
+ | `POST` | [`/v1/images/upscale`](#post-v1imagesupscale) | Upscale an image | image + ESRGAN model -> upscaled image |
17
+ | `POST` | [`/v1/audio/speech`](#post-v1audiospeech) | Generate speech from text | text -> audio |
13
18
  | `WS` | [`/realtime`](#ws-realtime) | Realtime Audio Transcription, OpenAI SDK compatible | streaming audio -> text |
14
- | `POST` | [`/v1/images/generations`](#post-v1imagesgenerations) | Image Generation | prompt -> image |
15
- | `POST` | [`/v1/images/edits`](#post-v1imagesedits) | Image Editing | image + prompt -> edited image |
16
- | `POST` | [`/v1/images/variations`](#post-v1imagesvariations) | Image Variations | image -> varied image |
17
- | `POST` | [`/v1/images/upscale`](#post-v1imagesupscale) | Image Upscaling | image + ESRGAN model -> upscaled image |
18
- | `GET` | [`/v1/models`](#get-v1models) | List models available locally | n/a |
19
- | `GET` | [`/v1/models/{model_id}`](#get-v1modelsmodel_id) | Retrieve a specific model by ID | n/a |
20
-
19
+ | `GET` | [`/v1/models`](#get-v1models) | List the server's models | n/a |
20
+ | `GET` | [`/v1/models/{id}`](#get-v1modelsid) | Read one model | n/a |
21
+ | `GET` | [`/v1/models/{id}/files`](#get-v1modelsidfiles) | List one model's local files | n/a |
22
+ | `GET` | [`/v1/models/{id}/options`](#get-v1modelsidoptions) | Read a model's recipe options by layer | n/a |
23
+ | `POST` | [`/v1/models/{id}/options`](#post-v1modelsidoptions) | Save a model's recipe options | n/a |
24
+ | `DELETE` | [`/v1/models/{id}/options`](#delete-v1modelsidoptions) | Reset a model's recipe options | n/a |
25
+ <!-- END GENERATED: openai.summary -->
26
+
27
+ <!-- BEGIN GENERATED: openai.chat_completions -->
21
28
  ## `POST /v1/chat/completions`
22
- <sub>![Status](https://img.shields.io/badge/status-partially_available-green)</sub>
29
+ <sub>![Status](https://img.shields.io/badge/status-partially_available-yellow)</sub>
30
+
31
+ Generates the next assistant message for a conversation, downloading and loading the model if it is not loaded.
23
32
 
24
- Chat Completions API. You provide a list of messages and receive a completion. This API will also load the model if it is not already loaded.
33
+ Requires `LEMONADE_API_KEY` when it is set.
25
34
 
26
35
  ### Parameters
27
36
 
28
37
  | Parameter | Required | Description | Status |
29
38
  |-----------|----------|-------------|--------|
30
- | `messages` | Yes | Array of messages in the conversation. Each message should have a `role` ("user" or "assistant") and `content` (the message text). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
31
- | `model` | Yes | The model to use for the completion. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
32
- | `stream` | No | If true, tokens will be sent as they are generated. If false, the response will be sent as a single message once complete. Defaults to false. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
33
- | `stop` | No | Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. Can be a string or an array of strings. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
34
- | `logprobs` | No | Include log probabilities of the output tokens. If true, returns the log probability of each output token. Defaults to false. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
35
- | `temperature` | No | What sampling temperature to use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
36
- | `repeat_penalty` | No | Number between 1.0 and 2.0. 1.0 means no penalty. Higher values discourage repetition. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
37
- | `top_k` | No | Integer that controls the number of top tokens to consider during sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
38
- | `top_p` | No | Float between 0.0 and 1.0 that controls the cumulative probability of top tokens to consider during nucleus sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
39
- | `tools` | No | A list of tools the model may call. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
40
- | `max_tokens` | No | An upper bound for the number of tokens that can be generated for a completion. Mutually exclusive with `max_completion_tokens`. This value is now deprecated by OpenAI in favor of `max_completion_tokens` | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
41
- | `max_completion_tokens` | No | An upper bound for the number of tokens that can be generated for a completion. Mutually exclusive with `max_tokens`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
42
-
43
- ### Example request
39
+ | `messages` | Yes | The conversation so far, in OpenAI's message format: each message has a `role` and `content`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
40
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
41
+ | `stream` | No | `true` sends the response as server-sent events as it progresses; `false` sends one response once complete. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
42
+ | `stop` | No | Sequences that stop generation, as a string or an array of strings. The returned text leaves them out. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
43
+ | `logprobs` | No | `true` returns the log probability of each output token. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
44
+ | `temperature` | No | Sampling temperature. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
45
+ | `repeat_penalty` | No | From `1.0` to `2.0`: `1.0` applies no penalty, and higher values discourage repetition. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
46
+ | `frequency_penalty` | No | From `-2.0` to `2.0`: positive values discourage tokens in proportion to how often they have appeared. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
47
+ | `seed` | No | Random seed for reproducible sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
48
+ | `top_k` | No | Number of top tokens to consider during sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
49
+ | `top_p` | No | Cumulative probability of the top tokens to consider during nucleus sampling, from `0.0` to `1.0`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
50
+ | `tools` | No | A list of tools the model may call. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
51
+ | `tool_choice` | No | Which tool the model must call, in OpenAI's format: `auto`, `none` or `required`. vLLM and cloud models also take an object naming a function, which llama.cpp models treat as `auto`. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
52
+ | `response_format` | No | The format the model must output, in OpenAI's format, such as `{"type": "json_object"}`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
53
+ | `max_tokens` | No | Upper bound on generated tokens. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
54
+ | `max_completion_tokens` | No | OpenAI's name for `max_tokens`. Send one of the two: when both are sent, the backend picks, and llama.cpp uses `max_tokens` while vLLM uses `max_completion_tokens`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
55
+ | `enable_thinking` | No | Lemonade extension: `false` turns off reasoning on models that think. `thinking: false` does the same, and `enable_thinking` wins when both are sent. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
56
+ | `route_trace` | No | Lemonade extension for `collection.router` models: `true` adds the per-condition `trace` to `x_lemonade_route`. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
57
+ | `ctx_size` | No | Lemonade extension: context size to load the model with, when this request loads it. Ignored when the model is already loaded. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
58
+
59
+ Naming a `collection.router` model routes the request to one of its candidates; see [Router API](./router.md).
60
+
61
+ ### Server-Side Tools
62
+
63
+ Omni collection orchestration is a Lemonade extension to the Chat Completions API; requests to other models are unaffected.
64
+
65
+ When `model` names an Omni **collection** model (`recipe: "collection.omni"`, e.g., `LMX-Omni-52B-Halo`), the route runs an internal tool-calling loop instead of a plain completion. The server injects the reference system prompt and tools, routes to the collection's chat component, executes the omni tools (image generation/editing, text-to-speech) against the matching components, and returns one OpenAI-compatible response. Generated media is embedded in the assistant `content`:
66
+
67
+ - **images** → markdown `![generated image](data:image/png;base64,…)`
68
+ - **speech** → `<audio>data:audio/mpeg;base64,…</audio>`
69
+
70
+ Both non-streaming and `stream: true` are supported. In streaming mode the media arrives in `content` deltas of at most 16,000 characters each, the moment its tool finishes, and an error arrives as `Error: <message>` text in `content` followed by `finish_reason: "stop"`.
71
+
72
+ **Merge semantics.** The built-in omni system prompt goes before a client-provided one. Client-provided `tools` are merged with the built-in omni tools. The server resolves omni tool calls internally; calls to client-provided tools are returned in a `finish_reason: "tool_calls"` response for the client to execute and resume. Targeting a collection name invokes the server-side loop; targeting a component LLM name bypasses it and returns a plain completion. See [Lemonade Omni Models](../dev/lemonade-omni.md) for details.
73
+
74
+ ### Examples
75
+
76
+ #### Non-streaming
44
77
 
45
78
  === "PowerShell"
46
79
 
@@ -50,243 +83,581 @@ Chat Completions API. You provide a list of messages and receive a completion. T
50
83
  -Method POST `
51
84
  -Headers @{ "Content-Type" = "application/json" } `
52
85
  -Body '{
53
- "model": "Qwen3-0.6B-GGUF",
54
- "messages": [
55
- {
56
- "role": "user",
57
- "content": "What is the population of Paris?"
58
- }
59
- ],
60
- "stream": false
61
- }'
86
+ "model": "Qwen3-0.6B-GGUF",
87
+ "messages": [{"role": "user", "content": "What is the population of Paris?"}],
88
+ "stream": false
89
+ }'
62
90
  ```
91
+
63
92
  === "Bash"
64
93
 
65
94
  ```bash
66
- curl -X POST http://localhost:13305/v1/chat/completions \
95
+ curl http://localhost:13305/v1/chat/completions \
67
96
  -H "Content-Type: application/json" \
68
97
  -d '{
69
- "model": "Qwen3-0.6B-GGUF",
70
- "messages": [
71
- {"role": "user", "content": "What is the population of Paris?"}
72
- ],
73
- "stream": false
74
- }'
98
+ "model": "Qwen3-0.6B-GGUF",
99
+ "messages": [{"role": "user", "content": "What is the population of Paris?"}],
100
+ "stream": false
101
+ }'
75
102
  ```
76
103
 
77
- ### Response format
78
-
79
- === "Non-streaming responses"
104
+ **Response:** `200 OK`
80
105
 
81
- ```json
106
+ ```json
107
+ {
108
+ "id": "chatcmpl-cJjl2vW81HgtjvknoAdqxCGKeEtNOG4u",
109
+ "object": "chat.completion",
110
+ "created": 1793827645,
111
+ "model": "Qwen3-0.6B-GGUF",
112
+ "system_fingerprint": "b12109-13ec8f614",
113
+ "choices": [
82
114
  {
83
- "id": "0",
84
- "object": "chat.completion",
85
- "created": 1742927481,
86
- "model": "Qwen3-0.6B-GGUF",
87
- "choices": [{
88
- "index": 0,
89
- "message": {
90
- "role": "assistant",
91
- "content": "Paris has a population of approximately 2.2 million people in the city proper."
92
- },
93
- "finish_reason": "stop"
94
- }]
115
+ "index": 0,
116
+ "message": {
117
+ "role": "assistant",
118
+ "content": "The population of Paris, France, is approximately **1,215,000** as of the latest available data. This number reflects the city's significant size and historical importance as the capital of France.",
119
+ "reasoning_content": "Okay, so I need to find out the population of Paris. Let me think... I remember ... (1,176 characters)"
120
+ },
121
+ "finish_reason": "stop"
95
122
  }
96
- ```
97
- === "Streaming responses"
98
- For streaming responses, the API returns a stream of server-sent events (however, Open AI recommends using their streaming libraries for parsing streaming responses):
123
+ ],
124
+ "usage": {
125
+ "prompt_tokens": 15,
126
+ "completion_tokens": 364,
127
+ "total_tokens": 379,
128
+ "prompt_tokens_details": {"cached_tokens": 0}
129
+ },
130
+ "timings": {
131
+ "cache_n": 0,
132
+ "predicted_ms": 3151.928,
133
+ "predicted_n": 364,
134
+ "predicted_per_second": 115.16760535139127,
135
+ "predicted_per_token_ms": 8.682997245179063,
136
+ "prompt_ms": 220.395,
137
+ "prompt_n": 15,
138
+ "prompt_per_second": 68.05962022731913,
139
+ "prompt_per_token_ms": 14.693000000000001
140
+ }
141
+ }
142
+ ```
99
143
 
100
- ```json
101
- {
102
- "id": "0",
103
- "object": "chat.completion.chunk",
104
- "created": 1742927481,
105
- "model": "Qwen3-0.6B-GGUF",
106
- "choices": [{
107
- "index": 0,
108
- "delta": {
109
- "role": "assistant",
110
- "content": "Paris"
111
- }
112
- }]
113
- }
144
+ #### Streaming
145
+
146
+ === "PowerShell"
147
+
148
+ ```powershell
149
+ Invoke-WebRequest `
150
+ -Uri "http://localhost:13305/v1/chat/completions" `
151
+ -Method POST `
152
+ -Headers @{ "Content-Type" = "application/json" } `
153
+ -Body '{
154
+ "model": "Qwen3-0.6B-GGUF",
155
+ "messages": [{"role": "user", "content": "What is the population of Paris?"}],
156
+ "stream": true
157
+ }'
114
158
  ```
115
159
 
116
- ### Image understanding input format (OpenAI-compatible)
160
+ === "Bash"
117
161
 
118
- To send images to `chat/completions`, pass a `messages[*].content` array that mixes `text` and `image_url` items. The image can be provided as a base64 data URL (for example, from `FileReader.readAsDataURL(...)` in web apps).
162
+ ```bash
163
+ curl http://localhost:13305/v1/chat/completions \
164
+ -H "Content-Type: application/json" \
165
+ -d '{
166
+ "model": "Qwen3-0.6B-GGUF",
167
+ "messages": [{"role": "user", "content": "What is the population of Paris?"}],
168
+ "stream": true
169
+ }'
170
+ ```
119
171
 
120
- #### Example request
172
+ **Response:** `200 OK`
121
173
 
122
- ```bash
123
- curl -X POST http://localhost:13305/v1/chat/completions \
124
- -H "Content-Type: application/json" \
125
- -d '{
126
- "model": "Qwen2.5-VL-7B-Instruct",
127
- "messages": [
128
- {
129
- "role": "user",
130
- "content": [
131
- {"type": "text", "text": "What is in this image?"},
132
- {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."}}
133
- ]
134
- }
135
- ],
136
- "stream": false
137
- }'
174
+ ```text
175
+ data: {"id": "chatcmpl-NoEWRv1s5czlHNYL4Vp5FhTw91Ou8XM1", "object": "chat.completion.chunk", "created": 1793827645, "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/50968a4468ef4233ed78cd7c3de230dd1d61a56b/Qwen3-0.6B-Q4_0.gguf", "system_fingerprint": "b12109-13ec8f614", "choices": [{"index": 0, "delta": {"role": "assistant", "content": null}, "finish_reason": null}]}
176
+ data: {"id": "chatcmpl-NoEWRv1s5czlHNYL4Vp5FhTw91Ou8XM1", "object": "chat.completion.chunk", "created": 1793827645, "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/50968a4468ef4233ed78cd7c3de230dd1d61a56b/Qwen3-0.6B-Q4_0.gguf", "system_fingerprint": "b12109-13ec8f614", "choices": [{"index": 0, "delta": {"reasoning_content": "Okay"}, "finish_reason": null}]}
177
+ data: {"id": "chatcmpl-NoEWRv1s5czlHNYL4Vp5FhTw91Ou8XM1", "object": "chat.completion.chunk", "created": 1793827645, "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/50968a4468ef4233ed78cd7c3de230dd1d61a56b/Qwen3-0.6B-Q4_0.gguf", "system_fingerprint": "b12109-13ec8f614", "choices": [{"index": 0, "delta": {"reasoning_content": ","}, "finish_reason": null}]}
178
+ ...
179
+ data: {"id": "chatcmpl-NoEWRv1s5czlHNYL4Vp5FhTw91Ou8XM1", "object": "chat.completion.chunk", "created": 1793827647, "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/50968a4468ef4233ed78cd7c3de230dd1d61a56b/Qwen3-0.6B-Q4_0.gguf", "system_fingerprint": "b12109-13ec8f614", "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}], "timings": {"cache_n": 14, "prompt_n": 1, "prompt_ms": 13.334, "prompt_per_token_ms": 13.334, "prompt_per_second": 74.99625018749063, "predicted_n": 179, "predicted_ms": 1827.578, "predicted_per_token_ms": 10.267292134831461, "predicted_per_second": 97.39666378124491}}
180
+ data: [DONE]
138
181
  ```
139
182
 
140
- #### Example response
183
+ #### Image input
184
+
185
+ Asks a vision model what `image.png` shows. A `messages[*].content` array mixes `text` and `image_url` items, with each image as a base64 data URL, such as one from `FileReader.readAsDataURL(...)` in a web app.
186
+
187
+ === "PowerShell"
188
+
189
+ ```powershell
190
+ $image = [Convert]::ToBase64String([IO.File]::ReadAllBytes("image.png"))
191
+ $body = @"
192
+ {
193
+ "model": "Qwen3.5-0.8B-GGUF",
194
+ "messages": [
195
+ {
196
+ "role": "user",
197
+ "content": [
198
+ {"type": "text", "text": "What is in this image?"},
199
+ {
200
+ "type": "image_url",
201
+ "image_url": {"url": "data:image/png;base64,$image"}
202
+ }
203
+ ]
204
+ }
205
+ ],
206
+ "stream": false
207
+ }
208
+ "@
209
+ Invoke-WebRequest `
210
+ -Uri "http://localhost:13305/v1/chat/completions" `
211
+ -Method POST `
212
+ -Headers @{ "Content-Type" = "application/json" } `
213
+ -Body $body
214
+ ```
215
+
216
+ === "Bash"
217
+
218
+ ```bash
219
+ curl http://localhost:13305/v1/chat/completions \
220
+ -H "Content-Type: application/json" \
221
+ -d "{
222
+ \"model\": \"Qwen3.5-0.8B-GGUF\",
223
+ \"messages\": [
224
+ {
225
+ \"role\": \"user\",
226
+ \"content\": [
227
+ {\"type\": \"text\", \"text\": \"What is in this image?\"},
228
+ {
229
+ \"type\": \"image_url\",
230
+ \"image_url\": {\"url\": \"data:image/png;base64,$(base64 -w0 image.png)\"}
231
+ }
232
+ ]
233
+ }
234
+ ],
235
+ \"stream\": false
236
+ }"
237
+ ```
238
+
239
+ **Response:** `200 OK`
141
240
 
142
241
  ```json
143
242
  {
144
- "id": "0",
243
+ "id": "chatcmpl-bykV6FdzHGGdiC5ePf9PBFOxRu0AFTBi",
145
244
  "object": "chat.completion",
146
- "created": 1742927481,
147
- "model": "Qwen2.5-VL-7B-Instruct",
148
- "choices": [{
149
- "index": 0,
150
- "message": {
151
- "role": "assistant",
152
- "content": "The image shows a red apple resting on a wooden table."
153
- },
154
- "finish_reason": "stop"
155
- }]
245
+ "created": 1793827657,
246
+ "model": "Qwen3.5-0.8B-GGUF",
247
+ "system_fingerprint": "b12109-13ec8f614",
248
+ "choices": [
249
+ {
250
+ "index": 0,
251
+ "message": {
252
+ "role": "assistant",
253
+ "content": "This is a logo image featuring a solid red circle centered on a gradient background. The background transitions from a teal and green mix on the left to a grey on the right side.",
254
+ "reasoning_content": "The user wants me to identify the visual content of the provided image.\n\n1. **A... (1,535 characters)"
255
+ },
256
+ "finish_reason": "stop"
257
+ }
258
+ ],
259
+ "usage": {
260
+ "prompt_tokens": 82,
261
+ "completion_tokens": 405,
262
+ "total_tokens": 487,
263
+ "prompt_tokens_details": {"cached_tokens": 0}
264
+ },
265
+ "timings": {
266
+ "cache_n": 0,
267
+ "predicted_ms": 1989.104,
268
+ "predicted_n": 405,
269
+ "predicted_per_second": 203.10652434462952,
270
+ "predicted_per_token_ms": 4.923524752475248,
271
+ "prompt_ms": 465.51,
272
+ "prompt_n": 82,
273
+ "prompt_per_second": 176.15088827307684,
274
+ "prompt_per_token_ms": 5.676951219512195
275
+ }
156
276
  }
157
277
  ```
158
278
 
159
- ### Server-side tools
160
-
161
- > **Note:** Omni collection orchestration is a Lemonade-specific extension to the Chat Completions API; it is not part of the OpenAI specification. Requests that target ordinary (non-collection) models are unaffected and are fully OpenAI-compatible.
162
-
163
- When `model` names an Omni **collection** model (`recipe: "collection.omni"`, e.g., `LMX-Omni-52B-Halo`), this endpoint runs an internal tool-calling loop instead of a plain completion. The server injects the reference system prompt and tools, routes to the collection's chat component, executes the omni tools (image generation/editing, text-to-speech) against the matching components, and returns one OpenAI-compatible response. Generated media is embedded in the assistant `content`:
164
-
165
- - **images** → markdown `![generated image](data:image/png;base64,…)`
166
- - **speech** → `<audio>data:audio/mpeg;base64,…</audio>`
279
+ #### Server-side tools
167
280
 
168
- Both non-streaming and `stream: true` are supported. In streaming mode the media arrives as a content delta on a `chat.completion.chunk` frame the moment its tool finishes.
281
+ Runs the [server-side tools](#server-side-tools) loop on `LMX-Omni-5.5B-Lite`, whose image tool draws the requested picture.
169
282
 
170
- **Merge semantics.** A client-provided system prompt is prepended by the built-in omni system prompt. Client-provided `tools` are merged with the built-in omni tools. The server resolves omni tool calls internally; calls to client-provided tools are returned in a `finish_reason: "tool_calls"` response for the client to execute and resume. Targeting a collection name invokes the server-side loop; targeting a component LLM name bypasses it and returns a plain completion. See [Lemonade Omni Models](../dev/lemonade-omni.md) for details.
283
+ === "PowerShell"
171
284
 
172
- #### Example request
285
+ ```powershell
286
+ Invoke-WebRequest `
287
+ -Uri "http://localhost:13305/v1/chat/completions" `
288
+ -Method POST `
289
+ -Headers @{ "Content-Type" = "application/json" } `
290
+ -Body '{
291
+ "model": "LMX-Omni-5.5B-Lite",
292
+ "messages": [{"role": "user", "content": "Draw a red apple on a table."}],
293
+ "stream": false
294
+ }'
295
+ ```
173
296
 
174
- ```bash
175
- curl -X POST http://localhost:13305/v1/chat/completions \
176
- -H "Content-Type: application/json" \
177
- -d '{
178
- "model": "LMX-Omni-52B-Halo",
179
- "messages": [{"role": "user", "content": "Draw a red apple on a table."}],
180
- "stream": false
181
- }'
182
- ```
297
+ === "Bash"
183
298
 
184
- #### Example response
299
+ ```bash
300
+ curl http://localhost:13305/v1/chat/completions \
301
+ -H "Content-Type: application/json" \
302
+ -d '{
303
+ "model": "LMX-Omni-5.5B-Lite",
304
+ "messages": [{"role": "user", "content": "Draw a red apple on a table."}],
305
+ "stream": false
306
+ }'
307
+ ```
185
308
 
186
- The image tool runs during the loop and its output is embedded as a markdown image in the assistant `content`:
309
+ **Response:** `200 OK`
187
310
 
188
311
  ```json
189
312
  {
190
- "id": "0",
313
+ "id": "chatcmpl-i37OtXcuKiFJ82bZjOEJmIDgbbrSLIWk",
191
314
  "object": "chat.completion",
192
- "created": 1742927481,
193
- "model": "LMX-Omni-52B-Halo",
194
- "choices": [{
195
- "index": 0,
196
- "message": {
197
- "role": "assistant",
198
- "content": "Here is a red apple on a table.\n\n![generated image](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...)"
199
- },
200
- "finish_reason": "stop"
201
- }]
315
+ "created": 1792076833,
316
+ "model": "LMX-Omni-5.5B-Lite",
317
+ "system_fingerprint": "b11393-dbe4c3ed4",
318
+ "choices": [
319
+ {
320
+ "index": 0,
321
+ "message": {
322
+ "role": "assistant",
323
+ "content": "I've generated an image of a red apple on a table for you! The image shows a cla... (393,258 characters)"
324
+ },
325
+ "finish_reason": "stop"
326
+ }
327
+ ],
328
+ "usage": {
329
+ "prompt_tokens": 1102,
330
+ "completion_tokens": 63,
331
+ "total_tokens": 1165,
332
+ "prompt_tokens_details": {"cached_tokens": 1082}
333
+ },
334
+ "timings": {
335
+ "cache_n": 1082,
336
+ "draft_n": 69,
337
+ "draft_n_accepted": 39,
338
+ "predicted_ms": 706.57,
339
+ "predicted_n": 63,
340
+ "predicted_per_second": 87.74785230055055,
341
+ "predicted_per_token_ms": 11.396290322580645,
342
+ "prompt_ms": 97.681,
343
+ "prompt_n": 20,
344
+ "prompt_per_second": 204.74810863934647,
345
+ "prompt_per_token_ms": 4.88405
346
+ }
202
347
  }
203
348
  ```
204
349
 
350
+ ### Response format
205
351
 
352
+ #### JSON
353
+
354
+ - `id` - Identifier of the completion.
355
+ - `object` - Always `"chat.completion"`.
356
+ - `created` - Unix timestamp of when the completion was created.
357
+ - `model` - The model that generated the response.
358
+ - `system_fingerprint` - Build identifier of the backend, when it reports one.
359
+ - `choices` - The generated messages, one per choice.
360
+ - `index` - Position of the choice in `choices`.
361
+ - `message` - The generated assistant message.
362
+ - `role` - Always `"assistant"`.
363
+ - `content` - The message text.
364
+ - `reasoning_content` - Reasoning models only: the model's thinking.
365
+ - `tool_calls` - Calls to the request's tools.
366
+ - `finish_reason` - Why generation stopped, such as `stop`, `length` or `tool_calls`.
367
+ - `usage` - Token counts for the request.
368
+ - `prompt_tokens` - Tokens in the prompt.
369
+ - `completion_tokens` - Tokens generated.
370
+ - `total_tokens` - Sum of `prompt_tokens` and `completion_tokens`.
371
+ - `prompt_tokens_details` - Breakdown of the prompt tokens, such as `cached_tokens`, when the backend reports it.
372
+ - `timings` - Prompt processing and generation timings, passed through from backends that report them, such as llama.cpp.
373
+ - `x_lemonade_route` - The routing decision, when `model` names a `collection.router` model. See [Routing](./router.md#routing-collectionrouter) for the decision object.
374
+
375
+ #### Event stream
376
+
377
+ One event per `data:` line. The stream ends with `data: [DONE]`.
378
+
379
+ ##### Chunk
380
+
381
+ A chunk of the assistant's message.
382
+
383
+ - `id` - Identifier of the completion, the same on every chunk.
384
+ - `object` - Always `"chat.completion.chunk"`.
385
+ - `created` - Unix timestamp of when the completion was created.
386
+ - `model` - The backend's name for the model, such as the GGUF file path for llama.cpp models.
387
+ - `system_fingerprint` - Build identifier of the backend, when it reports one.
388
+ - `choices` - The next part of each choice's message.
389
+ - `index` - Position of the choice in `choices`.
390
+ - `delta` - The part of the assistant message generated since the previous chunk.
391
+ - `role` - First chunk only: always `"assistant"`.
392
+ - `content` - The next message text.
393
+ - `reasoning_content` - Reasoning models only: the next part of the model's thinking.
394
+ - `tool_calls` - Parts of calls to the request's tools, matched by `index`: a call's first part carries its `id` and `function.name`, and later parts carry the next piece of `function.arguments`.
395
+ - `finish_reason` - Why generation stopped, such as `stop`, `length` or `tool_calls`, on the choice's last chunk; `null` before it.
396
+ - `usage` - Token counts for the request, on an extra last chunk with an empty `choices`. Sent when the request sets `stream_options.include_usage`, and always by vLLM models.
397
+ - `timings` - Final chunk only: prompt processing and generation timings, passed through from backends that report them, such as llama.cpp.
398
+ - `x_lemonade_route` - The routing decision, when `model` names a `collection.router` model. See [Routing](./router.md#routing-collectionrouter) for the decision object.
399
+
400
+ ##### Error event
401
+
402
+ Sent in place of the rest of the stream when the backend fails after the stream has started. No `[DONE]` follows it.
403
+
404
+ - `error` - The backend's error, as an object or, when the backend sent one, a string.
405
+ - `message` - What failed.
406
+ - `type` - The error's type, such as `backend_error`.
407
+ - `status_code` - The status the backend answered with, or `502` when it closed the stream without a response.
408
+
409
+ ### Errors
410
+
411
+ | Status | Code | When |
412
+ |--------|------|------|
413
+ | `400` | `invalid_json` | The body is not a JSON object |
414
+ | `400` | `missing_argument` | A required argument is missing |
415
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
416
+ | `400` | | `model` is not an LLM, which is checked after it loads |
417
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
418
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
419
+ | `404` | `model_not_found` | The requested model is not in the registry |
420
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
421
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
422
+ | `500` | | The backend reports an error; the backend's own status replaces this one when it sends one |
423
+ | `500` | | The request fails unexpectedly |
424
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
425
+ <!-- END GENERATED: openai.chat_completions -->
426
+
427
+ <!-- BEGIN GENERATED: openai.completions -->
206
428
  ## `POST /v1/completions`
207
- <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
429
+ <sub>![Status](https://img.shields.io/badge/status-partially_available-yellow)</sub>
430
+
431
+ Continues a text prompt, downloading and loading the model if it is not loaded.
208
432
 
209
- Text Completions API. You provide a prompt and receive a completion. This API will also load the model if it is not already loaded.
433
+ Requires `LEMONADE_API_KEY` when it is set.
210
434
 
211
435
  ### Parameters
212
436
 
213
437
  | Parameter | Required | Description | Status |
214
438
  |-----------|----------|-------------|--------|
215
- | `prompt` | Yes | The prompt to use for the completion. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
216
- | `model` | Yes | The model to use for the completion. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
217
- | `stream` | No | If true, tokens will be sent as they are generated. If false, the response will be sent as a single message once complete. Defaults to false. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
218
- | `stop` | No | Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. Can be a string or an array of strings. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
219
- | `echo` | No | Echo back the prompt in addition to the completion. Available on non-streaming mode. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
220
- | `logprobs` | No | Include log probabilities of the output tokens. If true, returns the log probability of each output token. Defaults to false. Only available when `stream=False`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
221
- | `temperature` | No | What sampling temperature to use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
222
- | `repeat_penalty` | No | Number between 1.0 and 2.0. 1.0 means no penalty. Higher values discourage repetition. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
223
- | `top_k` | No | Integer that controls the number of top tokens to consider during sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
224
- | `top_p` | No | Float between 0.0 and 1.0 that controls the cumulative probability of top tokens to consider during nucleus sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
225
- | `max_tokens` | No | An upper bound for the number of tokens that can be generated for a completion, including input tokens. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
226
-
227
- ### Example request
439
+ | `prompt` | Yes | The text to continue, as a string or an array of strings. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
440
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
441
+ | `stream` | No | `true` sends the response as server-sent events as it progresses; `false` sends one response once complete. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
442
+ | `stop` | No | Sequences that stop generation, as a string or an array of strings. The returned text leaves them out. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
443
+ | `echo` | No | `true` asks the backend to return the prompt before the completion. llama.cpp models ignore it. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
444
+ | `logprobs` | No | `true`, or a number of top alternatives per token, returns the log probability of each output token. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
445
+ | `temperature` | No | Sampling temperature. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
446
+ | `repeat_penalty` | No | From `1.0` to `2.0`: `1.0` applies no penalty, and higher values discourage repetition. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
447
+ | `frequency_penalty` | No | From `-2.0` to `2.0`: positive values discourage tokens in proportion to how often they have appeared. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
448
+ | `seed` | No | Random seed for reproducible sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
449
+ | `top_k` | No | Number of top tokens to consider during sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
450
+ | `top_p` | No | Cumulative probability of the top tokens to consider during nucleus sampling, from `0.0` to `1.0`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
451
+ | `max_tokens` | No | Upper bound on generated tokens. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
452
+ | `route_trace` | No | Lemonade extension for `collection.router` models: `true` adds the per-condition `trace` to `x_lemonade_route`. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
453
+ | `ctx_size` | No | Lemonade extension: context size to load the model with, when this request loads it. Ignored when the model is already loaded. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
454
+
455
+ Naming a `collection.router` model routes the request to one of its candidates; see [Router API](./router.md).
456
+
457
+ ### Examples
458
+
459
+ #### Non-streaming
228
460
 
229
461
  === "PowerShell"
230
462
 
231
463
  ```powershell
232
- Invoke-WebRequest -Uri "http://localhost:13305/v1/completions" `
464
+ Invoke-WebRequest `
465
+ -Uri "http://localhost:13305/v1/completions" `
233
466
  -Method POST `
234
467
  -Headers @{ "Content-Type" = "application/json" } `
235
468
  -Body '{
236
- "model": "Qwen3-0.6B-GGUF",
237
- "prompt": "What is the population of Paris?",
238
- "stream": false
239
- }'
469
+ "model": "Qwen3-0.6B-GGUF",
470
+ "prompt": "What is the population of Paris?",
471
+ "stream": false,
472
+ "max_tokens": 64
473
+ }'
240
474
  ```
241
475
 
242
476
  === "Bash"
243
477
 
244
478
  ```bash
245
- curl -X POST http://localhost:13305/v1/completions \
479
+ curl http://localhost:13305/v1/completions \
246
480
  -H "Content-Type: application/json" \
247
481
  -d '{
248
- "model": "Qwen3-0.6B-GGUF",
249
- "prompt": "What is the population of Paris?",
250
- "stream": false
251
- }'
482
+ "model": "Qwen3-0.6B-GGUF",
483
+ "prompt": "What is the population of Paris?",
484
+ "stream": false,
485
+ "max_tokens": 64
486
+ }'
252
487
  ```
253
488
 
254
- ### Response format
255
-
256
- The following format is used for both streaming and non-streaming responses:
489
+ **Response:** `200 OK`
257
490
 
258
491
  ```json
259
492
  {
260
- "id": "0",
493
+ "id": "chatcmpl-yRFRUAekD8zEgLPHmtMKr50iJa1iKKUU",
261
494
  "object": "text_completion",
262
- "created": 1742927481,
495
+ "created": 1793827667,
263
496
  "model": "Qwen3-0.6B-GGUF",
264
- "choices": [{
265
- "index": 0,
266
- "text": "Paris has a population of approximately 2.2 million people in the city proper.",
267
- "finish_reason": "stop"
268
- }],
497
+ "system_fingerprint": "b12109-13ec8f614",
498
+ "choices": [
499
+ {
500
+ "index": 0,
501
+ "text": " And what are the characteristics of Paris? Also, what is the capital of France? What is the capital of Italy? What is the capital of Spain? What is the capital of Nigeria? And what is the capital of India?\n\nOkay, let's tackle these questions. First, the population of Paris. I remember that",
502
+ "logprobs": null,
503
+ "finish_reason": "length"
504
+ }
505
+ ],
506
+ "usage": {
507
+ "prompt_tokens": 7,
508
+ "completion_tokens": 64,
509
+ "total_tokens": 71,
510
+ "prompt_tokens_details": {"cached_tokens": 0}
511
+ },
512
+ "timings": {
513
+ "cache_n": 0,
514
+ "predicted_ms": 3637.764,
515
+ "predicted_n": 64,
516
+ "predicted_per_second": 17.318330710843252,
517
+ "predicted_per_token_ms": 57.742285714285714,
518
+ "prompt_ms": 133.468,
519
+ "prompt_n": 7,
520
+ "prompt_per_second": 52.44702850121378,
521
+ "prompt_per_token_ms": 19.066857142857142
522
+ }
269
523
  }
270
524
  ```
271
525
 
526
+ #### Streaming
527
+
528
+ === "PowerShell"
529
+
530
+ ```powershell
531
+ Invoke-WebRequest `
532
+ -Uri "http://localhost:13305/v1/completions" `
533
+ -Method POST `
534
+ -Headers @{ "Content-Type" = "application/json" } `
535
+ -Body '{
536
+ "model": "Qwen3-0.6B-GGUF",
537
+ "prompt": "What is the population of Paris?",
538
+ "stream": true,
539
+ "max_tokens": 64
540
+ }'
541
+ ```
542
+
543
+ === "Bash"
544
+
545
+ ```bash
546
+ curl http://localhost:13305/v1/completions \
547
+ -H "Content-Type: application/json" \
548
+ -d '{
549
+ "model": "Qwen3-0.6B-GGUF",
550
+ "prompt": "What is the population of Paris?",
551
+ "stream": true,
552
+ "max_tokens": 64
553
+ }'
554
+ ```
555
+
556
+ **Response:** `200 OK`
557
+
558
+ ```text
559
+ data: {"id": "chatcmpl-NnGAa2fcrBou11qX4PgyeQCgXNJB9W6j", "object": "text_completion", "created": 1793827667, "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/50968a4468ef4233ed78cd7c3de230dd1d61a56b/Qwen3-0.6B-Q4_0.gguf", "system_fingerprint": "b12109-13ec8f614", "choices": [{"index": 0, "text": " And", "logprobs": null, "finish_reason": null}]}
560
+ data: {"id": "chatcmpl-NnGAa2fcrBou11qX4PgyeQCgXNJB9W6j", "object": "text_completion", "created": 1793827667, "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/50968a4468ef4233ed78cd7c3de230dd1d61a56b/Qwen3-0.6B-Q4_0.gguf", "system_fingerprint": "b12109-13ec8f614", "choices": [{"index": 0, "text": " how", "logprobs": null, "finish_reason": null}]}
561
+ data: {"id": "chatcmpl-NnGAa2fcrBou11qX4PgyeQCgXNJB9W6j", "object": "text_completion", "created": 1793827667, "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/50968a4468ef4233ed78cd7c3de230dd1d61a56b/Qwen3-0.6B-Q4_0.gguf", "system_fingerprint": "b12109-13ec8f614", "choices": [{"index": 0, "text": " is", "logprobs": null, "finish_reason": null}]}
562
+ ...
563
+ data: {"id": "chatcmpl-NnGAa2fcrBou11qX4PgyeQCgXNJB9W6j", "object": "text_completion", "created": 1793827670, "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3-0.6B-GGUF/snapshots/50968a4468ef4233ed78cd7c3de230dd1d61a56b/Qwen3-0.6B-Q4_0.gguf", "system_fingerprint": "b12109-13ec8f614", "choices": [{"index": 0, "text": "", "logprobs": null, "finish_reason": "length"}], "usage": {"prompt_tokens": 7, "completion_tokens": 64, "total_tokens": 71, "prompt_tokens_details": {"cached_tokens": 6}}, "timings": {"cache_n": 6, "prompt_n": 1, "prompt_ms": 5.813, "prompt_per_token_ms": 5.813, "prompt_per_second": 172.0282126268708, "predicted_n": 64, "predicted_ms": 3611.246, "predicted_per_token_ms": 57.32136507936508, "predicted_per_second": 17.445502189548982}}
564
+ data: [DONE]
565
+ ```
272
566
 
567
+ ### Response format
273
568
 
569
+ #### JSON
570
+
571
+ - `id` - Identifier of the completion.
572
+ - `object` - Always `"text_completion"`.
573
+ - `created` - Unix timestamp of when the completion was created.
574
+ - `model` - The model that generated the response.
575
+ - `system_fingerprint` - Build identifier of the backend, when it reports one.
576
+ - `choices` - The generated text, one entry per choice.
577
+ - `index` - Position of the choice in `choices`.
578
+ - `text` - The generated text.
579
+ - `logprobs` - Log probabilities of the output tokens when the request sets `logprobs`, else `null`.
580
+ - `finish_reason` - Why generation stopped, such as `stop` or `length`.
581
+ - `usage` - Token counts for the request.
582
+ - `prompt_tokens` - Tokens in the prompt.
583
+ - `completion_tokens` - Tokens generated.
584
+ - `total_tokens` - Sum of `prompt_tokens` and `completion_tokens`.
585
+ - `prompt_tokens_details` - Breakdown of the prompt tokens, such as `cached_tokens`, when the backend reports it.
586
+ - `timings` - Prompt processing and generation timings, passed through from backends that report them, such as llama.cpp.
587
+ - `x_lemonade_route` - The routing decision, when `model` names a `collection.router` model. See [Routing](./router.md#routing-collectionrouter) for the decision object.
588
+
589
+ #### Event stream
590
+
591
+ One event per `data:` line. The stream ends with `data: [DONE]`.
592
+
593
+ ##### Chunk
594
+
595
+ A chunk of the completion text.
596
+
597
+ - `id` - Identifier of the completion, the same on every chunk.
598
+ - `object` - Always `"text_completion"`.
599
+ - `created` - Unix timestamp of when the completion was created.
600
+ - `model` - The backend's name for the model, such as the GGUF file path for llama.cpp models.
601
+ - `system_fingerprint` - Build identifier of the backend, when it reports one.
602
+ - `choices` - The next part of each choice's text.
603
+ - `index` - Position of the choice in `choices`.
604
+ - `text` - The text generated since the previous chunk.
605
+ - `logprobs` - Log probabilities of the output tokens when the request sets `logprobs`, else `null`.
606
+ - `finish_reason` - Why generation stopped, such as `stop` or `length`, on the choice's last chunk; `null` before it.
607
+ - `usage` - Final chunk only: token counts for the request.
608
+ - `prompt_tokens` - Tokens in the prompt.
609
+ - `completion_tokens` - Tokens generated.
610
+ - `total_tokens` - Sum of `prompt_tokens` and `completion_tokens`.
611
+ - `prompt_tokens_details` - Breakdown of the prompt tokens, such as `cached_tokens`, when the backend reports it.
612
+ - `timings` - Final chunk only: prompt processing and generation timings, passed through from backends that report them, such as llama.cpp.
613
+ - `x_lemonade_route` - The routing decision, when `model` names a `collection.router` model. See [Routing](./router.md#routing-collectionrouter) for the decision object.
614
+
615
+ ##### Error event
616
+
617
+ Sent in place of the rest of the stream when the backend fails after the stream has started. No `[DONE]` follows it.
618
+
619
+ - `error` - The backend's error, as an object or, when the backend sent one, a string.
620
+ - `message` - What failed.
621
+ - `type` - The error's type, such as `backend_error`.
622
+ - `status_code` - The status the backend answered with, or `502` when it closed the stream without a response.
623
+
624
+ ### Errors
625
+
626
+ | Status | Code | When |
627
+ |--------|------|------|
628
+ | `400` | `invalid_json` | The body is not a JSON object |
629
+ | `400` | `missing_argument` | A required argument is missing |
630
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
631
+ | `400` | | `model` is not an LLM, which is checked after it loads |
632
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
633
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
634
+ | `404` | `model_not_found` | The requested model is not in the registry |
635
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
636
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
637
+ | `500` | | The backend reports an error; the backend's own status replaces this one when it sends one |
638
+ | `500` | | The backend's response lacks a field the route returns, such as `choices` or `labels` |
639
+ | `500` | | The request fails unexpectedly |
640
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
641
+ <!-- END GENERATED: openai.completions -->
642
+
643
+ <!-- BEGIN GENERATED: openai.embeddings -->
274
644
  ## `POST /v1/embeddings`
275
645
  <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
276
646
 
277
- Embeddings API. You provide input text and receive vector representations (embeddings) that can be used for semantic search, clustering, and similarity comparisons. This API will also load the model if it is not already loaded.
647
+ Returns vector representations (embeddings) of the input text for semantic search, clustering and similarity comparisons, downloading and loading the model if it is not loaded. Embedding models use the `llamacpp` or `flm` recipe.
278
648
 
279
- > **Note:** This endpoint is only available for models using the `llamacpp` or `flm` recipes. ONNX models (OGA recipes) do not support embeddings.
649
+ Requires `LEMONADE_API_KEY` when it is set.
280
650
 
281
651
  ### Parameters
282
652
 
283
653
  | Parameter | Required | Description | Status |
284
654
  |-----------|----------|-------------|--------|
285
- | `input` | Yes | The input text or array of texts to embed. Can be a string or an array of strings. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
286
- | `model` | Yes | The model to use for generating embeddings. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
287
- | `encoding_format` | No | The format to return embeddings in. Supported values: `"float"` (default), `"base64"`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
655
+ | `input` | Yes | The text to embed, as a string or an array of strings. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
656
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
657
+ | `encoding_format` | No | `float` or `base64`. Defaults to `float`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
658
+ | `ctx_size` | No | Lemonade extension: context size to load the model with, when this request loads it. Ignored when the model is already loaded. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
288
659
 
289
- ### Example request
660
+ ### Example
290
661
 
291
662
  === "PowerShell"
292
663
 
@@ -296,25 +667,25 @@ Embeddings API. You provide input text and receive vector representations (embed
296
667
  -Method POST `
297
668
  -Headers @{ "Content-Type" = "application/json" } `
298
669
  -Body '{
299
- "model": "nomic-embed-text-v1-GGUF",
300
- "input": ["Hello, world!", "How are you?"],
301
- "encoding_format": "float"
302
- }'
670
+ "model": "nomic-embed-text-v1-GGUF",
671
+ "input": ["Hello, world!", "How are you?"],
672
+ "encoding_format": "float"
673
+ }'
303
674
  ```
304
675
 
305
676
  === "Bash"
306
677
 
307
678
  ```bash
308
- curl -X POST http://localhost:13305/v1/embeddings \
679
+ curl http://localhost:13305/v1/embeddings \
309
680
  -H "Content-Type: application/json" \
310
681
  -d '{
311
- "model": "nomic-embed-text-v1-GGUF",
312
- "input": ["Hello, world!", "How are you?"],
313
- "encoding_format": "float"
314
- }'
682
+ "model": "nomic-embed-text-v1-GGUF",
683
+ "input": ["Hello, world!", "How are you?"],
684
+ "encoding_format": "float"
685
+ }'
315
686
  ```
316
687
 
317
- ### Response format
688
+ **Response:** `200 OK`
318
689
 
319
690
  ```json
320
691
  {
@@ -323,184 +694,355 @@ Embeddings API. You provide input text and receive vector representations (embed
323
694
  {
324
695
  "object": "embedding",
325
696
  "index": 0,
326
- "embedding": [0.0234, -0.0567, 0.0891, ...]
697
+ "embedding": [0.03605353832244873, 0.0006784393335692585, 0.004262526985257864, -0.02508561499416828, -0.028299998492002487, 0.030086658895015717, -0.012136111967265606, 0.01537393033504486, ... 760 more]
327
698
  },
328
- {
329
- "object": "embedding",
330
- "index": 1,
331
- "embedding": [0.0456, -0.0678, 0.1234, ...]
332
- }
699
+ ... 1 more
333
700
  ],
334
701
  "model": "nomic-embed-text-v1-GGUF",
335
- "usage": {
336
- "prompt_tokens": 12,
337
- "total_tokens": 12
338
- }
702
+ "usage": {"prompt_tokens": 12, "total_tokens": 12}
339
703
  }
340
704
  ```
341
705
 
342
- **Field Descriptions:**
343
-
344
- - `object` - Type of response object, always `"list"`
345
- - `data` - Array of embedding objects
346
- - `object` - Type of embedding object, always `"embedding"`
347
- - `index` - Index position of the input text in the request
348
- - `embedding` - Vector representation as an array of floats
349
- - `model` - Model identifier used to generate the embeddings
350
- - `usage` - Token usage statistics
351
- - `prompt_tokens` - Number of tokens in the input
352
- - `total_tokens` - Total tokens processed
706
+ ### Response format
353
707
 
708
+ - `object` - Always `"list"`.
709
+ - `data` - One embedding per input.
710
+ - `object` - Always `"embedding"`.
711
+ - `index` - Position of the input text in the request.
712
+ - `embedding` - The vector, as an array of floats, or a base64 string when `encoding_format` is `base64`.
713
+ - `model` - The model that produced the embeddings.
714
+ - `usage` - Token usage.
715
+ - `prompt_tokens` - Tokens in the input.
716
+ - `total_tokens` - Tokens processed in total.
717
+
718
+ ### Errors
719
+
720
+ | Status | Code | When |
721
+ |--------|------|------|
722
+ | `400` | `invalid_json` | The body is not a JSON object |
723
+ | `400` | `missing_argument` | A required argument is missing |
724
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
725
+ | `400` | | `model`'s backend cannot embed, such as a text-to-speech model |
726
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
727
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
728
+ | `404` | `model_not_found` | The requested model is not in the registry |
729
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
730
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
731
+ | `500` | | The backend reports an error; the backend's own status replaces this one when it sends one |
732
+ | `500` | | The request fails unexpectedly |
733
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
734
+ | `501` | | `model` is a llama.cpp model that is not an embedding model |
735
+ <!-- END GENERATED: openai.embeddings -->
736
+
737
+ <!-- BEGIN GENERATED: openai.responses -->
354
738
  ## `POST /v1/responses`
355
- <sub>![Status](https://img.shields.io/badge/status-partially_available-green)</sub>
739
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
356
740
 
357
- Responses API. You provide an input and receive a response. This API will also load the model if it is not already loaded.
741
+ Generates a model response to an input, in OpenAI's Responses format, downloading and loading the model if it is not loaded.
742
+
743
+ Requires `LEMONADE_API_KEY` when it is set.
358
744
 
359
745
  ### Parameters
360
746
 
361
747
  | Parameter | Required | Description | Status |
362
748
  |-----------|----------|-------------|--------|
363
- | `input` | Yes | A list of dictionaries or a string input for the model to respond to. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
364
- | `model` | Yes | The model to use for the response. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
365
- | `max_output_tokens` | No | The maximum number of output tokens to generate. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
366
- | `temperature` | No | What sampling temperature to use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
367
- | `repeat_penalty` | No | Number between 1.0 and 2.0. 1.0 means no penalty. Higher values discourage repetition. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
368
- | `top_k` | No | Integer that controls the number of top tokens to consider during sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
369
- | `top_p` | No | Float between 0.0 and 1.0 that controls the cumulative probability of top tokens to consider during nucleus sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
370
- | `stream` | No | If true, tokens will be sent as they are generated. If false, the response will be sent as a single message once complete. Defaults to false. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
371
-
749
+ | `input` | Yes | The input, as a string or an array of input items. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
750
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
751
+ | `max_output_tokens` | No | Upper bound on generated tokens. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
752
+ | `temperature` | No | Sampling temperature. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
753
+ | `repeat_penalty` | No | From `1.0` to `2.0`: `1.0` applies no penalty, and higher values discourage repetition. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
754
+ | `top_k` | No | Number of top tokens to consider during sampling. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
755
+ | `top_p` | No | Cumulative probability of the top tokens to consider during nucleus sampling, from `0.0` to `1.0`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
756
+ | `stream` | No | `true` sends the response as server-sent events as it progresses; `false` sends one response once complete. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
757
+ | `route_trace` | No | Lemonade extension for `collection.router` models: `true` adds the per-condition `trace` to `x_lemonade_route`. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
758
+ | `ctx_size` | No | Lemonade extension: context size to load the model with, when this request loads it. Ignored when the model is already loaded. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
372
759
 
373
- ### Streaming Events
760
+ Naming a `collection.router` model routes the request to one of its candidates; see [Router API](./router.md).
374
761
 
375
- The Responses API uses semantic events for streaming. Each event is typed with a predefined schema, so you can listen for events you care about. Our initial implementation only offers support to:
762
+ ### Examples
376
763
 
377
- - `response.created`
378
- - `response.output_text.delta`
379
- - `response.completed`
380
-
381
- For a full list of event types, see the [API reference for streaming](https://platform.openai.com/docs/api-reference/responses-streaming).
382
-
383
- ### Example request
764
+ #### Non-streaming
384
765
 
385
766
  === "PowerShell"
386
767
 
387
768
  ```powershell
388
- Invoke-WebRequest -Uri "http://localhost:13305/v1/responses" `
769
+ Invoke-WebRequest `
770
+ -Uri "http://localhost:13305/v1/responses" `
389
771
  -Method POST `
390
772
  -Headers @{ "Content-Type" = "application/json" } `
391
773
  -Body '{
392
- "model": "Llama-3.2-1B-Instruct-Hybrid",
393
- "input": "What is the population of Paris?",
394
- "stream": false
395
- }'
774
+ "model": "Llama-3.2-1B-Instruct-GGUF",
775
+ "input": "What is the population of Paris?",
776
+ "stream": false
777
+ }'
396
778
  ```
397
779
 
398
780
  === "Bash"
399
781
 
400
782
  ```bash
401
- curl -X POST http://localhost:13305/v1/responses \
783
+ curl http://localhost:13305/v1/responses \
402
784
  -H "Content-Type: application/json" \
403
785
  -d '{
404
- "model": "Llama-3.2-1B-Instruct-Hybrid",
405
- "input": "What is the population of Paris?",
406
- "stream": false
407
- }'
786
+ "model": "Llama-3.2-1B-Instruct-GGUF",
787
+ "input": "What is the population of Paris?",
788
+ "stream": false
789
+ }'
408
790
  ```
409
791
 
792
+ **Response:** `200 OK`
410
793
 
411
- ### Response format
412
-
413
- === "Non-streaming responses"
414
-
415
- ```json
794
+ ```json
795
+ {
796
+ "id": "resp_Cxow6uUoxWRLPnhhXU7QEvXjMrKaBGYi",
797
+ "object": "response",
798
+ "created_at": 1793827678,
799
+ "completed_at": 1793827678,
800
+ "model": "Llama-3.2-1B-Instruct-GGUF",
801
+ "status": "completed",
802
+ "output": [
416
803
  {
417
- "id": "0",
418
- "created_at": 1746225832.0,
419
- "model": "Llama-3.2-1B-Instruct-Hybrid",
420
- "object": "response",
421
- "output": [{
422
- "id": "0",
423
- "content": [{
804
+ "content": [
805
+ {
424
806
  "annotations": [],
425
- "text": "Paris has a population of approximately 2.2 million people in the city proper."
426
- }]
427
- }]
807
+ "logprobs": [],
808
+ "text": "As of the latest available data from the French National Institute for Statistics and Economic Studies (Institut National de la Statistique et des Études Économiques, INSEE), the population of Paris is approximately 2.1 million people within the city limits and over 12 million people in the metropolitan area, which includes the surrounding suburbs and regions.",
809
+ "type": "output_text"
810
+ }
811
+ ],
812
+ "id": "msg_zkaZoMfka6ivKETrMiMf7f8NzXFj7FCQ",
813
+ "role": "assistant",
814
+ "status": "completed",
815
+ "type": "message"
428
816
  }
817
+ ],
818
+ "usage": {
819
+ "input_tokens": 42,
820
+ "output_tokens": 73,
821
+ "total_tokens": 115,
822
+ "input_tokens_details": {"cached_tokens": 0}
823
+ }
824
+ }
825
+ ```
826
+
827
+ #### Streaming
828
+
829
+ === "PowerShell"
830
+
831
+ ```powershell
832
+ Invoke-WebRequest `
833
+ -Uri "http://localhost:13305/v1/responses" `
834
+ -Method POST `
835
+ -Headers @{ "Content-Type" = "application/json" } `
836
+ -Body '{
837
+ "model": "Llama-3.2-1B-Instruct-GGUF",
838
+ "input": "What is the population of Paris?",
839
+ "stream": true
840
+ }'
429
841
  ```
430
842
 
431
- === "Streaming Responses"
432
- For streaming responses, the API returns a series of events. Refer to [OpenAI streaming guide](https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses) for details.
843
+ === "Bash"
844
+
845
+ ```bash
846
+ curl http://localhost:13305/v1/responses \
847
+ -H "Content-Type: application/json" \
848
+ -d '{
849
+ "model": "Llama-3.2-1B-Instruct-GGUF",
850
+ "input": "What is the population of Paris?",
851
+ "stream": true
852
+ }'
853
+ ```
433
854
 
855
+ **Response:** `200 OK`
856
+
857
+ ```text
858
+ event: response.created
859
+ data: {"type": "response.created", "response": {"id": "resp_L7uzVj7alzqP0uYc4HYBpvdp60tAsd1Z", "object": "response", "status": "in_progress"}}
860
+ event: response.in_progress
861
+ data: {"type": "response.in_progress", "response": {"id": "resp_L7uzVj7alzqP0uYc4HYBpvdp60tAsd1Z", "object": "response", "status": "in_progress"}}
862
+ event: response.output_item.added
863
+ data: {"type": "response.output_item.added", "item": {"content": [], "id": "msg_depHpGR0ZqLNdgRXk7wGBHbhXKjkiC0M", "role": "assistant", "status": "in_progress", "type": "message"}, "output_index": 0}
864
+ ...
865
+ event: response.completed
866
+ data: {"type": "response.completed", "response": {"id": "resp_L7uzVj7alzqP0uYc4HYBpvdp60tAsd1Z", "object": "response", "created_at": 1793827678, "status": "completed", "model": "~/.cache/huggingface/hub/models--unsloth--Llama-3.2-1B-Instruct-GGUF/snapshots/b69aef112e9f895e6f98d7ae0949f72ff09aa401/Llama-3.2-1B-Instruct-UD-Q4_K_XL.gguf", "output": [{"type": "message", "status": "completed", "id": "msg_depHpGR0ZqLNdgRXk7wGBHbhXKjkiC0M", "content": [{"type": "output_text", "annotations": [], "logprobs": [], "text": "As of my knowledge cutoff in December 2023, the population of Paris, France is approximately 2.1 million people within the city limits and 12.2 million people in the larger metropolitan area, according to official statistics from the French National Institute for Statistics and Economic Studies (INSEE)."}], "role": "assistant"}], "usage": {"input_tokens": 42, "output_tokens": 61, "total_tokens": 103, "input_tokens_details": {"cached_tokens": 41}}}, "timings": {"cache_n": 41, "prompt_n": 1, "prompt_ms": 16.128, "prompt_per_token_ms": 16.128, "prompt_per_second": 62.00396825396825, "predicted_n": 61, "predicted_ms": 375.356, "predicted_per_token_ms": 6.255933333333333, "predicted_per_second": 159.84825072730953}}
867
+ data: [DONE]
868
+ ```
434
869
 
870
+ ### Response format
435
871
 
872
+ #### JSON
873
+
874
+ - `id` - Identifier of the response.
875
+ - `object` - Always `"response"`.
876
+ - `created_at` - Unix timestamp of when the response was created.
877
+ - `completed_at` - Unix timestamp of when the response completed, when the backend reports it.
878
+ - `model` - The model that generated the response.
879
+ - `status` - State of the response, such as `completed`.
880
+ - `output` - Output items; a message item carries its text in `content[].text`.
881
+ - `usage` - Token counts for the request.
882
+ - `input_tokens` - Tokens in the input.
883
+ - `output_tokens` - Tokens generated.
884
+ - `total_tokens` - Sum of `input_tokens` and `output_tokens`.
885
+ - `input_tokens_details` - Breakdown of the input tokens, such as `cached_tokens`, when the backend reports it.
886
+ - `x_lemonade_route` - The routing decision, when `model` names a `collection.router` model. See [Routing](./router.md#routing-collectionrouter) for the decision object.
887
+
888
+ #### Event stream
889
+
890
+ One event per `data:` line. The stream ends with `data: [DONE]`.
891
+
892
+ ##### Response event
893
+
894
+ An event of the response, passed through from the backend.
895
+
896
+ - `type` - Event type, passed through from the backend, such as `response.created`, `response.output_text.delta` or `response.completed`; see OpenAI's [streaming reference](https://platform.openai.com/docs/api-reference/responses-streaming).
897
+ - `response` - Lifecycle events, such as `response.created`, `response.in_progress` and `response.completed`, only: the response so far.
898
+ - `item` - Output item events, such as `response.output_item.added`, only: the output item.
899
+ - `output_index` - Zero-based index of the output item in the response's `output` array, on output-item and delta events.
900
+ - `item_id` - Delta events only: the `id` of the output item the text belongs to.
901
+ - `delta` - Delta events, such as `response.output_text.delta` and `response.reasoning_text.delta`, only: the next text.
902
+ - `timings` - `response.completed` only: prompt processing and generation timings, passed through from backends that report them, such as llama.cpp.
903
+ - `x_lemonade_route` - The routing decision, when `model` names a `collection.router` model. See [Routing](./router.md#routing-collectionrouter) for the decision object.
904
+
905
+ ##### Error event
906
+
907
+ Sent in place of the rest of the stream when the backend fails after the stream has started. No `[DONE]` follows it.
908
+
909
+ - `error` - The backend's error, as an object or, when the backend sent one, a string.
910
+ - `message` - What failed.
911
+ - `type` - The error's type, such as `backend_error`.
912
+ - `status_code` - The status the backend answered with, or `502` when it closed the stream without a response.
913
+
914
+ ### Errors
915
+
916
+ | Status | Code | When |
917
+ |--------|------|------|
918
+ | `400` | `invalid_json` | The body is not a JSON object |
919
+ | `400` | `missing_argument` | A required argument is missing |
920
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
921
+ | `400` | | The request does not stream and `model` is a FastFlowLM or cloud model, which has no Responses API |
922
+ | `400` | `model_not_applicable` | The request does not stream and `model` is not an LLM |
923
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
924
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
925
+ | `404` | `model_not_found` | The requested model is not in the registry |
926
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
927
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
928
+ | `500` | | The backend reports an error; the backend's own status replaces this one when it sends one |
929
+ | `500` | | The request fails unexpectedly |
930
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
931
+ <!-- END GENERATED: openai.responses -->
932
+
933
+ <!-- BEGIN GENERATED: openai.audio_transcriptions -->
436
934
  ## `POST /v1/audio/transcriptions`
437
- <sub>![Status](https://img.shields.io/badge/status-partial-yellow)</sub>
935
+ <sub>![Status](https://img.shields.io/badge/status-partially_available-yellow)</sub>
438
936
 
439
- Audio Transcription API. You provide an audio file and receive a text transcription. This API will also load the model if it is not already loaded.
937
+ Transcribes an audio file to text, loading the model on first use. Whisper (whisper.cpp), Moonshine and FastFlowLM models serve this endpoint.
440
938
 
441
- > **Note:** This endpoint uses [whisper.cpp](https://github.com/ggerganov/whisper.cpp) as the backend. Whisper models are automatically downloaded when first used.
442
- >
443
- > **Limitations:** Only `wav` audio input is currently supported. The `response_format` field supports `json`, `verbose_json`, `text`, `srt`, and `vtt`. On the FastFlowLM (FLM) backend, `srt` and `vtt` are rejected with a `400` because FLM returns no segment timestamps, and `verbose_json` returns the compact shape without a `segments` field.
939
+ Requires `LEMONADE_API_KEY` when it is set.
444
940
 
445
941
  ### Parameters
446
942
 
447
943
  | Parameter | Required | Description | Status |
448
944
  |-----------|----------|-------------|--------|
449
- | `file` | Yes | The audio file to transcribe. Supported formats: wav. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
450
- | `model` | Yes | The Whisper model to use for transcription (e.g., `Whisper-Tiny`, `Whisper-Base`, `Whisper-Small`). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
451
- | `language` | No | The language of the audio (ISO 639-1 code, e.g., `en`, `es`, `fr`). Defaults to `auto`, which tells whisper.cpp to detect the source language instead of using whisper-server's English default. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
452
- | `response_format` | No | The response format. Supported values: `json`, `verbose_json`, `text`, `srt`, `vtt`. `srt` and `vtt` require a backend that reports segment timestamps (whisper.cpp). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
945
+ | `file` | Yes | The audio file to transcribe, as WAV. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
946
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
947
+ | `language` | No | The language of the audio (ISO 639-1 code, e.g., `en`, `es`, `fr`). Defaults to `auto`, which detects it, for whisper.cpp models, and to the backend's own for the others. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
948
+ | `response_format` | No | `json`, `verbose_json`, `text`, `srt` or `vtt`. Defaults to `json`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
949
+ | `prompt` | No | Text to guide the transcription's style or continue a previous segment. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
950
+ | `temperature` | No | Sampling temperature. Defaults to `0` for whisper.cpp models, and to the backend's own for FastFlowLM models. Moonshine models ignore it. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
453
951
 
454
- ### Example request
952
+ ### Examples
455
953
 
456
- === "Windows"
954
+ #### Transcript
457
955
 
458
- ```bash
459
- curl -X POST http://localhost:13305/v1/audio/transcriptions ^
460
- -F "file=@C:\path\to\audio.wav" ^
461
- -F "model=Whisper-Tiny"
956
+ === "PowerShell"
957
+
958
+ ```powershell
959
+ curl.exe http://localhost:13305/v1/audio/transcriptions `
960
+ -F "model=Whisper-Tiny" `
961
+ -F file=@speech.wav
462
962
  ```
463
963
 
464
- === "Linux"
964
+ === "Bash"
465
965
 
466
966
  ```bash
467
- curl -X POST http://localhost:13305/v1/audio/transcriptions \
468
- -F "file=@/path/to/audio.wav" \
469
- -F "model=Whisper-Tiny"
967
+ curl http://localhost:13305/v1/audio/transcriptions \
968
+ -F "model=Whisper-Tiny" \
969
+ -F file=@speech.wav
470
970
  ```
471
971
 
472
- ### Response format
972
+ **Response:** `200 OK`
473
973
 
474
974
  ```json
475
- {
476
- "text": "Hello, this is a sample transcription of the audio file."
477
- }
975
+ {"text": " Just seeing if this is working.\n"}
478
976
  ```
479
977
 
480
- **Field Descriptions:**
978
+ #### Plain text
979
+
980
+ === "PowerShell"
981
+
982
+ ```powershell
983
+ curl.exe http://localhost:13305/v1/audio/transcriptions `
984
+ -F "model=Whisper-Tiny" `
985
+ -F "response_format=text" `
986
+ -F file=@speech.wav
987
+ ```
988
+
989
+ === "Bash"
990
+
991
+ ```bash
992
+ curl http://localhost:13305/v1/audio/transcriptions \
993
+ -F "model=Whisper-Tiny" \
994
+ -F "response_format=text" \
995
+ -F file=@speech.wav
996
+ ```
997
+
998
+ **Response:** `200 OK`
481
999
 
482
- - `text` - The transcribed text from the audio file
1000
+ ```text
1001
+ Just seeing if this is working.
1002
+ ```
483
1003
 
1004
+ ### Response format
484
1005
 
1006
+ - `text` - The transcribed text.
1007
+ - `segments` - `verbose_json` only: timestamped segments.
1008
+
1009
+ ### Errors
1010
+
1011
+ | Status | Code | When |
1012
+ |--------|------|------|
1013
+ | `400` | | The request is not `multipart/form-data` |
1014
+ | `400` | `missing_argument` | A required argument is missing |
1015
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
1016
+ | `400` | | `model` cannot transcribe, which is found after it loads; the error's `type` is `unsupported_operation` |
1017
+ | `400` | | `response_format` is `srt` or `vtt` and `model` is a FastFlowLM model |
1018
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
1019
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
1020
+ | `404` | `model_not_found` | The requested model is not in the registry |
1021
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
1022
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
1023
+ | `500` | | The backend reports an error; the backend's own status replaces this one when it sends one |
1024
+ | `500` | | The request fails unexpectedly |
1025
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
1026
+ <!-- END GENERATED: openai.audio_transcriptions -->
485
1027
 
486
1028
  ## `WS /realtime`
487
1029
  <sub>![Status](https://img.shields.io/badge/status-partial-yellow)</sub>
488
1030
 
489
1031
  Realtime Audio Transcription API via WebSocket (OpenAI SDK compatible). Stream audio from a microphone and receive transcriptions in real-time with Voice Activity Detection (VAD).
490
1032
 
491
- > **Limitations:** Only 16kHz mono PCM16 audio format is supported. Uses the same Whisper models as the HTTP transcription endpoint.
1033
+ Models with the `realtime-transcription` [label](#model-labels) serve it: the Whisper models and the Moonshine streaming models. Audio must be 16 kHz mono PCM16; downsample higher rates on the client.
492
1034
 
493
1035
  ### Connection
494
1036
 
495
- WebSocket upgrades are accepted **directly on the main HTTP port** (default 13305) — the same port as all REST endpoints. Connect with the model name:
1037
+ Connect on the main HTTP port (default 13305), the same port as the REST endpoints, naming the model:
496
1038
 
497
1039
  ```
498
1040
  ws://localhost:13305/v1/realtime?model=Whisper-Tiny
499
1041
  ```
500
1042
 
501
- Accepted paths are `/realtime` and `/logs/stream`, bare or under any of the standard prefixes (`/v1`, `/v0`, `/api/v1`, `/api/v0`), so OpenAI Realtime SDK clients (`/v1/realtime`) connect as-is. When `LEMONADE_API_KEY` is set, pass `?api_key=KEY` as a query parameter.
1043
+ OpenAI Realtime SDK clients connect to `/v1/realtime` as-is. When `LEMONADE_API_KEY` is set, pass `?api_key=KEY` as a query parameter.
502
1044
 
503
- A dedicated WebSocket port also remains for backward compatibility; it is OS-assigned and reported by the [`/v1/health`](./lemonade.md#get-v1health) endpoint (`websocket_port` field). New clients should prefer the main port:
1045
+ A dedicated WebSocket port also remains for backward compatibility. It is OS-assigned and reported as `websocket_port` by [`/v1/health`](./lemonade.md#get-v1health), and it speaks the identical protocol, so moving a client to the main port changes only its URL. The main port also leaves one port to expose remotely and works through reverse proxies that pass `Upgrade: websocket`:
504
1046
 
505
1047
  ```
506
1048
  ws://localhost:<websocket_port>/realtime?model=Whisper-Tiny
@@ -551,12 +1093,7 @@ Upon connection, the server sends a `session.created` message with a session ID.
551
1093
  }
552
1094
  ```
553
1095
 
554
- Audio should be:
555
- - 16kHz sample rate
556
- - Mono (single channel)
557
- - 16-bit signed integer (PCM16)
558
- - Base64 encoded
559
- - Sent in chunks (~85ms recommended)
1096
+ Send base64-encoded PCM16 audio in chunks of about 85 to 256 ms.
560
1097
 
561
1098
  ### Example: Transcription Result
562
1099
 
@@ -591,7 +1128,7 @@ VAD settings can be configured via `session.update`:
591
1128
  | `silence_duration_ms` | 800 | Silence duration to trigger speech end |
592
1129
  | `prefix_padding_ms` | 250 | Minimum speech duration before triggering |
593
1130
 
594
- Set `turn_detection` to `null` to disable server-side VAD and use explicit commits instead:
1131
+ Set `turn_detection` to `null` to disable server-side VAD and use explicit commits instead. The server then buffers audio and emits no VAD or interim transcription events until `input_audio_buffer.commit`:
595
1132
 
596
1133
  ```json
597
1134
  {
@@ -614,101 +1151,134 @@ A complete, runnable example:
614
1151
  python examples/realtime_transcription.py --model Whisper-Tiny
615
1152
  ```
616
1153
 
617
- ### Integration Notes
618
-
619
- - **Audio Format**: Server expects 16kHz mono PCM16. Higher sample rates must be downsampled client-side.
620
- - **Chunk Size**: Send audio in ~85-256ms chunks for optimal latency/efficiency.
621
- - **VAD Behavior**: Server automatically detects speech boundaries and triggers transcription on speech end.
622
- - **Manual Commit**: Set `turn_detection` to `null`, then use `input_audio_buffer.commit` to force transcription. In this mode the server buffers audio but does not emit VAD or interim transcription events.
623
- - **Clear Buffer**: Use `input_audio_buffer.clear` to discard audio without transcribing.
624
- - **Chunking**: We are still tuning the chunking to balance latency vs. accuracy.
625
- - **Migrating off the dedicated port**: Clients that discover `websocket_port` via `/v1/health` and connect there can switch to `ws://HOST:13305/v1/realtime?model=...` — the protocol (events, audio format, auth) is identical on both ports, so it is a URL change only. This also simplifies remote setups (one port to expose) and works through reverse proxies that pass `Upgrade: websocket`. Keep the `websocket_port` fallback only if you must support servers older than this release.
626
-
627
-
1154
+ <!-- BEGIN GENERATED: openai.images_generations -->
628
1155
  ## `POST /v1/images/generations`
629
- <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1156
+ <sub>![Status](https://img.shields.io/badge/status-partially_available-yellow)</sub>
630
1157
 
631
- Image Generation API. You provide a text prompt and receive a generated image.
1158
+ Generates an image from a text prompt. CPU inference takes minutes per image; a GPU backend is much faster.
632
1159
 
633
- > **Performance:** CPU inference takes ~4-5 minutes per image. GPU (ROCm) is significantly faster.
1160
+ Requires `LEMONADE_API_KEY` when it is set.
634
1161
 
635
1162
  ### Parameters
636
1163
 
637
1164
  | Parameter | Required | Description | Status |
638
1165
  |-----------|----------|-------------|--------|
639
1166
  | `prompt` | Yes | The text description of the image to generate. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
640
- | `model` | Yes | The diffusion model to use (e.g., `SD-Turbo`, `Krea-2-Turbo`). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
641
- | `size` | No | The size of the generated image. Format: `WIDTHxHEIGHT` (e.g., `512x512`, `256x256`). Default: `512x512`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
642
- | `n` | No | Number of images to generate. Currently only `1` is supported. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
643
- | `response_format` | No | Format of the response. Only `b64_json` (base64-encoded image) is supported. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
644
- | `steps` | No | Number of inference steps. SD-Turbo works well with 4 steps. Default varies by model. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
645
- | `cfg_scale` | No | Classifier-free guidance scale. SD-Turbo uses low values (~1.0). Default varies by model. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
646
- | `seed` | No | Random seed for reproducibility. If not specified, a random seed is used. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1167
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1168
+ | `size` | No | Image size as `WIDTHxHEIGHT`, such as `256x256`. Defaults to the model's own, else `512x512`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1169
+ | `n` | No | Number of images to generate. Defaults to `1`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1170
+ | `response_format` | No | Only `b64_json`, a base64-encoded image, is supported. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
1171
+ | `steps` | No | Number of inference steps; lower is faster, higher can improve quality. Defaults to the model's own setting, else the backend's. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1172
+ | `cfg_scale` | No | Classifier-free guidance scale. Defaults to the model's own, else `7.0`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1173
+ | `seed` | No | Random seed for reproducible output; a negative value picks a random seed. Defaults to the backend's fixed seed. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1174
+
1175
+ ### Example
1176
+
1177
+ === "PowerShell"
647
1178
 
648
- ### Example request
1179
+ ```powershell
1180
+ Invoke-WebRequest `
1181
+ -Uri "http://localhost:13305/v1/images/generations" `
1182
+ -Method POST `
1183
+ -Headers @{ "Content-Type" = "application/json" } `
1184
+ -Body '{
1185
+ "model": "SD-Turbo",
1186
+ "prompt": "A serene mountain landscape at sunset",
1187
+ "size": "512x512",
1188
+ "steps": 4,
1189
+ "response_format": "b64_json"
1190
+ }'
1191
+ ```
649
1192
 
650
1193
  === "Bash"
651
1194
 
652
1195
  ```bash
653
- curl -X POST http://localhost:13305/v1/images/generations \
1196
+ curl http://localhost:13305/v1/images/generations \
654
1197
  -H "Content-Type: application/json" \
655
1198
  -d '{
656
- "model": "SD-Turbo",
657
- "prompt": "A serene mountain landscape at sunset",
658
- "size": "512x512",
659
- "steps": 4,
660
- "response_format": "b64_json"
661
- }'
1199
+ "model": "SD-Turbo",
1200
+ "prompt": "A serene mountain landscape at sunset",
1201
+ "size": "512x512",
1202
+ "steps": 4,
1203
+ "response_format": "b64_json"
1204
+ }'
662
1205
  ```
663
1206
 
1207
+ **Response:** `200 OK`
1208
+
1209
+ ```json
1210
+ {
1211
+ "created": 1791943145,
1212
+ "data": [
1213
+ {
1214
+ "b64_json": "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAAEgnRFWHRwYXJhbWV0ZXJzAEEgc2VyZW5l... (886,104 characters)"
1215
+ }
1216
+ ],
1217
+ "output_format": "png"
1218
+ }
1219
+ ```
1220
+
1221
+ ### Response format
1222
+
1223
+ - `created` - Unix timestamp of when the image was generated.
1224
+ - `data` - The generated images, one entry per image.
1225
+ - `b64_json` - Base64-encoded PNG.
1226
+ - `generation_info` - Optional generation parameters and model metadata reported by the image backend.
1227
+ - `output_format` - File format of the images, such as `png`, when the backend reports it.
1228
+
1229
+ ### Errors
1230
+
1231
+ | Status | Code | When |
1232
+ |--------|------|------|
1233
+ | `400` | `invalid_json` | The body is not a JSON object |
1234
+ | `400` | `missing_argument` | A required argument is missing |
1235
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
1236
+ | `400` | | The backend's reply does not parse, or a request field not listed above has the wrong JSON type |
1237
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
1238
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
1239
+ | `404` | `model_not_found` | The requested model is not in the registry |
1240
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
1241
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
1242
+ | `500` | | The backend reports an error, for example when `model` cannot serve this route, an error whose `type` is `unsupported_operation` |
1243
+ | `500` | | The request fails unexpectedly |
1244
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
1245
+ <!-- END GENERATED: openai.images_generations -->
1246
+
1247
+ <!-- BEGIN GENERATED: openai.images_edits -->
664
1248
  ## `POST /v1/images/edits`
665
- <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1249
+ <sub>![Status](https://img.shields.io/badge/status-partially_available-yellow)</sub>
666
1250
 
667
- Image Editing API. You provide a source image and a text prompt describing the desired change, and receive an edited image.
1251
+ Edits one or more reference images as a prompt describes. The request is `multipart/form-data`. CPU inference takes minutes per image; a GPU backend is much faster.
668
1252
 
669
- > **Note:** This endpoint accepts `multipart/form-data` requests (not JSON). Use editing-capable models such as `Flux-2-Klein-4B` or `SD-Turbo`.
670
- >
671
- > **Performance:** CPU inference takes several minutes per image. GPU (ROCm) is significantly faster.
1253
+ Requires `LEMONADE_API_KEY` when it is set.
672
1254
 
673
1255
  ### Parameters
674
1256
 
675
1257
  | Parameter | Required | Description | Status |
676
1258
  |-----------|----------|-------------|--------|
677
- | `model` | Yes | The diffusion model to use (e.g., `Flux-2-Klein-4B`, `SD-Turbo`). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
678
- | `image` / `image[]` | Yes | The source image file to edit (PNG). Sent as a file in multipart/form-data. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1259
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1260
+ | `image` or `image[]` | Yes | PNG or JPEG image upload. For multiple references, repeat `image[]` in order. Files are forwarded without re-encoding. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
679
1261
  | `prompt` | Yes | A text description of the desired edit. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
680
- | `mask` | No | An optional mask image (PNG). White areas indicate regions to edit; black areas are preserved. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
681
- | `size` | No | The size of the output image. Format: `WIDTHxHEIGHT` (e.g., `512x512`). Default: `512x512`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
682
- | `n` | No | Number of images to generate. Allowed range: `1`–`10`. Default: `1`. Values outside this range are rejected with `400 Bad Request`. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
683
- | `response_format` | No | Format of the response. Only `b64_json` (base64-encoded image) is supported. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
684
- | `steps` | No | Number of inference steps. Default varies by model. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
685
- | `cfg_scale` | No | Classifier-free guidance scale. Default varies by model. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
686
- | `seed` | No | Random seed for reproducibility. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
687
- | `user` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
688
- | `background` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
689
- | `quality` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
690
- | `input_fidelity` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
691
- | `output_compression` | No | OpenAI API compatibility field. Accepted; silently ignored by the backend. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
692
-
693
- ### Example request
694
-
695
- === "Bash"
696
-
697
- ```bash
698
- curl -X POST http://localhost:13305/v1/images/edits \
699
- -F "model=Flux-2-Klein-4B" \
700
- -F "prompt=Add a red barn and mountains in the background, photorealistic" \
701
- -F "size=512x512" \
702
- -F "n=1" \
703
- -F "response_format=b64_json" \
704
- -F "image=@/path/to/source_image.png"
705
- ```
1262
+ | `mask` | No | A PNG mask: white areas are edited and black areas are kept. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1263
+ | `size` | No | Output size as `WIDTHxHEIGHT`, such as `512x512`. Defaults to the first reference image's size, scaled down to fit the model's `soft_pixel_cap` option (2048x2048 pixels, about 4 MP, unless the model sets one). An explicit size skips that cap; backend limits still apply. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1264
+ | `n` | No | Number of images to generate, from `1` to `10`. Defaults to `1`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1265
+ | `response_format` | No | Only `b64_json`, a base64-encoded image, is supported. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
1266
+ | `steps` | No | Number of inference steps. Defaults to the model's own. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1267
+ | `cfg_scale` | No | Classifier-free guidance scale. Defaults to the model's own. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1268
+ | `seed` | No | Random seed for reproducible output. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1269
+ | `user` | No | Accepted for OpenAI compatibility and ignored. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
1270
+ | `background` | No | Accepted for OpenAI compatibility and ignored. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
1271
+ | `quality` | No | Accepted for OpenAI compatibility and ignored. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
1272
+ | `input_fidelity` | No | Accepted for OpenAI compatibility and ignored. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
1273
+ | `output_compression` | No | Accepted for OpenAI compatibility and ignored. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
1274
+
1275
+ ### Edit with the OpenAI Python Client
706
1276
 
707
1277
  === "Python (OpenAI client)"
708
1278
 
709
1279
  ```python
710
1280
  from openai import OpenAI
711
- client = OpenAI(base_url="http://localhost:13305/api/v1", api_key="not-needed")
1281
+ client = OpenAI(base_url="http://localhost:13305/v1", api_key="not-needed")
712
1282
  with open("source_image.png", "rb") as image_file:
713
1283
  response = client.images.edit(
714
1284
  model="Flux-2-Klein-4B",
@@ -721,124 +1291,207 @@ Image Editing API. You provide a source image and a text prompt describing the d
721
1291
  open("edited_image.png", "wb").write(image_data)
722
1292
  ```
723
1293
 
724
- ---
725
-
726
- ## `POST /v1/images/variations`
727
- <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
728
-
729
- Image Variations API. You provide a source image and receive a variation of it.
730
-
731
- > **Note:** This endpoint accepts `multipart/form-data` requests (not JSON). Unlike `/images/edits`, a `prompt` parameter is not supported and will be ignored — the model generates a variation based solely on the input image.
732
- >
733
- > **Performance:** CPU inference takes several minutes per image. GPU (ROCm) is significantly faster.
1294
+ ### Example
734
1295
 
735
- ### Parameters
736
-
737
- | Parameter | Required | Description | Status |
738
- |-----------|----------|-------------|--------|
739
- | `model` | Yes | The Stable Diffusion model to use (e.g., `Flux-2-Klein-4B`, `SD-Turbo`). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
740
- | `image` | Yes | The source image file (PNG). Sent as a file in multipart/form-data. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
741
- | `size` | No | The size of the output image. Format: `WIDTHxHEIGHT` (e.g., `512x512`). Default: `512x512`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
742
- | `n` | No | Number of variations to generate. Integer between 1 and 10 inclusive. Default: `1`. Values outside this range result in a 400 Bad Request error. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
743
- | `response_format` | No | Format of the response. Only `b64_json` (base64-encoded image) is supported. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
744
- | `user` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
1296
+ === "PowerShell"
745
1297
 
746
- ### Example request
1298
+ ```powershell
1299
+ curl.exe http://localhost:13305/v1/images/edits `
1300
+ -F "model=Flux-2-Klein-4B" `
1301
+ -F "prompt=Add a red barn and mountains in the background, photorealistic" `
1302
+ -F "size=512x512" `
1303
+ -F "n=1" `
1304
+ -F "response_format=b64_json" `
1305
+ -F image=@image.png
1306
+ ```
747
1307
 
748
1308
  === "Bash"
749
1309
 
750
1310
  ```bash
751
- curl -X POST http://localhost:13305/v1/images/variations \
1311
+ curl http://localhost:13305/v1/images/edits \
752
1312
  -F "model=Flux-2-Klein-4B" \
1313
+ -F "prompt=Add a red barn and mountains in the background, photorealistic" \
753
1314
  -F "size=512x512" \
754
1315
  -F "n=1" \
755
1316
  -F "response_format=b64_json" \
756
- -F "image=@/path/to/source_image.png"
1317
+ -F image=@image.png
757
1318
  ```
758
1319
 
759
- === "Python (OpenAI client)"
1320
+ **Response:** `200 OK`
760
1321
 
761
- ```python
762
- from openai import OpenAI
763
- client = OpenAI(base_url="http://localhost:13305/api/v1", api_key="not-needed")
764
- with open("source_image.png", "rb") as image_file:
765
- response = client.images.create_variation(
766
- model="Flux-2-Klein-4B",
767
- image=image_file,
768
- size="512x512",
769
- n=1,
770
- )
771
- import base64
772
- image_data = base64.b64decode(response.data[0].b64_json)
1322
+ ```json
1323
+ {
1324
+ "created": 1791943169,
1325
+ "data": [
1326
+ {
1327
+ "b64_json": "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAAFR3RFWHRwYXJhbWV0ZXJzAEFkZCBhIHJl... (689,496 characters)"
1328
+ }
1329
+ ],
1330
+ "output_format": "png"
1331
+ }
1332
+ ```
1333
+
1334
+ ### Response format
1335
+
1336
+ - `created` - Unix timestamp of when the image was generated.
1337
+ - `data` - The generated images, one entry per image.
1338
+ - `b64_json` - Base64-encoded PNG.
1339
+ - `generation_info` - Optional generation parameters and model metadata reported by the image backend.
1340
+ - `output_format` - File format of the images, such as `png`, when the backend reports it.
1341
+
1342
+ ### Errors
1343
+
1344
+ | Status | Code | When |
1345
+ |--------|------|------|
1346
+ | `400` | | The request is not `multipart/form-data` |
1347
+ | `400` | `missing_argument` | A required argument is missing |
1348
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
1349
+ | `400` | | The form has files under both `image` and `image[]` |
1350
+ | `400` | | The backend's reply does not parse, or a request field not listed above has the wrong JSON type |
1351
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
1352
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
1353
+ | `404` | `model_not_found` | The requested model is not in the registry |
1354
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
1355
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
1356
+ | `500` | | The backend reports an error, for example when `model` cannot serve this route, an error whose `type` is `unsupported_operation` |
1357
+ | `500` | | The request fails unexpectedly |
1358
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
1359
+ <!-- END GENERATED: openai.images_edits -->
1360
+
1361
+ <!-- BEGIN GENERATED: openai.images_variations -->
1362
+ ## `POST /v1/images/variations`
1363
+ <sub>![Status](https://img.shields.io/badge/status-partially_available-yellow)</sub>
1364
+
1365
+ Generates a variation of a source image, sent as `multipart/form-data`. CPU inference takes minutes per image; a GPU backend is much faster.
1366
+
1367
+ Requires `LEMONADE_API_KEY` when it is set.
1368
+
1369
+ ### Parameters
1370
+
1371
+ | Parameter | Required | Description | Status |
1372
+ |-----------|----------|-------------|--------|
1373
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1374
+ | `image` or `image[]` | Yes | The source image (PNG). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1375
+ | `prompt` | No | Ignored; the variation depends only on the image. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
1376
+ | `size` | No | Output size as `WIDTHxHEIGHT`. Defaults to the model's own, else `512x512`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1377
+ | `n` | No | Number of images to generate, from `1` to `10`. Defaults to `1`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1378
+ | `response_format` | No | Only `b64_json`, a base64-encoded image, is supported. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
1379
+ | `user` | No | Accepted for OpenAI compatibility and ignored. | <sub>![Status](https://img.shields.io/badge/not_available-red)</sub> |
1380
+
1381
+ ### Create Variations with the OpenAI Python Client
1382
+
1383
+ === "Python (OpenAI client)"
1384
+
1385
+ ```python
1386
+ from openai import OpenAI
1387
+ client = OpenAI(base_url="http://localhost:13305/v1", api_key="not-needed")
1388
+ with open("source_image.png", "rb") as image_file:
1389
+ response = client.images.create_variation(
1390
+ model="Flux-2-Klein-4B",
1391
+ image=image_file,
1392
+ size="512x512",
1393
+ n=1,
1394
+ )
1395
+ import base64
1396
+ image_data = base64.b64decode(response.data[0].b64_json)
773
1397
  open("variation.png", "wb").write(image_data)
774
1398
  ```
775
1399
 
776
- ---
1400
+ ### Example
1401
+
1402
+ === "PowerShell"
777
1403
 
1404
+ ```powershell
1405
+ curl.exe http://localhost:13305/v1/images/variations `
1406
+ -F "model=Flux-2-Klein-4B" `
1407
+ -F "size=512x512" `
1408
+ -F "n=1" `
1409
+ -F "response_format=b64_json" `
1410
+ -F image=@image.png
1411
+ ```
1412
+
1413
+ === "Bash"
1414
+
1415
+ ```bash
1416
+ curl http://localhost:13305/v1/images/variations \
1417
+ -F "model=Flux-2-Klein-4B" \
1418
+ -F "size=512x512" \
1419
+ -F "n=1" \
1420
+ -F "response_format=b64_json" \
1421
+ -F image=@image.png
1422
+ ```
1423
+
1424
+ **Response:** `200 OK`
1425
+
1426
+ ```json
1427
+ {
1428
+ "created": 1791943188,
1429
+ "data": [
1430
+ {
1431
+ "b64_json": "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAAE3XRFWHRwYXJhbWV0ZXJzAHZhcmlhdGlv... (204,076 characters)"
1432
+ }
1433
+ ],
1434
+ "output_format": "png"
1435
+ }
1436
+ ```
1437
+
1438
+ ### Response format
1439
+
1440
+ - `created` - Unix timestamp of when the image was generated.
1441
+ - `data` - The generated images, one entry per image.
1442
+ - `b64_json` - Base64-encoded PNG.
1443
+ - `generation_info` - Optional generation parameters and model metadata reported by the image backend.
1444
+ - `output_format` - File format of the images, such as `png`, when the backend reports it.
1445
+
1446
+ ### Errors
1447
+
1448
+ | Status | Code | When |
1449
+ |--------|------|------|
1450
+ | `400` | | The request is not `multipart/form-data` |
1451
+ | `400` | `missing_argument` | A required argument is missing |
1452
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
1453
+ | `400` | | The backend's reply does not parse, or a request field not listed above has the wrong JSON type |
1454
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
1455
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
1456
+ | `404` | `model_not_found` | The requested model is not in the registry |
1457
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
1458
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
1459
+ | `500` | | The backend reports an error, for example when `model` cannot serve this route, an error whose `type` is `unsupported_operation` |
1460
+ | `500` | | The request fails unexpectedly |
1461
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
1462
+ <!-- END GENERATED: openai.images_variations -->
1463
+
1464
+ <!-- BEGIN GENERATED: openai.images_upscale -->
778
1465
  ## `POST /v1/images/upscale`
779
1466
  <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
780
1467
 
781
- Image Upscaling API. You provide a base64-encoded image and a Real-ESRGAN model name, and receive an upscaled image. The upscale factor depends on the selected model, and it is usually reflected in its name.
1468
+ Upscales an image with an upscaling model such as `RealESRGAN-x4plus`. The model sets the default upscale factor; TheNoise models also accept an optional `upscale_factor`.
782
1469
 
783
- > **Note:** Unlike `/images/edits` and `/images/variations`, this endpoint accepts a JSON body (not multipart/form-data). The image must be provided as a base64-encoded string.
1470
+ Requires `LEMONADE_API_KEY` when it is set.
784
1471
 
785
1472
  ### Parameters
786
1473
 
787
1474
  | Parameter | Required | Description | Status |
788
1475
  |-----------|----------|-------------|--------|
789
1476
  | `image` | Yes | Base64-encoded PNG image to upscale. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
790
- | `model` | Yes | The ESRGAN model to use (e.g., `RealESRGAN-x4plus`, `Remacri-4x-TheNoise`). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1477
+ | `model` | Yes | The upscaling model to use, such as `RealESRGAN-x4plus` or `Remacri-4x-TheNoise`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1478
+ | `upscale_factor` | No | Optional scale factor for TheNoise (up to the upscaler's native scale). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
791
1479
 
792
- ### Example request
1480
+ The model is downloaded on first use.
793
1481
 
794
- A typical workflow is to generate an image first, then upscale it:
1482
+ ### Upscale a Generated Image
795
1483
 
796
- === "Bash"
1484
+ To upscale an image from [`POST /v1/images/generations`](#post-v1imagesgenerations), pass its `b64_json` as `image`.
797
1485
 
798
- ```bash
799
- # Step 1: Generate an image and save the base64 response
800
- RESPONSE=$(curl -s -X POST http://localhost:13305/v1/images/generations \
801
- -H "Content-Type: application/json" \
802
- -d '{
803
- "model": "SD-Turbo",
804
- "prompt": "A serene mountain landscape at sunset",
805
- "size": "512x512",
806
- "steps": 4,
807
- "response_format": "b64_json"
808
- }')
809
-
810
- # Step 2: Build the upscale JSON payload and pipe it to curl via stdin
811
- # (base64 images are too large for command-line interpolation)
812
- echo "$RESPONSE" | python3 -c "
813
- import sys, json
814
- b64 = json.load(sys.stdin)['data'][0]['b64_json']
815
- print(json.dumps({'image': b64, 'model': 'RealESRGAN-x4plus'}))
816
- " | curl -X POST http://localhost:13305/v1/images/upscale \
817
- -H "Content-Type: application/json" \
818
- -d @-
819
- ```
1486
+ ### Example
820
1487
 
821
1488
  === "PowerShell"
822
1489
 
823
1490
  ```powershell
824
- # Step 1: Generate an image
825
- $genResponse = Invoke-WebRequest `
826
- -Uri "http://localhost:13305/v1/images/generations" `
827
- -Method POST `
828
- -Headers @{ "Content-Type" = "application/json" } `
829
- -Body '{
830
- "model": "SD-Turbo",
831
- "prompt": "A serene mountain landscape at sunset",
832
- "size": "512x512",
833
- "steps": 4,
834
- "response_format": "b64_json"
835
- }'
836
-
837
- # Step 2: Extract the base64 image
838
- $imageB64 = ($genResponse.Content | ConvertFrom-Json).data[0].b64_json
839
-
840
- # Step 3: Upscale the image with Real-ESRGAN
841
- $body = @{ image = $imageB64; model = "RealESRGAN-x4plus" } | ConvertTo-Json
1491
+ $image = [Convert]::ToBase64String([IO.File]::ReadAllBytes("image.png"))
1492
+ $body = @"
1493
+ {"image": "$image", "model": "RealESRGAN-x4plus"}
1494
+ "@
842
1495
  Invoke-WebRequest `
843
1496
  -Uri "http://localhost:13305/v1/images/upscale" `
844
1497
  -Method POST `
@@ -846,230 +1499,284 @@ A typical workflow is to generate an image first, then upscale it:
846
1499
  -Body $body
847
1500
  ```
848
1501
 
849
- === "Python (requests)"
850
-
851
- ```python
852
- import requests
853
- import base64
1502
+ === "Bash"
854
1503
 
855
- BASE_URL = "http://localhost:13305/api/v1"
856
-
857
- # Step 1: Generate an image
858
- gen_response = requests.post(f"{BASE_URL}/images/generations", json={
859
- "model": "SD-Turbo",
860
- "prompt": "A serene mountain landscape at sunset",
861
- "size": "512x512",
862
- "steps": 4,
863
- "response_format": "b64_json",
864
- })
865
- image_b64 = gen_response.json()["data"][0]["b64_json"]
866
-
867
- # Step 2: Upscale the image with Real-ESRGAN (512x512 -> 2048x2048)
868
- upscale_response = requests.post(f"{BASE_URL}/images/upscale", json={
869
- "image": image_b64,
870
- "model": "RealESRGAN-x4plus",
871
- })
872
-
873
- # Step 3: Save the upscaled image to a file
874
- upscaled_b64 = upscale_response.json()["data"][0]["b64_json"]
875
- with open("upscaled.png", "wb") as f:
876
- f.write(base64.b64decode(upscaled_b64))
1504
+ ```bash
1505
+ curl http://localhost:13305/v1/images/upscale \
1506
+ -H "Content-Type: application/json" \
1507
+ -d "{\"image\": \"$(base64 -w0 image.png)\", \"model\": \"RealESRGAN-x4plus\"}"
877
1508
  ```
878
1509
 
879
- ### Response format
1510
+ **Response:** `200 OK`
880
1511
 
881
1512
  ```json
882
1513
  {
883
- "created": 1742927481,
1514
+ "created": 1791943191,
884
1515
  "data": [
885
1516
  {
886
- "b64_json": "<base64-encoded upscaled PNG>"
1517
+ "b64_json": "iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAIAAADwf7zUAAADZHRFWHRwYXJhbWV0ZXJzAApTdGVwczog... (1,818,596 characters)"
887
1518
  }
888
1519
  ]
889
1520
  }
890
1521
  ```
891
1522
 
892
- **Field Descriptions:**
893
-
894
- - `created` - Unix timestamp of when the upscaled image was generated
895
- - `data` - Array containing the upscaled image
896
- - `b64_json` - Base64-encoded PNG of the upscaled image
897
-
898
- ### Error responses
899
-
900
- | Status Code | Condition | Example |
901
- |-------------|-----------|---------|
902
- | 400 | Missing `image` field | `{"error": {"message": "Missing 'image' field (base64 encoded)", "type": "invalid_request_error"}}` |
903
- | 400 | Missing `model` field | `{"error": {"message": "Missing 'model' field", "type": "invalid_request_error"}}` |
904
- | 404 | Unknown model name | `{"error": {"message": "Upscale model not found: bad-model", "type": "invalid_request_error"}}` |
905
- | 500 | Upscale failed | `{"error": {"message": "ESRGAN upscale failed", "type": "server_error"}}` |
906
-
907
- ---
1523
+ ### Response format
908
1524
 
1525
+ - `created` - Unix timestamp of when the upscaled image was generated.
1526
+ - `data` - The upscaled image.
1527
+ - `b64_json` - Base64-encoded PNG of the upscaled image.
1528
+ - `output_format` - File format of the images, such as `png`, when the backend reports it.
1529
+
1530
+ ### Errors
1531
+
1532
+ | Status | Code | When |
1533
+ |--------|------|------|
1534
+ | `400` | `invalid_json` | The body is not a JSON object |
1535
+ | `400` | `missing_argument` | A required argument is missing |
1536
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
1537
+ | `400` | | `model` has no `upscaling` label |
1538
+ | `400` | | `model`'s recipe cannot upscale |
1539
+ | `400` | | The backend's reply does not parse, or a request field not listed above has the wrong JSON type |
1540
+ | `404` | | `model` names no model in the registry, or downloading it fails |
1541
+ | `500` | | The backend produces no upscaled image |
1542
+ | `500` | | The request fails unexpectedly |
1543
+ <!-- END GENERATED: openai.images_upscale -->
1544
+
1545
+ <!-- BEGIN GENERATED: openai.audio_speech -->
909
1546
  ## `POST /v1/audio/speech`
910
- <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1547
+ <sub>![Status](https://img.shields.io/badge/status-partially_available-yellow)</sub>
911
1548
 
912
- Speech Generation API. You provide a text input and receive an audio file. Which engine serves the request depends on the model.
1549
+ Generates speech audio from text. The model selects the engine: `kokoro-v1` speaks with fixed voices ([Kokoros](https://github.com/lucasjinreal/Kokoros)), while `OpenMOSS-TTS` and `MOSS-TTS-Local` can also clone a voice or design one from a description. `MOSS-VoiceGen` is kept for compatibility.
913
1550
 
914
- > **Note:** Supported models are `kokoro-v1` (fixed voices, [Kokoros](https://github.com/lucasjinreal/Kokoros) backend) and the OpenMOSS family — `OpenMOSS-TTS` and `MOSS-TTS-Local` support cloning and integrated voice design. `MOSS-VoiceGen` remains available as a legacy compatibility model while existing GUI/settings paths migrate to the integrated design flow.
915
- >
916
- > **Limitations:** Which `response_format` values are accepted depends on the model's backend: `kokoro-v1` encodes `mp3`, `wav`, `opus`, and `pcm`; OpenMOSS v0.3 encodes buffered `wav` or `pcm`. Native streaming is narrower for both backends and uses `pcm` only, so an explicit non-PCM `response_format` on a streaming request is rejected rather than mislabeled or silently transcoded. OpenMOSS raw PCM is returned as `audio/pcm` with `X-MOSS-Sample-Rate` and `X-MOSS-Channels`, because its native format is model-dependent (24 kHz mono for OpenMOSS-TTS and 48 kHz stereo for MOSS-TTS-Local).
1551
+ Requires `LEMONADE_API_KEY` when it is set.
917
1552
 
918
1553
  ### Parameters
919
1554
 
920
1555
  | Parameter | Required | Description | Status |
921
1556
  |-----------|----------|-------------|--------|
922
1557
  | `input` | Yes | The text to speak. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
923
- | `model` | Yes | The model to use (e.g., `kokoro-v1`). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
924
- | `speed` | No | Speaking speed. Default: `1.0`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
925
- | `voice` | No | The voice to use. All OpenAI-defined voices can be used (`alloy`, `ash`, ...), as well as those defined by the kokoro model (`af_sky`, `am_echo`, ...). Default: `shimmer` | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
926
- | `voice` (OpenMOSS) | No | For OpenMOSS models the field is a free-text voice/style instruction instead of a fixed voice name (e.g. `a calm, deep male narrator voice`). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1558
+ | `model` | Yes | The model to run; loaded on first use. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1559
+ | `speed` | No | Speaking speed. Defaults to `1.0`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1560
+ | `voice` | No | For `kokoro-v1`, a voice name: OpenAI's voices (`alloy`, `ash`, ...) or Kokoro's own (`af_sky`, `am_echo`, ...), defaulting to `shimmer`. For OpenMOSS models, a free-text voice or style instruction, e.g. `a calm, deep male narrator voice`. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
927
1561
  | `reference_wav_b64` | No | Lemonade extension (OpenMOSS voice cloning): base64-encoded WAV sample of the voice to clone. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
928
- | `voice_design_description` | No | Lemonade extension (OpenMOSS voice design): a description of the voice to invent, e.g. `a warm low female voice with a British accent`. Lemonade renders a short sample in that voice and uses it as the reference, so the effect is the same as supplying `reference_wav_b64` yourself. Ignored when `reference_wav_b64` is also present. Design is opt-in through this field only — `voice` never triggers it. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
929
- | `response_format` | No | Container for the returned audio. Which values are accepted depends on the model's backend (see Limitations above). Default: `mp3` when buffered and `pcm` when streaming, falling back to the backend's first supported format when it cannot encode that default. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
930
- | `stream_format` | No | If set, the response is streamed. Only `audio` is supported. This selects the transport only — the container still comes from `response_format`, and an explicit one is honored on both transports. Default: not set| <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
1562
+ | `voice_design_description` | No | Lemonade extension (OpenMOSS voice design): a description of the voice to invent, e.g. `a warm low female voice with a British accent`. Lemonade renders a short sample in that voice and uses it as the reference, so the effect is the same as supplying `reference_wav_b64` yourself. Ignored when `reference_wav_b64` is also present. Only this field triggers design; `voice` never does. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1563
+ | `response_format` | No | Container for the returned audio. Defaults to `mp3` when buffered and `pcm` when streaming, or to the backend's first supported format when it cannot encode those. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
1564
+ | `stream_format` | No | `audio` streams the response. It selects the transport only: the container still comes from `response_format`. | <sub>![Status](https://img.shields.io/badge/partial-yellow)</sub> |
1565
+ | `stream` | No | `true` streams the response, like `stream_format: audio`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1566
+
1567
+ Accepted `response_format` values depend on the backend: `kokoro-v1` encodes `mp3`, `wav`, `opus` and `pcm`, and OpenMOSS encodes `wav` or `pcm`. Streaming produces `pcm` only. OpenMOSS PCM is returned as `audio/pcm` with `X-MOSS-Sample-Rate` and `X-MOSS-Channels` headers, since its format depends on the model (24 kHz mono for `OpenMOSS-TTS`, 48 kHz stereo for `MOSS-TTS-Local`).
1568
+
1569
+ ### Examples
931
1570
 
932
- ### Example request
1571
+ #### Non-streaming
1572
+
1573
+ === "PowerShell"
1574
+
1575
+ ```powershell
1576
+ Invoke-WebRequest `
1577
+ -Uri "http://localhost:13305/v1/audio/speech" `
1578
+ -Method POST `
1579
+ -Headers @{ "Content-Type" = "application/json" } `
1580
+ -Body '{
1581
+ "model": "kokoro-v1",
1582
+ "input": "Lemonade can speak!",
1583
+ "speed": 1.0,
1584
+ "response_format": "mp3"
1585
+ }' `
1586
+ -OutFile output.mp3
1587
+ ```
933
1588
 
934
1589
  === "Bash"
935
1590
 
936
1591
  ```bash
937
- curl -X POST http://localhost:13305/v1/audio/speech \
1592
+ curl http://localhost:13305/v1/audio/speech \
938
1593
  -H "Content-Type: application/json" \
939
1594
  -d '{
940
- "model": "kokoro-v1",
941
- "input": "Lemonade can speak!",
942
- "speed": 1.0,
943
- "steps": 4,
944
- "response_format": "mp3"
945
- }'
1595
+ "model": "kokoro-v1",
1596
+ "input": "Lemonade can speak!",
1597
+ "speed": 1.0,
1598
+ "response_format": "mp3"
1599
+ }' \
1600
+ --output output.mp3
946
1601
  ```
947
1602
 
948
- ### Response format
1603
+ **Response:** `200 OK`, `audio/mpeg`, 40,516 bytes
1604
+
1605
+ #### Streaming
1606
+
1607
+ === "PowerShell"
1608
+
1609
+ ```powershell
1610
+ Invoke-WebRequest `
1611
+ -Uri "http://localhost:13305/v1/audio/speech" `
1612
+ -Method POST `
1613
+ -Headers @{ "Content-Type" = "application/json" } `
1614
+ -Body '{
1615
+ "model": "kokoro-v1",
1616
+ "input": "Lemonade can speak!",
1617
+ "speed": 1.0,
1618
+ "stream_format": "audio"
1619
+ }' `
1620
+ -OutFile output.l16
1621
+ ```
949
1622
 
950
- The generated audio file is returned as-is.
1623
+ === "Bash"
951
1624
 
1625
+ ```bash
1626
+ curl http://localhost:13305/v1/audio/speech \
1627
+ -H "Content-Type: application/json" \
1628
+ -d '{
1629
+ "model": "kokoro-v1",
1630
+ "input": "Lemonade can speak!",
1631
+ "speed": 1.0,
1632
+ "stream_format": "audio"
1633
+ }' \
1634
+ --output output.l16
1635
+ ```
952
1636
 
1637
+ **Response:** `200 OK`, `audio/l16;rate=24000;endianness=little-endian`, 97,200 bytes
1638
+
1639
+ ### Errors
1640
+
1641
+ | Status | Code | When |
1642
+ |--------|------|------|
1643
+ | `400` | `invalid_json` | The body is not a JSON object |
1644
+ | `400` | `missing_argument` | A required argument is missing |
1645
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
1646
+ | `400` | `model_not_applicable` | `model` is not a text-to-speech model, which is checked before it loads |
1647
+ | `400` | | `response_format` is not a format `model`'s backend produces, or, when streaming, one it streams |
1648
+ | `400` | `model_load_refused` | The backend refuses the requested model as it is registered, before it starts anything, such as a decision model whose GGUF type the bundled llama.cpp does not support |
1649
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
1650
+ | `404` | `model_not_found` | The requested model is not in the registry |
1651
+ | `409` | `router_residency_conflict` | Loading the requested model would evict a model that a `collection.router` model needs resident on the same NPU |
1652
+ | `409` | `slots_pinned_error` | Every loaded model of the requested model's type is pinned, so none can be unloaded to make room |
1653
+ | `500` | | The request fails unexpectedly |
1654
+ | `500` | | The backend reports an error; the backend's own status replaces this one when it sends one |
1655
+ | `500` | `model_load_error` | Loading the requested model fails for another reason, such as a failed download or a backend that does not start |
1656
+ | `502` | | The backend produces no output, for example after it crashes or runs out of GPU memory |
1657
+ <!-- END GENERATED: openai.audio_speech -->
1658
+
1659
+ <!-- BEGIN GENERATED: openai.models -->
953
1660
  ## `GET /v1/models`
954
1661
  <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
955
1662
 
956
- Returns a list of models available on the server in an OpenAI-compatible format. Each model object includes extended fields like `checkpoint`, `recipe`, `size`, `downloaded`, `labels`, `context_length`, and, when known, `max_context_window`.
957
-
958
- By default, only models available locally (downloaded) are shown, matching OpenAI API behavior.
1663
+ Lists the models available on the server, in OpenAI's format extended with the fields described below.
959
1664
 
960
- When `lemond` is configured with cloud providers, cloud-routed models appear here alongside local ones with `recipe: "cloud"` and a `cloud_provider` field. They are dot-namespaced by provider (e.g. `fireworks.kimi-k2p5`) and accept the standard chat-completions / completions requests below — see [Cloud Offload](../guide/configuration/cloud.md).
1665
+ Requires `LEMONADE_API_KEY` when it is set.
961
1666
 
962
1667
  ### Parameters
963
1668
 
964
- | Parameter | Required | Description |
965
- |-----------|----------|-------------|
966
- | `show_all` | No | If set to `true`, returns all models from the catalog including those not yet downloaded. Defaults to `false`. |
1669
+ | Parameter | Required | Description | Status |
1670
+ |-----------|----------|-------------|--------|
1671
+ | `show_all` (query) | No | `true` also lists catalog models that are not downloaded. Defaults to `false`, which lists downloaded models only. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1672
+ | `include_paths` (query) | No | `true` adds each present checkpoint's absolute `path` to its `checkpoint_status` entry, which can reveal local usernames and the cache layout. Defaults to `false`. The files a model is made of have their own list, at [`GET /v1/models/{id}/files`](./openai.md#get-v1modelsidfiles). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
967
1673
 
968
- ### Example request
1674
+ A model alias whose target is listed appears too, as a copy of the target with the alias as its `id`.
969
1675
 
970
- ```bash
971
- # Show only downloaded models (OpenAI-compatible)
972
- curl http://localhost:13305/v1/models
1676
+ With cloud providers configured, their models are listed too, with `recipe: "cloud"`, a `cloud_provider` field and a provider-prefixed id such as `fireworks.kimi-k2p5`. Models of `openai`-wire providers accept chat completions and completions requests; `anthropic`-wire providers are served through [`POST /v1/messages`](./anthropic.md#post-v1messages). See [Cloud Offload](../guide/configuration/cloud.md).
973
1677
 
974
- # Show all models including not-yet-downloaded (extended usage)
975
- curl http://localhost:13305/v1/models?show_all=true
976
- ```
1678
+ `HEAD` returns `200 OK` with an empty body.
977
1679
 
978
- ### Response format
1680
+ ### Example
1681
+
1682
+ === "PowerShell"
1683
+
1684
+ ```powershell
1685
+ Invoke-WebRequest -Uri "http://localhost:13305/v1/models"
1686
+ ```
1687
+
1688
+ === "Bash"
1689
+
1690
+ ```bash
1691
+ curl http://localhost:13305/v1/models
1692
+ ```
1693
+
1694
+ **Response:** `200 OK`
979
1695
 
980
1696
  ```json
981
1697
  {
982
1698
  "object": "list",
983
1699
  "data": [
984
1700
  {
985
- "id": "Qwen3-0.6B-GGUF",
986
- "created": 1744173590,
1701
+ "id": "Phishing-Email-Detection-ONNX",
1702
+ "created": 1234567890,
987
1703
  "object": "model",
988
1704
  "owned_by": "lemonade",
989
- "checkpoint": "unsloth/Qwen3-0.6B-GGUF:Q4_0",
990
- "recipe": "llamacpp",
991
- "size": 0.38,
992
- "max_context_window": 40960,
993
- "context_length": 8192,
1705
+ "checkpoint": "lemonade-sdk/phishing-email-detection-distilbert-ONNX",
1706
+ "recipe": "onnxruntime",
1707
+ "size": 0.25,
994
1708
  "downloaded": true,
995
- "suggested": true,
996
1709
  "update_available": false,
997
- "labels": ["reasoning"]
1710
+ "suggested": false,
1711
+ "labels": ["classification"],
1712
+ "components": [],
1713
+ "checkpoints": {"main": "lemonade-sdk/phishing-email-detection-distilbert-ONNX"},
1714
+ "checkpoint_status": [
1715
+ {"type": "main", "present": true, "required": true, "registry": "huggingface"}
1716
+ ],
1717
+ "source": "huggingface",
1718
+ "registry_source": "huggingface",
1719
+ "recipe_options": {}
998
1720
  },
999
- {
1000
- "id": "Gemma-3-4b-it-GGUF",
1001
- "created": 1744173590,
1002
- "object": "model",
1003
- "owned_by": "lemonade",
1004
- "checkpoint": "ggml-org/gemma-3-4b-it-GGUF:Q4_K_M",
1005
- "recipe": "llamacpp",
1006
- "size": 3.61,
1007
- "downloaded": true,
1008
- "suggested": true,
1009
- "labels": ["hot", "vision"]
1010
- },
1011
- {
1012
- "id": "SD-Turbo",
1013
- "created": 1744173590,
1014
- "object": "model",
1015
- "owned_by": "lemonade",
1016
- "checkpoint": "stabilityai/sd-turbo:sd_turbo.safetensors",
1017
- "recipe": "sd-cpp",
1018
- "size": 5.2,
1019
- "downloaded": true,
1020
- "suggested": true,
1021
- "labels": ["image"],
1022
- "image_defaults": {
1023
- "steps": 4,
1024
- "cfg_scale": 1.0,
1025
- "width": 512,
1026
- "height": 512
1027
- }
1028
- }
1721
+ ... 10 more
1029
1722
  ]
1030
1723
  }
1031
1724
  ```
1032
1725
 
1033
- **Field Descriptions:**
1034
-
1035
- - `object` - Type of response object, always `"list"`
1036
- - `data` - Array of model objects with the following fields:
1037
- - `id` - Model identifier (used for loading and inference requests)
1038
- - `created` - Unix timestamp of when the model entry was created
1039
- - `object` - Type of object, always `"model"`
1040
- - `owned_by` - Owner of the model, always `"lemonade"`
1041
- - `checkpoint` - Full checkpoint identifier on Hugging Face
1042
- - `recipe` - Backend/device recipe used to load the model (e.g., `"ryzenai-llm"`, `"llamacpp"`, `"flm"`)
1043
- - `size` - Model size in GB (omitted for models without size information)
1044
- - `max_context_window` - Optional integer indicating the maximum model-supported text context discovered from local static metadata. Currently populated for downloaded GGUF/llama.cpp models and installed FLM text-context models.
1045
- - `context_length` - Number of tokens the model can handle in one request. Uses the loaded value when the model is running and the configured `ctx_size` otherwise (omitted when neither is known).
1046
- - `downloaded` - Boolean indicating if the model is downloaded and available locally
1047
- - `update_available` - Boolean indicating a newer commit exists on HuggingFace for this model. Only set for downloaded HF-backed models. `false` otherwise.
1048
- - `suggested` - Boolean indicating if the model is recommended for general use
1049
- - `labels` - Array of tags describing the model's capabilities and characteristics. See [Model Labels](#model-labels) for the full list.
1050
- - `image_defaults` - (Image models only) Default generation parameters for the model:
1051
- - `steps` - Number of inference steps (e.g., 4 for turbo models, 20 for standard models)
1052
- - `cfg_scale` - Classifier-free guidance scale (e.g., 1.0 for turbo models, 7.5 for standard models)
1053
- - `width` - Default image width in pixels
1054
- - `height` - Default image height in pixels
1055
- - `components` - (Omni collections only, `recipe: "collection.omni"`) Ordered array of the component model names that make up the collection
1056
- - `models` - (Omni collections only) Ordered array embedding each component's full model object (same shape as the entries in this list), parallel to `components`. This makes a collection's `/v1/models/{model_id}` response self-contained — exporting it produces a file that can be imported elsewhere via [`/v1/pull`](./lemonade.md#post-v1pull)
1726
+ ### Response format
1057
1727
 
1728
+ - `object` - Always `"list"`.
1729
+ - `data` - The model objects.
1730
+ - `id` - Model identifier for loading and inference requests.
1731
+ - `created` - Always `1234567890`, for OpenAI compatibility.
1732
+ - `object` - Always `"model"`.
1733
+ - `owned_by` - Always `"lemonade"`.
1734
+ - `checkpoint` - The main checkpoint in the model's registry; see `registry_source`.
1735
+ - `recipe` - Recipe used to load the model, e.g. `llamacpp` or `flm`.
1736
+ - `size` - Size in GB; omitted when unknown.
1737
+ - `max_context_window` - Largest text context the model supports, read from local metadata: downloaded GGUF models and installed FLM text models.
1738
+ - `context_length` - Tokens the model handles in one request: the loaded `ctx_size` while it runs, else the configured `ctx_size`, else `max_context_window`; omitted when none is known.
1739
+ - `max_output_tokens` - Cloud models only: the most tokens one completion can generate, when the provider reports it.
1740
+ - `max_completion_tokens` - Cloud models only: `max_output_tokens` under its OpenAI name.
1741
+ - `downloaded` - Whether the model is downloaded.
1742
+ - `update_available` - Whether the model's registry has a newer commit; `false` except for downloaded registry models.
1743
+ - `suggested` - Whether the model is recommended for general use.
1744
+ - `labels` - The model's capabilities and characteristics; see [Model Labels](./openai.md#model-labels).
1745
+ - `components` - The names of a collection's component models, in order; empty for other models.
1746
+ - `models` - Omni collections only: each component's full model object, in the order of `components`, so an exported collection can be imported elsewhere through [`/v1/pull`](./lemonade.md#post-v1pull).
1747
+ - `checkpoints` - Every checkpoint by role, such as `main`, `mmproj`, `draft`, `text_encoder` or `vae`.
1748
+ - The checkpoint that fills the role named by its key.
1749
+ - `checkpoint_status` - One entry per checkpoint of the model, answering whether its bytes are there.
1750
+ - `type` - Checkpoint slot, e.g. `main`, `mmproj`, `vae`.
1751
+ - `present` - `true` only when the checkpoint's bytes are complete and readable.
1752
+ - `required` - A missing required checkpoint keeps the model undownloaded; an optional one, loaded lazily, does not.
1753
+ - `registry` - Registry hosting the checkpoint; empty for local files and directories.
1754
+ - `path` - Absolute path to the checkpoint file; present only with `include_paths=true` and when `present` is `true`.
1755
+ - `source` - Where the model came from: a registry name, `local_upload`, `local_path` or `extra_models_dir`.
1756
+ - `registry_source` - Remote registry the checkpoint downloads from: `huggingface` or `modelscope`.
1757
+ - `recipe_options` - The model's own options: those its registry row authors, with what was saved in `recipe_options.json` on top.
1758
+ - `cloud_provider` - Cloud models only: the provider that serves the model.
1759
+ - `cost_input_per_million` - Cloud models only: USD per million input tokens, when the provider reports it.
1760
+ - `cost_output_per_million` - Cloud models only: USD per million output tokens, when the provider reports it.
1761
+ - `system_prompt` - Omni collections only: the collection's system prompt override.
1762
+ - `version` - Router collections only: the policy document's version.
1763
+ - `routing` - Router collections only: the routing policy.
1764
+ <!-- END GENERATED: openai.models -->
1058
1765
 
1059
1766
  ### Model Labels
1060
1767
 
1061
1768
  Labels describe what a model can do. A model may carry multiple labels.
1062
1769
 
1063
- **Deployment labels** — determine which backend endpoint the model is routed to.
1770
+ **Deployment labels**: determine which backend endpoint the model is routed to.
1064
1771
  Every model names exactly one deployment mode, and a model is never given two
1065
1772
  labels that name different modes:
1066
1773
 
1067
1774
  | Label | Endpoint | Description |
1068
1775
  |-------|----------|-------------|
1069
- | `chat` | `/chat/completions`, `/completions`, `/responses` | Text-generating LLM. This label is what makes a model an LLM — it is not inferred from `reasoning`/`vision`/`tool-calling`/`chat-transcription`, which are characteristics rather than deployment modes. |
1070
- | `transcription` | `/audio/transcriptions` | Speech-to-text transcription model (e.g. Whisper). An omni LLM that accepts audio in a chat turn is not one of these — it carries `chat` and the `chat-transcription` capability below. |
1776
+ | `chat` | `/chat/completions`, `/completions`, `/responses` | Text-generating LLM. This label is what makes a model an LLM; it is not inferred from `reasoning`/`vision`/`tool-calling`/`chat-transcription`, which are characteristics rather than deployment modes. |
1777
+ | `transcription` | `/audio/transcriptions` | Speech-to-text transcription model (e.g. Whisper). An omni LLM that accepts audio in a chat turn is not one of these: it carries `chat` and the `chat-transcription` capability below. |
1071
1778
  | `embeddings` | `/embeddings` | Produces text embedding vectors. Also accepted as `embedding`. |
1072
- | `reranking` | `/rerank` | Scores and reranks a list of passages given a query. Also reachable at the aliases `/reranking` and `/reranker`. |
1779
+ | `reranking` | `/rerank` | Scores and reranks a list of passages given a query. |
1073
1780
  | `image` | `/images/generations`, `/images/edits`, `/images/variations` | Text-to-image generation model. |
1074
1781
  | `tts` | `/audio/speech` | Text-to-speech synthesis model. |
1075
1782
  | `audio-generation` | `/audio/generations` | Text-to-audio generation model (e.g. music, sound effects). |
@@ -1077,16 +1784,17 @@ labels that name different modes:
1077
1784
  | `3d` | `/3d/generations` | Text- or image-to-3D mesh generation model. |
1078
1785
 
1079
1786
  When a model declares no deployment label at all, it inherits its recipe's
1080
- default — `chat` for `llamacpp`, `flm`, `ryzenai-llm`, `vllm` and `cloud`,
1787
+ default: `chat` for `llamacpp`, `flm`, `ryzenai-llm`, `vllm` and `cloud`,
1081
1788
  `transcription` for `whispercpp`, `image` for `sd-cpp`, `tts` for `kokoro`, and
1082
1789
  so on.
1083
1790
 
1084
1791
  Two label sets describe a model that cannot exist, and are refused rather than
1085
1792
  repaired:
1086
1793
 
1087
- - **A mode the recipe's backend does not serve.** `/classify` is served only by
1088
- `onnxruntime`, so `labels: ["classification"]` on a `llamacpp` model is an
1089
- error — register it as the chat model it is.
1794
+ - **A mode the recipe's backend does not serve.** `/classify` is served by
1795
+ `onnxruntime`, and by `llamacpp` only for a model that also has the
1796
+ `systemone` label. `labels: ["classification"]` on a `llamacpp` model without
1797
+ `systemone` is an error; register it as the chat model it is.
1090
1798
  - **Two different modes.** `labels: ["chat", "embeddings"]` on a `llamacpp` model
1091
1799
  is an error even though llama.cpp serves both: the subprocess is launched for
1092
1800
  one mode, so the second would name an endpoint it was never configured to
@@ -1094,37 +1802,48 @@ repaired:
1094
1802
  booleans count as mode claims here, exactly as the labels do.
1095
1803
 
1096
1804
  [`POST /v1/pull`](./lemonade.md#post-v1pull) answers `400` and registers nothing.
1097
- An entry already stored in `user_models.json` — written before these rules — is
1098
- skipped at startup with an error naming it, and the file is left untouched so it
1099
- can be corrected by hand.
1805
+ A stored `user_models.json` entry that breaks these rules is skipped at startup with
1806
+ an error naming it; the file is left for you to correct.
1100
1807
 
1101
- **Input-modality labels** — the model accepts additional input types in `/chat/completions`:
1808
+ **Input-modality labels**: the model accepts additional input types in `/chat/completions`:
1102
1809
 
1103
1810
  | Label | Description |
1104
1811
  |-------|-------------|
1105
1812
  | `vision` | Accepts image attachments in chat messages. |
1106
- | `chat-transcription` | Accepts audio attachments in chat messages and transcribes them as part of its answer (e.g. Qwen2.5-Omni). Like `vision`, this is something a chat model can do, not a deployment mode of its own — a model carrying it also carries `chat`. It is distinct from `transcription`, which deploys a dedicated ASR model on `/audio/transcriptions`. |
1813
+ | `chat-transcription` | Accepts audio attachments in chat messages and transcribes them as part of its answer (e.g. Qwen2.5-Omni). Like `vision`, this is something a chat model can do, not a deployment mode of its own; a model carrying it also carries `chat`. It is distinct from `transcription`, which deploys a dedicated ASR model on `/audio/transcriptions`. |
1107
1814
 
1108
- **Streaming labels** — capability flags for real-time features:
1815
+ **Classification capability labels:** carried alongside `classification`:
1816
+
1817
+ | Label | Description |
1818
+ |-------|-------------|
1819
+ | `zero-shot` | Takes its labels from each `/classify` request instead of a fixed label set (e.g. `LFM2.5-Encoder-350M-Prompt-Router-ONNX`). A router policy must use such a model in a `zero_shot` classifier, and a fixed-label model in a `classifier`; registration refuses either mismatch. |
1820
+
1821
+ **Streaming labels:** capability flags for real-time features:
1109
1822
 
1110
1823
  | Label | Description |
1111
1824
  |-------|-------------|
1112
1825
  | `realtime-transcription` | Supports the WebSocket `/realtime` endpoint for live microphone transcription. |
1113
1826
 
1114
- **Runtime labels** — affect backend launch defaults:
1827
+ **Runtime labels:** affect backend launch defaults:
1115
1828
 
1116
1829
  | Label | Description |
1117
1830
  |-------|-------------|
1118
1831
  | `mtp` | Enables llama.cpp MTP draft decoding defaults (`--spec-type draft-mtp --spec-draft-n-max 3 --spec-draft-p-min 0.75`); users can override these with `llamacpp_args`. |
1119
1832
 
1120
- **Image capability labels** — carried alongside `image`; they refine what the model is offered for without changing its deployment mode:
1833
+ **Execution labels:** control how a model is invoked:
1834
+
1835
+ | Label | Description |
1836
+ |-------|-------------|
1837
+ | `on-demand` | Runs through a task-specific endpoint without a resident model server, so `/v1/load` is not supported. Built-in upscalers use `/v1/images/upscale`. |
1838
+
1839
+ **Image capability labels:** carried alongside `image`; they refine what the model is offered for without changing its deployment mode:
1121
1840
 
1122
1841
  | Label | Description |
1123
1842
  |-------|-------------|
1124
1843
  | `edit` | Tuned for editing an input image (`/images/edits`). Also selects the model for the `edit_image` role in an omni collection. |
1125
1844
  | `upscaling` | Image upscaling model (e.g. Real-ESRGAN, `/images/upscale`). Used as a component in image pipelines rather than offered on its own. |
1126
1845
 
1127
- **Characteristic labels** — informational, do not affect routing:
1846
+ **Characteristic labels:** informational, do not affect routing:
1128
1847
 
1129
1848
  | Label | Description |
1130
1849
  |-------|-------------|
@@ -1132,61 +1851,442 @@ can be corrected by hand.
1132
1851
  | `reasoning` | Uses extended chain-of-thought reasoning (e.g. DeepSeek, Qwen3). |
1133
1852
  | `tool-calling` | Supports function/tool calling in chat completions. |
1134
1853
  | `coding` | Tuned for code generation and software tasks. |
1135
- | `experimental` | Not yet validated for production use. |
1136
-
1137
1854
 
1138
- ## `GET /v1/models/{model_id}`
1855
+ <!-- BEGIN GENERATED: openai.models_id -->
1856
+ ## `GET /v1/models/{id}`
1139
1857
  <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1140
1858
 
1141
- Retrieve a specific model by its ID. Returns the same model object format as the list endpoint above.
1859
+ Returns one model, as the same model object [`GET /v1/models`](#get-v1models) lists.
1860
+
1861
+ Requires `LEMONADE_API_KEY` when it is set.
1142
1862
 
1143
1863
  ### Parameters
1144
1864
 
1145
- | Parameter | Required | Description |
1146
- |-----------|----------|-------------|
1147
- | `model_id` | Yes | The ID of the model to retrieve. Must match one of the model IDs from the [models list](https://lemonade-server.ai/models.html). |
1865
+ | Parameter | Required | Description | Status |
1866
+ |-----------|----------|-------------|--------|
1867
+ | `id` (path) | Yes | Model id, as listed by [`GET /v1/models`](#get-v1models), or an alias. A trailing `:latest`, the tag Ollama clients append, names the bare id when nothing is registered under the tagged name. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1868
+ | `include_paths` (query) | No | `true` adds each present checkpoint's absolute `path` to its `checkpoint_status` entry, which can reveal local usernames and the cache layout. Defaults to `false`. The files a model is made of have their own list, at [`GET /v1/models/{id}/files`](./openai.md#get-v1modelsidfiles). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1148
1869
 
1149
- ### Example request
1870
+ ### Example
1150
1871
 
1151
- ```bash
1152
- curl http://localhost:13305/v1/models/Qwen3-0.6B-GGUF
1153
- ```
1872
+ === "PowerShell"
1154
1873
 
1155
- ### Response format
1874
+ ```powershell
1875
+ Invoke-WebRequest -Uri "http://localhost:13305/v1/models/Qwen3-0.6B-GGUF"
1876
+ ```
1877
+
1878
+ === "Bash"
1156
1879
 
1157
- Returns a single model object with the same fields as described in the [models list endpoint](#get-v1models) above. For Omni collections (`recipe: "collection.omni"`), the object additionally carries `components` (ordered component names) and `models` (each component's full model object) — see the [collection file documentation](../guide/configuration/custom-models.md#share-a-collection-export-import-and-hugging-face).
1880
+ ```bash
1881
+ curl http://localhost:13305/v1/models/Qwen3-0.6B-GGUF
1882
+ ```
1883
+
1884
+ **Response:** `200 OK`
1158
1885
 
1159
1886
  ```json
1160
1887
  {
1161
1888
  "id": "Qwen3-0.6B-GGUF",
1162
- "created": 1744173590,
1889
+ "created": 1234567890,
1163
1890
  "object": "model",
1164
1891
  "owned_by": "lemonade",
1165
1892
  "checkpoint": "unsloth/Qwen3-0.6B-GGUF:Q4_0",
1166
1893
  "recipe": "llamacpp",
1167
- "size": 0.38,
1894
+ "size": 0.356,
1168
1895
  "max_context_window": 40960,
1169
- "context_length": 8192,
1896
+ "context_length": 40960,
1170
1897
  "downloaded": true,
1898
+ "update_available": false,
1171
1899
  "suggested": true,
1172
- "labels": ["reasoning"],
1173
- "recipe_options": {
1900
+ "labels": ["chat", "reasoning", "tool-calling"],
1901
+ "components": [],
1902
+ "checkpoints": {"main": "unsloth/Qwen3-0.6B-GGUF:Q4_0"},
1903
+ "checkpoint_status": [
1904
+ {"type": "main", "present": true, "required": true, "registry": "huggingface"}
1905
+ ],
1906
+ "source": "huggingface",
1907
+ "registry_source": "huggingface",
1908
+ "recipe_options": {}
1909
+ }
1910
+ ```
1911
+
1912
+ ### Response format
1913
+
1914
+ - `id` - Model identifier for loading and inference requests.
1915
+ - `created` - Always `1234567890`, for OpenAI compatibility.
1916
+ - `object` - Always `"model"`.
1917
+ - `owned_by` - Always `"lemonade"`.
1918
+ - `checkpoint` - The main checkpoint in the model's registry; see `registry_source`.
1919
+ - `recipe` - Recipe used to load the model, e.g. `llamacpp` or `flm`.
1920
+ - `size` - Size in GB; omitted when unknown.
1921
+ - `max_context_window` - Largest text context the model supports, read from local metadata: downloaded GGUF models and installed FLM text models.
1922
+ - `context_length` - Tokens the model handles in one request: the loaded `ctx_size` while it runs, else the configured `ctx_size`, else `max_context_window`; omitted when none is known.
1923
+ - `max_output_tokens` - Cloud models only: the most tokens one completion can generate, when the provider reports it.
1924
+ - `max_completion_tokens` - Cloud models only: `max_output_tokens` under its OpenAI name.
1925
+ - `downloaded` - Whether the model is downloaded.
1926
+ - `update_available` - Whether the model's registry has a newer commit; `false` except for downloaded registry models.
1927
+ - `suggested` - Whether the model is recommended for general use.
1928
+ - `labels` - The model's capabilities and characteristics; see [Model Labels](./openai.md#model-labels).
1929
+ - `components` - The names of a collection's component models, in order; empty for other models.
1930
+ - `models` - Omni collections only: each component's full model object, in the order of `components`, so an exported collection can be imported elsewhere through [`/v1/pull`](./lemonade.md#post-v1pull).
1931
+ - `checkpoints` - Every checkpoint by role, such as `main`, `mmproj`, `draft`, `text_encoder` or `vae`.
1932
+ - The checkpoint that fills the role named by its key.
1933
+ - `checkpoint_status` - One entry per checkpoint of the model, answering whether its bytes are there.
1934
+ - `type` - Checkpoint slot, e.g. `main`, `mmproj`, `vae`.
1935
+ - `present` - `true` only when the checkpoint's bytes are complete and readable.
1936
+ - `required` - A missing required checkpoint keeps the model undownloaded; an optional one, loaded lazily, does not.
1937
+ - `registry` - Registry hosting the checkpoint; empty for local files and directories.
1938
+ - `path` - Absolute path to the checkpoint file; present only with `include_paths=true` and when `present` is `true`.
1939
+ - `source` - Where the model came from: a registry name, `local_upload`, `local_path` or `extra_models_dir`.
1940
+ - `registry_source` - Remote registry the checkpoint downloads from: `huggingface` or `modelscope`.
1941
+ - `recipe_options` - The model's own options: those its registry row authors, with what was saved in `recipe_options.json` on top.
1942
+ - `cloud_provider` - Cloud models only: the provider that serves the model.
1943
+ - `cost_input_per_million` - Cloud models only: USD per million input tokens, when the provider reports it.
1944
+ - `cost_output_per_million` - Cloud models only: USD per million output tokens, when the provider reports it.
1945
+ - `system_prompt` - Omni collections only: the collection's system prompt override.
1946
+ - `version` - Router collections only: the policy document's version.
1947
+ - `routing` - Router collections only: the routing policy.
1948
+
1949
+ ### Errors
1950
+
1951
+ | Status | Code | When |
1952
+ |--------|------|------|
1953
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
1954
+ | `404` | `model_not_found` | The requested model is not in the registry |
1955
+ <!-- END GENERATED: openai.models_id -->
1956
+
1957
+ <!-- BEGIN GENERATED: openai.models_id_files -->
1958
+ ## `GET /v1/models/{id}/files`
1959
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1960
+
1961
+ Lists the resolved local files of one model, for model-detail views such as the app's Files tab. It inventories one model, not drive storage.
1962
+
1963
+ Requires `LEMONADE_API_KEY` when it is set.
1964
+
1965
+ ### Parameters
1966
+
1967
+ | Parameter | Required | Description | Status |
1968
+ |-----------|----------|-------------|--------|
1969
+ | `id` (path) | Yes | Model id, as listed by [`GET /v1/models`](#get-v1models). An alias's id is not accepted here; use its target. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1970
+ | `include_paths` (query) | No | `true` adds each file's absolute `path`, which can reveal local usernames and the cache layout. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
1971
+
1972
+ ### Example
1973
+
1974
+ === "PowerShell"
1975
+
1976
+ ```powershell
1977
+ Invoke-WebRequest -Uri "http://localhost:13305/v1/models/Qwen3-0.6B-GGUF/files"
1978
+ ```
1979
+
1980
+ === "Bash"
1981
+
1982
+ ```bash
1983
+ curl http://localhost:13305/v1/models/Qwen3-0.6B-GGUF/files
1984
+ ```
1985
+
1986
+ **Response:** `200 OK`
1987
+
1988
+ ```json
1989
+ {
1990
+ "model_id": "Qwen3-0.6B-GGUF",
1991
+ "files": [
1992
+ {"name": "Qwen3-0.6B-Q4_0.gguf", "role": "main", "size_bytes": 382156480, "exists": true}
1993
+ ]
1994
+ }
1995
+ ```
1996
+
1997
+ ### Response format
1998
+
1999
+ - `model_id` - The model's public id.
2000
+ - `files` - The model's resolved files.
2001
+ - `name` - Base filename from the resolved path.
2002
+ - `path` - Absolute path; present only with `include_paths=true`.
2003
+ - `role` - Checkpoint role, for example `main`, `mmproj`, or another recipe-specific role.
2004
+ - `size_bytes` - File size in bytes. Directories are summed recursively. Missing files report `0`.
2005
+ - `exists` - Whether the resolved path currently exists on disk.
2006
+
2007
+ ### Errors
2008
+
2009
+ | Status | Code | When |
2010
+ |--------|------|------|
2011
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
2012
+ | `404` | `model_not_found` | The requested model is not in the registry |
2013
+ | `404` | `model_load_error` | Resolving or listing `id`'s files fails |
2014
+ <!-- END GENERATED: openai.models_id_files -->
2015
+
2016
+ <!-- BEGIN GENERATED: openai.models_id_options_get -->
2017
+ ## `GET /v1/models/{id}/options`
2018
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2019
+
2020
+ Reads a model's recipe options, separated by layer, without loading it. With `POST` and `DELETE` on the same path, this manages per-model options independently of [`/v1/load`](./lemonade.md#post-v1load).
2021
+
2022
+ Requires `LEMONADE_API_KEY` when it is set.
2023
+
2024
+ ### Parameters
2025
+
2026
+ | Parameter | Required | Description | Status |
2027
+ |-----------|----------|-------------|--------|
2028
+ | `id` (path) | Yes | Model id, as listed by [`GET /v1/models`](#get-v1models), or an alias. A trailing `:latest`, the tag Ollama clients append, names the bare id when nothing is registered under the tagged name. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
2029
+
2030
+ Per-architecture defaults are chosen by the architecture in the model's GGUF metadata, so a model that has not been downloaded yet shows every key with the value it has before those defaults apply.
2031
+
2032
+ ### Example
2033
+
2034
+ === "PowerShell"
2035
+
2036
+ ```powershell
2037
+ Invoke-WebRequest -Uri "http://localhost:13305/v1/models/Qwen3-0.6B-GGUF/options"
2038
+ ```
2039
+
2040
+ === "Bash"
2041
+
2042
+ ```bash
2043
+ curl http://localhost:13305/v1/models/Qwen3-0.6B-GGUF/options
2044
+ ```
2045
+
2046
+ **Response:** `200 OK`
2047
+
2048
+ ```json
2049
+ {
2050
+ "model_name": "Qwen3-0.6B-GGUF",
2051
+ "recipe": "llamacpp",
2052
+ "saved": {},
2053
+ "effective": {
2054
+ "auto_evict": null,
2055
+ "auto_update": null,
2056
+ "ctx_size": -1,
2057
+ "downsize_idle_timeout": 60,
2058
+ "evict_idle_timeout": 300,
2059
+ "evict_weight_factor": 1.0,
2060
+ "llamacpp_args": "--temp 0.6 --top-p 0.85 --top-k 20 --min-p 0.0 --repeat-penalty 1.0 --parallel 1",
2061
+ "llamacpp_backend": "vulkan",
2062
+ "llamacpp_device": "",
2063
+ "merge_args": true,
2064
+ "model_name": "Qwen3-0.6B-GGUF"
2065
+ },
2066
+ "defaults": {
2067
+ "auto_evict": null,
2068
+ "auto_update": null,
2069
+ "ctx_size": -1,
2070
+ "downsize_idle_timeout": 60,
2071
+ "evict_idle_timeout": 300,
2072
+ "evict_weight_factor": 1.0,
2073
+ "llamacpp_args": "--temp 0.6 --top-p 0.85 --top-k 20 --min-p 0.0 --repeat-penalty 1.0 --parallel 1",
2074
+ "llamacpp_backend": "vulkan",
2075
+ "llamacpp_device": "",
2076
+ "merge_args": true,
2077
+ "model_name": "Qwen3-0.6B-GGUF"
2078
+ },
2079
+ "resolved_ctx_size": 40960
2080
+ }
2081
+ ```
2082
+
2083
+ ### Response format
2084
+
2085
+ - `model_name` - The id from the URL. It appears again inside `effective` and `defaults` so that each one is a complete `/v1/load` body.
2086
+ - `recipe` - The recipe the option names belong to.
2087
+ - `saved` - The model's own entry in `recipe_options.json`: only what was explicitly saved, or `{}` when nothing is. It can also hold keys this endpoint does not accept, such as `pinned` written by `/v1/load`, so replay `effective` rather than `saved`.
2088
+ - `effective` - The body a [`POST /v1/load`](./lemonade.md#post-v1load) for this model uses now: every option the recipe accepts, resolved through the full priority chain. Posting it back whole saves every resolved value as an override, so send only the options that changed.
2089
+ - `defaults` - What `effective` becomes if `saved` is erased, in the same shape. A `ctx_size` of `-1` means the server picks the context size automatically.
2090
+ - `resolved_ctx_size` - The context size a load right now would use: the effective `ctx_size`, or the automatically computed size when that is `-1`.
2091
+
2092
+ ### Errors
2093
+
2094
+ | Status | Code | When |
2095
+ |--------|------|------|
2096
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
2097
+ | `404` | `model_not_found` | The requested model is not in the registry |
2098
+ | `500` | `internal_error` | Reading or saving the model's options fails, for example on an unwritable options file |
2099
+ <!-- END GENERATED: openai.models_id_options_get -->
2100
+
2101
+ <!-- BEGIN GENERATED: openai.models_id_options_post -->
2102
+ ## `POST /v1/models/{id}/options`
2103
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2104
+
2105
+ Saves recipe options for a model without loading it. The body is a flat object of the recipe options [`/v1/load`](./lemonade.md#post-v1load) accepts.
2106
+
2107
+ Requires `LEMONADE_API_KEY` when it is set.
2108
+
2109
+ ### Parameters
2110
+
2111
+ | Parameter | Required | Description | Status |
2112
+ |-----------|----------|-------------|--------|
2113
+ | `id` (path) | Yes | Model id, as listed by [`GET /v1/models`](#get-v1models), or an alias. A trailing `:latest`, the tag Ollama clients append, names the bare id when nothing is registered under the tagged name. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
2114
+ | `ctx_size` | No | Context size: a positive whole number, or `-1` to size it automatically even when the server-wide `ctx_size` is a specific number. Only recipes that take a context size use it, such as `llamacpp`, `flm`, `ryzenai-llm` and `vllm`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
2115
+ | `dry_run` | No | `true` reports what the request would do without doing it. Defaults to `false`. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
2116
+ | `model_name` | No | Ignored; the URL names the model. Accepted so `effective` can be posted back as is. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
2117
+
2118
+ The request merges into the model's saved entry, so keys it leaves out keep their values. `null` removes an option, as do `-1`, `""` and `"auto"` for any option but `ctx_size`, and the model falls back to the next layer of the [priority chain](./lemonade.md#post-v1load). [`DELETE`](#delete-v1modelsidoptions) removes every saved option at once.
2119
+
2120
+ A request that fails validation saves nothing.
2121
+
2122
+ With `dry_run`, the request is validated and resolved as a save would be, but nothing is persisted: `effective` and `resolved_ctx_size` describe the state the save would produce, while `saved` keeps reporting the entry on disk.
2123
+
2124
+ A running model keeps its current options until it is next loaded.
2125
+
2126
+ `pinned` is omitted from `effective` and `defaults`; it belongs to [`/v1/load`](./lemonade.md#post-v1load) and [`/internal/pin`](./internal.md#post-internalpin).
2127
+
2128
+ ### Example
2129
+
2130
+ === "PowerShell"
2131
+
2132
+ ```powershell
2133
+ Invoke-WebRequest `
2134
+ -Uri "http://localhost:13305/v1/models/Qwen3-4B-GGUF/options" `
2135
+ -Method POST `
2136
+ -Headers @{ "Content-Type" = "application/json" } `
2137
+ -Body '{"ctx_size": 8192}'
2138
+ ```
2139
+
2140
+ === "Bash"
2141
+
2142
+ ```bash
2143
+ curl http://localhost:13305/v1/models/Qwen3-4B-GGUF/options \
2144
+ -H "Content-Type: application/json" \
2145
+ -d '{"ctx_size": 8192}'
2146
+ ```
2147
+
2148
+ **Response:** `200 OK`
2149
+
2150
+ ```json
2151
+ {
2152
+ "model_name": "Qwen3-4B-GGUF",
2153
+ "recipe": "llamacpp",
2154
+ "saved": {"ctx_size": 8192},
2155
+ "effective": {
2156
+ "auto_evict": null,
2157
+ "auto_update": null,
1174
2158
  "ctx_size": 8192,
1175
- "llamacpp_args": "--no-mmap",
1176
- "llamacpp_backend": "rocm"
1177
- }
2159
+ "downsize_idle_timeout": 60,
2160
+ "evict_idle_timeout": 300,
2161
+ "evict_weight_factor": 1.0,
2162
+ "llamacpp_args": "--parallel 1",
2163
+ "llamacpp_backend": "vulkan",
2164
+ "llamacpp_device": "",
2165
+ "merge_args": true,
2166
+ "model_name": "Qwen3-4B-GGUF"
2167
+ },
2168
+ "defaults": {
2169
+ "auto_evict": null,
2170
+ "auto_update": null,
2171
+ "ctx_size": -1,
2172
+ "downsize_idle_timeout": 60,
2173
+ "evict_idle_timeout": 300,
2174
+ "evict_weight_factor": 1.0,
2175
+ "llamacpp_args": "--parallel 1",
2176
+ "llamacpp_backend": "vulkan",
2177
+ "llamacpp_device": "",
2178
+ "merge_args": true,
2179
+ "model_name": "Qwen3-4B-GGUF"
2180
+ },
2181
+ "resolved_ctx_size": 8192
1178
2182
  }
1179
2183
  ```
1180
2184
 
1181
- ### Error responses
2185
+ ### Response format
2186
+
2187
+ - `model_name` - The id from the URL. It appears again inside `effective` and `defaults` so that each one is a complete `/v1/load` body.
2188
+ - `recipe` - The recipe the option names belong to.
2189
+ - `saved` - The model's own entry in `recipe_options.json`: only what was explicitly saved, or `{}` when nothing is. It can also hold keys this endpoint does not accept, such as `pinned` written by `/v1/load`, so replay `effective` rather than `saved`.
2190
+ - `effective` - The body a [`POST /v1/load`](./lemonade.md#post-v1load) for this model uses now: every option the recipe accepts, resolved through the full priority chain. Posting it back whole saves every resolved value as an override, so send only the options that changed.
2191
+ - `defaults` - What `effective` becomes if `saved` is erased, in the same shape. A `ctx_size` of `-1` means the server picks the context size automatically.
2192
+ - `resolved_ctx_size` - The context size a load right now would use: the effective `ctx_size`, or the automatically computed size when that is `-1`.
2193
+
2194
+ ### Errors
2195
+
2196
+ | Status | Code | When |
2197
+ |--------|------|------|
2198
+ | `400` | `invalid_json` | The body is not a JSON object |
2199
+ | `400` | `invalid_argument` | An argument or a value nested in it has the wrong JSON type, or a value its schema does not list |
2200
+ | `400` | | The body names an option that the model's recipe does not accept |
2201
+ | `400` | | The body sets `pinned`, which belongs to `/v1/load` and `/internal/pin` |
2202
+ | `400` | | A field not listed above has the wrong JSON type |
2203
+ | `400` | | `ctx_size` is not a positive whole number or `-1`, or a whole-number option has a fractional value |
2204
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
2205
+ | `404` | `model_not_found` | The requested model is not in the registry |
2206
+ | `500` | `internal_error` | Reading or saving the model's options fails, for example on an unwritable options file |
2207
+ <!-- END GENERATED: openai.models_id_options_post -->
2208
+
2209
+ <!-- BEGIN GENERATED: openai.models_id_options_delete -->
2210
+ ## `DELETE /v1/models/{id}/options`
2211
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2212
+
2213
+ Resets a model to its defaults by erasing its `recipe_options.json` entry. Defaults from its registry entry and the server's configuration still apply.
2214
+
2215
+ Requires `LEMONADE_API_KEY` when it is set.
2216
+
2217
+ ### Parameters
2218
+
2219
+ | Parameter | Required | Description | Status |
2220
+ |-----------|----------|-------------|--------|
2221
+ | `id` (path) | Yes | Model id, as listed by [`GET /v1/models`](#get-v1models), or an alias. A trailing `:latest`, the tag Ollama clients append, names the bare id when nothing is registered under the tagged name. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
2222
+
2223
+ ### Example
2224
+
2225
+ === "PowerShell"
2226
+
2227
+ ```powershell
2228
+ Invoke-WebRequest `
2229
+ -Uri "http://localhost:13305/v1/models/Qwen3-4B-GGUF/options" `
2230
+ -Method DELETE
2231
+ ```
2232
+
2233
+ === "Bash"
1182
2234
 
1183
- If the model is not found, the endpoint returns a 404 error:
2235
+ ```bash
2236
+ curl -X DELETE http://localhost:13305/v1/models/Qwen3-4B-GGUF/options
2237
+ ```
2238
+
2239
+ **Response:** `200 OK`
1184
2240
 
1185
2241
  ```json
1186
2242
  {
1187
- "error": {
1188
- "message": "Model Qwen3-0.6B-GGUF has not been found",
1189
- "type": "not_found"
1190
- }
2243
+ "model_name": "Qwen3-4B-GGUF",
2244
+ "recipe": "llamacpp",
2245
+ "saved": {},
2246
+ "effective": {
2247
+ "auto_evict": null,
2248
+ "auto_update": null,
2249
+ "ctx_size": -1,
2250
+ "downsize_idle_timeout": 60,
2251
+ "evict_idle_timeout": 300,
2252
+ "evict_weight_factor": 1.0,
2253
+ "llamacpp_args": "--parallel 1",
2254
+ "llamacpp_backend": "vulkan",
2255
+ "llamacpp_device": "",
2256
+ "merge_args": true,
2257
+ "model_name": "Qwen3-4B-GGUF"
2258
+ },
2259
+ "defaults": {
2260
+ "auto_evict": null,
2261
+ "auto_update": null,
2262
+ "ctx_size": -1,
2263
+ "downsize_idle_timeout": 60,
2264
+ "evict_idle_timeout": 300,
2265
+ "evict_weight_factor": 1.0,
2266
+ "llamacpp_args": "--parallel 1",
2267
+ "llamacpp_backend": "vulkan",
2268
+ "llamacpp_device": "",
2269
+ "merge_args": true,
2270
+ "model_name": "Qwen3-4B-GGUF"
2271
+ },
2272
+ "resolved_ctx_size": 32768
1191
2273
  }
1192
2274
  ```
2275
+
2276
+ ### Response format
2277
+
2278
+ - `model_name` - The id from the URL. It appears again inside `effective` and `defaults` so that each one is a complete `/v1/load` body.
2279
+ - `recipe` - The recipe the option names belong to.
2280
+ - `saved` - The model's own entry in `recipe_options.json`: only what was explicitly saved, or `{}` when nothing is. It can also hold keys this endpoint does not accept, such as `pinned` written by `/v1/load`, so replay `effective` rather than `saved`.
2281
+ - `effective` - The body a [`POST /v1/load`](./lemonade.md#post-v1load) for this model uses now: every option the recipe accepts, resolved through the full priority chain. Posting it back whole saves every resolved value as an override, so send only the options that changed.
2282
+ - `defaults` - What `effective` becomes if `saved` is erased, in the same shape. A `ctx_size` of `-1` means the server picks the context size automatically.
2283
+ - `resolved_ctx_size` - The context size a load right now would use: the effective `ctx_size`, or the automatically computed size when that is `-1`.
2284
+
2285
+ ### Errors
2286
+
2287
+ | Status | Code | When |
2288
+ |--------|------|------|
2289
+ | `404` | `model_not_supported` | The requested model is filtered out on this system, such as an NPU model without an NPU |
2290
+ | `404` | `model_not_found` | The requested model is not in the registry |
2291
+ | `500` | `internal_error` | Reading or saving the model's options fails, for example on an unwritable options file |
2292
+ <!-- END GENERATED: openai.models_id_options_delete -->