Tutorials  /  AI/ML

Calling the Ollama API from Your Own Applications

LLudwig · August 2026 ·12 min read ·AI/ML, Tutorial

Ollama exposes an HTTP API on port 11434, and everything you build on top of it goes through that one service. The request shapes differ per endpoint: /api/generate is stateless, /api/chat carries the conversation, and the OpenAI-compatible routes under /v1 expect a different payload again. This tutorial covers the calls you need in application code: streaming, conversation state, JSON schema output, tool calling and model residency.

What is the Ollama API?

The Ollama API is a REST interface served by the ollama daemon on http://localhost:11434, which accepts JSON payloads on endpoints such as /api/generate, /api/chat and /api/embed and returns either NDJSON streams or single JSON objects.

The API has no authentication layer of its own. Anything that can reach the port can load models and run inference, which matters as soon as you move the daemon off your workstation.

Endpoint Method Purpose
/api/generate POST Single prompt, no conversation history
/api/chat POST Message list with roles, tool calling
/api/embed POST Vector embeddings for one or more inputs
/api/tags GET Models available locally
/api/ps GET Models currently loaded in memory
/v1/chat/completions POST OpenAI-compatible chat endpoint

Prerequisites

  • Ollama 0.5.0 or newer (ollama --version); structured outputs and the /api/embed endpoint are not available in older builds
  • At least one pulled model, for example ollama pull llama3.2
  • curl, plus Python 3.10+ or Node.js 18+ for the client examples
  • A host with enough VRAM for the model you intend to serve. For 7B models and larger, run the daemon on a GPU instance for LLM inference rather than on CPU

Check that the API is reachable

Before writing client code, confirm the daemon answers and knows the model you plan to call.

Console
$ curl http://localhost:11434/api/version
$ curl -s http://localhost:11434/api/tags | jq '.models[].name'

The first command returns {"version":"0.5.x"}. The second lists model names such as llama3.2:latest. If /api/tags is empty, pull a model first, because the API does not download models implicitly on a chat request.

Which endpoint should you use: /api/generate or /api/chat?

Use /api/generate for single-shot completions such as summarization or classification, and use /api/chat whenever the model needs the previous turns, a system prompt or tool definitions.

/api/generate takes a flat prompt string and can return a context array for simple continuation. /api/chat takes a messages array with system, user, assistant and tool roles, and it is the only endpoint that supports the tools field. New integrations should default to /api/chat.

Send your first request

Start with a non-streaming request. A single JSON object is easier to inspect than an NDJSON stream.

Console
$ curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [{"role": "user", "content": "Explain what an inode is in two sentences."}],
  "stream": false,
  "options": {"temperature": 0.2, "num_ctx": 4096}
}'

The answer sits in message.content. The response also carries total_duration, load_duration, prompt_eval_count and eval_count, all in nanoseconds or tokens. Log eval_count divided by eval_duration to track tokens per second per host.

The options object holds the runtime parameters: temperature, top_p, seed for reproducible sampling, and num_ctx for the context window. Values you do not set fall back to the model's Modelfile defaults.

GPU

Matching infrastructure at centron

Dedicated NVIDIA GPUs from German data centres, billed by the hour and ready in minutes. Rent a GPU server →

Stream tokens instead of waiting

With "stream": true, which is the default when you omit the field, Ollama returns newline-delimited JSON where each line is a complete JSON object carrying one content fragment.

graph TD
  A["Your application"] -->|"POST /api/chat"| B["Ollama daemon on :11434"]
  B --> C{"Model already resident?"}
  C -->|"No"| D["Load weights into VRAM"]
  C -->|"Yes"| E["Run inference"]
  D --> E
  E --> F{"stream: true?"}
  F -->|"Yes"| G["NDJSON: one JSON object per line"]
  F -->|"No"| H["Single JSON object"]
  G --> I["Final line with done: true and timings"]
  H --> I

Parse each line separately. Do not buffer the whole body and call json.loads once, because the payload is not a JSON array.

