Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

5. The OpenAI-Compatible REST API

Pass --api-port N to any local or cluster invocation (see Chapter 4) to start an OpenAI wire-compatible REST server alongside the REPL. No changes are required to GenerationLoop, the scheduler, or any node code — the API layer is a pure translation shim above RequestScheduler (architecture in Chapter 2).

Supported endpoints

MethodPathDescription
POST/v1/chat/completionsBlocking or SSE streaming completion
GET/v1/modelsList loaded models
GET/v1/models/{model}Retrieve a single model

Quick verification

# Start local mode with API
./juno local --model-path /path/to/model.gguf --api-port 8080

# Blocking completion
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf",
    "messages": [{"role": "user", "content": "What is Java?"}]
  }'

# Streaming completion
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf",
    "messages": [{"role": "user", "content": "Tell me a joke."}],
    "stream": true
  }'

# List models
curl http://localhost:8080/v1/models

Request field mapping

OpenAI fieldJuno internalNotes
modelmodelIdFirst loaded model if omitted
messages[].roleChatMessage.rolesystem / user / assistant
messages[].contentChatMessage.contentText only; image content not supported
temperatureSamplingParams.temperature0.0–2.0; default 0.7
top_pSamplingParams.topP0.0–1.0; default 0.9
max_completion_tokensSamplingParams.maxTokens1–32768; default 200
max_tokensSamplingParams.maxTokensDeprecated alias; max_completion_tokens takes precedence
frequency_penaltySamplingParams.repetitionPenaltyMapped: 1 + max(0, fp/2)
streamroute selectionfalse → blocking JSON; true → SSE
nOnly 1 accepted; other values → HTTP 400
stop, presence_penalty, logit_bias, user, seedSilently ignored for client compatibility

Juno request extensions (namespaced under x_juno_* to avoid OpenAI field conflicts):

FieldTypeDefaultDescription
x_juno_prioritystringNORMALScheduler priority: HIGH / NORMAL / LOW
x_juno_session_idstringStable session ID; enables KV-cache reuse across turns
x_juno_top_kinteger50Top-K sampling cutoff (0 = disabled)

Multi-turn conversation with KV-cache reuse

SESSION_ID = "sess-my-conversation-001"

def chat(messages):
    return client.chat.completions.create(
        model="tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf",
        messages=messages,
        extra_body={"x_juno_session_id": SESSION_ID},
    ).choices[0].message.content

history = []
for user_input in ["My name is Alice.", "What is my name?"]:
    history.append({"role": "user", "content": user_input})
    reply = chat(history)
    history.append({"role": "assistant", "content": reply})
    print(reply)

Error responses

Errors follow the OpenAI error envelope ({"error": {"message": ..., "type": ..., "code": ...}}):

HTTPcodeCause
400invalid_requestMissing/empty messages, n > 1, or invalid body
503service_unavailableNo model loaded or model not ready
429rate_limit_exceededScheduler queue full; Retry-After header set
500internal_errorUnexpected inference error

The full OpenAPI 3.0 specification is at api/src/main/resources/juno-api.yaml.

Additional JVM-local endpoints

Same server as above, Juno-native (non-OpenAI) shape:

MethodPathDescription
POST/v1/inferenceBlocking JSON completion (InferenceApiServer native shape)
POST/v1/inference/streamSSE stream; each data: line is JSON {"token":"…","isComplete":false} until terminal event

For programmatic access to this same server from JVM code — rather than curl or an OpenAI SDK — see JunoHttpClient in Chapter 6.


← Chapter 4: Running Modes  |  Table of Contents  |  Chapter 6: JVM Integration →