Routing Studio
Record a Routing Correction
Submit a playground ‘Reroute’ to teach Theo where the global classifier went wrong.
POST
/
api
/
v1
/
routing-preferences
/
corrections
Record a Routing Correction
curl --request POST \
--url https://api.example.com/api/v1/routing-preferences/corrections \
--header 'Content-Type: application/json' \
--data '
{
"prompt": "<string>",
"expected_mode": "<string>",
"source_message_id": "<string>"
}
'import requests
url = "https://api.example.com/api/v1/routing-preferences/corrections"
payload = {
"prompt": "<string>",
"expected_mode": "<string>",
"source_message_id": "<string>"
}
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({prompt: '<string>', expected_mode: '<string>', source_message_id: '<string>'})
};
fetch('https://api.example.com/api/v1/routing-preferences/corrections', 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/routing-preferences/corrections",
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([
'prompt' => '<string>',
'expected_mode' => '<string>',
'source_message_id' => '<string>'
]),
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/routing-preferences/corrections"
payload := strings.NewReader("{\n \"prompt\": \"<string>\",\n \"expected_mode\": \"<string>\",\n \"source_message_id\": \"<string>\"\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/routing-preferences/corrections")
.header("Content-Type", "application/json")
.body("{\n \"prompt\": \"<string>\",\n \"expected_mode\": \"<string>\",\n \"source_message_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/routing-preferences/corrections")
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 \"prompt\": \"<string>\",\n \"expected_mode\": \"<string>\",\n \"source_message_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyRecords a single routing correction — a turn where the developer disagreed with the resolved mode and supplied the mode they expected. Theo keeps the last 30 days of corrections per caller and clusters them by
When
expected_mode; once a cluster crosses the threshold, the playground surfaces an “I noticed a pattern” toast and the developer can ask Theo to suggest a rule derived from the cluster.
Corrections are personal-or-team scoped (matching the caller’s active org context) so a personal correction never triggers a team-level suggestion banner.
Authentication
Requires a Bearer token with thebilling API key scope. Idempotent — the same Idempotency-Key header replays the same response.
Body
string
required
The prompt that produced the unexpected mode. 1–4096 chars. Vendor-name scrubbed at the service layer.
string
required
The Theo mode the developer expected (e.g.
"think", "image", "insurance_quote").string
Optional message id of the turn the correction is attached to. ≤128 chars.
Request Examples
curl
curl -X POST https://www.hitheo.ai/api/v1/routing-preferences/corrections \
-H "Authorization: Bearer $THEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Look at this indemnity clause",
"expected_mode": "think"
}'
Response
{
"id": "corr_abc123",
"ok": true,
"cluster_threshold": 3,
"pattern_suggestion_available": true,
"pattern_expected_mode": "think",
"pattern_count": 3
}
pattern_suggestion_available is true, call POST /api/v1/routing-preferences/suggest-rule with the matching expected_mode to receive a deterministic regex derived from the cluster.
Errors
400 routing_correction_invalid— Generic shape failure.400 routing_correction_vendor_name— Prompt mentions an upstream provider name (Claude, OpenAI, etc.).400 routing_correction_invalid_mode—expected_modeisn’t a known Theo mode.
Was this page helpful?
Record a Routing Correction
curl --request POST \
--url https://api.example.com/api/v1/routing-preferences/corrections \
--header 'Content-Type: application/json' \
--data '
{
"prompt": "<string>",
"expected_mode": "<string>",
"source_message_id": "<string>"
}
'import requests
url = "https://api.example.com/api/v1/routing-preferences/corrections"
payload = {
"prompt": "<string>",
"expected_mode": "<string>",
"source_message_id": "<string>"
}
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({prompt: '<string>', expected_mode: '<string>', source_message_id: '<string>'})
};
fetch('https://api.example.com/api/v1/routing-preferences/corrections', 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/routing-preferences/corrections",
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([
'prompt' => '<string>',
'expected_mode' => '<string>',
'source_message_id' => '<string>'
]),
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/routing-preferences/corrections"
payload := strings.NewReader("{\n \"prompt\": \"<string>\",\n \"expected_mode\": \"<string>\",\n \"source_message_id\": \"<string>\"\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/routing-preferences/corrections")
.header("Content-Type", "application/json")
.body("{\n \"prompt\": \"<string>\",\n \"expected_mode\": \"<string>\",\n \"source_message_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/routing-preferences/corrections")
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 \"prompt\": \"<string>\",\n \"expected_mode\": \"<string>\",\n \"source_message_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body