local-lemonade 1.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.
@@ -0,0 +1,2537 @@
1
+ # Lemonade API
2
+
3
+ We have designed a set of Lemonade-specific endpoints to enable client applications by extending the existing cloud-focused APIs (e.g., OpenAI). These extensions allow for a greater degree of UI/UX responsiveness in native applications by allowing applications to:
4
+
5
+ - Download models at setup time.
6
+ - Pre-load models at UI-loading-time, as opposed to completion-request time.
7
+ - Unload models to save memory space.
8
+ - Understand system resources and state to make dynamic choices.
9
+
10
+ | Method | Endpoint | Description |
11
+ |--------|----------|-------------|
12
+ | `POST` | [`/v1/pull`](#post-v1pull) | Install a model |
13
+ | `POST` | [`/v1/models/register`](#post-v1modelsregister) | Register or update a user model definition without downloading it |
14
+ | `POST` | [`/v1/routing/validate`](#post-v1routingvalidate) | Evaluate an ad-hoc routing policy against a prompt without registering it |
15
+ | `GET` | [`/v1/downloads`](#get-v1downloads) | List server-owned model download jobs |
16
+ | `POST` | [`/v1/downloads/control`](#post-v1downloadscontrol) | Pause, cancel, or remove server-owned model download jobs |
17
+ | `GET` | [`/v1/registry/search`](#get-v1registrysearch) | Search Hugging Face or ModelScope for model repositories |
18
+ | `GET` | [`/v1/pull/variants`](#get-v1pullvariants) | Enumerate GGUF variants for a Hugging Face checkpoint |
19
+ | `POST` | [`/v1/delete`](#post-v1delete) | Delete a model |
20
+ | `POST` | [`/v1/load`](#post-v1load) | Load a model |
21
+ | `POST` | [`/v1/unload`](#post-v1unload) | Unload a model |
22
+ | `POST` | [`/v1/audio/generations`](#post-v1audiogenerations) | Generate audio (music or sound effects) from a text prompt |
23
+ | `POST` | [`/v1/classify`](#post-v1classify) | Classify input text with an encoder classifier (label scores) |
24
+ | `POST` | [`/v1/3d/generations`](#post-v13dgenerations) | Generate a textured 3D mesh (GLB) from an image |
25
+ | `POST` | [`/v1/models/check-updates`](#post-v1modelscheck-updates) | Manually check downloaded models for upstream updates |
26
+ | `GET` | [`/v1/models/{id}/files`](#get-v1modelsidfiles) | List resolved local file metadata for one model |
27
+ | `GET` | [`/v1/models/{id}/options`](#get-v1modelsidoptions) | Read a model's saved, effective, and default recipe options |
28
+ | `POST` | [`/v1/models/{id}/options`](#post-v1modelsidoptions) | Save recipe options for a model without loading it |
29
+ | `DELETE` | [`/v1/models/{id}/options`](#delete-v1modelsidoptions) | Reset a model's recipe options to defaults |
30
+ | `GET` | [`/v1/docs`](#get-v1docs) | List the API reference pages bundled with the running server |
31
+ | `GET` | [`/v1/docs/{page}`](#get-v1docspage) | Read one bundled API reference page |
32
+ | `GET` | [`/v1/health`](#get-v1health) | Check server status, such as models loaded |
33
+ | `GET` | [`/v1/stats`](#get-v1stats) | Performance statistics from the last request |
34
+ | `GET` | [`/v1/system-stats`](#get-v1system-stats) | Current host resource usage |
35
+ | `GET` | [`/v1/system-info`](#get-v1system-info) | System information and device enumeration |
36
+ | `POST` | [`/v1/install`](#post-v1install) | Install or update a backend, or register a cloud provider |
37
+ | `POST` | [`/v1/install/dry-run`](#post-v1installdry-run) | Resolve backend install metadata without downloading the backend asset |
38
+ | `POST` | [`/v1/uninstall`](#post-v1uninstall) | Remove a backend or cloud provider |
39
+ | `POST` | [`/v1/cloud/auth`](#post-v1cloudauth) | Set an in-memory API key for a cloud provider |
40
+ | `DELETE` | [`/v1/cloud/auth/{provider}`](#delete-v1cloudauthprovider) | Clear the in-memory API key for a cloud provider |
41
+ | `WS` | [`/logs/stream`](#log-streaming-api-websocket) | Log Streaming |
42
+ | `GET` | [`/live`](#get-live) | Check server liveness for load balancers and orchestrators |
43
+ | `GET` | [`/metrics`](#get-metrics) | Prometheus metrics scrape endpoint |
44
+ | `POST` | [`/internal/telemetry/flush`](#post-internaltelemetryflush) | Force-flush all queued telemetry trace spans |
45
+ | `GET` | [`/internal/aliases`](#get-internalaliases) | List all active model aliases |
46
+ | `POST` | [`/internal/aliases`](#post-internalaliases) | Create or update a model alias |
47
+ | `DELETE` | [`/internal/aliases/{alias}`](#delete-internalaliasesalias) | Remove a model alias |
48
+
49
+ ## `POST /v1/classify`
50
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
51
+
52
+ Run an encoder text-classifier (PII, prompt-safety, domain, etc.) on an input string and return per-label scores in `[0, 1]`. The target model must use the `onnxruntime` recipe. Both sequence-classification (one label set) and token-classification (aggregated span labels) models are supported.
53
+
54
+ **Supported architectures:** single-sequence encoder families — BERT, DistilBERT, RoBERTa, XLM-RoBERTa, DeBERTa (v1/v2), ELECTRA, ALBERT, CamemBERT. A stock `optimum-cli export onnx` directory of one of these works as-is.
55
+
56
+ A servable model directory is `model.onnx` + `tokenizer.json` + `config.json`. The `config.json` is **always required**: it declares the architecture, which is checked against the list above so an unsupported family (e.g. XLNet, which uses different segment/special-token conventions) is **rejected at load time** rather than served with wrong scores. The output contract (labels, normalization, token budget) is read from that same config; an optional `manifest.json` overrides it but does not replace the config. Without a manifest, inference assumes **single-label softmax**; a multi-label (sigmoid) model must declare `problem_type: multi_label_classification` in its config or ship a `manifest.json`.
57
+
58
+ This endpoint provides the classification capability that the router's `classifier` condition type will consume; the live routing-policy wiring is tracked in [#2384](https://github.com/lemonade-sdk/lemonade/issues/2384).
59
+
60
+ The endpoint is available at:
61
+
62
+ - `/v1/classify`
63
+ - `/api/v1/classify`
64
+ - `/v0/classify`
65
+ - `/api/v0/classify`
66
+
67
+ ### Parameters
68
+
69
+ | Field | Type | Required | Description |
70
+ |-------|------|----------|-------------|
71
+ | `model` | string | yes* | Classifier model id (a model with the `onnxruntime` recipe). *Optional when a classification model is already loaded; the loaded model is used and echoed in the response. |
72
+ | `input` | string | yes | Text to classify. `text` is accepted as an alias. |
73
+ | `top_k` | integer | no | Return only the highest-scoring `k` labels. |
74
+
75
+ ### Example request
76
+
77
+ ```bash
78
+ curl -X POST http://localhost:13305/v1/classify -H "Content-Type: application/json" -d '{"model": "Phishing-Email-Detection-ONNX", "input": "Please verify your account at http://secure-login.example now."}'
79
+ ```
80
+
81
+ ### Response format
82
+
83
+ ```json
84
+ {
85
+ "object": "classification",
86
+ "model": "Phishing-Email-Detection-ONNX",
87
+ "labels": {
88
+ "LABEL_1": 0.982,
89
+ "LABEL_0": 0.011,
90
+ "LABEL_2": 0.005,
91
+ "LABEL_3": 0.002
92
+ }
93
+ }
94
+ ```
95
+
96
+ Label names come from the model's `id2label` — from `config.json`, or from `manifest.json` when one is present to override it; some upstream models only declare generic `LABEL_<n>` names — see the model card for their meaning.
97
+
98
+ Malformed requests (invalid JSON, missing `input`/`text`, non-string fields, non-positive `top_k`) return `400` with an `error` object before any model is loaded.
99
+
100
+ ## Routing (`collection.router`)
101
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
102
+
103
+ Naming a registered `collection.router` model in the `model` field of a
104
+ `chat/completions` or `completions` request triggers the routing engine: the
105
+ server picks a candidate by the policy's first-matching rule (fail-open to
106
+ `default_model`) and forwards the request to it. No dedicated endpoint or `"auto"`
107
+ model is involved.
108
+
109
+ The decision is reported on the response:
110
+
111
+ - Header **`x-lemonade-route`** — the matched rule id, or `default`.
112
+ - Request field **`route_trace: true`** adds an **`x_lemonade_route`** object to the
113
+ response body: `{ route_to, matched_rule, default_used, outputs, trace[] }`
114
+ (`route_to` is the candidate that answered). For streaming responses it is
115
+ attached to the first SSE event.
116
+
117
+ See [Router Policies](../dev/router-policy.md) for authoring the policy.
118
+
119
+ ## `POST /v1/routing/validate`
120
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
121
+
122
+ Evaluate a routing policy document against a prompt and return the decision the
123
+ engine would make, without registering the policy or dispatching the user
124
+ request to the selected candidate. This is the endpoint behind the Router
125
+ Builder's **Test Prompt** tab: it lets a policy be iterated on before it is
126
+ attached to a `collection.router` model.
127
+
128
+ The endpoint performs parser-level structural policy validation: every
129
+ `candidates` entry, `default_model`, rule `route_to`, and classifier model must
130
+ be listed in `components`. It does not consult the live model registry:
131
+ component names are accepted as-is, so a policy can be tested before its
132
+ candidates are downloaded. Because names and component model types are not
133
+ resolved through the registry, registration-time registry checks (for example,
134
+ whether a `semantic_similarity` model can embed or a `classifier` model can
135
+ classify/chat) are not performed by this endpoint.
136
+
137
+ Deterministic conditions (`keywords_any`, `regex`, `min_chars`, `metadata`, …)
138
+ are evaluated locally. Model-backed conditions (`semantic_similarity`,
139
+ `classifier`, and `llm`, including `routing.router`) may load and run their
140
+ referenced models. A model-evaluation failure is handled by the classifier's
141
+ `on_error` policy (`match_false` by default), so routing normally continues to a
142
+ later rule or falls through to `default_model` rather than treating the policy
143
+ as invalid.
144
+
145
+ The endpoint is available at:
146
+
147
+ - `/v1/routing/validate`
148
+ - `/api/v1/routing/validate`
149
+ - `/v0/routing/validate`
150
+ - `/api/v0/routing/validate`
151
+
152
+ ### Parameters
153
+
154
+ | Field | Type | Required | Description |
155
+ |-------|------|----------|-------------|
156
+ | `policy` | object | yes | A `collection.router` policy document. `model_name` is accepted but is not required for validation. See [Router Policies](../dev/router-policy.md). |
157
+ | `prompt` | string | no | The prompt text to route. Defaults to `""`, which still exercises `min_chars` (0 chars) and any prompt-independent rules. |
158
+ | `has_images` | boolean | no | Simulate a request carrying image input. Default `false`. |
159
+ | `has_tools` | boolean | no | Simulate a request carrying tool definitions. Default `false`. |
160
+ | `metadata` | object | no | String-valued metadata pairs matched by `metadata` conditions. |
161
+
162
+ ### Example request
163
+
164
+ ```bash
165
+ curl -X POST http://localhost:13305/api/v1/routing/validate \
166
+ -H "Content-Type: application/json" \
167
+ -d '{
168
+ "policy": {
169
+ "version": "1",
170
+ "recipe": "collection.router",
171
+ "components": ["Qwen3-8B-GGUF", "vllm.qwen3-32b"],
172
+ "routing": {
173
+ "candidates": ["Qwen3-8B-GGUF", "vllm.qwen3-32b"],
174
+ "default_model": "Qwen3-8B-GGUF",
175
+ "rules": [
176
+ {
177
+ "id": "code-to-big",
178
+ "match": {"keywords_any": ["def ", "function", "compile"]},
179
+ "route_to": "vllm.qwen3-32b"
180
+ }
181
+ ]
182
+ }
183
+ },
184
+ "prompt": "please write a def to reverse a list"
185
+ }'
186
+ ```
187
+
188
+ ### Response format
189
+
190
+ ```json
191
+ {
192
+ "decision": {
193
+ "version": "1",
194
+ "route_to": "vllm.qwen3-32b",
195
+ "matched_rule": "code-to-big",
196
+ "default_used": false,
197
+ "outputs": {},
198
+ "trace": [
199
+ { "condition": "keywords_any", "result": true }
200
+ ]
201
+ },
202
+ "normalized_policy": {
203
+ "version": "1",
204
+ "recipe": "collection.router",
205
+ "components": ["Qwen3-8B-GGUF", "vllm.qwen3-32b"],
206
+ "routing": {
207
+ "candidates": ["Qwen3-8B-GGUF", "vllm.qwen3-32b"],
208
+ "default_model": "Qwen3-8B-GGUF",
209
+ "rules": [
210
+ {
211
+ "id": "code-to-big",
212
+ "match": {"keywords_any": ["def ", "function", "compile"]},
213
+ "route_to": "vllm.qwen3-32b"
214
+ }
215
+ ]
216
+ }
217
+ }
218
+ }
219
+ ```
220
+
221
+ `decision` has the same shape as the `x_lemonade_route` object a routed
222
+ completion returns with `route_trace: true`, and the trace is always included
223
+ here. When no rule matches, `matched_rule` is empty, `default_used` is `true`,
224
+ and `route_to` is the policy's `default_model`.
225
+
226
+ `normalized_policy` echoes the policy as it was actually evaluated. The policy
227
+ above uses explicit `routing.rules`, so it comes back unchanged. The field earns
228
+ its place when a policy uses the `routing.router` shorthand: that sugar is
229
+ desugared into an explicit `llm` classifier plus one identity rule per
230
+ candidate, so a `routing` block authored as:
231
+
232
+ ```json
233
+ {
234
+ "router": {
235
+ "type": "llm",
236
+ "model": "Qwen3-8B-GGUF",
237
+ "prompt": "Pick the best model for this request."
238
+ },
239
+ "candidates": ["Qwen3-8B-GGUF", "vllm.qwen3-32b"],
240
+ "default_model": "Qwen3-8B-GGUF"
241
+ }
242
+ ```
243
+
244
+ is echoed back with `router` removed and synthesized `classifiers`/`rules`:
245
+
246
+ ```json
247
+ {
248
+ "candidates": ["Qwen3-8B-GGUF", "vllm.qwen3-32b"],
249
+ "default_model": "Qwen3-8B-GGUF",
250
+ "classifiers": [
251
+ {
252
+ "id": "__router",
253
+ "type": "llm",
254
+ "model": "Qwen3-8B-GGUF",
255
+ "prompt": "Pick the best model for this request.",
256
+ "labels": ["Qwen3-8B-GGUF", "vllm.qwen3-32b"]
257
+ }
258
+ ],
259
+ "rules": [
260
+ {
261
+ "id": "__route_0",
262
+ "match": {"classifier": "__router", "label": "Qwen3-8B-GGUF", "min_score": 1.0},
263
+ "route_to": "Qwen3-8B-GGUF"
264
+ },
265
+ {
266
+ "id": "__route_1",
267
+ "match": {"classifier": "__router", "label": "vllm.qwen3-32b", "min_score": 1.0},
268
+ "route_to": "vllm.qwen3-32b"
269
+ }
270
+ ]
271
+ }
272
+ ```
273
+
274
+ Match `decision.matched_rule` against this document rather than the one you
275
+ sent — a policy authored with only `routing.router` has no `routing.rules` of
276
+ its own, only the synthesized `__route_0`, `__route_1`, … rules shown here.
277
+
278
+ ### Error responses
279
+
280
+ | Status | Condition |
281
+ |--------|-----------|
282
+ | `400` | Body is not valid JSON, `policy` is missing or not an object, `prompt` is not a string, `has_images`/`has_tools` are not booleans, or `metadata` is not an object of string values. |
283
+ | `400` | The policy document is invalid or internally inconsistent; the `error` field is prefixed with `Invalid routing policy:`. |
284
+
285
+ ## `POST /v1/models/check-updates`
286
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
287
+
288
+ Explicitly checks downloaded Hugging Face-backed models for newer upstream
289
+ commits. This is the manual counterpart to the startup update check and works
290
+ even when `auto_check_model_updates=false`.
291
+
292
+ Full offline mode remains authoritative: when `offline=true`, this endpoint
293
+ returns HTTP 409 and does not make network requests.
294
+
295
+ ### Example request
296
+
297
+ ```bash
298
+ curl -X POST http://localhost:13305/v1/models/check-updates
299
+ ```
300
+
301
+ The same action is available from the CLI:
302
+
303
+ ```bash
304
+ lemonade check-updates
305
+ ```
306
+
307
+ ### Response format
308
+
309
+ ```json
310
+ {
311
+ "status": "success",
312
+ "updates_available": 2,
313
+ "models": [
314
+ "Qwen3-4B-GGUF",
315
+ "Whisper-Tiny"
316
+ ]
317
+ }
318
+ ```
319
+
320
+ The endpoint is available at:
321
+
322
+ - `/v1/models/check-updates`
323
+ - `/api/v1/models/check-updates`
324
+ - `/v0/models/check-updates`
325
+ - `/api/v0/models/check-updates`
326
+
327
+ ## `GET /v1/models/{id}/files`
328
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
329
+
330
+ List resolved local file metadata for a single model. This endpoint is intended for model-detail UIs such as the Files tab. It is per-model inventory, not system or drive storage accounting.
331
+
332
+ The endpoint is available at:
333
+
334
+ - `/v1/models/{id}/files`
335
+ - `/api/v1/models/{id}/files`
336
+ - `/v0/models/{id}/files`
337
+ - `/api/v0/models/{id}/files`
338
+
339
+ By default, the response does not include absolute filesystem paths. Trusted local clients that need paths for native UI actions can request them explicitly with `?include_paths=true`. Absolute paths may reveal local usernames and cache layout, so clients should only request them when that disclosure is acceptable.
340
+
341
+ ### Example request
342
+
343
+ ```bash
344
+ curl http://localhost:13305/v1/models/Qwen3-4B/files
345
+ ```
346
+
347
+ ### Response format
348
+
349
+ ```json
350
+ {
351
+ "model_id": "Qwen3-4B",
352
+ "files": [
353
+ {
354
+ "name": "model.gguf",
355
+ "role": "main",
356
+ "size_bytes": 123456789,
357
+ "exists": true
358
+ },
359
+ {
360
+ "name": "mmproj.gguf",
361
+ "role": "mmproj",
362
+ "size_bytes": 12345678,
363
+ "exists": true
364
+ }
365
+ ]
366
+ }
367
+ ```
368
+
369
+ ### Optional path disclosure
370
+
371
+ ```bash
372
+ curl 'http://localhost:13305/v1/models/Qwen3-4B/files?include_paths=true'
373
+ ```
374
+
375
+ When `include_paths=true` is supplied, each file entry also includes `path`:
376
+
377
+ ```json
378
+ {
379
+ "name": "model.gguf",
380
+ "path": "/abs/path/model.gguf",
381
+ "role": "main",
382
+ "size_bytes": 123456789,
383
+ "exists": true
384
+ }
385
+ ```
386
+
387
+ ### Fields
388
+
389
+ | Field | Description |
390
+ |-------|-------------|
391
+ | `model_id` | Public model ID for the requested model. |
392
+ | `files` | Array of resolved model files known to the registry. |
393
+ | `files[].name` | Base filename from the resolved path. |
394
+ | `files[].path` | Absolute resolved path on the local system. Only included when `include_paths=true`; privacy-sensitive. |
395
+ | `files[].role` | Checkpoint role, for example `main`, `mmproj`, or another recipe-specific role. |
396
+ | `files[].size_bytes` | File size in bytes. Directories are summed recursively. Missing files report `0`. |
397
+ | `files[].exists` | Whether the resolved path currently exists on disk. |
398
+
399
+ ## `GET /v1/models/{id}/options`
400
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
401
+
402
+ Read 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`](#post-v1load).
403
+
404
+ ### Example request
405
+
406
+ ```bash
407
+ curl http://localhost:13305/v1/models/Qwen3-0.6B-GGUF/options
408
+ ```
409
+
410
+ ### Response format
411
+
412
+ `effective` is the exact request body a [`POST /v1/load`](#post-v1load) for this model uses right now, with every option the recipe accepts resolved through the full priority chain. `defaults` is what a reset model would get. For `llamacpp`, with `--no-mmap` saved and the context size left automatic:
413
+
414
+ ```json
415
+ {
416
+ "model_name": "Qwen3-0.6B-GGUF",
417
+ "recipe": "llamacpp",
418
+ "saved": {
419
+ "llamacpp_args": "--no-mmap"
420
+ },
421
+ "effective": {
422
+ "auto_evict": null,
423
+ "ctx_size": -1,
424
+ "downsize_idle_timeout": 60,
425
+ "evict_idle_timeout": 300,
426
+ "evict_weight_factor": 1.0,
427
+ "llamacpp_args": "--no-mmap",
428
+ "llamacpp_backend": "vulkan",
429
+ "llamacpp_device": "",
430
+ "merge_args": true,
431
+ "model_name": "Qwen3-0.6B-GGUF"
432
+ },
433
+ "defaults": {
434
+ "auto_evict": null,
435
+ "ctx_size": -1,
436
+ "downsize_idle_timeout": 60,
437
+ "evict_idle_timeout": 300,
438
+ "evict_weight_factor": 1.0,
439
+ "llamacpp_args": "",
440
+ "llamacpp_backend": "vulkan",
441
+ "llamacpp_device": "",
442
+ "merge_args": true,
443
+ "model_name": "Qwen3-0.6B-GGUF"
444
+ },
445
+ "resolved_ctx_size": 32768
446
+ }
447
+ ```
448
+
449
+ | Field | Description |
450
+ |-------|-------------|
451
+ | `model_name` | The id from the URL. It appears again inside `effective` and `defaults` so that each one is a complete `/v1/load` body. |
452
+ | `recipe` | The recipe the option names belong to. |
453
+ | `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`. |
454
+ | `effective` | The `/v1/load` body shown above. Posting it back whole to this endpoint saves every resolved value as an override, so send only the options the user changed. |
455
+ | `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. |
456
+ | `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`. |
457
+
458
+ > Note: per-architecture defaults come from the model's GGUF metadata. For a model that has not been downloaded yet, every key is still present but carries the value it has before those defaults apply.
459
+
460
+ ## `POST /v1/models/{id}/options`
461
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
462
+
463
+ Save recipe options for a model without loading it. The request body is a flat object of the same recipe options [`/v1/load`](#post-v1load) accepts. The URL identifies the model; a `model_name` in the body is ignored.
464
+
465
+ The request merges into the model's saved entry, so keys you don't mention are left alone. `null` removes an option, and the model falls back to the next layer of the [priority chain](#post-v1load). [`DELETE`](#delete-v1modelsidoptions) removes every saved option at once.
466
+
467
+ `dry_run: true` validates and resolves the request identically but persists nothing: `effective` and `resolved_ctx_size` describe the state the save would produce, while `saved` keeps reporting the entry on disk. Use it to preview a change before committing it.
468
+
469
+ `ctx_size` takes a positive whole number, or `-1` to pin the model to automatic sizing even when the server-wide `ctx_size` is a specific number.
470
+
471
+ A `400` reports an unrecognized option name, an option from a different recipe, a value of the wrong type, or an invalid `ctx_size`, and nothing from that request is saved.
472
+
473
+ Saving never loads or reloads the model, so a model that is already running keeps its current options until it is next loaded.
474
+
475
+ > Note: `pinned` is not settable here and is omitted from `effective` and `defaults`. It belongs to [`/v1/load`](#post-v1load) and `/internal/pin`.
476
+
477
+ ### Example requests
478
+
479
+ Save a context size without loading the model:
480
+
481
+ ```bash
482
+ curl -X POST http://localhost:13305/v1/models/Qwen3-0.6B-GGUF/options \
483
+ -H "Content-Type: application/json" \
484
+ -d '{"ctx_size": 8192, "llamacpp_backend": "vulkan"}'
485
+ ```
486
+
487
+ Set the context size back to automatic, leaving the backend choice saved:
488
+
489
+ ```bash
490
+ curl -X POST http://localhost:13305/v1/models/Qwen3-0.6B-GGUF/options \
491
+ -H "Content-Type: application/json" \
492
+ -d '{"ctx_size": -1}'
493
+ ```
494
+
495
+ ### Response format
496
+
497
+ Same as [`GET /v1/models/{id}/options`](#get-v1modelsidoptions), reflecting the state after the write.
498
+
499
+ ## `DELETE /v1/models/{id}/options`
500
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
501
+
502
+ Reset a model to its defaults by erasing its `recipe_options.json` entry entirely. The model keeps the defaults that come from its registry entry and from the server's global configuration; only the user's saved overrides are removed.
503
+
504
+ ### Example request
505
+
506
+ ```bash
507
+ curl -X DELETE http://localhost:13305/v1/models/Qwen3-0.6B-GGUF/options
508
+ ```
509
+
510
+ ### Response format
511
+
512
+ Same as [`GET /v1/models/{id}/options`](#get-v1modelsidoptions), with `saved` now `{}`.
513
+
514
+ ## `POST /v1/models/register`
515
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
516
+
517
+ Register or update a `user.*` model definition without downloading model files.
518
+ Use this endpoint when registration and installation are separate actions.
519
+ `POST /v1/pull` remains the install/download path and performs the same internal
520
+ registration step before downloading.
521
+
522
+ The endpoint is available at:
523
+
524
+ - `/v1/models/register`
525
+ - `/api/v1/models/register`
526
+ - `/v0/models/register`
527
+ - `/api/v0/models/register`
528
+
529
+ ### Parameters
530
+
531
+ | Parameter | Required | Description |
532
+ |-----------|----------|-------------|
533
+ | `model_name` | Yes | Non-empty namespaced model name under `user.*`. |
534
+ | `recipe` | Yes | Lemonade recipe associated with the model definition. |
535
+ | `checkpoint` | No | Main checkpoint, when the recipe uses one. |
536
+ | `checkpoints` | No | Named checkpoints for multi-checkpoint models. |
537
+ | `source` | No | Registry or local source. Remote values are `huggingface` or `modelscope`. |
538
+ | `labels` | No | Additional model labels. |
539
+ | `components` | No | Already-registered component model names for collection recipes. |
540
+
541
+ A checkpoint is intentionally not universally required: registration is a model
542
+ metadata operation and some present or future model types may not have local
543
+ weights. `/pull` remains the operation that attempts installation/download.
544
+
545
+ The endpoint accepts one model definition. An embedded `models` array represents
546
+ multiple definitions and remains a collection-import concern; register those
547
+ component definitions first when using this endpoint.
548
+
549
+ Example request:
550
+
551
+ ```bash
552
+ curl -X POST http://localhost:13305/v1/models/register \\
553
+ -H "Content-Type: application/json" \\
554
+ -d '{
555
+ "model_name": "user.Phi-4-Mini-GGUF",
556
+ "checkpoint": "unsloth/Phi-4-mini-instruct-GGUF:Q4_K_M",
557
+ "recipe": "llamacpp"
558
+ }'
559
+ ```
560
+
561
+ Example response:
562
+
563
+ ```json
564
+ {
565
+ "status": "success",
566
+ "model_name": "Phi-4-Mini-GGUF",
567
+ "canonical_model_name": "user.Phi-4-Mini-GGUF",
568
+ "model": {
569
+ "id": "Phi-4-Mini-GGUF",
570
+ "recipe": "llamacpp",
571
+ "downloaded": false
572
+ }
573
+ }
574
+ ```
575
+
576
+ `model_name` is the public ID exposed by `/v1/models`; `canonical_model_name` is
577
+ the stable `user.*` registration ID. Registration updates `user_models.json` and
578
+ invalidates the model cache, but does not start a model download.
579
+
580
+ ## `POST /v1/pull`
581
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
582
+
583
+ Register and install models for use with Lemonade Server.
584
+
585
+ ### Parameters
586
+
587
+ The Lemonade Server built-in model registry has a collection of model names that can be pulled and loaded. The `pull` endpoint can install any registered model, and it can also register-then-install any model available on Hugging Face.
588
+
589
+ **Common Parameters**
590
+
591
+ | Parameter | Required | Description |
592
+ |-----------|----------|-------------|
593
+ | `stream` | No | If `true`, returns Server-Sent Events (SSE) with download progress. Defaults to `false`. |
594
+ | `subscribe` | No | Only applies when `stream=true`. If `false`, the server starts a background model download job and returns a JSON snapshot immediately instead of keeping the HTTP response subscribed to SSE progress. Defaults to `true` for backwards compatibility. |
595
+
596
+ **Install a Model that is Already Registered**
597
+
598
+ | Parameter | Required | Description |
599
+ |-----------|----------|-------------|
600
+ | `model_name` | Yes | [Lemonade Server model name](https://lemonade-server.ai/models.html) to install. |
601
+
602
+ Example request:
603
+
604
+ ```bash
605
+ curl -X POST http://localhost:13305/v1/pull \
606
+ -H "Content-Type: application/json" \
607
+ -d '{
608
+ "model_name": "Qwen3-0.6B-GGUF"
609
+ }'
610
+ ```
611
+
612
+ Response format:
613
+
614
+ ```json
615
+ {
616
+ "status":"success",
617
+ "message":"Installed model: Qwen3-0.6B-GGUF"
618
+ }
619
+ ```
620
+
621
+ In case of an error, the status will be `error` and the message will contain the error message.
622
+
623
+ **Register and Install a Model**
624
+
625
+ Registration will place an entry for that model in the `user_models.json` file, which is located in the user's Lemonade config directory (default: `~/.config/lemonade`). Then, the model will be installed. Once the model is registered and installed, it will show up in the `models` endpoint alongside the built-in models and can be loaded.
626
+
627
+ The `recipe` field defines which software framework and device will be used to load and run the model.
628
+
629
+ > Note: the `model_name` for registering a new model must use the `user` namespace, to prevent collisions with built-in models. For example, `user.Phi-4-Mini-GGUF`.
630
+
631
+ | Parameter | Required | Description |
632
+ |-----------|----------|-------------|
633
+ | `model_name` | Yes | Namespaced [Lemonade Server model name](https://lemonade-server.ai/models.html) to register and install. |
634
+ | `recipe` | Yes | Lemonade API recipe to load the model with. |
635
+ | `checkpoint` | Yes`*` | HuggingFace "main" checkpoint to install. |
636
+ | `checkpoints` | No | HuggingFace checkpoints to install, for multi-checkpoint models. |
637
+ | `reasoning` | No | Whether the model is a reasoning model, like DeepSeek (default: false). Adds 'reasoning' label. |
638
+ | `vision` | No | Whether the model has vision capabilities for processing images (default: false). Adds 'vision' label. |
639
+ | `embedding` | No | Whether the model is an embedding model (default: false). Adds 'embeddings' label. |
640
+ | `reranking` | No | Whether the model is a reranking model (default: false). Adds 'reranking' label. |
641
+ | `mmproj` | No | Multimodal Projector (mmproj) file to use for vision models. |
642
+
643
+ A model definition requires at least a `main` checkpoint. This can be either
644
+ be specified with the `checkpoint` parameter, or a `main` key in the
645
+ `checkpoints` dict.
646
+
647
+ Each backend serves a fixed set of [deployment modes](openai.md#model-labels),
648
+ and a model deploys in exactly one of them. Naming a mode the recipe cannot
649
+ serve, or naming two — whether through `labels` or through the `embedding` /
650
+ `reranking` parameters — is rejected with `400` and nothing is registered:
651
+
652
+ ```bash
653
+ curl -X POST http://localhost:8000/api/v1/pull \
654
+ -H "Content-Type: application/json" \
655
+ -d '{"model_name": "user.Clf", "recipe": "llamacpp",
656
+ "checkpoint": "example/model:Q4_K_M", "labels": ["classification"]}'
657
+ ```
658
+
659
+ ```json
660
+ {"error": "Model 'user.Clf': recipe 'llamacpp' cannot serve 'classification'. It serves 'chat', 'embeddings', 'reranking'. Omit the label to deploy as 'chat'."}
661
+ ```
662
+
663
+ ```bash
664
+ curl -X POST http://localhost:8000/api/v1/pull \
665
+ -H "Content-Type: application/json" \
666
+ -d '{"model_name": "user.Both", "recipe": "llamacpp",
667
+ "checkpoint": "example/model:Q4_K_M", "labels": ["chat", "embeddings"]}'
668
+ ```
669
+
670
+ ```json
671
+ {"error": "Model 'user.Both': a model deploys in exactly one mode, but these labels name two: 'chat' and 'embeddings'. Register one model per mode."}
672
+ ```
673
+
674
+ Omitting the deployment label entirely is always valid — the recipe's default is
675
+ applied.
676
+
677
+ Other checkpoint types may also be specified depending on the model type.
678
+ This list is not exhaustive, and may change or grow over time as models
679
+ and backends evolve:
680
+ * `mmproj` - used by vision models, if not already embedded in `main`
681
+ * `draft` - used by dflash, eagle, and multitoken-prediction, if not already embedded in `main`
682
+ * `text_encoder` - text-to-token encoder used by image generation
683
+ * `vae` - variational autoencoder used by image generation
684
+
685
+ Example request:
686
+
687
+ ```bash
688
+ # Single checkpoint
689
+ curl -X POST http://localhost:13305/v1/pull \
690
+ -H "Content-Type: application/json" \
691
+ -d '{
692
+ "model_name": "user.Phi-4-Mini-GGUF",
693
+ "checkpoint": "unsloth/Phi-4-mini-instruct-GGUF:Q4_K_M",
694
+ "recipe": "llamacpp"
695
+ }'
696
+ ```
697
+
698
+ Instead of defining a model by `checkpoint` and `mmproj`, a model can also
699
+ be defined with a dict of checkpoint types and paths. These requests do the
700
+ same thing, but the syntax for pulling the mmproj differs.
701
+
702
+ ```bash
703
+ # Multi-checkpoint
704
+ curl -X POST http://localhost:13305/v1/pull \
705
+ -H "Content-Type: application/json" \
706
+ -d '{
707
+ "model_name": "user.My-Gemma3",
708
+ "checkpoint": "ggml-org/gemma-3-4b-it-GGUF:Q4_K_M",
709
+ "mmproj": "mmproj-model-f16.gguf",
710
+ "vision": true,
711
+ "recipe": "llamacpp"
712
+ }'
713
+ ```
714
+
715
+ ```bash
716
+ # Multi-checkpoint
717
+ curl -X POST http://localhost:13305/v1/pull \
718
+ -H "Content-Type: application/json" \
719
+ -d '{
720
+ "model_name": "user.My-Gemma3",
721
+ "checkpoints": {
722
+ "main": "ggml-org/gemma-3-4b-it-GGUF:Q4_K_M",
723
+ "mmproj": "ggml-org/gemma-3-4b-it-GGUF:mmproj-model-f16.gguf"
724
+ },
725
+ "vision": true,
726
+ "recipe": "llamacpp"
727
+ }'
728
+ ```
729
+
730
+ Response format:
731
+
732
+ ```json
733
+ {
734
+ "status":"success",
735
+ "message":"Installed model: user.Phi-4-Mini-GGUF"
736
+ }
737
+ ```
738
+
739
+ In case of an error, the status will be `error` and the message will contain the error message.
740
+
741
+ **Register an Omni-Model**
742
+
743
+ An omni collection is a collection type that bundles several models into a single entry that can be loaded, pulled, or deleted as a unit. Use `recipe: "collection.omni"` with a `components` array instead of `checkpoint`.
744
+
745
+ | Parameter | Required | Description |
746
+ |-----------|----------|-------------|
747
+ | `model_name` | Yes | Namespaced model name, e.g. `user.MyKit`. |
748
+ | `recipe` | Yes | Must be `"collection.omni"`. |
749
+ | `components` | Yes | Ordered, non-empty array of model names. Each component must be a regular model. |
750
+ | `models` | No | Ordered array of full model definitions, one per `components` entry (the same fields as single-model registration, keyed by `model_name`). When present, component names that are not yet registered are registered from these definitions; names that already exist keep their local definition. When absent, every `components` entry must already exist in the registry (built-in or a previously registered `user.*` model). |
751
+
752
+ Components do not need to be downloaded already — any not-yet-downloaded components are pulled by the same call. Deleting the collection removes only the collection entry; components stay on disk.
753
+
754
+ Example request:
755
+
756
+ ```bash
757
+ curl -X POST http://localhost:13305/v1/pull \
758
+ -H "Content-Type: application/json" \
759
+ -d '{
760
+ "model_name": "user.MyKit",
761
+ "recipe": "collection.omni",
762
+ "components": ["Qwen3-0.6B-GGUF", "Whisper-Tiny", "SD-Turbo"]
763
+ }'
764
+ ```
765
+
766
+ ### Import an Exported Model File
767
+
768
+ Files written by `lemonade export` (and the desktop app's Export button) are import-ready
769
+ `/v1/pull` request bodies — POST the file contents verbatim to register and install the model.
770
+ This works for regular models and collections alike; exported collection files additionally
771
+ carry `components` plus a `models` array embedding each component's definition (see the
772
+ `models` parameter above). For the file format and the export/import/Hugging Face workflows,
773
+ see [Share a collection](../guide/configuration/custom-models.md#share-a-collection-export-import-and-hugging-face).
774
+
775
+ ### Streaming Response (stream=true)
776
+
777
+ When `stream=true`, the endpoint returns Server-Sent Events with real-time download progress:
778
+
779
+ ```
780
+ event: progress
781
+ data: {"file":"model.gguf","file_index":1,"total_files":2,"bytes_downloaded":1073741824,"bytes_total":2684354560,"percent":40}
782
+
783
+ event: progress
784
+ data: {"file":"config.json","file_index":2,"total_files":2,"bytes_downloaded":1024,"bytes_total":1024,"percent":100}
785
+
786
+ event: complete
787
+ data: {"file_index":2,"total_files":2,"percent":100}
788
+ ```
789
+
790
+ **Event Types:**
791
+
792
+ | Event | Description |
793
+ |-------|-------------|
794
+ | `progress` | Sent during download with current file and byte progress |
795
+ | `complete` | Sent when all files are downloaded successfully |
796
+ | `error` | Sent if download fails, with `error` field containing the message |
797
+
798
+ ### Server-owned download mode (`stream=true`, `subscribe=false`)
799
+
800
+ By default, `stream=true` keeps the `/v1/pull` HTTP response subscribed to Server-Sent Events until the download finishes. Clients that need download state to survive a renderer reload, tab close, or reconnect can also send `subscribe=false`.
801
+
802
+ When `stream=true` and `subscribe=false`, `/v1/pull` starts a server-owned model download job and returns a JSON snapshot immediately. The job continues on the server. Clients can poll [`GET /v1/downloads`](#get-v1downloads) to restore progress and can use [`POST /v1/downloads/control`](#post-v1downloadscontrol) to pause, cancel, or remove the job.
803
+
804
+ Example request:
805
+
806
+ ```bash
807
+ curl -X POST http://localhost:13305/v1/pull \
808
+ -H "Content-Type: application/json" \
809
+ -d '{
810
+ "model_name": "Qwen3-0.6B-GGUF",
811
+ "stream": true,
812
+ "subscribe": false
813
+ }'
814
+ ```
815
+
816
+ Example response:
817
+
818
+ ```json
819
+ {
820
+ "id": "model:Qwen3-0.6B-GGUF",
821
+ "type": "model",
822
+ "model_name": "Qwen3-0.6B-GGUF",
823
+ "status": "downloading",
824
+ "running": true,
825
+ "file": "",
826
+ "file_index": 0,
827
+ "total_files": 0,
828
+ "bytes_downloaded": 0,
829
+ "bytes_total": 0,
830
+ "total_download_size": 0,
831
+ "bytes_previously_downloaded": 0,
832
+ "completed_files_bytes": 0,
833
+ "cumulative_bytes_downloaded": 0,
834
+ "overall_bytes_downloaded": 0,
835
+ "percent": 0,
836
+ "complete": false
837
+ }
838
+ ```
839
+
840
+ ## `GET /v1/downloads`
841
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
842
+
843
+ List server-owned model download jobs that were started with `POST /v1/pull` using `stream=true` and `subscribe=false`.
844
+
845
+ This endpoint is intended for clients that need to restore download-manager state after a reload or reconnect. Active, paused, cancelled, and errored jobs remain visible until the client removes them. Completed jobs remain visible briefly so clients can observe completion and refresh model state.
846
+
847
+ ### Example request
848
+
849
+ ```bash
850
+ curl http://localhost:13305/v1/downloads
851
+ ```
852
+
853
+ ### Response format
854
+
855
+ ```json
856
+ [
857
+ {
858
+ "id": "model:Qwen3-0.6B-GGUF",
859
+ "type": "model",
860
+ "model_name": "Qwen3-0.6B-GGUF",
861
+ "status": "downloading",
862
+ "running": true,
863
+ "file": "model.gguf",
864
+ "file_index": 1,
865
+ "total_files": 2,
866
+ "bytes_downloaded": 1073741824,
867
+ "bytes_total": 2684354560,
868
+ "total_download_size": 2684355584,
869
+ "bytes_previously_downloaded": 0,
870
+ "completed_files_bytes": 0,
871
+ "cumulative_bytes_downloaded": 1073741824,
872
+ "overall_bytes_downloaded": 1073741824,
873
+ "percent": 40,
874
+ "complete": false
875
+ }
876
+ ]
877
+ ```
878
+
879
+ ### Download job fields
880
+
881
+ | Field | Description |
882
+ |-------|-------------|
883
+ | `id` | Stable download id. Model downloads use `model:<model_name>`. |
884
+ | `type` | Download type. Currently `model` for server-owned jobs. |
885
+ | `model_name` | Lemonade model name associated with the job. |
886
+ | `status` | Current state: `downloading`, `paused`, `cancelled`, `completed`, or `error`. |
887
+ | `running` | Whether the download worker is still active. A terminal-looking status may still have `running=true` while the worker is releasing resources. |
888
+ | `file`, `file_index`, `total_files` | Current file progress within the download. |
889
+ | `bytes_downloaded`, `bytes_total`, `percent` | Current-file byte progress as reported by the downloader. |
890
+ | `total_download_size` | Total expected bytes across all files when known. |
891
+ | `bytes_previously_downloaded` | Bytes already present on disk for the current file when resuming or skipping existing data. |
892
+ | `completed_files_bytes` | Bytes from files completed before the current file. |
893
+ | `cumulative_bytes_downloaded`, `overall_bytes_downloaded` | Total bytes downloaded across the whole job. `overall_bytes_downloaded` is kept as a compatibility alias. |
894
+ | `complete` | `true` when the download completed successfully. |
895
+ | `error` | Error message, present only for failed jobs. |
896
+
897
+ ## `POST /v1/downloads/control`
898
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
899
+
900
+ Control a server-owned model download job.
901
+
902
+ ### Parameters
903
+
904
+ | Parameter | Required | Description |
905
+ |-----------|----------|-------------|
906
+ | `id` | Yes | Download id returned by `POST /v1/pull` or `GET /v1/downloads`, for example `model:Qwen3-0.6B-GGUF`. |
907
+ | `action` | Yes | One of `pause`, `cancel`, or `remove`. |
908
+
909
+ ### Actions
910
+
911
+ | Action | Description |
912
+ |--------|-------------|
913
+ | `pause` | Requests the worker to stop and keeps the job visible as `paused`. The worker may briefly report `running=true` while it unwinds. |
914
+ | `cancel` | Requests the worker to stop and marks the job as `cancelled`. Clients should wait for `running=false` before deleting partial files. |
915
+ | `remove` | Removes a stopped job from the server registry. If the worker is still running, the server keeps the job visible and treats the request as a cancel request until the worker stops. |
916
+
917
+ ### Example request
918
+
919
+ ```bash
920
+ curl -X POST http://localhost:13305/v1/downloads/control \
921
+ -H "Content-Type: application/json" \
922
+ -d '{
923
+ "id": "model:Qwen3-0.6B-GGUF",
924
+ "action": "pause"
925
+ }'
926
+ ```
927
+
928
+ ### Response format
929
+
930
+ For `pause` and `cancel`, the endpoint returns the latest job snapshot:
931
+
932
+ ```json
933
+ {
934
+ "id": "model:Qwen3-0.6B-GGUF",
935
+ "type": "model",
936
+ "model_name": "Qwen3-0.6B-GGUF",
937
+ "status": "paused",
938
+ "running": false,
939
+ "file": "model.gguf",
940
+ "file_index": 1,
941
+ "total_files": 2,
942
+ "bytes_downloaded": 1073741824,
943
+ "bytes_total": 2684354560,
944
+ "percent": 40,
945
+ "complete": false
946
+ }
947
+ ```
948
+
949
+ For `remove`, the endpoint returns:
950
+
951
+ ```json
952
+ {"status":"ok"}
953
+ ```
954
+
955
+ If the job is already missing and `action` is `remove`, the endpoint returns:
956
+
957
+ ```json
958
+ {"status":"ok","missing":true}
959
+ ```
960
+
961
+ ## `GET /v1/registry/search`
962
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
963
+
964
+ Search a remote model registry (Hugging Face or ModelScope) for repositories matching a text query. This endpoint returns **candidate repositories** based on registry metadata; it does not verify that a repository contains servable files. The desktop app's Model Manager follows up with [`/v1/pull/variants`](#get-v1pullvariants) on each candidate and only offers a download for repositories whose file listing passes that validation.
965
+
966
+ Requires network access: returns 400 with code `lemond_offline` when the server is in offline mode.
967
+
968
+ ### Parameters
969
+
970
+ | Parameter | Required | Description |
971
+ |-----------|----------|-------------|
972
+ | `query` | Yes | Search text, minimum 3 characters after trimming. `q` is accepted as an alias. |
973
+ | `source` | No | Registry to search: `huggingface` (default) or `modelscope`. Aliases `hf` and `ms` are accepted; the canonical name is echoed in the response. |
974
+ | `limit` | No | Maximum number of results, an integer from 1 to 50. Default 12. |
975
+ | `format` | No | The only accepted value is `gguf`. Biases search and ranking toward GGUF repositories and echoes `"format": "gguf"` in the response. |
976
+
977
+ Example request:
978
+
979
+ ```bash
980
+ curl 'http://localhost:13305/v1/registry/search?source=modelscope&query=qwen&format=gguf'
981
+ ```
982
+
983
+ ### Response
984
+
985
+ ```json
986
+ {
987
+ "source": "modelscope",
988
+ "query": "qwen",
989
+ "format": "gguf",
990
+ "total": 128,
991
+ "results": [
992
+ {
993
+ "repository_id": "Qwen/Qwen2.5-3B-Instruct-GGUF",
994
+ "display_name": "Qwen2.5-3B-Instruct-GGUF",
995
+ "source": "modelscope",
996
+ "repository_type": "model",
997
+ "description": "GGUF quantizations of Qwen2.5-3B-Instruct",
998
+ "tags": ["gguf", "chat"],
999
+ "task": "text-generation",
1000
+ "downloads": 222500,
1001
+ "likes": 12,
1002
+ "has_gguf": true
1003
+ }
1004
+ ]
1005
+ }
1006
+ ```
1007
+
1008
+ | Field | Description |
1009
+ |-------|-------------|
1010
+ | `source`, `query` | Echoed input (`source` canonicalized to `huggingface` or `modelscope`). |
1011
+ | `format` | Present only when `format=gguf` was requested. |
1012
+ | `total` | Total match count reported by the upstream registry; may exceed the number of returned results. |
1013
+ | `results[]` | Up to `limit` repositories, each with `repository_id`, `display_name`, `source`, `repository_type`, `description`, `tags`, `task`, `downloads`, `likes`, and `has_gguf`. `has_gguf` is a hint derived from registry metadata, not proof of a servable model — [`/v1/pull/variants`](#get-v1pullvariants) performs the authoritative file-level validation. |
1014
+
1015
+ ### Error responses
1016
+
1017
+ | Status | Cause |
1018
+ |--------|-------|
1019
+ | 400 | `query` shorter than 3 characters, invalid `source`, `limit`, or `format`, or the server is in offline mode (`code: lemond_offline`). |
1020
+ | 429 | The upstream registry rate-limited the request. |
1021
+ | 502 | Other upstream transport or parsing failures; the body includes the upstream status code when available. |
1022
+
1023
+ ## `GET /v1/pull/variants`
1024
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1025
+
1026
+ Inspect a Hugging Face GGUF repository and enumerate the variants (quantizations and sharded folder groups) available for installation. Used by the `lemonade pull <owner/repo>` CLI flow and by the desktop app's model search to auto-populate the install form. The endpoint reads only public Hugging Face metadata; if the `HF_TOKEN` environment variable is set on the server, it is forwarded as a bearer token to access gated repositories.
1027
+
1028
+ ### Parameters
1029
+
1030
+ | Parameter | Required | Description |
1031
+ |-----------|----------|-------------|
1032
+ | `checkpoint` | Yes | Hugging Face repo id, e.g. `unsloth/Qwen3-8B-GGUF`. Passed as a query string. |
1033
+
1034
+ Example request:
1035
+
1036
+ ```bash
1037
+ curl 'http://localhost:13305/v1/pull/variants?checkpoint=unsloth/Qwen3-8B-GGUF'
1038
+ ```
1039
+
1040
+ ### Response
1041
+
1042
+ ```json
1043
+ {
1044
+ "checkpoint": "unsloth/Qwen3-8B-GGUF",
1045
+ "recipe": "llamacpp",
1046
+ "suggested_name": "Qwen3-8B-GGUF",
1047
+ "suggested_labels": ["vision"],
1048
+ "mmproj_files": ["mmproj-model-f16.gguf"],
1049
+ "variants": [
1050
+ {
1051
+ "name": "Q4_K_M",
1052
+ "primary_file": "Qwen3-8B-Q4_K_M.gguf",
1053
+ "files": ["Qwen3-8B-Q4_K_M.gguf"],
1054
+ "sharded": false,
1055
+ "size_bytes": 4920000000
1056
+ },
1057
+ {
1058
+ "name": "Q8_0",
1059
+ "primary_file": "Q8_0/Qwen3-8B-Q8_0-00001-of-00002.gguf",
1060
+ "files": ["Q8_0/Qwen3-8B-Q8_0-00001-of-00002.gguf", "Q8_0/Qwen3-8B-Q8_0-00002-of-00002.gguf"],
1061
+ "sharded": true,
1062
+ "size_bytes": 8500000000
1063
+ }
1064
+ ]
1065
+ }
1066
+ ```
1067
+
1068
+ | Field | Description |
1069
+ |-------|-------------|
1070
+ | `checkpoint` | Echoed input. |
1071
+ | `recipe` | Suggested recipe (always `llamacpp` today; future expansion may return other values). |
1072
+ | `suggested_name` | Repo id stripped of the `owner/` prefix; suitable for use as the `user.<name>` model name. |
1073
+ | `suggested_labels` | Inferred labels — `vision` if any `mmproj-*.gguf` files exist, plus `embeddings`/`reranking` if those substrings appear in the repo id. |
1074
+ | `mmproj_files` | Bare filenames of `mmproj-*.gguf` files in the repo; the first one should be passed as `mmproj` to `/v1/pull` for vision models. |
1075
+ | `variants[]` | Top quantizations for the repo, capped at 5. Each entry has `name` (e.g. `Q4_K_M`, `UD-Q4_K_XL`), `primary_file`, `files`, `sharded`, and `size_bytes` (from the HF `?blobs=true` listing). Ranked by frequency of use in `server_models.json` (`Q4_K_M`, `UD-Q4_K_XL`, `Q8_0`, `Q4_0` first, everything else sorted lexicographically). The CLI `lemonade pull` menu adds a free-text "Other" option for quants outside the top 5. |
1076
+
1077
+ ### Error responses
1078
+
1079
+ | Status | Cause |
1080
+ |--------|-------|
1081
+ | 400 | `checkpoint` query parameter missing or malformed (must contain `/`). |
1082
+ | 404 | Hugging Face returned 404 for the checkpoint. |
1083
+ | 500 | Other transport or parsing failures; the response body contains an `error` message. |
1084
+
1085
+ ## `POST /v1/delete`
1086
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1087
+
1088
+ Delete a model by removing it from local storage. If the model is currently loaded, it will be unloaded first.
1089
+
1090
+ > Note: deleting a collection (`recipe: "collection.omni"`) removes only the collection entry from `user_models.json`; its components stay on disk. Delete the components individually if you want to free their disk space.
1091
+
1092
+ ### Parameters
1093
+
1094
+ | Parameter | Required | Description |
1095
+ |-----------|----------|-------------|
1096
+ | `model_name` | Yes | [Lemonade Server model name](https://lemonade-server.ai/models.html) to delete. |
1097
+
1098
+ Example request:
1099
+
1100
+ ```bash
1101
+ curl -X POST http://localhost:13305/v1/delete \
1102
+ -H "Content-Type: application/json" \
1103
+ -d '{
1104
+ "model_name": "Qwen3-0.6B-GGUF"
1105
+ }'
1106
+ ```
1107
+
1108
+ Response format:
1109
+
1110
+ ```json
1111
+ {
1112
+ "status":"success",
1113
+ "message":"Deleted model: Qwen3-0.6B-GGUF"
1114
+ }
1115
+ ```
1116
+
1117
+ In case of an error, the status will be `error` and the message will contain the error message.
1118
+
1119
+ ## `POST /v1/load`
1120
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1121
+
1122
+ Explicitly load a registered model into memory. This is useful to ensure that the model is loaded before you make a request. Installs the model if necessary.
1123
+
1124
+ > Note: loading a collection (`recipe: "collection.omni"`) loads each of its components in turn. Per-model options like `ctx_size` or `llamacpp_backend` are not forwarded to components — set them on each component's own `recipe_options.json` entry instead.
1125
+
1126
+ Recipe option fields on `/v1/load` have three-state semantics. Omitting a field keeps using its saved per-model value. Passing explicit `null` ignores only that saved key for this load and falls through to the lower default layers without changing `recipe_options.json`. Passing a concrete value overrides the saved value. For `*_args`, a concrete value replaces the model/architecture args scope for that load; backend/machine args remain only when `merge_args` is true. `ctx_size: -1` is a concrete value meaning automatic context sizing, not a tombstone. With `save_options: true`, concrete values are persisted as usual while a `null` tombstone preserves the existing saved value for that key.
1127
+
1128
+ ### Parameters
1129
+
1130
+ | Parameter | Required | Applies to | Description |
1131
+ |-----------|----------|------------|-------------|
1132
+ | `model_name` | Yes | All | [Lemonade Server model name](https://lemonade-server.ai/models.html) to load. |
1133
+ | `pinned` | No | All | Boolean. If true, pins the loaded model to prevent LRU eviction. Defaults to `false`. |
1134
+ | `save_options` | No | All | Boolean. If true, saves recipe options to `recipe_options.json`. Any previously stored value for `model_name` is replaced. To save options without loading, or to change one option without resending the rest, use [`POST /v1/models/{id}/options`](#post-v1modelsidoptions) instead. |
1135
+ | `ctx_size` | No | llamacpp, flm, ryzenai-llm | Context size for the model. Overrides the default value. Pass `-1` to size it automatically instead of using a saved value; omit it to use the saved value. |
1136
+ | `llamacpp_backend` | No | llamacpp | LlamaCpp backend to use (`vulkan`, `rocm`, `metal` or `cpu`). |
1137
+ | `llamacpp_args` | No | llamacpp | Custom arguments to pass to llama-server. The following are NOT allowed: `-m`, `--port`, `--ctx-size`, `-ngl`, `--jinja`, `--mmproj`, `--embeddings`, `--reranking`. |
1138
+ | `whispercpp_backend` | No | whispercpp | WhisperCpp backend: `npu` or `cpu` on Windows; `cpu` or `vulkan` on Linux. Default is `npu` if supported. |
1139
+ | `whispercpp_args` | No | whispercpp | Custom arguments to pass to whisper-server. The following are NOT allowed: `-m`, `--model`, `--port`. Example: `--convert`. |
1140
+ | `steps` | No | sd-cpp | Number of inference steps for image generation. Default: 20. |
1141
+ | `cfg_scale` | No | sd-cpp | Classifier-free guidance scale for image generation. Default: 7.0. |
1142
+ | `width` | No | sd-cpp | Image width in pixels. Default: 512. |
1143
+ | `height` | No | sd-cpp | Image height in pixels. Default: 512. |
1144
+ | `merge_args` | No | All | Boolean. If true (default), backend/machine `*_args` are inherited; concrete request `*_args` replace model/architecture args while keeping backend args. If false, no inherited custom args or overridable runtime defaults are applied. |
1145
+
1146
+ **Setting Priority:**
1147
+
1148
+ When loading a model, settings are applied in this priority order:
1149
+ 1. Values explicitly passed in the `load` request (highest priority)
1150
+ 2. Per-model values configurable in `recipe_options.json` (see below for details)
1151
+ 3. Values from environment variables or server startup arguments (see [Server Configuration](../guide/configuration/README.md))
1152
+ 4. Default hardcoded values in `lemond` (lowest priority)
1153
+
1154
+
1155
+ ### Per-model options
1156
+
1157
+ You can configure recipe-specific options on a per-model basis. Lemonade manages a file called `recipe_options.json` in the user's Lemonade config directory (default: `~/.config/lemonade`). The available options depend on the model's recipe:
1158
+
1159
+ ```json
1160
+ {
1161
+ "user.Qwen2.5-Coder-1.5B-Instruct": {
1162
+ "ctx_size": 16384,
1163
+ "llamacpp_backend": "vulkan",
1164
+ "llamacpp_args": "-np 2 -kvu"
1165
+ },
1166
+ "Qwen3-Coder-30B-A3B-Instruct-GGUF" : {
1167
+ "llamacpp_backend": "rocm"
1168
+ },
1169
+ "whisper-large-v3-turbo-q8_0.bin": {
1170
+ "whispercpp_backend": "npu",
1171
+ "whispercpp_args": "--convert"
1172
+ }
1173
+ }
1174
+ ```
1175
+
1176
+ Note that model names include any applicable prefix, such as `user.` and `extra.`.
1177
+
1178
+ ### Example requests
1179
+
1180
+ Basic load:
1181
+
1182
+ ```bash
1183
+ curl -X POST http://localhost:13305/v1/load \
1184
+ -H "Content-Type: application/json" \
1185
+ -d '{
1186
+ "model_name": "Qwen3-0.6B-GGUF"
1187
+ }'
1188
+ ```
1189
+
1190
+ Load with custom settings:
1191
+
1192
+ ```bash
1193
+ curl -X POST http://localhost:13305/v1/load \
1194
+ -H "Content-Type: application/json" \
1195
+ -d '{
1196
+ "model_name": "Qwen3-0.6B-GGUF",
1197
+ "ctx_size": 8192,
1198
+ "llamacpp_backend": "rocm",
1199
+ "llamacpp_args": "--flash-attn on --no-mmap"
1200
+ }'
1201
+ ```
1202
+
1203
+ Load and save settings:
1204
+
1205
+ ```bash
1206
+ curl -X POST http://localhost:13305/v1/load \
1207
+ -H "Content-Type: application/json" \
1208
+ -d '{
1209
+ "model_name": "Qwen3-0.6B-GGUF",
1210
+ "ctx_size": 8192,
1211
+ "llamacpp_backend": "vulkan",
1212
+ "llamacpp_args": "--no-context-shift --no-mmap",
1213
+ "save_options": true
1214
+ }'
1215
+ ```
1216
+
1217
+ Load a Whisper model with NPU backend and conversion enabled:
1218
+
1219
+ ```bash
1220
+ curl -X POST http://localhost:13305/v1/load \
1221
+ -H "Content-Type: application/json" \
1222
+ -d '{
1223
+ "model_name": "whisper-large-v3-turbo-q8_0.bin",
1224
+ "whispercpp_backend": "npu",
1225
+ "whispercpp_args": "--convert"
1226
+ }'
1227
+ ```
1228
+
1229
+ Load an image generation model with custom settings:
1230
+
1231
+ ```bash
1232
+ curl -X POST http://localhost:13305/v1/load \
1233
+ -H "Content-Type: application/json" \
1234
+ -d '{
1235
+ "model_name": "sd-turbo",
1236
+ "steps": 4,
1237
+ "cfg_scale": 1.0,
1238
+ "width": 512,
1239
+ "height": 512
1240
+ }'
1241
+ ```
1242
+
1243
+ ### Response format
1244
+
1245
+ ```json
1246
+ {
1247
+ "status":"success",
1248
+ "message":"Loaded model: Qwen3-0.6B-GGUF"
1249
+ }
1250
+ ```
1251
+
1252
+ In case of an error, the status will be `error` and the message will contain the error message.
1253
+
1254
+ ## `POST /v1/unload`
1255
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1256
+
1257
+ Explicitly unload a model from memory. This is useful to free up memory while still leaving the server process running (which takes minimal resources but a few seconds to start).
1258
+
1259
+ ### Parameters
1260
+
1261
+ | Parameter | Required | Description |
1262
+ |-----------|----------|-------------|
1263
+ | `model_name` | No | Name of the specific model to unload. If not provided, all loaded models will be unloaded. |
1264
+
1265
+ ### Example requests
1266
+
1267
+ Unload a specific model:
1268
+
1269
+ ```bash
1270
+ curl -X POST http://localhost:13305/v1/unload \
1271
+ -H "Content-Type: application/json" \
1272
+ -d '{"model_name": "Qwen3-0.6B-GGUF"}'
1273
+ ```
1274
+
1275
+ Unload all models:
1276
+
1277
+ ```bash
1278
+ curl -X POST http://localhost:13305/v1/unload
1279
+ ```
1280
+
1281
+ ### Response format
1282
+
1283
+ Success response:
1284
+
1285
+ ```json
1286
+ {
1287
+ "status": "success",
1288
+ "message": "Model unloaded successfully"
1289
+ }
1290
+ ```
1291
+
1292
+ Error response (model not found):
1293
+
1294
+ ```json
1295
+ {
1296
+ "status": "error",
1297
+ "message": "Model not found: Qwen3-0.6B-GGUF"
1298
+ }
1299
+ ```
1300
+
1301
+ In case of an error, the status will be `error` and the message will contain the error message.
1302
+
1303
+
1304
+
1305
+ ## `POST /v1/audio/generations`
1306
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1307
+
1308
+ Audio Generation API. You provide a text prompt and receive a generated audio clip. The loaded model decides the kind of audio: music with ACE-Step models (e.g. `ACE-Step-Music`), sound effects with ThinkSound models (e.g. `ThinkSound-SFX`).
1309
+
1310
+ This endpoint is not part of the OpenAI API (OpenAI's audio endpoints cover speech and transcription only), so it is a Lemonade-specific extension.
1311
+
1312
+ > **Performance:** generation runs on the GPU (Vulkan, ROCm, or CUDA) and takes from seconds (short sound effects) to minutes (full-length music) depending on duration and hardware.
1313
+
1314
+ ### Parameters
1315
+
1316
+ | Parameter | Required | Description |
1317
+ |-----------|----------|-------------|
1318
+ | `model` | Yes | The audio-generation model to use (e.g., `ThinkSound-SFX`, `ACE-Step-Music`). |
1319
+ | `prompt` | Yes | Text description of the music or sound effect to generate. For music, this is the style description: genre, mood, tempo, instruments, and voice. |
1320
+ | `lyrics` | No | Lyrics to sing (ACE-Step only). When present and not empty, the track is generated with vocals singing these lyrics. Omitting the field, an empty string, or the sentinel `[Instrumental]` (any case) produces an instrumental track. See [Lyrics](#lyrics) below for the expected format. |
1321
+ | `vocal_language` | No | BCP-47 language code of the lyrics, e.g. `en`, `fr`, `ja` (ACE-Step only). Default: `en`. |
1322
+ | `duration` | No | Length of the clip in seconds. Defaults to the backend's native default. |
1323
+ | `steps` | No | Number of inference steps. Lower is faster, higher can improve quality. |
1324
+ | `cfg` | No | Classifier-free guidance strength (ThinkSound only). |
1325
+ | `seed` | No | Random seed for reproducibility. |
1326
+ | `response_format` | No | Output encoding. Only formats the backend natively produces are accepted (currently `wav`); other values are rejected with `400 Bad Request`. Default: `wav`. |
1327
+
1328
+ ### Lyrics
1329
+
1330
+ ACE-Step vocals are a two-stage pipeline inside the backend: a language model first turns the style description and lyrics into audio codes, then the diffusion synthesizer renders those codes into audio. The instrumental path skips the language-model stage entirely, which also means lyrics embedded in the `prompt` field are treated as style text — they are never sung. Vocal generations take noticeably longer than instrumental ones of the same duration because of the extra language-model pass.
1331
+
1332
+ Format the `lyrics` value the way the ACE-Step authors recommend:
1333
+
1334
+ - Mark each song section with a structure tag on its own line: `[verse]`, `[chorus]`, `[bridge]`, `[intro]`, `[outro]`.
1335
+ - Write one sung phrase per line and separate sections with a blank line.
1336
+ - Describe the voice ("gentle female vocals", "raspy male baritone") in `prompt`, not in the lyrics.
1337
+ - Lyrics may be in any supported language; set `vocal_language` to match.
1338
+
1339
+ ### Response
1340
+
1341
+ On success the raw audio bytes are returned with the matching content type (`audio/wav`). On failure the response is JSON with an `error` object: `400` for invalid requests, `404` for unknown models, `500` when the backend reports an error, and `502` when the backend produces no output.
1342
+
1343
+ ### Example request
1344
+
1345
+ ```bash
1346
+ curl -X POST http://localhost:13305/v1/audio/generations \
1347
+ -H "Content-Type: application/json" \
1348
+ -d '{
1349
+ "model": "ThinkSound-SFX",
1350
+ "prompt": "glass shattering on a stone floor",
1351
+ "duration": 5,
1352
+ "seed": 42
1353
+ }' \
1354
+ --output clip.wav
1355
+ ```
1356
+
1357
+ ### Example request (music with vocals)
1358
+
1359
+ ```bash
1360
+ curl -X POST http://localhost:13305/v1/audio/generations \
1361
+ -H "Content-Type: application/json" \
1362
+ -d '{
1363
+ "model": "ACE-Step-Music",
1364
+ "prompt": "warm acoustic folk ballad, fingerpicked guitar, gentle female vocals",
1365
+ "lyrics": "[verse]\nMoonlight spills across the floor\nShadows dancing by the door\n\n[chorus]\nWe sing until the morning light\nCarried on the wind tonight",
1366
+ "duration": 60
1367
+ }' \
1368
+ --output song.wav
1369
+ ```
1370
+
1371
+ ## `POST /v1/3d/generations`
1372
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
1373
+
1374
+ 3D Generation API. You provide an input image and receive a textured 3D mesh as a glTF-binary (`.glb`) file. Serves TRELLIS models (e.g. `TRELLIS-3D`). The input image must be PNG, JPEG, BMP, or GIF.
1375
+
1376
+ This endpoint is not part of the OpenAI API, so it is a Lemonade-specific extension.
1377
+
1378
+ > **Performance:** 3D reconstruction runs on the GPU (Vulkan, ROCm, or CUDA) and takes on the order of minutes; higher cascade resolutions take longer.
1379
+
1380
+ ### Parameters
1381
+
1382
+ | Parameter | Required | Description |
1383
+ |-----------|----------|-------------|
1384
+ | `model` | Yes | The 3D-generation model to use (e.g., `TRELLIS-3D`). |
1385
+ | `image` | Yes | Base64-encoded input image (optionally a `data:` URL). |
1386
+ | `resolution` | No | Cascade resolution: `512`, `1024`, or `1536`. Default: `512`. |
1387
+ | `bg_removal` | No | Background removal mode: `threshold` or `birefnet`. Use `birefnet` for photos with real backgrounds. |
1388
+ | `uv` | No | UV atlas method: `xatlas` (default) or `box`. `xatlas` runs a full UV unwrap giving every face unique atlas space — best quality, but chart computation is superlinear in face count. `box` is a faster 6-plane projection with occlusion-aware bucket assignment and depth-tested rasterization; small texture artifacts remain possible in concave regions. |
1389
+ | `seed` | No | Random seed for reproducibility. |
1390
+ | `response_format` | No | Output encoding. Only formats the backend natively produces are accepted (currently `glb`); other values are rejected with `400 Bad Request`. Default: `glb`. |
1391
+
1392
+ ### Response
1393
+
1394
+ On success the raw mesh bytes are returned as `model/gltf-binary`. On failure the response is JSON with an `error` object: `400` for invalid requests, `404` for unknown models, `500` when the backend reports an error, and `502` when the backend produces no output.
1395
+
1396
+ ### Example request
1397
+
1398
+ ```bash
1399
+ curl -X POST http://localhost:13305/v1/3d/generations \
1400
+ -H "Content-Type: application/json" \
1401
+ -d "{
1402
+ \"model\": \"TRELLIS-3D\",
1403
+ \"image\": \"$(base64 -w0 input.png)\",
1404
+ \"resolution\": 512,
1405
+ \"seed\": 42
1406
+ }" \
1407
+ --output model.glb
1408
+ ```
1409
+
1410
+ ## `GET /v1/docs`
1411
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1412
+
1413
+ List the API reference pages bundled with the server. The documentation ships with the
1414
+ server, so it describes the version you are actually running and requires no internet
1415
+ access.
1416
+
1417
+ Fetch this index first, then read the pages it advertises. New pages can be added in
1418
+ future releases without breaking clients, because every entry carries its own URL.
1419
+
1420
+ ### Parameters
1421
+
1422
+ This endpoint does not take any parameters.
1423
+
1424
+ ### Example request
1425
+
1426
+ ```bash
1427
+ curl http://localhost:13305/v1/docs
1428
+ ```
1429
+
1430
+ ### Example response
1431
+
1432
+ ```json
1433
+ {
1434
+ "version": "11.8.0",
1435
+ "format": "text/markdown",
1436
+ "docs": [
1437
+ {
1438
+ "id": "api/README",
1439
+ "title": "Lemonade Endpoints Spec",
1440
+ "url": "/v1/docs/api/README",
1441
+ "bytes": 1272
1442
+ },
1443
+ {
1444
+ "id": "api/lemonade",
1445
+ "title": "Lemonade API",
1446
+ "url": "/v1/docs/api/lemonade",
1447
+ "bytes": 96847
1448
+ }
1449
+ ]
1450
+ }
1451
+ ```
1452
+
1453
+ `url` is returned with the same prefix used to request the index, so a client that queries
1454
+ `/api/v0/docs` receives `/api/v0/docs/...` URLs.
1455
+
1456
+ ## `GET /v1/docs/{page}`
1457
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1458
+
1459
+ Read one page, served as `Content-Type: text/markdown`. `{page}` is the `id` from the index,
1460
+ which mirrors the path used on the documentation website; the `.md` suffix is optional.
1461
+
1462
+ ### Example request
1463
+
1464
+ ```bash
1465
+ curl http://localhost:13305/v1/docs/api/lemonade
1466
+ ```
1467
+
1468
+ Unknown pages return `404`.
1469
+
1470
+ ### Reading the files directly
1471
+
1472
+ The same files are installed on disk, so they can be read without a running server:
1473
+
1474
+ | Platform | Path |
1475
+ |----------|------|
1476
+ | Windows (per-user) | `%LOCALAPPDATA%\lemonade_server\bin\resources\docs\` |
1477
+ | Windows (all users) | `C:\Program Files\Lemonade Server\bin\resources\docs\` |
1478
+ | macOS | `/Library/Application Support/Lemonade/resources/docs/` |
1479
+ | Linux (local) | `/usr/local/share/lemonade-server/resources/docs/` |
1480
+ | Linux (system) | `/usr/share/lemonade-server/resources/docs/` |
1481
+ | Linux (optional prefix) | `/opt/share/lemonade-server/resources/docs/` |
1482
+ | Linux (per-user) | `~/.local/share/lemonade-server/resources/docs/` |
1483
+
1484
+ ## `GET /v1/health`
1485
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1486
+
1487
+ Check the health of the server. This endpoint returns information about loaded models.
1488
+
1489
+ ### Parameters
1490
+
1491
+ This endpoint does not take any parameters.
1492
+
1493
+ ### Example request
1494
+
1495
+ ```bash
1496
+ curl http://localhost:13305/v1/health
1497
+ ```
1498
+
1499
+ ### Response format
1500
+
1501
+ ```json
1502
+ {
1503
+ "status": "ok",
1504
+ "version":"9.3.3",
1505
+ "websocket_port":9000,
1506
+ "model_loaded": "Llama-3.2-1B-Instruct-Hybrid",
1507
+ "all_models_loaded": [
1508
+ {
1509
+ "model_name": "Llama-3.2-1B-Instruct-Hybrid",
1510
+ "checkpoint": "amd/Llama-3.2-1B-Instruct-awq-g128-int4-asym-fp16-onnx-hybrid",
1511
+ "last_use": 1732123456.789,
1512
+ "type": "llm",
1513
+ "device": "gpu npu",
1514
+ "pinned": true,
1515
+ "recipe": "ryzenai-llm",
1516
+ "pid": 12345,
1517
+ "launch_command": [
1518
+ "~/.cache/lemonade/bin/ryzenai/npu/ryzenai-server.exe",
1519
+ "-m", "~/.cache/lemonade/models/Llama-3.2-1B-Instruct-Hybrid",
1520
+ "--port", "8001",
1521
+ "--ctx-size", "4096"
1522
+ ],
1523
+ "recipe_options": {
1524
+ "ctx_size": 4096
1525
+ },
1526
+ "backend_url": "http://127.0.0.1:8001/v1"
1527
+ },
1528
+ {
1529
+ "model_name": "nomic-embed-text-v1-GGUF",
1530
+ "checkpoint": "nomic-ai/nomic-embed-text-v1-GGUF:Q4_K_S",
1531
+ "last_use": 1732123450.123,
1532
+ "type": "embedding",
1533
+ "device": "gpu",
1534
+ "pinned": false,
1535
+ "recipe": "llamacpp",
1536
+ "pid": 12346,
1537
+ "launch_command": [
1538
+ "~/.cache/lemonade/bin/llamacpp/rocm-stable/llama-server.exe",
1539
+ "-m", "~/.cache/huggingface/hub/models--nomic-ai--nomic-embed-text-v1-GGUF/.../nomic-embed-text-v1.Q4_K_S.gguf",
1540
+ "--ctx-size", "8192",
1541
+ "--port", "8002",
1542
+ "--no-mmap"
1543
+ ],
1544
+ "recipe_options": {
1545
+ "ctx_size": 8192,
1546
+ "llamacpp_args": "--no-mmap",
1547
+ "llamacpp_backend": "rocm"
1548
+ },
1549
+ "backend_url": "http://127.0.0.1:8002/v1"
1550
+ }
1551
+ ],
1552
+ "pinned_models": {
1553
+ "transcription":0,
1554
+ "embedding":0,
1555
+ "image":0,
1556
+ "llm":1,
1557
+ "reranking":0,
1558
+ "tts":0
1559
+ },
1560
+ "max_models": {
1561
+ "transcription":1,
1562
+ "embedding":1,
1563
+ "image":1,
1564
+ "llm":1,
1565
+ "reranking":1,
1566
+ "tts":1
1567
+ },
1568
+ "telemetry": {
1569
+ "enabled": false
1570
+ },
1571
+ "update_check_done": true
1572
+ }
1573
+ ```
1574
+
1575
+ **Field Descriptions:**
1576
+
1577
+ - `status` - Server health status, always `"ok"`
1578
+ - `version` - Version number of Lemonade Server
1579
+ - `model_loaded` - Model name of the most recently accessed model
1580
+ - `update_check_done` - Whether the background HuggingFace model update check has completed at startup. Poll this field after server start to know when `update_available` fields are ready.
1581
+ - `all_models_loaded` - Array of all currently loaded models with details:
1582
+ - `model_name` - Name of the loaded model
1583
+ - `checkpoint` - Full checkpoint identifier
1584
+ - `last_use` - Unix timestamp of last access (load or inference)
1585
+ - `type` - Model type: `"llm"`, `"embedding"`, `"reranking"`, `"transcription"`, `"image"`, or `"tts"`
1586
+ - `device` - Space-separated device list: `"cpu"`, `"gpu"`, `"npu"`, or combinations like `"gpu npu"`
1587
+ - `pinned` - Boolean indicating if the model is currently pinned to prevent auto-eviction
1588
+ - `is_busy` - Boolean indicating if the model has active requests or maintenance in progress
1589
+ - `is_streaming` - Boolean indicating if the model is actively generating output tokens (true after first chunk arrives, false when all streaming requests complete)
1590
+ - `backend_url` - URL of the backend server process handling this model (useful for debugging)
1591
+ - `pid` - The Process ID (PID) of the backend engine handling this model
1592
+ - `launch_command` - *(optional)* The command used to start the backend engine, as an array with the program first and its arguments after it. Every local backend has one. Cloud models don't, because they don't start a program. The values shown are the ones actually used, so a `ctx_size` of `auto` appears here as a real number, and any flags Lemonade added on its own are included.
1593
+ - `recipe` - Backend/device recipe used to load the model (e.g., `"ryzenai-llm"`, `"llamacpp"`, `"flm"`)
1594
+ - `recipe_options` - Options used to load the model (e.g., `"ctx_size"`, `"llamacpp_backend"`, `"llamacpp_args"`, `"whispercpp_args"`)
1595
+ - `pinned_models` - Counts of pinned models currently loaded in memory per model type (e.g., `llm`, `embedding`, etc.)
1596
+ - `max_models` - Maximum number of models that can be loaded simultaneously per type (set via `max_loaded_models` in [Server Configuration](../guide/configuration/README.md)):
1597
+ - `llm` - Maximum LLM/chat models
1598
+ - `embedding` - Maximum embedding models
1599
+ - `reranking` - Maximum reranking models
1600
+ - `transcription` - Maximum speech-to-text models
1601
+ - `image` - Maximum image models
1602
+ - `tts` - Maximum text-to-speech models
1603
+ - `websocket_port` - *(optional)* Port of the WebSocket server for the [Realtime Audio Transcription API](./openai.md#ws-realtime) and [Log Streaming API](#log-streaming-api-websocket). Only present when the WebSocket server is running. The port is OS-assigned or set via `--websocket-port`.
1604
+ - `telemetry` - Structured telemetry state object:
1605
+ - `enabled` - Boolean indicating if telemetry collection is active
1606
+ - `captures` - *(optional)* Array of captured telemetry components (e.g., `["inputs", "outputs", "thinking"]`), only present when `enabled` is `true`.
1607
+
1608
+ ## `GET /v1/stats`
1609
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1610
+
1611
+ Performance statistics from the last request.
1612
+
1613
+ ### Parameters
1614
+
1615
+ This endpoint does not take any parameters.
1616
+
1617
+ ### Example request
1618
+
1619
+ ```bash
1620
+ curl http://localhost:13305/v1/stats
1621
+ ```
1622
+
1623
+ ### Response format
1624
+
1625
+ ```json
1626
+ {
1627
+ "time_to_first_token": 2.14,
1628
+ "tokens_per_second": 33.33,
1629
+ "input_tokens": 128,
1630
+ "output_tokens": 5,
1631
+ "prompt_tokens": 9,
1632
+ "cache_tokens": 96,
1633
+ "request_count_total": 12,
1634
+ "input_tokens_total": 1536,
1635
+ "output_tokens_total": 60,
1636
+ "prompt_tokens_total": 108,
1637
+ "cache_tokens_total": 1152,
1638
+ "routing_decisions_total": 4,
1639
+ "routing_switches_total": 1
1640
+ }
1641
+ ```
1642
+
1643
+ **Field Descriptions:**
1644
+
1645
+ - `time_to_first_token` - Time in seconds until the first token was generated
1646
+ - `tokens_per_second` - Generation speed in tokens per second
1647
+ - `input_tokens` - Number of tokens processed
1648
+ - `output_tokens` - Number of tokens generated
1649
+ - `prompt_tokens` - Total prompt tokens including cached tokens
1650
+ - `cache_tokens` - Prompt tokens served from the backend's prefix cache on the last request (llama.cpp `timings.cache_n`, or `usage.prompt_tokens_details.cached_tokens` / Responses-API `input_tokens_details.cached_tokens` from OpenAI-compatible cloud providers). `null` when the last request did not report cache usage
1651
+ - `*_total` - Cumulative counters since server start
1652
+ - `routing_decisions_total` - Routing decisions made by `collection.router` dispatch
1653
+ - `routing_switches_total` - Routing decisions that changed a conversation's routed model (a proxy for route ping-pong; conversations are identified by a hash of the system prompt and first user message)
1654
+
1655
+ ## `GET /v1/system-stats`
1656
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1657
+
1658
+ Current host resource usage as measured by the Lemonade Server process. This endpoint is useful for first-party clients and dashboards that need lightweight runtime telemetry without scraping Prometheus.
1659
+
1660
+ ### Parameters
1661
+
1662
+ This endpoint does not take any parameters.
1663
+
1664
+ ### Example request
1665
+
1666
+ ```bash
1667
+ curl http://localhost:13305/v1/system-stats
1668
+ ```
1669
+
1670
+ ### Response format
1671
+
1672
+ ```json
1673
+ {
1674
+ "cpu_percent": 12.3,
1675
+ "memory_gb": 8.4,
1676
+ "gpu_percent": 45.0,
1677
+ "vram_gb": 2.1,
1678
+ "npu_percent": null
1679
+ }
1680
+ ```
1681
+
1682
+ **Field Descriptions:**
1683
+
1684
+ - `cpu_percent` - System CPU utilization percentage, or `null` when unavailable
1685
+ - `memory_gb` - System RAM currently in use, in GiB
1686
+ - `gpu_percent` - GPU utilization percentage, or `null` when unavailable
1687
+ - `vram_gb` - GPU memory currently in use, in GiB, or `null` when unavailable
1688
+ - `npu_percent` - NPU utilization percentage, or `null` when unavailable
1689
+
1690
+ GPU, VRAM, and NPU telemetry availability depends on the operating system and installed drivers. Unsupported values are returned as `null`.
1691
+
1692
+ ## `GET /metrics`
1693
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1694
+
1695
+ Prometheus scrape endpoint for Lemonade Server. The endpoint returns Prometheus text exposition format and is intended to be scraped by Prometheus, not by Grafana directly.
1696
+
1697
+ Unlike most Lemonade API endpoints, `/metrics` is root-level only. It is not mounted under `/api/v0/`, `/api/v1/`, `/v0/`, or `/v1/`.
1698
+
1699
+ `HEAD /metrics` is also supported and returns `200 OK` with an empty body.
1700
+
1701
+ ### Authentication
1702
+
1703
+ If `LEMONADE_API_KEY` is set, `/metrics` requires bearer authentication. Either the regular API key or `LEMONADE_ADMIN_API_KEY` is accepted.
1704
+
1705
+ If only `LEMONADE_ADMIN_API_KEY` is set and `LEMONADE_API_KEY` is unset, `/metrics` is accessible without authentication, matching regular API endpoint behavior.
1706
+
1707
+ ### Polling and Refresh Rate
1708
+
1709
+ The `/metrics` endpoint has no internal refresh timer. It renders the latest server state at the moment it is scraped.
1710
+
1711
+ Polling frequency is configured in Prometheus via `scrape_interval`, for example:
1712
+
1713
+ ```yaml
1714
+ global:
1715
+ scrape_interval: 10s
1716
+ ```
1717
+
1718
+ Grafana queries Prometheus. Grafana's dashboard refresh controls how often panels query Prometheus, but it does not control how often Prometheus scrapes Lemonade.
1719
+
1720
+ ### Example request
1721
+
1722
+ ```bash
1723
+ curl http://localhost:13305/metrics
1724
+ ```
1725
+
1726
+ With API-key auth:
1727
+
1728
+ ```bash
1729
+ curl http://localhost:13305/metrics \
1730
+ -H "Authorization: Bearer $LEMONADE_API_KEY"
1731
+ ```
1732
+
1733
+ ### Response format
1734
+
1735
+ The response uses Prometheus text exposition format:
1736
+
1737
+ ```text
1738
+ # HELP lemonade_server_up Whether the Lemonade server is running.
1739
+ # TYPE lemonade_server_up gauge
1740
+ lemonade_server_up 1
1741
+ # HELP lemonade_server_info Lemonade server build information.
1742
+ # TYPE lemonade_server_info gauge
1743
+ lemonade_server_info{version="10.4.0"} 1
1744
+ ```
1745
+
1746
+ Content type:
1747
+
1748
+ ```text
1749
+ text/plain; version=0.0.4; charset=utf-8
1750
+ ```
1751
+
1752
+ ### Lemonade Metric Families
1753
+
1754
+ The authoritative metric-family list is generated by the `/metrics` implementation in [`src/cpp/server/server.cpp`](../../src/cpp/server/server.cpp). Search for `handle_metrics` and `metrics.describe(...)` to see the current names, types, labels, and descriptions.
1755
+
1756
+ Unsupported, unavailable, null, NaN, and infinity values are omitted rather than emitted as samples.
1757
+
1758
+ ### llama.cpp Backend Metrics
1759
+
1760
+ When a loaded model uses the `llamacpp` recipe, Lemonade makes a best-effort scrape of the loaded backend process's private `/metrics` endpoint. Backend scrape failures do not fail the Lemonade `/metrics` response.
1761
+
1762
+ Scraped llama.cpp metrics are normalized under the `lemonade_llamacpp_*` prefix and labeled with the same Lemonade model metadata used by `lemonade_model_info`.
1763
+
1764
+ Lemonade starts llama.cpp backends with metrics enabled so these backend metrics are available whenever the backend supports them.
1765
+
1766
+ ## `GET /v1/system-info`
1767
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1768
+
1769
+ System information endpoint that provides complete hardware details and device enumeration.
1770
+
1771
+ ### Example request
1772
+
1773
+ ```bash
1774
+ curl "http://localhost:13305/v1/system-info"
1775
+ ```
1776
+
1777
+ ### Response format
1778
+
1779
+ ```json
1780
+ {
1781
+ "OS Version": "Windows-10-10.0.26100-SP0",
1782
+ "Processor": "AMD Ryzen AI 9 HX 375 w/ Radeon 890M",
1783
+ "Physical Memory": "32.0 GB",
1784
+ "OEM System": "ASUS Zenbook S 16",
1785
+ "BIOS Version": "1.0.0",
1786
+ "CPU Max Clock": "5100 MHz",
1787
+ "Windows Power Setting": "Balanced",
1788
+ "model_storage": {
1789
+ "path": "/path/to/models",
1790
+ "used_bytes": 123456789,
1791
+ "total_bytes": 987654321,
1792
+ "free_bytes": 864197532
1793
+ },
1794
+ "devices": {
1795
+ "cpu": {
1796
+ "name": "AMD Ryzen AI 9 HX 375 w/ Radeon 890M",
1797
+ "cores": 12,
1798
+ "threads": 24,
1799
+ "available": true,
1800
+ "family": "x86_64"
1801
+ },
1802
+ "amd_gpu": [
1803
+ {
1804
+ "name": "AMD Radeon(TM) 890M Graphics",
1805
+ "vram_gb": 0.5,
1806
+ "available": true,
1807
+ "family": "gfx1150"
1808
+ }
1809
+ ],
1810
+ "amd_npu": {
1811
+ "name": "AMD Ryzen AI 9 HX 375 w/ Radeon 890M",
1812
+ "power_mode": "Default",
1813
+ "available": true,
1814
+ "family": "XDNA2"
1815
+ }
1816
+ },
1817
+ "recipes": {
1818
+ "llamacpp": {
1819
+ "default_backend": "vulkan",
1820
+ "backends": {
1821
+ "vulkan": {
1822
+ "devices": ["cpu", "amd_gpu"],
1823
+ "state": "installed",
1824
+ "message": "",
1825
+ "action": "",
1826
+ "version": "b7869"
1827
+ },
1828
+ "rocm": {
1829
+ "devices": ["amd_gpu"],
1830
+ "state": "installable",
1831
+ "message": "Backend is supported but not installed.",
1832
+ "action": "lemonade backends install llamacpp:rocm"
1833
+ },
1834
+ "metal": {
1835
+ "devices": [],
1836
+ "state": "unsupported",
1837
+ "message": "Requires macOS",
1838
+ "action": ""
1839
+ },
1840
+ "cpu": {
1841
+ "devices": ["cpu"],
1842
+ "state": "update_required",
1843
+ "message": "Backend update is required before use.",
1844
+ "action": "lemonade backends install llamacpp:cpu"
1845
+ }
1846
+ }
1847
+ },
1848
+ "whispercpp": {
1849
+ "default_backend": "default",
1850
+ "backends": {
1851
+ "default": {
1852
+ "devices": ["cpu"],
1853
+ "state": "installable",
1854
+ "message": "Backend is supported but not installed.",
1855
+ "action": "lemonade backends install whispercpp:default"
1856
+ }
1857
+ }
1858
+ },
1859
+ "sd-cpp": {
1860
+ "default_backend": "default",
1861
+ "backends": {
1862
+ "default": {
1863
+ "devices": ["cpu"],
1864
+ "state": "installable",
1865
+ "message": "Backend is supported but not installed.",
1866
+ "action": "lemonade backends install sd-cpp:default"
1867
+ }
1868
+ }
1869
+ },
1870
+ "flm": {
1871
+ "default_backend": "default",
1872
+ "backends": {
1873
+ "default": {
1874
+ "devices": ["amd_npu"],
1875
+ "state": "installed",
1876
+ "message": "",
1877
+ "action": "",
1878
+ "version": "1.2.0"
1879
+ }
1880
+ }
1881
+ },
1882
+ "ryzenai-llm": {
1883
+ "default_backend": "default",
1884
+ "backends": {
1885
+ "default": {
1886
+ "devices": ["amd_npu"],
1887
+ "state": "installed",
1888
+ "message": "",
1889
+ "action": ""
1890
+ }
1891
+ }
1892
+ }
1893
+ }
1894
+ }
1895
+ ```
1896
+
1897
+ **Field Descriptions:**
1898
+
1899
+ - **System fields:**
1900
+ - `OS Version` - Operating system name and version
1901
+ - `Processor` - CPU model name
1902
+ - `Physical Memory` - Total RAM
1903
+ - `OEM System` - System/laptop model name (Windows only)
1904
+ - `BIOS Version` - BIOS information (Windows only)
1905
+ - `CPU Max Clock` - Maximum CPU clock speed (Windows only)
1906
+ - `Windows Power Setting` - Current power plan (Windows only)
1907
+
1908
+ - `model_storage` - Drive-level storage information for the active configured model storage path. Values are reported in bytes for storage meters; this is not a recursive sum of Lemonade model files.
1909
+ - `path` - Active model storage path from server configuration
1910
+ - `used_bytes` - Used bytes on the model-storage drive
1911
+ - `total_bytes` - Total capacity of the model-storage drive
1912
+ - `free_bytes` - Free bytes available to the Lemonade Server process on the model-storage drive
1913
+
1914
+ - `devices` - Hardware devices detected on the system (no software/support information)
1915
+ - `cpu` - CPU information (name, cores, threads)
1916
+ - `amd_gpu` - Array of AMD GPUs, both integrated and discrete (if present)
1917
+ - `nvidia_gpu` - Array of NVIDIA GPUs (if present)
1918
+ - `amd_npu` - AMD NPU device (if present)
1919
+
1920
+ - `recipes` - Software recipes and their backend support status
1921
+ - Each recipe (e.g., `llamacpp`, `whispercpp`, `flm`) contains:
1922
+ - `default_backend` - Preferred backend selected by server policy for this system (present when at least one backend is not `unsupported`)
1923
+ - `backends` - Available backends for this recipe
1924
+ - Each backend contains:
1925
+ - `devices` - List of devices **on this system** that support this backend (empty if not supported)
1926
+ - `state` - Backend lifecycle state: `unsupported`, `installable`, `update_required`, or `installed`
1927
+ - `message` - Human-readable status text for GUI and CLI users. Required for `unsupported`, `installable`, and `update_required`; empty for `installed`.
1928
+ - `action` - Actionable user instruction string. For install/update cases this is typically an exact CLI command; for other states it may be empty or another actionable value (for example, a URL).
1929
+ - `version` - Installed or configured backend version (when available)
1930
+ - `cloud` - Cloud OpenAI-compatible providers configured on this server (omitted when no providers are installed). Contains:
1931
+ - `providers` - Array, one entry per installed provider:
1932
+ - `name` - Provider name used as the model-name prefix (e.g. `fireworks`).
1933
+ - `base_url` - Persisted base URL from `config.json`.
1934
+ - `auth_header_name` - Header this provider's API key is sent in (default `Authorization`).
1935
+ - `auth_header_prefix` - Value prefix placed before the key (default `Bearer `).
1936
+ - `wire_format` - Request/response shape this provider speaks: `openai` (default) or `anthropic`.
1937
+ - `env_var` - Canonical environment variable name for this provider's API key (e.g. `LEMONADE_FIREWORKS_API_KEY`). The variable's *name* is reported, never its value.
1938
+ - `env_var_set` - `true` if the env var is set in `lemond`'s environment.
1939
+ - `runtime_key_set` - `true` if an in-memory key has been supplied via `POST /v1/cloud/auth` this session.
1940
+ - `models_discovered` - Number of chat-capable models currently in the catalog for this provider.
1941
+
1942
+ ## `POST /v1/install`
1943
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
1944
+
1945
+ Install or update a backend for a specific recipe/backend pair, **or** register a cloud OpenAI-compatible provider. The request body is dispatched by the `backend` field: any value other than `"cloud"` is treated as a local backend install.
1946
+
1947
+ ### Install a local backend
1948
+
1949
+ If the backend is already installed but outdated, this endpoint updates it to the configured version.
1950
+
1951
+ | Parameter | Required | Description |
1952
+ |-----------|----------|-------------|
1953
+ | `recipe` | Yes | Recipe name (for example, `llamacpp`, `flm`, `whispercpp`, `sd-cpp`, `ryzenai-llm`) |
1954
+ | `backend` | Yes | Backend name within the recipe (for example, `vulkan`, `rocm`, `cpu`, `default`) |
1955
+ | `stream` | No | If `true`, returns Server-Sent Events with progress. Defaults to `false`. |
1956
+ | `force` | No | If `true`, bypasses hardware filtering for `unsupported` backends and attempts installation anyway. Defaults to `false`. |
1957
+
1958
+ Example request:
1959
+
1960
+ ```bash
1961
+ curl -X POST http://localhost:13305/v1/install \
1962
+ -H "Content-Type: application/json" \
1963
+ -d '{
1964
+ "recipe": "llamacpp",
1965
+ "backend": "vulkan",
1966
+ "stream": false
1967
+ }'
1968
+ ```
1969
+
1970
+ Response format:
1971
+
1972
+ ```json
1973
+ {
1974
+ "status":"success",
1975
+ "recipe":"llamacpp",
1976
+ "backend":"vulkan"
1977
+ }
1978
+ ```
1979
+
1980
+ In case of an error, returns an `error` field with details.
1981
+
1982
+ ### Install a cloud provider
1983
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
1984
+
1985
+ Registers an OpenAI-compatible chat provider. The base URL is persisted to `config.json`; the optional `api_key` lives in `lemond` process memory only (cleared on restart). See the [Cloud Offload guide](../guide/configuration/cloud.md) for the full workflow.
1986
+
1987
+ | Parameter | Required | Description |
1988
+ |-----------|----------|-------------|
1989
+ | `backend` | Yes | Must be the literal string `"cloud"`. |
1990
+ | `provider` | Yes | Short identifier (e.g. `fireworks`). Used as the model-name prefix. |
1991
+ | `base_url` | Yes | OpenAI-compatible base URL ending in `/v1` (or equivalent). |
1992
+ | `api_key` | No | Optional. If set, stored in process memory; honors env-wins precedence (see `/v1/cloud/auth`). |
1993
+ | `allow_insecure_http` | No | Default `false`. Must be `true` to send an API key to an `http://` base URL. |
1994
+ | `auth_header_name` | No | Header carrying the API key. Must be a valid HTTP header name. Default `"Authorization"`. |
1995
+ | `auth_header_prefix` | No | Value prefix before the key. Default `"Bearer "`; pass `""` for gateways that expect the bare key. |
1996
+ | `wire_format` | No | `"openai"` (default) or `"anthropic"`. An `"anthropic"` provider is served from `POST /v1/messages` only; any other value returns 400. |
1997
+
1998
+ Optional fields are applied only when present in the request body. Re-installing a provider without them keeps its stored values, so updating just the `base_url` never resets a custom auth header or the `allow_insecure_http` opt-in.
1999
+
2000
+ Example request:
2001
+
2002
+ ```bash
2003
+ curl -X POST http://localhost:13305/v1/install \
2004
+ -H "Content-Type: application/json" \
2005
+ -d '{
2006
+ "backend": "cloud",
2007
+ "provider": "fireworks",
2008
+ "base_url": "https://api.fireworks.ai/inference/v1"
2009
+ }'
2010
+ ```
2011
+
2012
+ Response format:
2013
+
2014
+ ```json
2015
+ {
2016
+ "status": "success",
2017
+ "backend": "cloud",
2018
+ "provider": "fireworks",
2019
+ "base_url": "https://api.fireworks.ai/inference/v1",
2020
+ "auth_header_name": "Authorization",
2021
+ "auth_header_prefix": "Bearer ",
2022
+ "wire_format": "openai",
2023
+ "models_discovered": 12,
2024
+ "auth_state": {
2025
+ "env_var_set": true,
2026
+ "runtime_key_set": false
2027
+ }
2028
+ }
2029
+ ```
2030
+
2031
+ `models_discovered` is `0` when no API key is resolvable. If `api_key` is supplied but the provider's env var is also set, the response includes a `warning` string explaining the env var took precedence.
2032
+
2033
+ ## `POST /v1/install/dry-run`
2034
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
2035
+
2036
+ Resolve the backend install metadata that [`POST /v1/install`](#post-v1install)
2037
+ would use for a recipe/backend pair, without downloading or installing the
2038
+ backend asset. Nothing is installed and no existing installation is modified.
2039
+
2040
+ Resolution uses the normal backend install-parameter machinery. It may consult
2041
+ local configuration and, if a backend version is configured as `latest`, query
2042
+ GitHub release metadata. The endpoint does not download the backend asset and
2043
+ does not check whether the returned asset URL exists.
2044
+
2045
+ The `arch` parameter mocks ROCm GPU architecture detection while the install
2046
+ parameters are resolved. This makes the endpoint useful in CI for checking
2047
+ architecture-to-asset resolution on hardware that is not present on the runner.
2048
+ The repository's `test/server_gfx_topology.py` uses the endpoint for this
2049
+ resolution step and separately checks the resulting release URLs or
2050
+ split-archive manifests.
2051
+
2052
+ The endpoint is available at:
2053
+
2054
+ - `/v1/install/dry-run`
2055
+ - `/api/v1/install/dry-run`
2056
+ - `/v0/install/dry-run`
2057
+ - `/api/v0/install/dry-run`
2058
+
2059
+ ### Parameters
2060
+
2061
+ | Parameter | Required | Description |
2062
+ |-----------|----------|-------------|
2063
+ | `recipe` | Yes | Recipe name, for example `llamacpp`, `whispercpp`, or `vllm`. |
2064
+ | `backend` | Yes | Backend name within the recipe, for example `vulkan`, `rocm`, or `rocm-nightly`. |
2065
+ | `arch` | No | ROCm GPU architecture to use for this call, for example `gfx1201`. When provided, it overrides ROCm architecture detection while install parameters are resolved. When omitted, normal host detection is used; if resolution succeeds, `arch` is returned as `""` and `supported` is `true`. |
2066
+
2067
+ ### Example request
2068
+
2069
+ ```bash
2070
+ curl -X POST http://localhost:13305/v1/install/dry-run \
2071
+ -H "Content-Type: application/json" \
2072
+ -d '{
2073
+ "recipe": "whispercpp",
2074
+ "backend": "rocm",
2075
+ "arch": "gfx1201"
2076
+ }'
2077
+ ```
2078
+
2079
+ ### Response format
2080
+
2081
+ ```json
2082
+ {
2083
+ "recipe": "whispercpp",
2084
+ "backend": "rocm",
2085
+ "arch": "gfx1201",
2086
+ "repo": "lemonade-sdk/whisper.cpp-rocm",
2087
+ "version": "v1.8.4",
2088
+ "filename": "whisper-v1.8.4-linux-rocm-gfx120X.tar.gz",
2089
+ "url": "https://github.com/lemonade-sdk/whisper.cpp-rocm/releases/download/v1.8.4/whisper-v1.8.4-linux-rocm-gfx120X.tar.gz",
2090
+ "supports_split_archive": false,
2091
+ "supported": true
2092
+ }
2093
+ ```
2094
+
2095
+ | Field | Description |
2096
+ |-------|-------------|
2097
+ | `recipe`, `backend`, `arch` | Echo the requested values. If `arch` was omitted, it is returned as an empty string. |
2098
+ | `repo`, `version`, `filename` | Install parameters produced by the backend-specific resolver. The default version pin comes from `backend_versions.json`; runtime version policy can override it. |
2099
+ | `url` | GitHub release-download URL constructed from `repo`, `version`, and `filename`. The endpoint does not check this URL. |
2100
+ | `supported` | Whether Lemonade's local recipe/backend support matrix accepts the requested `arch`. This is not a release-asset existence check. When `arch` is omitted, it is `true` on a successful response. |
2101
+ | `supports_split_archive` | Whether the recipe supports assets published as multiple archive parts. When `true`, the real download path can consult a `.partcount` manifest. |
2102
+
2103
+ A device ISA may resolve to a family target name used by the release repository.
2104
+ For example, `gfx1201` resolves to the `gfx120X` family used in the Whisper
2105
+ filename above. That mapping is defined by `rocm_asset_families` in
2106
+ `backend_versions.json`.
2107
+
2108
+ An explicit architecture outside Lemonade's support matrix can still produce
2109
+ install metadata; in that case `supported` is `false`. Callers that need to
2110
+ verify the release asset itself must check the returned URL, or the corresponding
2111
+ split-archive manifest, separately.
2112
+
2113
+ ### Error responses
2114
+
2115
+ | Status | Condition |
2116
+ |--------|-----------|
2117
+ | `400` | `recipe` or `backend` is missing or empty. |
2118
+ | `500` | The body is invalid JSON or install-parameter resolution fails, for example because the recipe/backend pair is unknown, the platform is unsupported, required architecture detection is unavailable, or version resolution fails. |
2119
+
2120
+ Error responses contain an `error` string. If `arch` was parsed before the
2121
+ failure, the response may also include that `arch` value.
2122
+
2123
+ ## `POST /v1/uninstall`
2124
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2125
+
2126
+ Uninstall a backend for a specific recipe/backend pair, **or** remove a cloud provider. Dispatched by the `backend` field, mirroring `/v1/install`.
2127
+
2128
+ ### Uninstall a local backend
2129
+
2130
+ If loaded models are using that backend, they are unloaded first.
2131
+
2132
+ | Parameter | Required | Description |
2133
+ |-----------|----------|-------------|
2134
+ | `recipe` | Yes | Recipe name |
2135
+ | `backend` | Yes | Backend name |
2136
+
2137
+ Example request:
2138
+
2139
+ ```bash
2140
+ curl -X POST http://localhost:13305/v1/uninstall \
2141
+ -H "Content-Type: application/json" \
2142
+ -d '{
2143
+ "recipe": "llamacpp",
2144
+ "backend": "vulkan"
2145
+ }'
2146
+ ```
2147
+
2148
+ Response format:
2149
+
2150
+ ```json
2151
+ {
2152
+ "status":"success",
2153
+ "recipe":"llamacpp",
2154
+ "backend":"vulkan"
2155
+ }
2156
+ ```
2157
+
2158
+ In case of an error, returns an `error` field with details.
2159
+
2160
+ ### Uninstall a cloud provider
2161
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
2162
+
2163
+ Removes the provider record from `config.json`, drops its in-memory API key (if any), and evicts every discovered model for that provider from the cache. Returns 404 if the provider was never installed.
2164
+
2165
+ | Parameter | Required | Description |
2166
+ |-----------|----------|-------------|
2167
+ | `backend` | Yes | Must be the literal string `"cloud"`. |
2168
+ | `provider` | Yes | Installed provider name. |
2169
+
2170
+ Example request:
2171
+
2172
+ ```bash
2173
+ curl -X POST http://localhost:13305/v1/uninstall \
2174
+ -H "Content-Type: application/json" \
2175
+ -d '{
2176
+ "backend": "cloud",
2177
+ "provider": "fireworks"
2178
+ }'
2179
+ ```
2180
+
2181
+ Response format:
2182
+
2183
+ ```json
2184
+ {
2185
+ "status": "success",
2186
+ "backend": "cloud",
2187
+ "provider": "fireworks",
2188
+ "models_evicted": 12
2189
+ }
2190
+ ```
2191
+
2192
+ ## `POST /v1/cloud/auth`
2193
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
2194
+
2195
+ Set an in-memory API key for a previously-installed cloud provider, and trigger a refresh of that provider's discovered model list. The key lives in `lemond` process memory only — it is never written to disk and is cleared on `lemond` restart. For persistence across restarts, set `LEMONADE_<PROVIDER>_API_KEY` in `lemond`'s environment instead.
2196
+
2197
+ ### Authentication precedence
2198
+
2199
+ If `LEMONADE_<PROVIDER>_API_KEY` is set in `lemond`'s environment, the env var takes precedence and this endpoint returns **409 Conflict** without storing the supplied key. This is the safety guarantee that lets an operator provision a "house" key via env without worrying about a client silently overriding it.
2200
+
2201
+ ### Parameters
2202
+
2203
+ | Parameter | Required | Description |
2204
+ |-----------|----------|-------------|
2205
+ | `provider` | Yes | Installed provider name. |
2206
+ | `api_key` | Yes | API key to store in `lemond` process memory. |
2207
+
2208
+ ### Example request
2209
+
2210
+ ```bash
2211
+ curl -X POST http://localhost:13305/v1/cloud/auth \
2212
+ -H "Content-Type: application/json" \
2213
+ -d '{
2214
+ "provider": "fireworks",
2215
+ "api_key": "fw-XXXXX"
2216
+ }'
2217
+ ```
2218
+
2219
+ ### Response format (success — 200)
2220
+
2221
+ ```json
2222
+ {
2223
+ "provider": "fireworks",
2224
+ "auth_state": {
2225
+ "env_var_set": false,
2226
+ "runtime_key_set": true
2227
+ },
2228
+ "models_discovered": 12
2229
+ }
2230
+ ```
2231
+
2232
+ ### Response format (env-var conflict — 409)
2233
+
2234
+ ```json
2235
+ {
2236
+ "error": {
2237
+ "type": "auth_conflict",
2238
+ "env_var": "LEMONADE_FIREWORKS_API_KEY",
2239
+ "message": "LEMONADE_FIREWORKS_API_KEY is set in the lemond process; the env var takes precedence and the supplied API key was not stored."
2240
+ }
2241
+ }
2242
+ ```
2243
+
2244
+ ### Other error responses
2245
+
2246
+ | Status | Cause |
2247
+ |---|---|
2248
+ | `400` | Body is missing `provider` or `api_key`, or one of them is empty. |
2249
+ | `404` | Provider is not installed. Call `POST /v1/install` with `backend:"cloud"` first. |
2250
+
2251
+ ## `DELETE /v1/cloud/auth/{provider}`
2252
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
2253
+
2254
+ Clear the in-memory API key for a provider. Any env-var-based key (`LEMONADE_<PROVIDER>_API_KEY`) remains in effect. If no env-var key is set, the provider's discovered models are evicted from the catalog since they are no longer authenticatable.
2255
+
2256
+ ### Example request
2257
+
2258
+ ```bash
2259
+ curl -X DELETE http://localhost:13305/v1/cloud/auth/fireworks
2260
+ ```
2261
+
2262
+ ### Response format
2263
+
2264
+ ```json
2265
+ {
2266
+ "provider": "fireworks",
2267
+ "cleared_runtime_key": true,
2268
+ "auth_state": {
2269
+ "env_var_set": false,
2270
+ "runtime_key_set": false
2271
+ }
2272
+ }
2273
+ ```
2274
+
2275
+ `cleared_runtime_key` is `false` when no in-memory key was present (e.g., the only key was from the env var).
2276
+
2277
+ ## Log Streaming API (WebSocket)
2278
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2279
+
2280
+ Stream server logs over WebSocket. Clients connect, send a subscribe message, and receive a snapshot of recent log history followed by live log entries as they occur.
2281
+
2282
+ ### Connection
2283
+
2284
+ The WebSocket server shares the same port as the [Realtime Audio Transcription API](./openai.md#ws-realtime). Discover the port via the [`/v1/health`](#get-v1health) endpoint (`websocket_port` field), then connect:
2285
+
2286
+ ```
2287
+ ws://localhost:<websocket_port>/logs/stream
2288
+ ```
2289
+
2290
+ After connecting, send a `logs.subscribe` message to start receiving logs.
2291
+
2292
+ ### Client → Server Messages
2293
+
2294
+ | Message Type | Description |
2295
+ |--------------|-------------|
2296
+ | `logs.subscribe` | Subscribe to log stream. Optional `after_seq` field to resume from a specific sequence number. |
2297
+
2298
+ ### Server → Client Messages
2299
+
2300
+ | Message Type | Description |
2301
+ |--------------|-------------|
2302
+ | `logs.snapshot` | Initial batch of retained log entries (up to 5000). Sent once after subscribing. |
2303
+ | `logs.entry` | A single live log entry. Sent as new log lines are emitted. |
2304
+ | `error` | Error message (e.g., invalid subscribe request). |
2305
+
2306
+ ### Example: Subscribe to Logs
2307
+
2308
+ Subscribe from the beginning (full backlog):
2309
+
2310
+ ```json
2311
+ {
2312
+ "type": "logs.subscribe",
2313
+ "after_seq": null
2314
+ }
2315
+ ```
2316
+
2317
+ Resume after a known sequence number (e.g., on reconnect):
2318
+
2319
+ ```json
2320
+ {
2321
+ "type": "logs.subscribe",
2322
+ "after_seq": 1042
2323
+ }
2324
+ ```
2325
+
2326
+ ### Example: Snapshot Response
2327
+
2328
+ ```json
2329
+ {
2330
+ "type": "logs.snapshot",
2331
+ "entries": [
2332
+ {
2333
+ "seq": 1,
2334
+ "timestamp": "2025-03-30 14:22:01.123",
2335
+ "severity": "Info",
2336
+ "tag": "Server",
2337
+ "line": "2025-03-30 14:22:01.123 [Info] (Server) Starting Lemonade Server..."
2338
+ }
2339
+ ]
2340
+ }
2341
+ ```
2342
+
2343
+ ### Example: Live Entry
2344
+
2345
+ ```json
2346
+ {
2347
+ "type": "logs.entry",
2348
+ "entry": {
2349
+ "seq": 1043,
2350
+ "timestamp": "2025-03-30 14:22:05.456",
2351
+ "severity": "Info",
2352
+ "tag": "Router",
2353
+ "line": "2025-03-30 14:22:05.456 [Info] (Router) Model loaded successfully"
2354
+ }
2355
+ }
2356
+ ```
2357
+
2358
+ ### Log Entry Fields
2359
+
2360
+ | Field | Type | Description |
2361
+ |-------|------|-------------|
2362
+ | `seq` | integer | Monotonically increasing sequence number. Use for dedup and resume. |
2363
+ | `timestamp` | string | Formatted timestamp from the log system. |
2364
+ | `severity` | string | Log level: `Trace`, `Debug`, `Info`, `Warning`, `Error`, `Fatal`. |
2365
+ | `tag` | string | Log source tag (e.g., `Server`, `Router`, component name). |
2366
+ | `line` | string | The full formatted log line. |
2367
+
2368
+ ### Integration Notes
2369
+
2370
+ - **Reconnection**: Track the last `seq` received and pass it as `after_seq` on reconnect to avoid duplicate entries.
2371
+ - **Backlog**: The server retains up to 5000 recent log entries. The snapshot may be smaller if fewer entries exist.
2372
+ - **Platform availability**: WebSocket log streaming is available on all platforms (Windows, Linux, and macOS).
2373
+
2374
+ ## `GET /live`
2375
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2376
+
2377
+ Lightweight liveness probe for load balancers and orchestrators. Unlike [`/v1/health`](#get-v1health), this endpoint does no work beyond confirming the process is up — it does not inspect loaded models or backends — so it is safe to poll at high frequency. `HEAD /live` is also supported and returns `200 OK` with an empty body.
2378
+
2379
+ Unlike the other endpoints on this page, `/live` is not versioned and is not mounted under the `/api/v0/`, `/api/v1/`, `/v0/`, `/v1/` prefixes.
2380
+
2381
+ ### Example request
2382
+
2383
+ ```bash
2384
+ curl http://localhost:13305/live
2385
+ ```
2386
+
2387
+ ### Response format
2388
+
2389
+ ```json
2390
+ {"status":"ok"}
2391
+ ```
2392
+
2393
+ ## Job Engine API
2394
+
2395
+ <sub>![Status](https://img.shields.io/badge/status-experimental-orange)</sub>
2396
+
2397
+ Run client-posted sequences of server operations as durable, background **jobs** — steps that pass data forward, branch on results, and have a pause / interrupt / resume / delete / query lifecycle that survives client disconnect and server restart. Exclusive ops (`load`/`unload`/`chat`) hold a Router slot so normal traffic queues behind a running job.
2398
+
2399
+ | Method | Path | Purpose |
2400
+ |--------|------|---------|
2401
+ | `POST` | `/v1/jobs` | Create a job from `{name, definition:{steps} \| steps, inputs}`; returns `202 {"id"}`, or `400` on an invalid step graph. |
2402
+ | `GET` | `/v1/jobs` | List job summaries. |
2403
+ | `GET` | `/v1/jobs/{id}` | Full job record (status, per-step state, context). |
2404
+ | `POST` | `/v1/jobs/{id}/pause` | Stop after the current step. |
2405
+ | `POST` | `/v1/jobs/{id}/interrupt` | Cancel the current step now; resumable. |
2406
+ | `POST` | `/v1/jobs/{id}/resume` | Continue a paused/interrupted job. |
2407
+ | `DELETE` | `/v1/jobs/{id}` | Remove a job. |
2408
+
2409
+ See [`docs/dev/job-system.md`](../dev/job-system.md) for the step schema, op set, and lifecycle, and [`docs/dev/job-expression-language.md`](../dev/job-expression-language.md) for the `when`/`branch` expression grammar.
2410
+
2411
+ ## Internal Endpoints
2412
+
2413
+ Internal endpoints are used for server control and configuration. By default, they are secured by `LEMONADE_ADMIN_API_KEY` (if set) to separate control privileges from standard inference operations.
2414
+
2415
+ ## `POST /internal/telemetry/flush`
2416
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2417
+
2418
+ Forces the in-memory telemetry queue to flush all buffered trace spans immediately to the configured OTLP collector. This call blocks until all currently queued spans are serialized and sent.
2419
+
2420
+ #### Parameters
2421
+
2422
+ None.
2423
+
2424
+ Example request:
2425
+
2426
+ ```bash
2427
+ curl -X POST http://localhost:13305/internal/telemetry/flush
2428
+ ```
2429
+
2430
+ #### Response Format
2431
+
2432
+ Returns a JSON object indicating successful completion of the flush operation:
2433
+
2434
+ ```json
2435
+ {
2436
+ "status": "flushed"
2437
+ }
2438
+ ```
2439
+
2440
+ ## `GET /internal/aliases`
2441
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2442
+
2443
+ Retrieves a list of all active model alias mappings.
2444
+
2445
+ #### Parameters
2446
+
2447
+ None.
2448
+
2449
+ Example request:
2450
+
2451
+ ```bash
2452
+ curl http://localhost:13305/internal/aliases
2453
+ ```
2454
+
2455
+ #### Response Format
2456
+
2457
+ Returns a JSON object containing an array of active alias objects:
2458
+
2459
+ ```json
2460
+ {
2461
+ "aliases": [
2462
+ {
2463
+ "alias": "my-alias-1",
2464
+ "target": "user.custom-llama",
2465
+ "downloaded": true,
2466
+ "recipe": "llamacpp"
2467
+ }
2468
+ ]
2469
+ }
2470
+ ```
2471
+
2472
+ ## `POST /internal/aliases`
2473
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2474
+
2475
+ Binds a model alias to a target model name.
2476
+
2477
+ #### Parameters
2478
+
2479
+ | Field | Type | Required | Description |
2480
+ |-------|------|----------|-------------|
2481
+ | `alias` | string | yes | The alias name to create or update. |
2482
+ | `target` | string | yes | The target model name or canonical ID (also accepted as `model`). |
2483
+
2484
+ Example request:
2485
+
2486
+ ```bash
2487
+ curl -X POST http://localhost:13305/internal/aliases \
2488
+ -H "Content-Type: application/json" \
2489
+ -d '{
2490
+ "alias": "my-alias-1",
2491
+ "target": "user.custom-llama"
2492
+ }'
2493
+ ```
2494
+
2495
+ #### Response Format
2496
+
2497
+ Returns a JSON object confirming the alias binding:
2498
+
2499
+ ```json
2500
+ {
2501
+ "status": "ok",
2502
+ "alias": "my-alias-1",
2503
+ "target": "user.custom-llama"
2504
+ }
2505
+ ```
2506
+
2507
+ Returns HTTP `400 Bad Request` if required fields are missing or invalid.
2508
+
2509
+ ## `DELETE /internal/aliases/{alias}`
2510
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
2511
+
2512
+ Removes an existing model alias binding by name.
2513
+
2514
+ #### Parameters
2515
+
2516
+ | Path Parameter | Type | Description |
2517
+ |----------------|------|-------------|
2518
+ | `alias` | string | The alias name to remove. |
2519
+
2520
+ Example request:
2521
+
2522
+ ```bash
2523
+ curl -X DELETE http://localhost:13305/internal/aliases/my-alias-1
2524
+ ```
2525
+
2526
+ #### Response Format
2527
+
2528
+ Returns a JSON object confirming deletion:
2529
+
2530
+ ```json
2531
+ {
2532
+ "status": "deleted",
2533
+ "alias": "my-alias-1"
2534
+ }
2535
+ ```
2536
+
2537
+ Returns HTTP `404 Not Found` if the alias does not exist.