> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hitheo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Routing rules

> Tune Theo's routing engine for your domain with per-key rules, few-shot examples, and confidence overrides.

Routing Studio is Theo's per-customer routing layer. You describe how your users phrase requests, and Theo's routing engine biases the classifier toward the right mode — without you ever having to manage a separate "preference" object. Rules live directly on each API key: open the key, add the words that should fire a rule, pick the destination, and save.

A **routing rule** maps a list of keywords (or, for power users, a raw regex) to a Theo engine. Rules run BEFORE Theo's global intent classifier so your domain wins on ambiguity. The routing engine layers them on top of its global cascade and never lets them escape Theo's safety locks (stealth, agentic) or leak upstream vendor names into your customers' surfaces.

Alongside rules you can also configure:

* **Few-shot examples** — prompt / expected-mode pairs injected into the classifier's system prompt (up to 8 per request).
* **Confidence-floor overrides** — per-mode replacements for Theo's global `0.85` promotion floor. Lower a floor to make intent promotion easier; raise it to be conservative.

Most teams will only ever edit rules on the keys page. Shared rule sets are a power-user feature for one rule list that needs to span many keys or act as a team default.

## Edit rules on a key

Open [`api.hitheo.ai/dashboard/keys`](https://api.hitheo.ai/dashboard/keys), find the API key you want to tune, and click the **Routing** pill. The panel inlines everything you need:

* **Inherited context** — if no per-key rules are set, the panel tells you whether the key currently inherits the team default, your personal default, or nothing.
* **Rules** — one row per rule. Each row has a keyword chip input, a destination combobox, and a Gentle / Balanced / Strict toggle. The empty state offers a few one-click starter templates (Pricing questions, Customer complaints, Legal review, Photo & image requests) and a Blank rule chip for power users.
* **More options** — a small caret on each row reveals a note field, a raw regex textarea, an optional Theo engine pin, and a raw confidence slider for fine-grained control.
* **Try a prompt** — a compact input appears once you've started editing. Type a sample customer prompt and the panel renders a one-line diff (`baseline mode → with-rules mode`) so you can see exactly what would change before saving.

Click **Save changes** to write the rules to the key. Click **Clear per-key rules** to drop them entirely and fall back to inherited defaults.

### Advanced: hand-written regex

The chip editor projects keywords down to the same `pattern` string the API uses internally. To author a rule with a raw ECMAScript regex (lookaheads, anchors, capturing groups), expand the row's **More options** caret and edit the pattern textarea. Pasting a regex switches the row to Advanced mode automatically so we never silently rewrite your pattern; loading a key whose pattern doesn't round-trip through the chip editor opens that row in Advanced mode by default.

## ContractIQ — a complete walkthrough

ContractIQ is a fictional legal-tech tool. Their users paste contract clauses and ask Theo to analyze them. Without any rules, "look at this clause" routes to `fast` (a short reply); with one rule on the key, it routes to `think` (deep analysis).

### 1. Set rules on the API key

<CodeGroup>
  ```bash curl theme={null}
  curl -X PUT https://www.hitheo.ai/api/v1/keys/$KEY_ID/routing-rules \
    -H "Authorization: Bearer $THEO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "rules": [
        {
          "pattern": "\\\\b(clause|provision|indemnity|warranty)\\\\b",
          "target_mode": "think",
          "confidence": 0.92,
          "description": "Contract terms get the analytical engine."
        }
      ],
      "examples": [
        { "prompt": "Look at this clause", "expected_mode": "think" },
        { "prompt": "Compare these two indemnity sections", "expected_mode": "think" }
      ]
    }'
  ```

  ```typescript SDK theme={null}
  await theo.routingRules.set(keyId, {
    rules: [
      {
        pattern: "\\\\b(clause|provision|indemnity|warranty)\\\\b",
        target_mode: "think",
        confidence: 0.92,
        description: "Contract terms get the analytical engine.",
      },
    ],
    examples: [
      { prompt: "Look at this clause", expected_mode: "think" },
      { prompt: "Compare these two indemnity sections", expected_mode: "think" },
    ],
  });
  ```
</CodeGroup>

The response confirms the rules are active on the key:

```json theme={null}
{
  "key_id": "22222222-2222-4222-8222-222222222222",
  "rules": [
    {
      "id": "r_a4b8c01x9y",
      "pattern": "\\\\b(clause|provision|indemnity|warranty)\\\\b",
      "target_mode": "think",
      "confidence": 0.92,
      "description": "Contract terms get the analytical engine."
    }
  ],
  "examples": [...],
  "active": true,
  "shared_binding": null
}
```

The `active: true` field confirms the rules are driving completions on this key right now. To drop the rules and fall back to inherited defaults, send a `DELETE` to the same endpoint (or call `theo.routingRules.clear(keyId)` from the SDK).

### 2. Send a completion

```bash theme={null}
curl -X POST https://www.hitheo.ai/api/v1/completions \
  -H "Authorization: Bearer $THEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "Look at this indemnity clause", "mode": "auto", "stream": true }'
```

The SSE `meta` event now carries a `customer_preference` field that confirms a rule fired:

```json theme={null}
{
  "routing": {
    "requested_mode": "auto",
    "resolved_mode": "think",
    "promoted": true,
    "reason": "customer_preference",
    "explanation": "Routing Studio rule matched: \"indemnity clause\".",
    "customer_preference": {
      "rule_matched": "r_a4b8c01x9y",
      "examples_injected": 2
    }
  }
}
```

The playground renders a green chip next to the routing receipt's promotion reason whenever your rules drove the decision.

### 3. Test changes before they ship

Use the test endpoint to replay a fixture prompt with vs. without the preference:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "https://www.hitheo.ai/api/v1/routing-preferences/$PREF_ID/test" \
    -H "Authorization: Bearer $THEO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "prompt": "Compare these two warranty sections", "mode": "auto" }'
  ```

  ```typescript SDK theme={null}
  const diff = await theo.routingPreferences.test(pref.id, {
    prompt: "Compare these two warranty sections",
    mode: "auto",
  });
  console.log(diff.baseline.resolved_mode);         // "fast"
  console.log(diff.with_preference.resolved_mode);  // "think"
  ```
</CodeGroup>

Both blobs match the same `routing` telemetry shape your production callers receive — the diff is a precise, deterministic preview of what your tuning does.

#### Testing an unsaved draft

The dashboard test bench replays the draft you're currently editing — it never makes you save first. If you're building your own UI on top of the API, hit the no-id `POST /api/v1/routing-preferences/test` route with the full draft body. Same response shape as the by-id route; nothing is persisted.

```bash theme={null}
curl -X POST https://www.hitheo.ai/api/v1/routing-preferences/test \
  -H "Authorization: Bearer $THEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Look at this clause",
    "mode": "auto",
    "draft": {
      "name": "ContractIQ Legal (draft)",
      "rules": [
        { "pattern": "\\b(clause|provision)\\b", "target_mode": "think", "confidence": 0.92 }
      ],
      "examples": []
    }
  }'
