Skip to content

API reference

The server speaks HTTP/1.1 with keep-alive, chunked request bodies and Server-Sent Events for streaming. The base URL is wherever serve bound, http://127.0.0.1:8080 by default.

Routes

MethodPathPurpose
POST/v1/chat/completionsOpenAI chat completion, streaming and tool calling
POST/v1/messagesAnthropic message, streaming and tool use
GET/v1/modelsThe loaded model
GET/v1/models/{id}The loaded model, 404 for any other id
GET/health, /healthzReadiness, no key needed
GET/metricsPrometheus text
OPTIONSanyCORS preflight, no key needed
POST, GET/v1/sessionsCreate and list sessions
GET, DELETE/v1/sessions/{id}Inspect and delete a session
POST/v1/sessions/{id}/generateAsk a question over a session’s context
POST/v1/sessions/{id}/pushQueue data for background ingestion
POST, GET/v1/sessions/{id}/flashRegister and list flash queries
DELETE/v1/sessions/{id}/flash/{fid}Remove a flash query
GET/v1/sessions/{id}/eventsThe session’s event stream (SSE)
GET/v1/sessions/{id}/wsThe session’s event stream and push channel (WebSocket)

The session routes are described on the Sessions page. A trailing slash is ignored. A known path with the wrong method answers 405; an unknown path answers 404.

Authentication

Every route except OPTIONS, /health and /healthz requires the license key the server was started with:

Authorization: Bearer LSK-...

x-api-key: LSK-... is accepted too, which is what Anthropic SDKs send. Dashes and whitespace inside the key are ignored. A missing or wrong key answers 401 with missing or invalid license key; send it as 'Authorization: Bearer <LSK-...>', in the envelope of the route (Anthropic’s for /v1/messages, OpenAI’s elsewhere). A WebSocket upgrade without the key is refused with a plain HTTP 401 before the handshake.

Errors

OpenAI routes, the session routes included:

{"error": {"message": "'top_k' is not supported", "type": "invalid_request_error"}}
Statustype
401authentication_error
402, 403permission_error
400, 404, 405, 408, 409, 413, 431invalid_request_error
429rate_limit_error
5xxserver_error

/v1/messages uses Anthropic’s envelope:

{"type": "error", "error": {"type": "invalid_request_error", "message": "'max_tokens' is required"}}

with authentication_error (401), permission_error (403), not_found_error (404), rate_limit_error (429), invalid_request_error (other 4xx), overloaded_error (529) and api_error (5xx).

On a streaming response the status line has already gone out, so an engine error arrives as an event: an OpenAI error object as a data: frame followed by data: [DONE], or an Anthropic event: error frame.

Request parameters the engine cannot honour are refused by name with a 400. The exceptions are model, which is accepted and ignored on every route, and metadata on /v1/messages.

POST /v1/chat/completions

Terminal window
curl http://127.0.0.1:8080/v1/chat/completions \
-H "Authorization: Bearer $LAYERSCALE_LICENSE_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "system", "content": "Be brief."},
{"role": "user", "content": "Why is the sky blue?"}
],
"max_tokens": 200,
"temperature": 0.2
}'

Request

FieldDefaultNotes
messagesrequiredRoles system, user, assistant, tool (ipython is accepted as tool). content is a string or an array of {"type": "text", "text": ...} parts; other part types are refused.
max_tokens or max_completion_tokens512
temperature0.60 is greedy.
top_p0.9
seednone
stopnoneA string, or an array of up to 4 strings of at most 64 bytes each. Excluded from the returned text.
streamfalse
stream_optionsnone{"include_usage": true} appends a final usage chunk.
toolsnone{"type": "function", "function": {"name", "description", "parameters"}}.
tool_choiceautoauto or none. required and a named function are refused.
reasoning_effortnonenone, minimal, low (no thinking) or medium, high, xhigh (thinking), for templates that render a reasoning setting.
chat_template_kwargsnonedate_string sets the template’s date. Other keys are refused.
session_idnoneRun the turn against a session.
ignore_eosfalseKeep generating past end of sequence, to exactly max_tokens.

Assistant messages may carry tool_calls and reasoning_content; tool messages carry tool_call_id. Refused by name: n or best_of other than 1, logprobs, top_logprobs, logit_bias, top_k, min_p, repetition_penalty, a nonzero presence_penalty or frequency_penalty, a response_format other than text, and the deprecated functions and function_call.

Response

{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1757000000,
"model": "devstral-small-2507",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "..."},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 31, "completion_tokens": 57, "total_tokens": 88}
}

finish_reason is stop, length or tool_calls. On a session turn, usage.prompt_tokens_details.cached_tokens reports the prompt tokens served from held KV; it is present only when nonzero.

Streaming

With "stream": true the response is text/event-stream. Each data: frame is a chat.completion.chunk: first a delta of {"role": "assistant"}, then content deltas, then a final chunk carrying finish_reason, then data: [DONE]. With include_usage, every chunk carries "usage": null and one more chunk with empty choices and the totals precedes [DONE]. No bytes are sent while the prompt is being prefilled.

