Completions
OpenAI-Compatible Chat Completions
Drop-in replacement for the OpenAI Chat Completions API. Point the OpenAI SDK at Theo and keep writing code the way you already do.
POST
/
api
/
v1
/
chat
/
completions
OpenAI-Compatible Chat Completions
curl --request POST \
--url https://api.example.com/api/v1/chat/completions \
--header 'Content-Type: application/json' \
--data '
{
"model": "<string>",
"messages": [
{}
],
"stream": true,
"temperature": 123,
"reasoning_effort": "<string>",
"conversation_id": "<string>",
"skills": [
"<string>"
],
"metadata": {}
}
'import requests
url = "https://api.example.com/api/v1/chat/completions"
payload = {
"model": "<string>",
"messages": [{}],
"stream": True,
"temperature": 123,
"reasoning_effort": "<string>",
"conversation_id": "<string>",
"skills": ["<string>"],
"metadata": {}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
model: '<string>',
messages: [{}],
stream: true,
temperature: 123,
reasoning_effort: '<string>',
conversation_id: '<string>',
skills: ['<string>'],
metadata: {}
})
};
fetch('https://api.example.com/api/v1/chat/completions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/chat/completions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => '<string>',
'messages' => [
[
]
],
'stream' => true,
'temperature' => 123,
'reasoning_effort' => '<string>',
'conversation_id' => '<string>',
'skills' => [
'<string>'
],
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/chat/completions"
payload := strings.NewReader("{\n \"model\": \"<string>\",\n \"messages\": [\n {}\n ],\n \"stream\": true,\n \"temperature\": 123,\n \"reasoning_effort\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"skills\": [\n \"<string>\"\n ],\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/chat/completions")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"<string>\",\n \"messages\": [\n {}\n ],\n \"stream\": true,\n \"temperature\": 123,\n \"reasoning_effort\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"skills\": [\n \"<string>\"\n ],\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/chat/completions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"<string>\",\n \"messages\": [\n {}\n ],\n \"stream\": true,\n \"temperature\": 123,\n \"reasoning_effort\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"skills\": [\n \"<string>\"\n ],\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_bodyTheo implements the OpenAI Chat Completions wire protocol at
Supported
Pass any Theo-branded model ID.
Unknown model strings fall back to
When to use
POST /api/v1/chat/completions. Any application that uses the OpenAI SDK can point baseURL at https://www.hitheo.ai/api/v1, swap in a Theo API key, and keep working — the endpoint accepts the same { model, messages, stream, temperature, ... } shape and returns the same chat.completion / chat.completion.chunk objects.
The response shape is identical to OpenAI. The routing underneath is Theo — intent classification, model selection, and automatic failover. Pass
model: "theo-1-auto" to let Theo pick the best engine per request.Base URL
Use the canonicalwww host. The OpenAI SDK appends /chat/completions, so this base URL resolves to the real POST /api/v1/chat/completions route:
https://www.hitheo.ai/api/v1
Always use the
www host. The apex https://hitheo.ai 307-redirects to www, and most HTTP clients strip the Authorization header across that redirect — the request arrives unauthenticated and returns 401.Authentication
Uses the standard Bearer-token header. Your Theo API key replaces your OpenAI key.Authorization: Bearer theo_sk_...
Drop-in Example (OpenAI SDK)
import OpenAI from "openai";
const theo = new OpenAI({
apiKey: process.env.THEO_API_KEY,
baseURL: "https://www.hitheo.ai/api/v1",
});
const res = await theo.chat.completions.create({
model: "theo-1-auto",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Write me a haiku about Miami." },
],
});
console.log(res.choices[0].message.content);
from openai import OpenAI
theo = OpenAI(
api_key=os.environ["THEO_API_KEY"],
base_url="https://www.hitheo.ai/api/v1",
)
res = theo.chat.completions.create(
model="theo-1-auto",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Write me a haiku about Miami."},
],
)
print(res.choices[0].message.content)
curl -X POST https://www.hitheo.ai/api/v1/chat/completions \
-H "Authorization: Bearer $THEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "theo-1-auto",
"messages": [
{ "role": "user", "content": "Write me a haiku about Miami." }
]
}'
Streaming
Setstream: true to receive an SSE stream of chat.completion.chunk objects terminated by data: [DONE], exactly as OpenAI does.
const stream = await theo.chat.completions.create({
model: "theo-1-auto",
messages: [{ role: "user", content: "Stream me a short poem." }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
Supported model Values
Pass any Theo-branded model ID. theo-1-auto is recommended so Theo’s intent classifier picks the best engine per request; pass a specific engine if you want to pin the routing.
| Model ID | Theo Mode | Best For |
|---|---|---|
theo-1-auto | auto | Let Theo pick the right engine per prompt |
theo-1-flash | fast | Quick responses, short chats, classification |
theo-1-reason | think | Deep reasoning, analysis, planning |
theo-1-code | code | Code generation and review |
theo-1-create | image | Image generation |
theo-1-motion | video | Video generation |
theo-1-research | research | Multi-step web research with citations |
theo-1-roast | roast | Unfiltered humor and sharp critique |
theo-1-genui | genui | Generative UI components |
theo-1-analyze | domain_analysis | Domain-specific analysis for business operations, finance, and compliance |
theo-1-extract | data_extraction | OCR and structured data extraction |
theo-1-vision | vision | Multimodal image analysis |
auto and Theo routes the request like any other prompt.
Request Body
The body is the standard OpenAI Chat Completion shape. Fields not listed below are accepted but ignored (e.g.top_p, n, max_tokens, user).
string
default:"theo-1-auto"
A Theo model ID. See the table above for valid values.
object[]
required
The conversation so far. Each message has
{ role, content }. Supported roles: system, user, assistant. tool messages are accepted but ignored (Theo owns tool-call state internally).The last user message is treated as the prompt. All system messages are merged into a single system prompt that overrides Theo’s default persona. Prior user/assistant turns are injected as conversation context.boolean
default:"false"
When
true, returns an SSE stream of chat.completion.chunk objects terminated by data: [DONE].number
Sampling temperature (0–2).
string
OpenAI-style thinking control.
none or minimal = answer directly; low, medium or high = think before answering. Theo also accepts reasoning: auto (default), on or off, which wins when both are sent. Applies to tool calls too.string
Theo-specific. Attach this completion to an existing Theo conversation so its memory persists across channels. Omit to send a stateless request.
string[]
Theo-specific. Activate skills by slug for this request. Merged with the user’s installed skills.
object
Theo-specific. Arbitrary key-value data attached to the audit log.
Response
A standard OpenAIchat.completion (or chat.completion.chunk for streaming). Theo-specific metadata is returned under a theo_metadata extension so it doesn’t collide with existing OpenAI client expectations.
{
"id": "chatcmpl_...",
"object": "chat.completion",
"created": 1715872800,
"model": "theo-1-flash",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "Neon waves on sand..." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 24,
"completion_tokens": 38,
"total_tokens": 62
},
"theo_metadata": {
"mode": "auto",
"resolved_mode": "fast",
"tools_used": [],
"artifacts": [],
"engine": "theo-core"
}
}
When to use /v1/completions instead
- You need a single-turn prompt string instead of a message array.
- You want the richer native Theo response (follow-ups, artifacts, tool traces) instead of the OpenAI shape.
- You want to control
personaorresponse_styleper request.
Per-key orchestration applies
This endpoint runs the same per-key orchestration as the native endpoint: whatever you configured for the API key — the Orchestrator graph, model pins, Gateway Guardrails, Routing Studio preferences, the model allowlist, and the context-window guard — governs requests here too. So a key you shaped in the dashboard or via the MCP tools behaves identically whether you call it through the OpenAI SDK or the native API. Guardrails can deny (422), a model outside the key’s allowlist is rejected (403), and an oversize request returns 400 context_length_exceeded. Keys with no such configuration are unaffected — they route on Theo’s defaults exactly as before.
Errors
Returns the same error envelope as every other v1 endpoint:{
"error": {
"message": "Validation failed: messages must contain at least one message.",
"type": "invalid_request_error",
"code": "validation_error"
}
}
| Status | Code | Description |
|---|---|---|
| 400 | validation_error | messages is missing, empty, or malformed |
| 400 | empty_prompt | messages contained only system/tool messages |
| 400 | context_length_exceeded | Request is larger than any model on the key can hold — add a larger-context model to the orchestrator or shorten the request |
| 401 | invalid_api_key | Missing or invalid Authorization: Bearer theo_sk_... |
| 402 | insufficient_credits | Account balance is too low for the pre-flight reservation |
| 403 | model_not_allowed | The request resolved to a model outside this key’s model allowlist |
| 422 | guardrail_violation | A Gateway Guardrails policy on this key denied the request |
| 422 | orchestrator_no_path | The key’s orchestrator graph couldn’t resolve a path for this request |
| 404 | not_found | conversation_id not found |
| 429 | rate_limit_exceeded | Check Retry-After |
| 500 | server_error | Internal error |
Was this page helpful?
OpenAI-Compatible Chat Completions
curl --request POST \
--url https://api.example.com/api/v1/chat/completions \
--header 'Content-Type: application/json' \
--data '
{
"model": "<string>",
"messages": [
{}
],
"stream": true,
"temperature": 123,
"reasoning_effort": "<string>",
"conversation_id": "<string>",
"skills": [
"<string>"
],
"metadata": {}
}
'import requests
url = "https://api.example.com/api/v1/chat/completions"
payload = {
"model": "<string>",
"messages": [{}],
"stream": True,
"temperature": 123,
"reasoning_effort": "<string>",
"conversation_id": "<string>",
"skills": ["<string>"],
"metadata": {}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
model: '<string>',
messages: [{}],
stream: true,
temperature: 123,
reasoning_effort: '<string>',
conversation_id: '<string>',
skills: ['<string>'],
metadata: {}
})
};
fetch('https://api.example.com/api/v1/chat/completions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/chat/completions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => '<string>',
'messages' => [
[
]
],
'stream' => true,
'temperature' => 123,
'reasoning_effort' => '<string>',
'conversation_id' => '<string>',
'skills' => [
'<string>'
],
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/chat/completions"
payload := strings.NewReader("{\n \"model\": \"<string>\",\n \"messages\": [\n {}\n ],\n \"stream\": true,\n \"temperature\": 123,\n \"reasoning_effort\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"skills\": [\n \"<string>\"\n ],\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/chat/completions")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"<string>\",\n \"messages\": [\n {}\n ],\n \"stream\": true,\n \"temperature\": 123,\n \"reasoning_effort\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"skills\": [\n \"<string>\"\n ],\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/chat/completions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"<string>\",\n \"messages\": [\n {}\n ],\n \"stream\": true,\n \"temperature\": 123,\n \"reasoning_effort\": \"<string>\",\n \"conversation_id\": \"<string>\",\n \"skills\": [\n \"<string>\"\n ],\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body