```

## Order of precedence

Routing Studio sits inside Theo's broader routing engine. The full cascade, first match wins:

1. **Agentic locks** (`insurance_*`, `data_extraction`) — never promoted.
2. **Attachments → vision** — image attachments on a non-media caller route to `vision`.
3. **Skill exclusive override** — a skill with `intensity: 100` and a `modelPreference` wins.
4. **Routing Studio rule** — your rules fire here.
5. **Intent classifier** — runs with your few-shot examples injected and uses your per-mode confidence-floor overrides.
6. **Stealth family preservation** — promotions from a `stealth_*` caller stay inside the stealth family.
7. **Conversation hint** — falls back to the prior-turn artifact type when the caller used `auto`.
8. **Requested mode** — default.

Your rules **cannot** escape agentic family locks (legal/healthcare/data-extraction modes) and **cannot** escape stealth-family isolation. A rule that targets a stealth-incompatible mode for a stealth caller is silently remapped to the stealth equivalent.

## Confidence-floor overrides

The classifier won't promote a caller's mode unless its confidence is ≥ `0.85` (`PROMOTION_CONFIDENCE_FLOOR`). For specialist domains where the classifier is often correct at lower confidence, lower the floor:

```json theme={null}
{
  "name": "Legal Default",
  "confidence_floor_overrides": {
    "think": 0.65
  }
}
```

For domains where false promotions are expensive, raise it:

```json theme={null}
{
  "name": "Cost-Sensitive",
  "confidence_floor_overrides": {
    "image": 0.95,
    "video": 0.95
  }
}
```

All values are clamped server-side to `[0.5, 0.99]` — neither end of the range can disable the floor entirely.

## Brand-safety contract

Every customer-authored field — name, description, rule pattern, rule description, example prompt — is scanned for upstream vendor names (Claude, OpenAI, Anthropic, Gemini, fal.ai, etc.) and rejected at validation time. Theo's surfaces always read as Theo. Your customers never see the underlying model providers.

The same scan runs on Routing Studio's outputs — the `customer_preference` block your callers receive is guaranteed to be free of vendor names.

## Limits

| Limit                             | Value                      |
| --------------------------------- | -------------------------- |
| Rules per preference              | 50                         |
| Examples per preference           | 30                         |
| Examples injected into classifier | 8 (highest-priority first) |
| Pattern length                    | 512 chars                  |
| Example prompt length             | 1024 chars                 |
| Confidence range                  | `[0.5, 0.99]` (clamped)    |
| Test prompt length                | 4096 chars                 |
| Cache TTL                         | 5 minutes (Redis)          |

## Team vs. personal scope

Pass `scope: "team"` on create to scope the preference to your active org. Team preferences become candidates for org defaults; team admins (with the team config permission) can manage them. Without a team binding, a key inherits its scope's default → the user's personal default → no preference.

```typescript theme={null}
await theo.routingPreferences.create({
  name: "ACME Legal Default",
  scope: "team",
  is_default: true,
  rules: [...],
});
```

Setting `is_default: true` on a second team preference returns `409 conflict` — at most one default per scope.

## Common patterns

* **Domain vocabulary biasing.** Use rules for hard keywords ("policy", "claim", "rune"), use examples for phrasings ("draft me a response to this complaint" → `think`).
* **Cost containment.** Raise floors on `image`/`video` for personal keys; let team keys keep the defaults.
* **Speed bias.** Lower the `fast` floor and add examples like `{ prompt: "thanks!", expected_mode: "fast" }` so short replies don't escalate to `think`.

## Per-key vs. shared sets — when to use which

If you're tuning one or two keys, edit rules directly on the keys page. The dashboard hides everything but the rule editor itself — no "preference" to create, no binding to wire up.

Reach for shared sets when:

* You want one rule list to span many API keys (e.g. every key on your team uses the same legal vocabulary).
* You want a team default that new keys inherit automatically.
* You're scripting bulk changes and would rather author rules in source control once than mirror them across keys.

Under the hood the per-key endpoint and the shared-set endpoints write to the same `routing_preferences` table; the only thing that differs is whether the row is named for a specific key (hidden from the shared list) or carries a customer-chosen name.

## Next steps

* [`/api/v1/routing-preferences` reference](/api-reference/routing-preferences/list-preferences) — full request/response schemas for every endpoint.
* [Routing Studio SDK reference](/sdk-reference/routing-studio) — `theo.routingPreferences.*`, `theo.routingRules.*`, and the per-key binding helpers.
* [Engine Routing & Fallbacks](/core-concepts/model-routing) — where Routing Studio fits inside Theo's broader routing engine.
