API Reference

Specification and usage examples for the HTTP API that LLM AI Server with llama.cpp exposes on the device. It covers the Ollama-compatible endpoints (/api/*), the OpenAI-compatible endpoints (/v1/chat/completions, /v1/completions, and more), the Anthropic Messages API-compatible endpoints (/v1/messages, /v1/messages/count_tokens), the llama.cpp WebUI-compatible endpoints (/props, /slots, and more), and the app-specific model control endpoints (/models/load, /models/unload), based on the current OllamaApiServer implementation.

Related docs: User Manual | Technical Specification | Privacy Policy

Ollama-compatible OpenAI-compatible Anthropic-compatible SSE / NDJSON streaming Embeddings Structured Output Multimodal Tools / Function Calling Reasoning

Contents

1. Basics

2. Compatibility summary

For chat, generation, and embedding workloads the API is compatible with both the Ollama and OpenAI specifications. Token statistics are returned as well (Ollama's eval_count family and OpenAI's usage), so clients that rely on them — Open WebUI, LangChain, and similar — work unchanged. Because the app is built around a single on-device model and a single generation slot, repository management endpoints are not provided.

CategoryDetails
Ollama-compatiblePOST /api/generate, POST /api/chat, GET/POST /api/tags, POST /api/embed, POST /api/embeddings (legacy), POST /api/tokenize. Response shape is {model, created_at, response|message, done}. The final chunk carries total_duration, load_duration, prompt_eval_count, prompt_eval_duration, eval_count, and eval_duration (nanoseconds). Tool paths add done_reason, tool_calls, and reasoning_content.
OpenAI-compatiblePOST /v1/chat/completions (SSE streaming terminated by data: [DONE]), POST /v1/completions (legacy Completions, object: "text_completion"), GET /v1/models, POST /v1/embeddings, POST /v1/responses/input_tokens. Responses include id (chatcmpl-… / cmpl-…), created, object, choices[].message/delta/text, finish_reason, and — for non-streaming calls — usage.
Anthropic-compatiblePOST /v1/messages (Messages API; streaming uses named SSE events message_start / content_block_* / message_delta / message_stop), POST /v1/messages/count_tokens. Supports system, multi-turn, thinking (thinking blocks), image/audio input, and basic tool_use / tool_result. Responses are a content[] block array (text / thinking / tool_use) with stop_reason and usage (input_tokens / output_tokens).
llama.cpp WebUI-compatibleGET /props, GET /slots, GET /health (/v1/health), GET /models, and bundled WebUI serving.
Extensions (app-specific)POST /models/load and POST /models/unload for explicit model control, multimodal inputs (image_url / input_audio; Anthropic image / audio blocks), shared MCP / Function Definitions settings, reasoning_content, sampler-chain ordering via samplers, and an option to append performance metrics to the output text.
Not implemented (caveats)Model repository management (/api/show, /api/pull, /api/push, /api/create, /api/copy, /api/delete, /api/ps, /api/version), the full /v1/responses API, logprobs, and multiple candidates (n > 1) are not supported. Streaming (SSE) responses do not carry usage (stream_options.include_usage is ignored), and the usage object on /v1/embeddings is a placeholder of 0. stop / stop_sequences are accepted but not enforced.

Separately from those statistics fields, enabling "Show Performance Metrics" appends token counts, elapsed time, speed, and device temperature as text at the end of the generated body (an extra delta for OpenAI, an extra chunk for Ollama).

3. Common behavior

3-1. Concurrency and busy queue

3-2. Error responses

/api/* and the app-specific endpoints return {"error": "..."}, while /v1/* endpoints return an OpenAI-style error object.

// /api/* error shape
{ "error": "'input' is required" }

// /v1/* error shape
{
  "error": {
    "message": "No messages provided",
    "type": "invalid_request_error",
    "code": 400
  }
}
StatusMeaning
400Malformed JSON, missing required field, unsupported content[].type, unsupported modality, unsupported image format, and so on
404Unknown route
405Unsupported HTTP method
413Request body larger than 64 MB
500Configuration load failure, generation or embedding failure
503Busy (queue full / wait timeout / reset in progress / model in use during unload)

If an error occurs mid-stream, /api/* emits a chunk containing error, and /v1/* emits an SSE event with the error object followed by data: [DONE] before closing the stream. If the client disconnects, native generation is cancelled.

4. POST/api/generate

Single-prompt generation (Ollama generate compatible). Streams NDJSON by default.

Request

FieldTypeDescription
modelstringProfile name. Defaults to "default".
promptstringUser prompt.
systemstring?Optional system prompt (takes precedence over the profile setting).
streamboolDefaults to true; set false for a single JSON response.
optionsobject?Ollama-style sampling / context settings. num_ctx, num_predict, temperature, top_k, top_p, min_p, repeat_penalty, repeat_last_n, and mirostat* are honored (see §13).
format / grammarstring|objectOptional structured-output constraint (see §14).
thinkbool|string?Optional reasoning toggle (see §15).
toolsarray?Optional. Switches to the internal tool-execution loop (see §17). A non-array value returns 400.

Non-streaming response

curl http://localhost:11434/api/generate -d '{
  "model": "my-profile",
  "prompt": "What is the capital of Japan?",
  "stream": false
}'
{
  "model": "my-profile",
  "created_at": "2026-08-13T03:10:00.000Z",
  "response": "The capital of Japan is Tokyo.",
  "done": true,
  "total_duration": 1843000000,
  "load_duration": 12000000,
  "prompt_eval_count": 18,
  "prompt_eval_duration": 231000000,
  "eval_count": 12,
  "eval_duration": 1600000000
}

Streaming response (NDJSON)

One JSON object per line. Each chunk has "done": false and a partial response; the final chunk has "done": true plus the statistics fields.

{"model":"my-profile","created_at":"...","response":"The capital","done":false}
{"model":"my-profile","created_at":"...","response":" of Japan is","done":false}
{"model":"my-profile","created_at":"...","response":"","done":true,"total_duration":1843000000,"prompt_eval_count":18,"eval_count":12,...}

Durations are nanoseconds, matching the Ollama definition (total_duration = load_duration + prompt_eval_duration + eval_duration), so clients that compute tok/s work as-is.

5. POST/api/chat

Conversation generation from a messages array (Ollama chat compatible). Streams NDJSON by default.

Request

FieldTypeDescription
modelstringProfile name. Defaults to "default".
messagesarrayRequired. Array of {role, content}; role is system / user / assistant / tool. content is a string or a multimodal parts array (see §16).
streamboolDefaults to true.
optionsobject?Same as §4 (num_ctx, num_predict, and so on).
format / grammarstring|objectOptional (see §14).
thinkbool|string?Optional (see §15).
toolsarray?Optional (see §17).

Non-streaming response

curl http://localhost:11434/api/chat -d '{
  "model": "my-profile",
  "stream": false,
  "options": { "num_ctx": 8192, "temperature": 0.7 },
  "messages": [
    {"role": "system", "content": "You are a concise assistant."},
    {"role": "user", "content": "How tall is Mount Fuji?"}
  ]
}'
{
  "model": "my-profile",
  "created_at": "2026-08-13T03:10:00.000Z",
  "message": { "role": "assistant", "content": "3,776 m." },
  "done": true,
  "total_duration": 980000000,
  "load_duration": 8000000,
  "prompt_eval_count": 34,
  "prompt_eval_duration": 180000000,
  "eval_count": 9,
  "eval_duration": 792000000
}

Streaming response (NDJSON)

{"model":"my-profile","created_at":"...","message":{"role":"assistant","content":"3,776"},"done":false}
{"model":"my-profile","created_at":"...","message":{"role":"assistant","content":" m."},"done":false}
{"model":"my-profile","created_at":"...","message":{"role":"assistant","content":""},"done":true,"eval_count":9,...}

For reasoning-enabled models the thinking portion is emitted as message.reasoning_content rather than in the body (see §15).

6. GETPOST/api/tags

Lists available models (profiles). Ollama tags compatible.

curl http://localhost:11434/api/tags
{
  "models": [
    {
      "name": "my-profile",
      "model": "my-profile",
      "modified_at": "2026-08-13T03:10:00.000Z",
      "size": 0,
      "details": {
        "format": "gguf",
        "family": "llama",
        "parameter_size": "unknown",
        "quantization_level": "unknown"
      }
    }
  ]
}

size and parameter_size cannot be determined on device, so they are fixed values (0 / "unknown"). For file size, load status, and modalities (vision / audio), use /v1/models (see §8).

7. POST/v1/chat/completions

OpenAI Chat Completions compatible. Streams SSE by default. Point the official OpenAI SDK's base_url at this server and it works as-is.

Request

FieldTypeDescription
modelstringProfile name. If empty or omitted, resolves to the currently loaded model, then to the default profile.
messagesarrayRequired, OpenAI format. content is a string or an array of {type, ...} parts (see §16).
streamboolDefaults to true.
max_tokens / n_predictintGeneration cap. n_predict: 0 performs pre-encode only (see §13).
response_formatobject?json_object / json_schema / text (see §14).
reasoning_format / chat_template_kwargsstring / objectReasoning control (see §15).
tools / tool_choice / parallel_tool_callsarray / any / boolOptional (see §17).
Sampling fieldsnumber, etc.temperature, top_p, top_k, min_p, presence_penalty, frequency_penalty, samplers, and more (see §13). They override profile settings for that request only.

Non-streaming response

curl http://localhost:11434/v1/chat/completions -d '{
  "model": "my-profile",
  "stream": false,
  "messages": [
    {"role": "user", "content": "Say hello in Japanese."}
  ]
}'
{
  "id": "chatcmpl-4f0a1c2e5b7d4a9f8c3e1b6d2a7f0e5c",
  "object": "chat.completion",
  "created": 1786000000,
  "model": "my-profile",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "こんにちは!" },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 14,
    "completion_tokens": 5,
    "total_tokens": 19
  }
}

Streaming response (SSE)

First a chunk with delta.role="assistant", then chunks carrying delta.content, then a chunk with finish_reason:"stop" followed by data: [DONE]. All chunks share the same id and created.

data: {"id":"chatcmpl-4f0a…","object":"chat.completion.chunk","created":1786000000,"model":"my-profile","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"id":"chatcmpl-4f0a…","object":"chat.completion.chunk","created":1786000000,"model":"my-profile","choices":[{"index":0,"delta":{"content":"こん"},"finish_reason":null}]}

data: {"id":"chatcmpl-4f0a…","object":"chat.completion.chunk","created":1786000000,"model":"my-profile","choices":[{"index":0,"delta":{"content":"にちは!"},"finish_reason":null}]}

data: {"id":"chatcmpl-4f0a…","object":"chat.completion.chunk","created":1786000000,"model":"my-profile","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Note: streaming chunks do not include usage (stream_options.include_usage is not supported). If you need token statistics, use stream: false, or the Ollama-side /api/chat, whose final chunk carries eval_count and friends.

7-2. POST/v1/completions

OpenAI legacy Completions compatible. The prompt is fed to the model verbatim (no chat template) for plain text completion. The response uses object: "text_completion" with the text on choices[].text. This protocol does not carry multimodal input (use chat / Messages for image or audio).

Request

FieldTypeDescription
modelstringProfile name. When empty/omitted it resolves to the currently loaded model, else the default profile.
promptstring | string[]Required. A string, or an array of strings (joined by newlines). Empty/missing returns 400.
streamboolDefaults to false. true for SSE.
max_tokens / n_predictintGeneration token cap (§13).
sampling / grammarnumber, etc.temperature, top_p, top_k, samplers, and more (§13); GBNF grammar (§14).

Non-streaming response

curl http://localhost:11434/v1/completions -d '{
  "model": "my-profile",
  "prompt": "Introduce Tokyo in three lines",
  "max_tokens": 128,
  "stream": false
}'
{
  "id": "cmpl-2b7d4a9f8c3e1b6d2a7f0e5c4f0a1c2e",
  "object": "text_completion",
  "created": 1786000000,
  "model": "my-profile",
  "choices": [
    { "index": 0, "text": "Tokyo is the capital of Japan. …", "finish_reason": "stop", "logprobs": null }
  ],
  "usage": { "prompt_tokens": 12, "completion_tokens": 40, "total_tokens": 52 }
}

Streaming response (SSE)

Each chunk uses object: "text_completion" with a partial string on choices[].text. A final chunk carries finish_reason: "stop", followed by data: [DONE].

data: {"id":"cmpl-…","object":"text_completion","created":1786000000,"model":"my-profile","choices":[{"index":0,"text":"Tokyo is","finish_reason":null,"logprobs":null}]}

data: {"id":"cmpl-…","object":"text_completion","created":1786000000,"model":"my-profile","choices":[{"index":0,"text":"","finish_reason":"stop","logprobs":null}]}

data: [DONE]

7-3. POST/v1/messages (Anthropic Messages API)

Anthropic Messages API compatible. Point an Anthropic SDK / client's base URL at this server (the x-api-key / anthropic-version headers are accepted but ignored). Supports system, multi-turn, thinking (thinking blocks), image/audio input, and basic tool_use / tool_result round-trips. Defaults to non-streaming (stream:false); max_tokens is required.

Request

FieldTypeDescription
modelstringProfile name. Sending a real Claude model name (e.g. claude-3-5-sonnet) still resolves to the locally loaded model.
max_tokensintRequired. Missing returns 400.
messagesarrayRequired. Array of {role, content}. role is user / assistant. content is a string or an array of text / image / audio / tool_use / tool_result blocks (§16).
systemstring | array?Optional system prompt (string or array of text blocks).
streamboolDefaults to false. true for named SSE events.
thinkingobject?{"type":"enabled"|"disabled"} (§15).
tools / tool_choicearray / object?Optional. Anthropic format (name / description / input_schema). tool_choice is auto / any / tool (§17).
temperature / top_p / top_knumberOptional sampling overrides (§13).

Non-streaming response

curl http://localhost:11434/v1/messages -d '{
  "model": "my-profile",
  "max_tokens": 256,
  "messages": [
    {"role": "user", "content": "How tall is Mount Fuji?"}
  ]
}'
{
  "id": "msg_988412deab264f1abf8904b938f389a7",
  "type": "message",
  "role": "assistant",
  "model": "my-profile",
  "content": [
    { "type": "text", "text": "It is 3,776 m." }
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": { "input_tokens": 15, "output_tokens": 8 }
}

With thinking enabled, content starts with a {"type":"thinking","thinking":"…"} block followed by {"type":"text","text":"…"}. Tool calls return a {"type":"tool_use","id":…,"name":…,"input":{…}} block with stop_reason:"tool_use".

Streaming response (named SSE events)

Emits Anthropic-style events. Text arrives as text_delta, thinking as thinking_delta, and tool arguments as input_json_delta.

event: message_start
data: {"type":"message_start","message":{"id":"msg_…","type":"message","role":"assistant","model":"my-profile","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":15,"output_tokens":0}}}

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"It is 3,776 m."}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":8}}

event: message_stop
data: {"type":"message_stop"}

Token counting: POST /v1/messages/count_tokens

Returns only the input token count for the prompt, without generating.

curl http://localhost:11434/v1/messages/count_tokens -d '{
  "model": "my-profile",
  "messages": [{"role": "user", "content": "hello world"}]
}'
{ "input_tokens": 4 }

Errors use the Anthropic shape ({"type":"error","error":{"type":…,"message":…}}). A missing max_tokens is an invalid_request_error (400).

8. GET/v1/models & /models

Returns the OpenAI-compatible model list plus a llama.cpp WebUI-oriented models array with status and modality information.

curl http://localhost:11434/v1/models
{
  "object": "list",
  "data": [
    {
      "id": "my-profile",
      "name": "my-profile",
      "object": "model",
      "owned_by": "llamacpp",
      "created": 1786000000,
      "in_cache": true,
      "path": "/data/.../my-model.gguf",
      "status": { "value": "loaded" },
      "tags": []
    }
  ],
  "models": [
    {
      "name": "my-profile",
      "model": "my-profile",
      "modified_at": "2026-08-13T03:10:00.000Z",
      "size": 4100000000,
      "capabilities": ["multimodal"],
      "modalities": { "vision": true, "audio": false },
      "details": { "format": "gguf", "family": "llama", "parameter_size": "unknown", "quantization_level": "unknown" }
    }
  ]
}

data[] is OpenAI-compatible and models[] is llama.cpp WebUI-compatible. status.value is loaded or unloaded, and in_cache reports whether the GGUF file exists on the device. modalities reflects real capabilities for a loaded model, or values inferred from the file otherwise. details.family is the model family inferred from the file name.

9. POSTEmbeddings

Assign an embedding GGUF (a BERT-family or *-embedding model) to a profile and you can fetch vectors in both Ollama and OpenAI shapes. All three endpoints share the same native implementation and differ only in request/response shape.

EndpointInputOutput
POST /api/embed (current Ollama){model, input}, where input is a string or array of strings.{model, embeddings: [[...], ...], total_duration, load_duration, prompt_eval_count}
POST /api/embeddings (legacy Ollama){model, prompt} (single string){embedding: [...]}
POST /v1/embeddings (OpenAI){model, input}, where input is a string or array of strings.{object:"list", data:[{object:"embedding", index, embedding:[...]}], model, usage}

Example (Ollama shape, batch input)

curl http://localhost:11434/api/embed -d '{
  "model": "my-embedding-profile",
  "input": ["weather in Tokyo", "weather in Osaka"]
}'
{
  "model": "my-embedding-profile",
  "embeddings": [[0.0123, -0.0456, ...], [0.0210, -0.0388, ...]],
  "total_duration": 84000000,
  "load_duration": 3000000,
  "prompt_eval_count": 12
}

Example (OpenAI shape)

curl http://localhost:11434/v1/embeddings -d '{
  "model": "my-embedding-profile",
  "input": "text of the search query"
}'
{
  "object": "list",
  "data": [
    { "object": "embedding", "index": 0, "embedding": [0.0123, -0.0456, ...] }
  ],
  "model": "my-embedding-profile",
  "usage": { "prompt_tokens": 0, "total_tokens": 0 }
}

Empty input ("" or an empty array) returns 400; a failed embedding returns 500. Long inputs are truncated internally to the model's n_batch. The usage object on /v1/embeddings is a compatibility placeholder and is always 0.

10. POSTToken counting

Returns the token IDs / token count for a text using the loaded model's tokenizer. Useful for estimating context usage and for chunking.

Ollama shape: POST /api/tokenize

curl http://localhost:11434/api/tokenize -d '{
  "model": "my-profile",
  "content": "Hello, world"
}'
{ "tokens": [123, 4567, 89, 1011] }

OpenAI shape: POST /v1/responses/input_tokens

curl http://localhost:11434/v1/responses/input_tokens -d '{
  "model": "my-profile",
  "input": "Hello, world"
}'
{ "input_tokens": 4, "object": "response.input_tokens" }

/api/tokenize requires content; /v1/responses/input_tokens requires input (an empty string returns 400). If model is omitted on /v1/responses/input_tokens, it resolves to the currently loaded model, then to the default profile.

11. POST/models/load & /models/unload

App-specific endpoints that control model residency without running inference — handy for warming up before a benchmark or for freeing memory.

# Load a profile (returns immediately if already loaded)
curl http://localhost:11434/models/load -d '{"model": "my-profile"}'
{ "success": true }

# Unload (free memory)
curl http://localhost:11434/models/unload -d '{"model": "my-profile"}'
{ "success": true }

12. GET/props, /slots, /health

These endpoints are mainly used to initialize the bundled (llama.cpp-style) WebUI. Add ?model=<profile-name> to target a specific profile.

curl "http://localhost:11434/props?model=my-profile"
curl http://localhost:11434/health

13. Generation & sampling parameters

13-1. Context size (num_ctx)

Setting options.num_ctx (or a top-level num_ctx) applies that context length on the next load. If it differs from the currently loaded value the model is reloaded automatically, so expect the first request to take longer.

{ "model": "my-profile", "options": { "num_ctx": 16384 }, "messages": [...] }

13-2. Generation length

Use num_predict, n_predict, or max_tokens — all are accepted, both inside options and at the top level, in that order of precedence. n_predict: 0 performs pre-encode only and returns an empty response early (useful for warming up prompt evaluation).

13-3. Sampling parameters

Include these keys in the request body (for /api/* they may also go inside options) to override profile settings for that request; anything omitted keeps the profile value.

KeyTypeSetting
temperaturenumberTemperature
top_kintTop-k
top_pnumberTop-p
min_pnumberMin-p
typ_pnumberTypical-p
dynatemp_range / dynatemp_exponentnumberDynamic temperature
xtc_probability / xtc_thresholdnumberXTC sampler
repeat_last_nintPenalty window (tokens)
repeat_penaltynumberRepeat penalty
presence_penalty / frequency_penaltynumberPresence / frequency penalty
dry_multiplier / dry_base / dry_allowed_length / dry_penalty_last_nnumber / intDRY sampler
mirostat / mirostat_tau / mirostat_etaint / numberMirostat
samplersstring[]Order in which the sampler chain is applied (e.g. ["top_k","typ_p","top_p","min_p","temperature"]). An empty array or omitting the field restores the profile's default order.

The keys honored inside options are num_ctx, num_predict, n_predict, temperature, top_k, top_p, min_p, repeat_penalty, repeat_last_n, mirostat, mirostat_tau, and mirostat_eta. Put the other samplers (DRY, XTC, dynatemp, typ_p, …) at the top level.

14. Structured output (JSON / GBNF)

Generation can be constrained by a schema or a grammar. Both the Ollama and OpenAI shapes are accepted; the constraint applies to that request only and never leaks into the next one.

FieldStyleDescription
formatOllama"json" forces arbitrary JSON; passing a JSON Schema object constrains output to that schema.
grammarOllama / llama.cppRaw GBNF grammar.
response_formatOpenAI{"type":"json_object"}, {"type":"json_schema","json_schema":{"name":...,"schema":{...}}}, or {"type":"text"} (no constraint).
// Ollama: structured output via JSON Schema
curl http://localhost:11434/api/chat -d '{
  "model": "my-profile",
  "stream": false,
  "format": {
    "type": "object",
    "properties": {
      "city": { "type": "string" },
      "temperature_c": { "type": "number" }
    },
    "required": ["city", "temperature_c"]
  },
  "messages": [{"role": "user", "content": "Tokyo temperature as JSON"}]
}'
// OpenAI: response_format
curl http://localhost:11434/v1/chat/completions -d '{
  "model": "my-profile",
  "stream": false,
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "weather",
      "schema": {
        "type": "object",
        "properties": { "city": {"type":"string"}, "temperature_c": {"type":"number"} },
        "required": ["city", "temperature_c"]
      }
    }
  },
  "messages": [{"role": "user", "content": "Tokyo weather as JSON"}]
}'
// Raw GBNF (allow only yes / no)
{
  "model": "my-profile",
  "prompt": "Is Mount Fuji the highest mountain in Japan?",
  "grammar": "root ::= \"yes\" | \"no\""
}

15. Reasoning control

For models with a thinking phase (DeepSeek-R1, the Qwen3 family, and so on), the thinking portion is separated from the body and returned as reasoning_content. Whether it is enabled is resolved in this order:

  1. think (the Ollama standard): true / false, or a string (anything other than "false" enables it).
  2. reasoning_format: "none" disables, "auto" enables.
  3. chat_template_kwargs: enable_thinking / enable_reasoning (bool), or reasoning ("off" disables).
  4. The profile's "Enable Think" setting.
// Explicitly disable thinking (Ollama shape)
{ "model": "my-profile", "think": false, "messages": [...] }

// Suppress reasoning output in the OpenAI shape
{ "model": "my-profile", "reasoning_format": "none", "messages": [...] }
// Response example (OpenAI, non-streaming)
{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "reasoning_content": "The user is asking for elevation. Mount Fuji is 3,776 m.",
      "content": "3,776 m."
    },
    "finish_reason": "stop"
  }]
}

When streaming, chunks carrying delta.reasoning_content arrive before the body chunks. When reasoning is disabled, tags such as <think> emitted by the model are stripped from the body.

16. Multimodal inputs

On /api/chat and /v1/chat/completions you can pass images and audio by making content an array of parts. Anthropic's /v1/messages likewise accepts image / audio blocks inside its content array (see below). In all cases the loaded model must support vision / audio; otherwise the request returns 400. Check support via modalities in /v1/models, and load a model that has an mmproj / projector.

typeDescription
text / input_textText (in the text field).
image_urlimage_url.url takes a data:image/...;base64, data URL or an HTTP/HTTPS image URL. Remote fetches are capped at 10 MB with 10-second connect and read timeouts.
input_audioinput_audio.data (base64) and input_audio.format ("wav" or "mp3").

Anthropic /v1/messages blocks: images use {"type":"image","source":{"type":"base64","media_type":"image/jpeg","data":"…"}} (source.type:"url" also works). Audio is accepted as a local extension: {"type":"audio","source":{"data":"<base64>","format":"wav"}}. Internally these are converted to the OpenAI image_url / input_audio parts and processed through the same mmproj path.

Supported image formats: only what the native decoder (stb_image) can read — JPEG, PNG, BMP, GIF, and similar. HEIC/HEIF, WebP, and AVIF — the default capture formats on modern Android — are not supported and return 400 with an explanatory message. Convert to JPEG or PNG first.

curl http://localhost:11434/v1/chat/completions -d '{
  "model": "my-vision-profile",
  "stream": false,
  "messages": [
    {
      "role": "user",
      "content": [
        {"type": "text", "text": "Describe this image."},
        {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ..."}}
      ]
    }
  ]
}'
// Audio input (wav / mp3)
{
  "role": "user",
  "content": [
    {"type": "text", "text": "Transcribe this audio."},
    {"type": "input_audio", "input_audio": {"data": "UklGR...", "format": "wav"}}
  ]
}

17. Tools / Function Calling

Passing tools in the request — or enabling shared MCP / Function Definitions in the app's MCP settings — switches generation to the internal tool-execution loop. It works on /api/generate, /api/chat, /v1/chat/completions, and /v1/messages.

Anthropic /v1/messages: tools use the Anthropic format (name / description / input_schema), and tool_choice accepts auto / any / tool. Responses come back as an assistant tool_use block (stop_reason:"tool_use"); send a user tool_result block on the next turn to complete the round-trip. When shared MCP is enabled, MCP tools are executed server-side automatically (tools passed in the request are client-executed).

// Request (OpenAI-compatible tool definition)
{
  "model": "my-profile",
  "stream": false,
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Return the current weather for a city",
        "parameters": {
          "type": "object",
          "properties": { "city": { "type": "string" } },
          "required": ["city"]
        }
      }
    }
  ],
  "messages": [{"role": "user", "content": "What is the weather in Tokyo?"}]
}
// Response (non-streaming)
{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "created": 1786000000,
  "model": "my-profile",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "",
        "tool_calls": [
          {
            "id": "call_1",
            "type": "function",
            "function": { "name": "get_weather", "arguments": "{\"city\":\"Tokyo\"}" }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ],
  "usage": { "prompt_tokens": 96, "completion_tokens": 21, "total_tokens": 117 }
}

18. Client examples

OpenAI Python SDK

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="not-needed",  # no auth; any string works
)

resp = client.chat.completions.create(
    model="my-profile",
    messages=[{"role": "user", "content": "Hello"}],
    stream=False,
)
print(resp.choices[0].message.content)
print(resp.usage)  # prompt_tokens / completion_tokens / total_tokens

OpenAI-compatible streaming (Python)

stream = client.chat.completions.create(
    model="my-profile",
    messages=[{"role": "user", "content": "Write one haiku"}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta
    if delta.content:
        print(delta.content, end="", flush=True)

Structured output (Python)

resp = client.chat.completions.create(
    model="my-profile",
    messages=[{"role": "user", "content": "Tokyo temperature as JSON"}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "weather",
            "schema": {
                "type": "object",
                "properties": {"city": {"type": "string"}, "temperature_c": {"type": "number"}},
                "required": ["city", "temperature_c"],
            },
        },
    },
    stream=False,
)
import json; print(json.loads(resp.choices[0].message.content))

Embeddings (Python)

vec = client.embeddings.create(
    model="my-embedding-profile",
    input=["weather in Tokyo", "weather in Osaka"],
)
print(len(vec.data), len(vec.data[0].embedding))

Anthropic Python SDK

from anthropic import Anthropic

client = Anthropic(
    base_url="http://localhost:11434",  # serves /v1/messages
    api_key="not-needed",               # auth is ignored
)

msg = client.messages.create(
    model="my-profile",
    max_tokens=256,                     # required
    messages=[{"role": "user", "content": "Hello"}],
)
print(msg.content[0].text)
print(msg.usage.input_tokens, msg.usage.output_tokens)

Anthropic Messages (curl / SSE)

curl -N http://localhost:11434/v1/messages -d '{
  "model": "my-profile",
  "max_tokens": 256,
  "stream": true,
  "messages": [{"role": "user", "content": "Write a haiku"}]
}'

Official Ollama Python client

from ollama import Client

client = Client(host="http://localhost:11434")

res = client.chat(
    model="my-profile",
    messages=[{"role": "user", "content": "Introduce yourself"}],
    options={"num_ctx": 8192, "temperature": 0.7},
)
print(res["message"]["content"])
print(res["eval_count"], res["eval_duration"])  # usable for tok/s

emb = client.embed(model="my-embedding-profile", input="search query")
print(len(emb["embeddings"][0]))

Ollama-compatible (curl / NDJSON)

curl -N http://localhost:11434/api/chat -d '{
  "model": "my-profile",
  "messages": [{"role": "user", "content": "Introduce yourself"}]
}'

JavaScript (fetch / SSE)

const res = await fetch("http://localhost:11434/v1/chat/completions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "my-profile",
    messages: [{ role: "user", content: "Hello" }],
    stream: true,
  }),
});

const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  for (const line of decoder.decode(value).split("\n")) {
    if (!line.startsWith("data: ")) continue;
    const data = line.slice(6);
    if (data === "[DONE]") break;
    const json = JSON.parse(data);
    process.stdout.write(json.choices[0].delta.content ?? "");
  }
}

Warm up → generate → release (curl)

# 1. Keep the model resident (no inference)
curl http://localhost:11434/models/load -d '{"model":"my-profile"}'

# 2. Check the prompt's token count
curl http://localhost:11434/api/tokenize -d '{"model":"my-profile","content":"a long input text…"}'

# 3. Generate
curl http://localhost:11434/api/chat -d '{"model":"my-profile","stream":false,"messages":[{"role":"user","content":"Summarize it"}]}'

# 4. Free memory
curl http://localhost:11434/models/unload -d '{"model":"my-profile"}'

This page reflects the current Android implementation (OllamaApiServer). Response samples are formatted for readability; actual fields and defaults may change between app versions.