python
import json
import requests
PAYLOAD = {
    "model": "llama3.2",
    "messages": [{"role": "user", "content": "List three uses for /proc."}],
    "stream": True,
}
with requests.post("http://localhost:11434/api/chat", json=PAYLOAD, stream=True, timeout=120) as r:
    r.raise_for_status()
    for line in r.iter_lines():
        if not line:
            continue
        chunk = json.loads(line)
        if chunk.get("done"):
            print()
            break
        print(chunk["message"]["content"], end="", flush=True)

The same request from Node.js without streaming looks like this:

javascript
const res = await fetch("http://localhost:11434/api/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "llama3.2",
    messages: [{ role: "user", content: "Explain the TCP backlog." }],
    stream: false
  })
});
const data = await res.json();
console.log(data.message.content);

Set a generous client timeout. A cold model load on a large quantization can take 30 seconds or more before the first token arrives.

How do you keep conversation context?

The Ollama server keeps no conversation state, so your application must resend the complete messages array on every request, including the previous assistant replies you want the model to remember.

python
import requests
URL = "http://localhost:11434/api/chat"
history = [{"role": "system", "content": "You answer as a Linux administrator. Be terse."}]
def ask(question):
    history.append({"role": "user", "content": question})
    r = requests.post(URL, json={"model": "llama3.2", "messages": history, "stream": False}, timeout=120)
    r.raise_for_status()
    reply = r.json()["message"]
    history.append(reply)
    return reply["content"]
print(ask("Which command shows open sockets?"))
print(ask("Add the process name to that command."))

The history grows with every turn and is limited by num_ctx. Once the token count approaches the context window, drop the oldest user and assistant pairs while keeping the system message, or summarize them into a single message.

Force structured JSON output

Pass a JSON Schema in the format field to make the model emit machine-readable output. This works on /api/chat and /api/generate since Ollama 0.5.0. The older "format": "json" value still works, but it only guarantees valid JSON, not a specific shape.

Console
$ curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [{"role": "user", "content": "Parse: web01 is down since 14:05, severity high"}],
  "stream": false,
  "format": {
    "type": "object",
    "properties": {
      "host": {"type": "string"},
      "since": {"type": "string"},
      "severity": {"type": "string", "enum": ["low", "medium", "high"]}
    },
    "required": ["host", "since", "severity"]
  }
}'

message.content is now a JSON string matching the schema, so your client parses it with a second json.loads. Keep temperature low for extraction tasks and state the expected fields in the prompt as well; the schema constrains the grammar, not the semantics.

Call your own functions with tool calling

Declare functions in the tools array of /api/chat. The model does not execute anything. It returns a tool_calls entry, your code runs the function and sends the result back as a message with the role tool.

json
{
  "model": "llama3.1",
  "stream": false,
  "messages": [{"role": "user", "content": "What is the disk usage on web01?"}],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_disk_usage",
        "description": "Return disk usage in percent for a host",
        "parameters": {
          "type": "object",
          "properties": {"host": {"type": "string"}},
          "required": ["host"]
        }
      }
    }
  ]
}

The reply contains message.tool_calls[0].function.name and a parsed arguments object. Append the assistant message, then append {"role": "tool", "content": "71"} and call /api/chat again so the model can phrase the final answer. Tool calling requires a model trained for it, such as llama3.1, qwen2.5 or mistral-nemo. Models without tool support ignore the tools field and answer in prose.

Generate embeddings for retrieval

Use POST /api/embed with an embedding model to turn text into vectors. The input field accepts a single string or an array, which is the cheaper option for batch indexing.

Console
$ curl http://localhost:11434/api/embed -d '{
  "model": "nomic-embed-text",
  "input": ["systemd unit files live in /etc/systemd/system", "journalctl reads the binary journal"]
}'

The response holds embeddings as an array of float arrays, one per input, with 768 dimensions for nomic-embed-text. Embedding models are small enough to run acceptably without a GPU, so an indexing worker and its vector database fit on a scalable Linux VM while the chat model stays on GPU hardware. Keep the generation and embedding workloads on separate daemons if both are under load, because a single Ollama instance serializes requests per loaded model by default.

Use the OpenAI-compatible endpoint

Ollama serves /v1/chat/completions, /v1/embeddings and /v1/models in the OpenAI request format, so existing SDK code works after changing the base URL.

