API reference
Chat.
Find a model, then talk to it: one answer or a stream, with tool calls and JSON output when the model supports them.
GET/v1/models
List models
The online models (LLMs) this key can call. Send the returned id as model, exactly as it is.
Example
curl https://api.ompera.net/v1/models \
-H "Authorization: Bearer $OMPERA_API_KEY"Response
{
"object": "list",
"data": [
{
"id": "my/local",
"object": "model",
"owned_by": "ompera-owner",
"name": "Qwen3 4B",
"kind": "chat",
"capabilities": ["chat", "streaming", "tools", "json_object", "json_schema"],
"available_nodes": 1,
"capacity": 1,
"inflight": 0
}
]
}One entry per model. Pool models also carry pool and copies.
| Field | Meaning |
|---|---|
id | The address to send as model. |
owned_by | ompera-owner: your own model (my/…). ompera-pool: a model from a member of your pool (<pool>/…). ompera-community: an Ompera network model, available to every key. |
capabilities | Features the model passed: streaming, tools, json_object, json_schema. A request that needs a missing one is refused with unsupported_model_capability. |
pool, copies | Pool models only: the pool, and how many members run the model now. Ompera sends each request to a free copy. |
capacity, inflight | How many requests the model can run at once, and how many are running now. |
Why a model may be missing
Only online models are listed, and the key must allow them. Models connected from a computer (my/… and pool models) need a key with Allow Unshielded models. If your model is offline, its address still works: requests fail with provider_unavailable unless the key's offline setting sends them to a pool model (offline settings).
POST/v1/chat/completions
Chat completions
Send the conversation, get the assistant's message. The standard OpenAI request body.
Request
| Field | Notes |
|---|---|
modelrequired | An id from List models. |
messagesrequired | The conversation: {"role", "content"} objects with the roles system, user, assistant and tool. |
stream | true sends the answer while it is written. See Streaming. |
tools, tool_choice | Functions the model may call. See Tool calls. |
response_format | text, json_object or json_schema. See JSON output. |
| Anything else | temperature, max_tokens, top_p, stop, seed and other fields reach the model server unchanged; what they do depends on that server. |
Example
curl
curl https://api.ompera.net/v1/chat/completions \
-H "Authorization: Bearer $OMPERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "MODEL_ADDRESS",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
]
}'Python
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.ompera.net/v1", api_key=os.environ["OMPERA_API_KEY"])
reply = client.chat.completions.create(
model="MODEL_ADDRESS",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
],
)
print(reply.choices[0].message.content)JavaScript
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.ompera.net/v1", apiKey: process.env.OMPERA_API_KEY });
const reply = await client.chat.completions.create({
model: "MODEL_ADDRESS",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" },
],
});
console.log(reply.choices[0].message.content);Response
{
"id": "chatcmpl-8f2c",
"object": "chat.completion",
"model": "my/local",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "Hello! How can I help?"},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 21, "completion_tokens": 7, "total_tokens": 28}
}The body comes from the model server; model is set to the address that answered, and usage is there when the server reports it. The X-Ompera-Model header carries the same address. It differs from the one you sent only when the key's offline setting switched to another pool model.
Streaming
Set "stream": true to receive the answer as server-sent events while the model writes it. The model needs the streaming capability.
Python
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.ompera.net/v1", api_key=os.environ["OMPERA_API_KEY"])
stream = client.chat.completions.create(
model="MODEL_ADDRESS",
messages=[{"role": "user", "content": "Write a haiku about the sea."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)JavaScript
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://api.ompera.net/v1", apiKey: process.env.OMPERA_API_KEY });
const stream = await client.chat.completions.create({
model: "MODEL_ADDRESS",
messages: [{ role: "user", content: "Write a haiku about the sea." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}curl
curl -N https://api.ompera.net/v1/chat/completions \
-H "Authorization: Bearer $OMPERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"MODEL_ADDRESS","stream":true,"messages":[{"role":"user","content":"Write a haiku about the sea."}]}'What the events look like
data: {"object":"chat.completion.chunk","model":"my/local","choices":[{"index":0,"delta":{"role":"assistant","content":"Waves"}}]}
data: {"object":"chat.completion.chunk","model":"my/local","choices":[{"index":0,"delta":{"content":" fold into foam"}}]}
data: {"object":"chat.completion.chunk","model":"my/local","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]A chunk may have no content, or an empty choices array when it only carries usage.
When the model fails mid-stream
The status is already 200, so the error arrives as an event, followed by [DONE]. The OpenAI SDKs raise it as an API error; with raw SSE, check each event for error and treat a stream without finish_reason as incomplete.
data: {"error":{"code":"provider_protocol_error","message":"provider returned an invalid streaming response"}}
data: [DONE]Tool calls
Describe functions in tools; the model answers with tool_calls when it wants one run. The model needs the tools capability.
import json, os
from openai import OpenAI
client = OpenAI(base_url="https://api.ompera.net/v1", api_key=os.environ["OMPERA_API_KEY"])
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather in a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
messages = [{"role": "user", "content": "What's the weather in Lisbon?"}]
while True:
reply = client.chat.completions.create(model="MODEL_ADDRESS", messages=messages, tools=tools)
message = reply.choices[0].message
if not message.tool_calls:
break
messages.append(message)
for call in message.tool_calls:
arguments = json.loads(call.function.arguments)
result = {"city": arguments["city"], "temperature_c": 21} # call your real function here
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
print(message.content)When streaming, tool calls arrive in pieces: join delta.tool_calls by index before parsing the arguments.
JSON output
json_schema asks the model to follow your schema; json_object asks only for valid JSON, so describe the shape in the prompt. They are separate capabilities: check the model's capabilities first.
import json, os
from openai import OpenAI
client = OpenAI(base_url="https://api.ompera.net/v1", api_key=os.environ["OMPERA_API_KEY"])
reply = client.chat.completions.create(
model="MODEL_ADDRESS",
messages=[{"role": "user", "content": "Anna is 31 and lives in Porto."}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "person",
"schema": {
"type": "object",
"properties": {"name": {"type": "string"}, "age": {"type": "integer"}, "city": {"type": "string"}},
"required": ["name", "age", "city"],
},
},
},
)
person = json.loads(reply.choices[0].message.content)Small models can still return invalid JSON: validate the result, and retry or fall back if parsing fails.