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
Contents
1. Basics
- Base URL:
http://localhost:<port>(default port11434). While on Wi-Fi, a LAN URL (http://<device-ip>:<port>) is also available on the same port. - Shared port: The API and the WebUI are served on the same port. GET requests outside API paths fall through to the WebUI.
- Authentication: There is no API-key auth (the
Authorizationheader is ignored). Intended for same-device or local-network use. - CORS: All responses include
Access-Control-Allow-Origin: *, andOPTIONSpreflight returns204withAllow-Methods: GET, POST, OPTIONS. - Content types: Request bodies are JSON (
application/json). Non-streaming responses are JSON; streaming responses use NDJSON (application/x-ndjson, chunked) for/api/*and SSE (text/event-stream) for/v1/*. - Body limit: Request bodies may be up to 64 MB; larger bodies return
413. The limit is sized for multimodal requests carrying base64 images or audio. - Model selection:
modelis a configuration (profile) name. List profiles via/api/tagsor/v1/models.
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.
| Category | Details |
|---|---|
| Ollama-compatible | POST /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-compatible | POST /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-compatible | POST /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-compatible | GET /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
- There is a single inference slot (single generation). Additional requests during generation are queued (up to
10) and wait up to600seconds by default (configurable via the "busy timeout" setting;0= unlimited). - Queue overflow or wait timeout returns
503. - While a model reset is in progress, new requests are rejected and queued requests are aborted (
503). - Embedding, tokenize, and model-load requests acquire the same slot as generation.
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
}
}
| Status | Meaning |
|---|---|
400 | Malformed JSON, missing required field, unsupported content[].type, unsupported modality, unsupported image format, and so on |
404 | Unknown route |
405 | Unsupported HTTP method |
413 | Request body larger than 64 MB |
500 | Configuration load failure, generation or embedding failure |
503 | Busy (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
| Field | Type | Description |
|---|---|---|
model | string | Profile name. Defaults to "default". |
prompt | string | User prompt. |
system | string? | Optional system prompt (takes precedence over the profile setting). |
stream | bool | Defaults to true; set false for a single JSON response. |
options | object? | 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 / grammar | string|object | Optional structured-output constraint (see §14). |
think | bool|string? | Optional reasoning toggle (see §15). |
tools | array? | 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
| Field | Type | Description |
|---|---|---|
model | string | Profile name. Defaults to "default". |
messages | array | Required. Array of {role, content}; role is system / user / assistant / tool. content is a string or a multimodal parts array (see §16). |
stream | bool | Defaults to true. |
options | object? | Same as §4 (num_ctx, num_predict, and so on). |
format / grammar | string|object | Optional (see §14). |
think | bool|string? | Optional (see §15). |
tools | array? | 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
| Field | Type | Description |
|---|---|---|
model | string | Profile name. If empty or omitted, resolves to the currently loaded model, then to the default profile. |
messages | array | Required, OpenAI format. content is a string or an array of {type, ...} parts (see §16). |
stream | bool | Defaults to true. |
max_tokens / n_predict | int | Generation cap. n_predict: 0 performs pre-encode only (see §13). |
response_format | object? | json_object / json_schema / text (see §14). |
reasoning_format / chat_template_kwargs | string / object | Reasoning control (see §15). |
tools / tool_choice / parallel_tool_calls | array / any / bool | Optional (see §17). |
| Sampling fields | number, 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
| Field | Type | Description |
|---|---|---|
model | string | Profile name. When empty/omitted it resolves to the currently loaded model, else the default profile. |
prompt | string | string[] | Required. A string, or an array of strings (joined by newlines). Empty/missing returns 400. |
stream | bool | Defaults to false. true for SSE. |
max_tokens / n_predict | int | Generation token cap (§13). |
sampling / grammar | number, 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
| Field | Type | Description |
|---|---|---|
model | string | Profile name. Sending a real Claude model name (e.g. claude-3-5-sonnet) still resolves to the locally loaded model. |
max_tokens | int | Required. Missing returns 400. |
messages | array | Required. 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). |
system | string | array? | Optional system prompt (string or array of text blocks). |
stream | bool | Defaults to false. true for named SSE events. |
thinking | object? | {"type":"enabled"|"disabled"} (§15). |
tools / tool_choice | array / object? | Optional. Anthropic format (name / description / input_schema). tool_choice is auto / any / tool (§17). |
temperature / top_p / top_k | number | Optional 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.
| Endpoint | Input | Output |
|---|---|---|
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 }
- If
modelis omitted, it resolves to the currently loaded model, then to the default profile. - A failed load returns
500with{"success": false, "error": "Failed to load model"}. /models/unloadfrees the model only when the named profile is the one currently loaded, and returns503(Model is busy) during generation.
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.
/props: Returnsdefault_generation_settings(n_ctx, sampling values,n_predict,seed,stop,grammar),chat_template,modalities,model_path,build_info, andwebui_settings(systemMessage, think display, shared MCP / Function Definitions settings).total_slotsis1.max_tokensappears only when an explicit generation cap is configured (it is omitted when unlimited)./slots: A one-element slot array withid,n_ctx,params(sampling values),is_processing, and more./health,/v1/health:{"status":"ok","role":"model"|"router","webui":true}.GET /api:{"status":"Ollama is running"}— the reachability probe Ollama clients use.
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.
| Key | Type | Setting |
|---|---|---|
temperature | number | Temperature |
top_k | int | Top-k |
top_p | number | Top-p |
min_p | number | Min-p |
typ_p | number | Typical-p |
dynatemp_range / dynatemp_exponent | number | Dynamic temperature |
xtc_probability / xtc_threshold | number | XTC sampler |
repeat_last_n | int | Penalty window (tokens) |
repeat_penalty | number | Repeat penalty |
presence_penalty / frequency_penalty | number | Presence / frequency penalty |
dry_multiplier / dry_base / dry_allowed_length / dry_penalty_last_n | number / int | DRY sampler |
mirostat / mirostat_tau / mirostat_eta | int / number | Mirostat |
samplers | string[] | 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.
| Field | Style | Description |
|---|---|---|
format | Ollama | "json" forces arbitrary JSON; passing a JSON Schema object constrains output to that schema. |
grammar | Ollama / llama.cpp | Raw GBNF grammar. |
response_format | OpenAI | {"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:
think(the Ollama standard):true/false, or a string (anything other than"false"enables it).reasoning_format:"none"disables,"auto"enables.chat_template_kwargs:enable_thinking/enable_reasoning(bool), orreasoning("off"disables).- 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.
| type | Description |
|---|---|
text / input_text | Text (in the text field). |
image_url | image_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_audio | input_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).
toolsis an OpenAI-compatible array; a non-array value returns400.tool_choiceandparallel_tool_callsare accepted.- With shared MCP / Function Definitions enabled, the same tool configuration also applies to the main-screen prompt and to every API path.
- Besides
tool_callsreturned directly by the model, responses may include calls extracted from tool-call markers in the body or fromreasoning_content. - The Ollama shape reports
done_reason("stop"/"tool_calls"); the OpenAI shape reportsfinish_reason.
// 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.