> ## 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 Studio

> Manage per-customer routing preferences and per-key rules from the SDK.

Routing Studio lets you bias Theo's routing engine for your domain. The SDK exposes three namespaces:

* `theo.routingPreferences` — shared preference objects (one rule set, many keys).
* `theo.routingRules` — per-key rule editing without an explicit preference object.
* `theo.keys.getRoutingPreference` / `setRoutingPreference` — bind a shared preference to a key.

See the [Routing Studio guide](/guides/routing-studio) for the full ContractIQ walkthrough and the [API reference](/api-reference/routing-preferences/list-preferences) for the underlying HTTP shapes.

## Shared preferences

### List

```typescript theme={null}
const prefs = await theo.routingPreferences.list();
```

### Get

```typescript theme={null}
const pref = await theo.routingPreferences.get(prefId);
```

### Create

`scope: "team"` requires an active org and the team-config permission. Idempotent via the `Idempotency-Key` header.

```typescript theme={null}
const pref = await theo.routingPreferences.create({
  name: "ContractIQ Legal",
  description: "Contract terms get the analytical engine.",
  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" },
  ],
  confidence_floor_overrides: { think: 0.65 },
});
```

### Update

```typescript theme={null}
await theo.routingPreferences.update(pref.id, { enabled: false });
```

Sending `rules` or `examples` replaces the entire collection.

### Delete

Cascade-removes every key binding pointing at the preference.

```typescript theme={null}
await theo.routingPreferences.delete(pref.id);
```

### Test (replay against the routing engine)

Replays a prompt twice — once without the preference (baseline) and once applied — so you can render a side-by-side diff.

```typescript 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"
```

Both blobs match the `routing` telemetry your callers receive on a real completion.

## Per-key routing rules

The preference-free shortcut: the server find-or-creates a hidden per-key preference under the hood and ensures the binding row points at it. Reach for this when you're tuning a single key.

### Get

```typescript theme={null}
const snap = await theo.routingRules.get(keyId);
console.log(snap.active);          // true if these rules drive completions today
console.log(snap.shared_binding);  // populated when the key is bound to a shared preference
```

### Effective

The snapshot that will actually drive completions for the key right now, plus a `source` enum so you can render inherited context.

```typescript theme={null}
const eff = await theo.routingRules.effective(keyId);
console.log(eff.source);          // "key" | "org_default" | "user_default" | "none"
console.log(eff.preference_name); // "This key's rules" | "ACME Legal Default" | null
```

### Set

Replaces the per-key rules / examples / overrides and ensures the binding points at the hidden row.

```typescript 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" }],
});
```

### Clear

Drops the binding (the key falls back to org default → user default → none) and deletes the hidden preference row. Existing bindings to a shared preference are left alone.

```typescript theme={null}
await theo.routingRules.clear(keyId);
```

## Key bindings (shared preferences)

Bind an existing shared preference to a key, or clear the binding.

```typescript theme={null}
// Read the current binding
const { preference_id, inherits_default } =
  await theo.keys.getRoutingPreference(keyId);

// Bind a preference
await theo.keys.setRoutingPreference(keyId, pref.id);

// Clear the binding (key falls back to org default → user default → none)
await theo.keys.setRoutingPreference(keyId, null);
```

## Types

The SDK exports first-class types for every shape — `switch` and `if` blocks narrow without `as any`:

* `RoutingPreferenceSummary` — full preference row.
* `RoutingRuleInput` — single keyword/regex rule (`pattern`, `target_mode`, optional `target_engine`, `confidence`, optional `description`).
* `RoutingExampleInput` — few-shot example (`prompt`, `expected_mode`, optional `expected_engine`).
* `RoutingPreferenceTestResult` — `{ baseline, with_preference }` envelope returned by `test`.
* `KeyRoutingRulesSnapshot` — per-key snapshot with `active` + `shared_binding`.
* `KeyEffectiveRouting` — effective routing snapshot with the `source` enum.

## Telemetry

When a preference contributes to a completion's routing decision, the `meta` and `done` SSE events surface a `customer_preference` block:

```json theme={null}
{
  "routing": {
    "reason": "customer_preference",
    "explanation": "Routing Studio rule matched: \"clause\".",
    "customer_preference": {
      "id": "1f4f2c1a-22ce-4b07-9c0b-9c4f4b9b1d2e",
      "name": "ContractIQ Legal",
      "rule_matched": "r_a4b8c01x9y",
      "examples_injected": 2
    }
  }
}
```

Point your SDK at the `meta` event's `routing` field to render the same affordance in your product.
