Reference
Errors
Every status llamay returns, what causes it, and what to do. The messages below are the ones the server actually emits — they are quoted from the code rather than paraphrased, so searching this page for the string you got will find it.
The shape of an error
Errors come back in the shape of whichever API you called, because a client written for that API already knows how to read it.
// OpenAI and llamay-native routes
{"error": {"message": "messages is empty", "type": "Bad Request"}}
// Ollama-compatible routes
{"error": "no model named \"llama3:8b\""}
type is the HTTP status text rather than a private taxonomy, so
there is no second vocabulary to learn and nothing to keep in step with the
status line.
Whose mistake was it?
Four of these, produced rather than quoted:
Transcript — the whole session as text
~/llamay $ llamay serve -m qwen2.5:0.5b -addr localhost:11439
time=2026-09-18T08:51:02.365-04:00 level=INFO msg="device graph attached" max-tokens=256
time=2026-09-18T08:51:02.365-04:00 level=INFO msg="llamay serving" addr=localhost:11439 mode
l=qwen2.5:0.5b arch=qwen2 max-loaded=2 keepalive=5m0s params="0.36 GB" context=32768 threads
=7 kv=f32 lexicon=0
OpenAI POST http://localhost:11439/v1/chat/completions
Anthropic POST http://localhost:11439/v1/messages
Embeddings POST http://localhost:11439/v1/embeddings (start with -embed <encoder.gguf
>)
Rerank POST http://localhost:11439/rerank (start with -rerank <reranker.gg
uf>)
Contexts POST http://localhost:11439/v1/contexts (then /{id}/fork?n=8, /{id}/sna
pshot)
Models POST http://localhost:11439/api/pull (and /api/copy, /api/delete)
Stats GET http://localhost:11439/v1/stats
~/llamay $ # the server stays up; the rest of this is a second terminal
~/llamay $ # a model that is not there
~/llamay $ curl -s http://localhost:11439/v1/chat/completions -H 'content-type: application/
json' \
> -d '{"model":"not-a-model:9b","messages":[{"role":"user","content":"hi"}]}' | jq
{
"error": {
"message": "no model named \"not-a-model:9b\". Available:\n all-minilm:latest\n gemma2
:2b\n gemma3:270m\n gpt2:q8_0\n qwen2.5:0.5b\n smollm2:135m\n azmx-code-test:latest (ol
lama)",
"type": "Not Found"
}
}
~/llamay $ # a body that is not JSON
~/llamay $ curl -s http://localhost:11439/v1/chat/completions -H 'content-type: application/
json' -d 'nonsense' | jq
{
"error": {
"message": "cannot parse request: invalid character 'o' in literal null (expecting 'u')"
,
"type": "Bad Request"
}
}
~/llamay $ # a route that does not exist
~/llamay $ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:11439/v1/nope
404
~/llamay $ # and a file that is not a GGUF
~/llamay $ llamay info -m README.md
llamay info: README.md: not a GGUF file: magic is 0x6c6c2023, want 0x46554747
400 — the request needs changing
| Message | What happened | Fix |
|---|---|---|
messages is empty |
No conversation to continue. | Send at least one message. |
messages[N].role is not a role name |
A role that is not an identifier. Roles are written next to the chat template's markers — on some templates inside one — so a role carrying whitespace or markers could forge a turn. | Use a plain name: letters, digits, _, -, ., up to 64 characters. New roles are fine; developer works. |
cannot parse request: …malformed JSON: … |
The body is not JSON, or a field has the wrong type. | The text after the colon names the field and the offset. |
send prompt or messages, not both |
Two ways of saying the same thing, and the server will not guess. | Pick one. |
send tokens or messages |
A truncate that named neither. | Give tokens as a number, or messages to measure. |
cannot truncate to N positions |
A negative length. | Zero or more. |
context holds N positions; cannot truncate to M |
Growing is not truncating, and doing nothing silently would hide a bug in the caller. | Truncate to at most what the context holds. |
n must be a number, got "…" |
?n= on a fork was not an integer. |
— |
precision must be f32, vq8 or q8 |
An unknown KV cache precision. | See Models and formats. vq8 is the one to want. |
encoding_format "…" is not one of float, base64 |
— | — |
query is required · no documents · texts is an empty array |
A rerank or embedding call missing half its input. | — |
Asking a model to do what it is not
Three of the 400s exist because the alternative is worse than an error. An encoder asked to generate text produces a vector interpreted as tokens, which is fluent and meaningless.
bge-small is an encoder: it produces one vector per input and cannot generate text.
bge-reranker is a cross-encoder: it scores a query against a document and cannot generate text.
this server is serving qwen2.5-7b, a decoder, and has no encoder loaded.
The last one is the common case: /v1/embeddings against a server
started with only -m. Start it with -embed as well —
one process can hold a decoder, an encoder and a cross-encoder at once, which
is what makes a local retrieval stack a single binary.
401 — no key, or the wrong one
this server requires an API key: send Authorization: Bearer <key>
Sent with WWW-Authenticate: Bearer realm="llamay", so a client
knows what is missing rather than guessing at the route. Both spellings work:
Authorization: Bearer <key> and
X-Api-Key: <key>. The comparison is constant-time.
/healthz is the one route that never asks. Whatever reads it is
usually a load balancer or a container probe with no way to carry a secret,
and it reports nothing a caller could not learn by connecting.
403 — refused on purpose
| Message | Meaning |
|---|---|
origin not permitted | Studio or the MCP endpoint checking the browser origin against the address it is serving on. Not from -cors — see below. |
this llamay serves Studio without -studio-exec | Command execution is off. It is not a default. |
<caller> is cleared to CONFIDENTIAL and this request is SECRET<caller> is not read into ORCON<caller> holds no clearance on this server | Classification markings: the request's X-Classification is above the caller's clearance, or in a compartment they lack. Markings |
this server requires every request to declare its classification: send X-Classification: … | Markings are on and there is no default level. |
this server enforces classification markings, and a bare snapshot carries none; … | Restore is refused under markings; move contexts with export and import instead. Moving a context |
this context belongs to another caller, or to you at another marking; … | An import for somebody else. |
… snapshot names its weights by layout only, not by content … | A snapshot from before 0.3.0, refused under -profile enterprise or a marking. -allow-legacy-snapshots accepts it. |
a web page at … may not call this server; start the server with -cors … | A page on another site sent a POST, PUT or DELETE. A browser sends a form-shaped request with no preflight, so this is refused here rather than left to CORS. |
a web page at … may not change this server's model store; start the server with -cors … | The same, for a pull, copy or delete. |
A CORS refusal has no error body
This is the one refusal you will not find in a response, because there is
nothing to find. When -cors does not name the calling origin the
preflight is answered 204 with no CORS headers on it, and the
browser refuses the real request on its own — your code sees a network error,
not a status.
TypeError: Failed to fetch
Access to fetch at 'http://localhost:11435/v1/chat/completions' from origin
'https://example.com' has been blocked by CORS policy
That message comes from the browser console, not from llamay. Start the
server with -cors <your origin>. Saying no by saying
nothing is deliberate: it keeps a site that merely guessed out of the logs,
and there is no useful thing to tell a caller that was never authorised.
404 — named something that is not there
no model named "llama3:8b". Available:
qwen2.5-7b-instruct:q4_k_m
gemma2:2b (ollama)
The list is the point: a miss that only says "not found" leaves you guessing at names you have no way to enumerate. It spans both llamay's store and Ollama's.
no such context on this model: "ctx_9". A context belongs to the model it was
created on; if this one was built against another model, create it again
against this one.
Contexts are per engine and an engine is per model, so an id made on one model
is not found on another. KV computed under one set of weights is
meaningless under another, so this is refused rather than reinterpreted. On the
context routes the model is a query parameter:
POST /v1/contexts/ctx_…/fork?model=qwen2.5-7b.
409 — the state does not allow it
| Message | Meaning |
|---|---|
this context is in use by another request; retry when that one finishes | One request writes to a context at a time. Retry, or fork the context for parallel work. |
kv: snapshot was taken with different weights: snapshot holds "…", this cache holds "…" | A restore into another model. Restore it where the same file is served. |
this context was computed by weights with content … | An import whose weights have the same layout but a different SHA-256 — a fine-tune and its base, say. |
413 — larger than this server takes
request body is 41943040 bytes; this server accepts 33554432
Bodies are limited to 32 MiB, checked from the declared length before the bytes are read. Snapshots and imports have their own limit, sized to the largest context the model can hold. For a large PDF or a long recording, split it.
415 — a format this route does not read
not a WAV file: send RIFF/WAVE audio (PCM 8/16/24/32-bit or 32/64-bit float, any rate, any number of channels)
Transcription reads WAV only. Convert with
ffmpeg -i in.m4a out.wav. Speech
421 — Studio, from another machine or host name
Studio answers this machine only: open it at http://localhost on the machine running llamay, or reach it through an SSH tunnel (ssh -L 11435:localhost:11435 host)
Studio answers only requests that come from the machine running the server
and are addressed to localhost, a *.localhost name
or a loopback address — whatever keys the server has. Use it there, or
through an SSH tunnel, which arrives from the machine itself.
429 — the queue is full
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Requests past the queue bound are refused rather than queued. A
request that will wait longer than the client will wait is work nobody reads,
so shedding it is cheaper than serving it — and a 429 with a
Retry-After is something a client can act on, which a 500 is not.
If you see these steadily rather than in bursts, the fix is capacity or
admission control, not a shorter backoff: raise -concurrency if the
device has headroom, add an instance if it does not, and put a queue in front
— see deployment patterns.
501 — not something llamay does
/api/push, /api/create and /api/blobs
are refused by name, with the reason: llamay publishes nothing and reads no
Modelfiles. /api/pull, /api/copy and
/api/delete answer 501 on a server started with
-pull=false.
503 — not now
this server has no model. Pull one:
POST /api/pull {"model": "..."}
A generation route on a server whose store is empty. It names the route that
ends the state rather than only reporting it, and /api/pull is
served in this state on purpose — it is the only way out.
this server could not write an audit record, and is configured to stop rather than serve work it cannot account for—-audit-strictis on and a write failed. Every route but/healthzanswers this until the server is restarted with a writable audit file.server is busy: …— the encoder or the reranker is saturated. Retry shortly.
500 — ours
A genuine server fault: a file that will not read, a device that will not
allocate, a bug. The message is the underlying error rather than a generic
one. If you can reproduce it, the message and the
llamay version output are the two things worth including in a
report.
One class of 500 is worth knowing about because it used to be miscategorised: an unknown model name on the context routes answered 500 while every chat route answered 404 for the identical error. That is fixed — a name with a typo in it is the request's mistake, and a 500 tells the client nothing it can act on while paging somebody who cannot help.
And one from the CLI: a snapshot declining weights that did not produce it.
Transcript — the whole session as text
~/llamay $ llamay ctx build -m qwen2.5:0.5b -o build/demo/work/from-qwen.llamayctx -p "Two p lus two is" prefilled 4 tokens in 47ms saved build/demo/work/from-qwen.llamayctx (96.07 KiB) in 5ms ~/llamay $ # now try to restore it under a different model ~/llamay $ llamay ctx restore -m smollm2:135m -i build/demo/work/from-qwen.llamayctx llamay ctx: kv: snapshot was taken with different weights: snapshot holds "590bc10b059c7189" , this cache holds "3920124fb79ca350" ~/llamay $ # refused, named, and non-zero — not a silent wrong answer
Errors during a stream
A stream that has already begun has sent 200 and its headers, so
a later failure cannot change the status. The connection ends without a
[DONE] frame.
saw_done = False
for line in stream:
if line.strip() == "data: [DONE]":
saw_done = True
if not saw_done:
# Truncated. Do not treat what you have as a complete answer.
raise RuntimeError("the stream ended early")
Checking for [DONE] is the only way to tell a finished answer
from a truncated one, and it is the check most clients skip.
Not errors: finish reasons
A 200 with a short answer is not a failure, and the reason says which kind of ending it was.
finish_reason | Means |
|---|---|
stop | The model ended its turn, or a stop sequence was reached. |
length | max_tokens, or the context filled. The answer is cut mid-thought. |
tool_calls | It wants a tool run. Nothing failed. |
On the Anthropic route these are end_turn,
stop_sequence, max_tokens and tool_use,
and stop_sequence is reported separately from
end_turn because a caller's delimiter arriving is a different
event from the model finishing its sentence.
Where to go next
The HTTP API documents every field these errors are
about. Serving covers -concurrency
and what is logged.
Install has the failures that happen
before the server starts.