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?

4xx — YOURS change the request a field, a name, a body. Retrying it unchanged fails again. 429 — NEITHER wait and retry the queue is full. Retry-After says how long to back off. 5xx — OURS nothing you can fix report it with the message. Retrying may work; it may not. The distinction is load-bearing. A client that gets a 500 has learnt nothing about whether to retry; one that gets a 429 with a Retry-After can back off or shed load, which is why overload is not a 500 here.
Three behaviours, and the status is what tells them apart.

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
Being told no, four ways. Every status llamay returns is emitted by a line you can read. These are four of them, produced rather than quoted. Recorded 2026-09-18 · Apple M4, macOS 26.0.1 · llamay v0.1.58-1-gb14f550 · metal:Apple M4 · 0:21

400 — the request needs changing

MessageWhat happenedFix
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

MessageMeaning
origin not permittedStudio 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-execCommand 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

MessageMeaning
this context is in use by another request; retry when that one finishesOne 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.

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
A snapshot that refuses the wrong weights. A context is only meaningful under the model that produced it. The file records which one, and a restore under different weights is an error rather than quiet nonsense. Recorded 2026-09-18 · Apple M4, macOS 26.0.1 · llamay v0.1.58-1-gb14f550 · metal:Apple M4 · 0:10

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_reasonMeans
stopThe model ended its turn, or a stop sequence was reached.
lengthmax_tokens, or the context filled. The answer is cut mid-thought.
tool_callsIt 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.