Resources
Custom Providers
Register, list, and delete your own OpenAI-compatible model endpoints (bring-your-own models).
POST
/
api
/
v1
/
custom-providers
Custom Providers
curl --request POST \
--url https://api.example.com/api/v1/custom-providers \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"base_url": "<string>",
"api_key": "<string>",
"models": [
{
"id": "<string>",
"label": "<string>",
"contextWindow": 123
}
],
"local": true
}
'import requests
url = "https://api.example.com/api/v1/custom-providers"
payload = {
"name": "<string>",
"base_url": "<string>",
"api_key": "<string>",
"models": [
{
"id": "<string>",
"label": "<string>",
"contextWindow": 123
}
],
"local": True
}
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({
name: '<string>',
base_url: '<string>',
api_key: '<string>',
models: [{id: '<string>', label: '<string>', contextWindow: 123}],
local: true
})
};
fetch('https://api.example.com/api/v1/custom-providers', 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/custom-providers",
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([
'name' => '<string>',
'base_url' => '<string>',
'api_key' => '<string>',
'models' => [
[
'id' => '<string>',
'label' => '<string>',
'contextWindow' => 123
]
],
'local' => true
]),
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/custom-providers"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"base_url\": \"<string>\",\n \"api_key\": \"<string>\",\n \"models\": [\n {\n \"id\": \"<string>\",\n \"label\": \"<string>\",\n \"contextWindow\": 123\n }\n ],\n \"local\": true\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/custom-providers")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"base_url\": \"<string>\",\n \"api_key\": \"<string>\",\n \"models\": [\n {\n \"id\": \"<string>\",\n \"label\": \"<string>\",\n \"contextWindow\": 123\n }\n ],\n \"local\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/custom-providers")
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 \"name\": \"<string>\",\n \"base_url\": \"<string>\",\n \"api_key\": \"<string>\",\n \"models\": [\n {\n \"id\": \"<string>\",\n \"label\": \"<string>\",\n \"contextWindow\": 123\n }\n ],\n \"local\": true\n}"
response = http.request(request)
puts response.read_bodyRegister any OpenAI-compatible chat-completions endpoint as a custom provider, then reference its models by the namespaced id
Returns
Then pin it on a key:
custom:<providerId>:<modelId> in a model plan or on an orchestrator-graph Model node. Prompts to your own models are free and never draw Theo credits — you pay your provider directly.
Authentication
Requires an API key withbilling scope.
Register a provider
POST /api/v1/custom-providers
string
required
Display name (1–120 chars).
string
required
The endpoint base URL — must be
http(s).string
required
The provider’s API key. Stored encrypted; never returned by the API.
object[]
required
1–50 models. Each:
{ id, label, contextWindow? }.Show model fields
Show model fields
string
required
The raw model id sent to your endpoint (1–200 chars).
string
required
Display label (1–120 chars).
integer
Optional declared context window in tokens. When set, Theo’s context-window guard can catch oversize requests to this model before they reach your endpoint (and route around it when a larger model is available on the key). Omit if unknown — the model is then treated as unbounded and passes the guard through.
boolean
On-device / on-prem: reachable only from a linked desktop, never dispatched from the cloud. Default
false.{ id } — the provider id. Reference a model as custom:<id>:<modelId>.
List providers
GET /api/v1/custom-providers → { providers: [{ id, name, base_url, models }] }. API keys are never included.
Delete a provider
DELETE /api/v1/custom-providers/{id} → { deleted: true }.
Example
curl -X POST https://www.hitheo.ai/api/v1/custom-providers \
-H "Authorization: Bearer $THEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My vLLM",
"base_url": "https://llm.example.com/v1",
"api_key": "sk-...",
"models": [{ "id": "my-llama-70b", "label": "Llama 70B", "contextWindow": 131072 }]
}'
await theo.keys.setModelPlan("KEY_ID", {
single_model: { upstream_id: "custom:PROVIDER_ID:my-llama-70b" },
});
Was this page helpful?
⌘I
Custom Providers
curl --request POST \
--url https://api.example.com/api/v1/custom-providers \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"base_url": "<string>",
"api_key": "<string>",
"models": [
{
"id": "<string>",
"label": "<string>",
"contextWindow": 123
}
],
"local": true
}
'import requests
url = "https://api.example.com/api/v1/custom-providers"
payload = {
"name": "<string>",
"base_url": "<string>",
"api_key": "<string>",
"models": [
{
"id": "<string>",
"label": "<string>",
"contextWindow": 123
}
],
"local": True
}
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({
name: '<string>',
base_url: '<string>',
api_key: '<string>',
models: [{id: '<string>', label: '<string>', contextWindow: 123}],
local: true
})
};
fetch('https://api.example.com/api/v1/custom-providers', 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/custom-providers",
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([
'name' => '<string>',
'base_url' => '<string>',
'api_key' => '<string>',
'models' => [
[
'id' => '<string>',
'label' => '<string>',
'contextWindow' => 123
]
],
'local' => true
]),
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/custom-providers"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"base_url\": \"<string>\",\n \"api_key\": \"<string>\",\n \"models\": [\n {\n \"id\": \"<string>\",\n \"label\": \"<string>\",\n \"contextWindow\": 123\n }\n ],\n \"local\": true\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/custom-providers")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"base_url\": \"<string>\",\n \"api_key\": \"<string>\",\n \"models\": [\n {\n \"id\": \"<string>\",\n \"label\": \"<string>\",\n \"contextWindow\": 123\n }\n ],\n \"local\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/custom-providers")
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 \"name\": \"<string>\",\n \"base_url\": \"<string>\",\n \"api_key\": \"<string>\",\n \"models\": [\n {\n \"id\": \"<string>\",\n \"label\": \"<string>\",\n \"contextWindow\": 123\n }\n ],\n \"local\": true\n}"
response = http.request(request)
puts response.read_body