Tool calling

Offer tools and read finish_reason: "tool_calls":

"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_...",
"type": "function",
"function": {"name": "get_weather", "arguments": "{\"city\": \"Paris\"}"}
}]
}

arguments is a JSON string. Text beside the calls is returned as content; with no text it is null. Send the result back as a tool message with the same tool_call_id. When streaming, each call arrives as two chunks: one with id, type, the function name and empty arguments, then one with the complete arguments string.

POST /v1/messages

The Anthropic Messages API over the same engine.

Terminal window
curl http://127.0.0.1:8080/v1/messages \
-H "x-api-key: $LAYERSCALE_LICENSE_KEY" \
-H "Content-Type: application/json" \
-d '{
"system": "Be brief.",
"messages": [{"role": "user", "content": "Why is the sky blue?"}],
"max_tokens": 200
}'

Request

FieldDefaultNotes
messagesrequiredRoles user and assistant. content is a string or an array of blocks: text and tool_result in user messages; text, tool_use and thinking in assistant messages.
systemnoneA string or an array of text blocks.
max_tokensrequired
temperature0.6
top_p0.9
seednone
stop_sequencesnoneAn array of strings.
streamfalse
toolsnone{"name", "description", "input_schema"}.
tool_choice{"type": "auto"}auto or none.
session_idnoneRun the turn against a session.
ignore_eosfalse
metadataAccepted and ignored.

top_k and thinking are refused.

Response

{
"id": "msg_...",
"type": "message",
"role": "assistant",
"model": "devstral-small-2507",
"content": [{"type": "text", "text": "..."}],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {"input_tokens": 31, "output_tokens": 57}
}

stop_reason is end_turn, max_tokens, stop_sequence or tool_use. A tool call is a tool_use block with id, name and input (the parsed arguments, or the raw string if they were not valid JSON). On a session turn, usage.cache_read_input_tokens reports the tokens served from held KV.

Streaming

Named SSE events in Anthropic’s order: message_start, content_block_start, content_block_delta (text_delta for text, input_json_delta for tool arguments), content_block_stop, message_delta with the stop reason and usage, message_stop. An engine error mid-stream is an error event.

GET /v1/models

Terminal window
curl http://127.0.0.1:8080/v1/models -H "Authorization: Bearer $LAYERSCALE_LICENSE_KEY"
{"object": "list", "data": [{"id": "devstral-small-2507", "object": "model", "created": 1757000000, "owned_by": "local"}]}

GET /v1/models/{id} returns the same entry when id matches the model name (case-insensitive) and 404 otherwise. There is exactly one model per server.

GET /health

No key needed. 200 while the engine is stepping or idle, 503 when the engine thread has stopped or stalled. /healthz is an alias. The body is the same either way; status is ok or unavailable:

{
"status": "ok",
"uptime_ms": 120345,
"queued": 0,
"running": 2,
"kv_pages_total": 4096,
"kv_pages_free": 3900,
"connections": 3,
"steps": 15020,
"preemptions": 0,
"requests_started": 41,
"requests_ok": 39,
"requests_failed": 0,
"requests_cancelled": 0,
"prompt_tokens": 12034,
"generated_tokens": 8871,
"sessions": 1,
"session_turns": 6
}

The remaining fields mirror the counters /metrics exports.

GET /metrics

Prometheus text; the key is required. Every metric is prefixed layerscale_.

Counters: requests_started_total, requests_ok_total, requests_failed_total, requests_cancelled_total, prompt_tokens_total, generated_tokens_total, steps_total, step_tokens_total, speculative_accepted_total, preemptions_total, connections_rejected_total, session_turns_total, prefix_cached_tokens_total, session_cached_tokens_total, session_ingested_tokens_total, session_flash_hits_total, session_ready_exits_total, tool_calls_malformed_total, tool_calls_recovered_total, tool_calls_jump_forward_total.

Gauges: queued, running, kv_pages_total, kv_pages_free, connections, sessions, ready.

Terminal window
curl http://127.0.0.1:8080/metrics -H "Authorization: Bearer $LAYERSCALE_LICENSE_KEY"

CORS

Every response carries Access-Control-Allow-Origin: *. A preflight OPTIONS answers 204, allowing GET, POST and OPTIONS with the Content-Type and Authorization headers, and needs no key.

Limits

LimitValueWhen exceeded
Request body32 MiB413
Request headers64 KiB431
Time for a request to arrive in full60 s408
Request bodies buffered across all connections256 MiB503
Socket read or write30 sconnection closed
stop sequences4, each at most 64 bytes400
WebSocket frame16 MiBconnection closed

There is no fixed connection cap; each connection is a thread and the host’s limits apply. A prompt that reaches the context length is refused with prompt has N tokens but the context limit is M (raise --ctx). A non-streaming request whose client disconnects is cancelled; a streaming one stops at the first failed write.