API Server
The initrunner serve command exposes any agent as an OpenAI-compatible HTTP API. Use InitRunner agents as drop-in replacements for OpenAI in any client that speaks the chat completions format — including the official OpenAI SDKs, curl, and tools like Open WebUI.
Quick Start
# Start the server
initrunner serve role.yaml
# With authentication
initrunner serve role.yaml --api-key my-secret-key
# Custom host/port
initrunner serve role.yaml --host 0.0.0.0 --port 3000CLI Options
| Option | Type | Default | Description |
|---|---|---|---|
role_file | Path | (required) | Path to the role YAML file |
--host | str | 127.0.0.1 | Host to bind to (0.0.0.0 for all interfaces) |
--port | int | 8000 | Port to listen on |
--api-key | str | null | API key for Bearer token authentication |
--audit-db | Path | ~/.initrunner/audit.db | Audit database path |
--no-audit | bool | false | Disable audit logging |
Endpoints
GET /health
Always returns 200 OK. Not protected by authentication.
{"status": "ok"}GET /v1/models
Lists available models. Returns the agent's metadata.name as the model ID.
{
"object": "list",
"data": [
{
"id": "my-agent",
"object": "model",
"created": 1700000000,
"owned_by": "initrunner"
}
]
}POST /v1/chat/completions
The main chat completions endpoint. Accepts the standard OpenAI request format.
| Field | Type | Default | Description |
|---|---|---|---|
model | str | "" | Model name (ignored — uses role config) |
messages | list | [] | Conversation messages (role + content) |
stream | bool | false | Enable SSE streaming |
Streaming
When stream: true, the server responds with Server-Sent Events (SSE):
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"}}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"delta":{"content":"Hello"}}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]Multi-Turn Conversations
Use the X-Conversation-Id header for server-side conversation history:
- Send a request with
X-Conversation-Id: conv-001. - The server stores message history after each request.
- Subsequent requests with the same ID use stored history — only the last user message is the new prompt.
- Conversations expire after 1 hour of inactivity.
Authentication
When --api-key is set, all /v1/* endpoints require:
Authorization: Bearer <api-key>The /health endpoint is never protected.
Usage Examples
curl
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "Hello!"}]
}'curl (with auth and conversation)
# First message
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer my-secret-key" \
-H "X-Conversation-Id: conv-001" \
-d '{"messages": [{"role": "user", "content": "My name is Alice."}]}'
# Follow-up
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer my-secret-key" \
-H "X-Conversation-Id: conv-001" \
-d '{"messages": [{"role": "user", "content": "What is my name?"}]}'OpenAI Python SDK
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8000/v1",
api_key="my-secret-key", # or "unused" if no --api-key set
)
response = client.chat.completions.create(
model="my-agent",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)OpenAI Python SDK (streaming)
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8000/v1",
api_key="unused",
)
stream = client.chat.completions.create(
model="my-agent",
messages=[{"role": "user", "content": "Tell me a story."}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")OpenAI Node.js SDK
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "http://127.0.0.1:8000/v1",
apiKey: "my-secret-key",
});
const response = await client.chat.completions.create({
model: "my-agent",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.choices[0].message.content);