python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
resp = client.chat.completions.create(
    model="llama3.2",
    messages=[{"role": "user", "content": "One sentence on epoll."}],
)
print(resp.choices[0].message.content)

The api_key value is required by the SDK but ignored by Ollama. Use this route for compatibility with existing tooling. Use the native /api/chat route when you need keep_alive, num_ctx or the raw timing fields, which the compatibility layer does not expose.

Why is the first request slow?

The first request after an idle period is slow because Ollama unloads model weights from memory after five minutes of inactivity and has to reload them, which shows up as a large load_duration in the response.

Control this with the keep_alive field per request or the OLLAMA_KEEP_ALIVE environment variable for the daemon. A value of -1 keeps the model resident indefinitely, 0 unloads it immediately after the request.

Console
$ curl http://localhost:11434/api/chat -d '{"model":"llama3.2","messages":[],"keep_alive":-1}'
$ curl -s http://localhost:11434/api/ps | jq '.models[] | {name, size_vram, expires_at}'

An empty messages array loads the model without running inference, which is the standard warm-up call after a deployment. /api/ps then shows the resident models and their VRAM footprint.

Expose the API beyond localhost

The daemon binds to 127.0.0.1:11434 by default. To accept requests from other hosts, set OLLAMA_HOST in a systemd override.

Console
$ sudo systemctl edit ollama.service
ini
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
Environment="OLLAMA_KEEP_ALIVE=30m"
Console
$ sudo systemctl daemon-reload
$ sudo systemctl restart ollama

The port is now unauthenticated on every interface. Restrict it with a firewall rule to the application subnet and put a reverse proxy in front that terminates TLS and checks an API key. For browser clients, set OLLAMA_ORIGINS to the allowed origins, otherwise the preflight request fails.

Verification

Run one scripted request end to end and check the fields your application depends on.

Console
$ curl -s http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [{"role": "user", "content": "Reply with the single word: ok"}],
  "stream": false
}' | jq '{content: .message.content, done_reason, eval_count}'

Expected output resembles the following:

json
{
  "content": "ok",
  "done_reason": "stop",
  "eval_count": 3
}

A done_reason of length means the model hit the token limit, not the end of its answer. Raise num_predict in options in that case.

Troubleshooting

  • curl: (7) Failed to connect to ... port 11434: the daemon is not running or still bound to localhost. Check with systemctl status ollama and ss -tlnp | grep 11434.
  • {"error":"model 'llama3.2' not found, try pulling it first"}: the API does not download models on demand. Run ollama pull llama3.2 on the host serving the API, not on the client.
  • Stream parsing crashes with Extra data: the body is NDJSON, not a JSON array. Parse line by line, as in the streaming example above.
  • CORS errors in the browser: set OLLAMA_ORIGINS="https://<your-domain>" in the systemd override and restart the service.

Wrap-up

For application code, /api/chat with an explicit messages array is the endpoint to build on: it covers system prompts, tool calls and JSON schema output in one request shape. Resend the full history yourself, keep keep_alive aligned with your traffic pattern, and never expose port 11434 without a proxy that authenticates requests.

Read next

Jetzt 200 € Guthaben sichern

Testen Sie Ihr Setup auf ccloud³

Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.

Ludwig Technische Redaktion

Schreibt bei centron über Linux-Administration, Container und Datenbanken – mit Fokus auf Anleitungen, die im Betrieb tatsächlich funktionieren.

Kategorie AI/ML
Teilen
Noch offene Fragen?

Our team will help you with your specific setup - in German or English, by people who run the platform themselves.

War dieses Tutorial hilfreich?

Your answer is stored anonymously and helps us improve our tutorials.

Kommentare

No comments yet - be the first to ask a question about this tutorial.

Sign in to comment

Comments are open to centron customers. Sign in to your account to ask a question about this tutorial.

Weiterlesen

Das könnte Sie auch interessieren

Jetzt kostenlos anfangen

Melden Sie sich an und erhalten Sie in den ersten 60 Tagen ein Guthaben von 200 € bei centron.

Dieses Werbeangebot gilt nur für neue Konten. Angebot ausschließlich für Gewerbetreibende.

Jetzt loslegen Sales kontaktieren