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

# Keys

> Create and configure API keys from code — model plan, orchestrator graph, skills, routing, and guardrails — via theo.keys.*.

The `theo.keys` namespace creates API keys and shapes their **per-key orchestration** programmatically: the model plan, the orchestrator graph, the skill allowlist, the routing preference, and the guardrail policy. It's the SDK surface behind the dashboard's Orchestrator and behind the [MCP configuration tools](/mcp/tools).

<Note>
  Every method here requires the calling key to hold the **`billing`** scope (key creation and per-key configuration are account-management operations). Whatever you configure applies to that key on **both** the native `/v1/completions` and the OpenAI-compatible `/v1/chat/completions` endpoints.
</Note>

Engine ids used below are the real upstream ids (this is a configuration surface): `arca-velox-5.1` (fast), `arca-magnus-5.1` (deep reasoning), `anthropic/claude-sonnet-4.6`, `anthropic/claude-opus-4.8`, `anthropic/claude-haiku-4.5`, `google/gemini-2.5-pro`, `openai/gpt-4.1`, plus your own `custom:<providerId>:<model>`.

## `keys.create`

Create a new API key. The raw secret is returned **once** — store it immediately.

```ts theme={null}
const created = await theo.keys.create({
  name: "coding backend",
  scopes: ["completions", "skills", "tools"], // optional; defaults applied server-side
  scope: "personal",                           // or "org" (+ org_id) for a team key
});
console.log(created.key);   // theo_sk_… (shown once)
console.log(created.id);    // the key UUID — use it for the calls below
```

| Field    | Type                                                                    | Notes                                         |
| -------- | ----------------------------------------------------------------------- | --------------------------------------------- |
| `name`   | `string`                                                                | Optional label.                               |
| `scopes` | `("completions" \| "skills" \| "tools" \| "connectors" \| "billing")[]` | Optional scope set.                           |
| `scope`  | `"personal" \| "org"`                                                   | `org` creates a team key (requires `org_id`). |
| `org_id` | `string`                                                                | Target org for a team key.                    |

Returns `CreatedApiKey` (`{ id, key, name, scopes, … }`).

## `keys.getModelPlan` / `keys.setModelPlan`

The model plan is the simplest way to control which engine a key runs. `single_model` pins **one** engine for every text turn — a deterministic passthrough that skips the classifier (ideal for coding / IDE backends, including a `custom:` [BYO model](#bring-your-own-models)). `bindings` instead remap individual modes.

```ts theme={null}
// Pin one engine for everything (deterministic passthrough):
await theo.keys.setModelPlan(created.id, {
  single_model: { upstream_id: "anthropic/claude-opus-4.8" },
});

// Or remap per mode (classifier still routes; each mode's engine is pinned):
await theo.keys.setModelPlan(created.id, {
  bindings: [
    { mode: "fast",  upstream_id: "arca-velox-5.1" },
    { mode: "think", upstream_id: "arca-magnus-5.1" },
  ],
});

// Clear the single-model pin:
await theo.keys.setModelPlan(created.id, { single_model: null });

const plan = await theo.keys.getModelPlan(created.id); // KeyModelPlan
```

Each binding accepts an optional `fallback_upstream_id` used if the primary engine is unavailable.

## `keys.getOrchestratorGraph` / `keys.setOrchestratorGraph`

For full control, set a runnable orchestrator graph — `Entry → Classifier / Router → Model → Guardrail`. An **enabled** graph must have an entry node and at least one model node with an engine.

```ts theme={null}
await theo.keys.setOrchestratorGraph(created.id, {
  enabled: true,
  nodes: [
    { id: "entry", type: "entry", position: { x: 0, y: 0 }, config: { label: "User Prompt" } },
    { id: "classifier", type: "classifier", position: { x: 300, y: 0 }, config: { label: "Classifier" } },
    { id: "m_fast", type: "model", position: { x: 640, y: -80 }, config: { upstreamId: "arca-velox-5.1", label: "fast" } },
    { id: "m_think", type: "model", position: { x: 640, y: 80 }, config: { upstreamId: "arca-magnus-5.1", label: "think" } },
  ],
  edges: [
    { id: "e_in", source: "entry", target: "classifier" },
    { id: "e_fast", source: "classifier", target: "m_fast", modes: ["fast"] },
    { id: "e_think", source: "classifier", target: "m_think", modes: ["think"] },
  ],
});

const { graph } = await theo.keys.getOrchestratorGraph(created.id); // graph is null when none is set
```

Guardrail nodes (`type: "guardrail"`, `config.guardrailPolicyId`) placed before the model screen the prompt (input); placed after it, they screen the reply (output). Router nodes apply the key's bound [routing preference](#routing--guardrails).

## `keys.getSkills` / `keys.setSkills`

Set the key's skill allowlist. Each binding grants one skill; an optional `mode` scopes it to a single mode (omit / `null` = all modes).

```ts theme={null}
await theo.keys.setSkills(created.id, [
  { skill_id: "sk_deep_research", mode: "think" },
  { skill_id: "sk_content_writer" },
]);

const { bindings } = await theo.keys.getSkills(created.id);
```

An empty allowlist means **inherit-all** (the key can call any skill the owner has installed).

## Routing & guardrails

Two more binders live on `keys` and are documented alongside their feature pages:

* **`keys.getRoutingPreference` / `keys.setRoutingPreference`** — bind a Routing Studio preference (keyword/regex → mode). See [Routing Studio](/sdk-reference/routing-studio).
* **`keys.getGuardrailPolicy` / `keys.setGuardrailPolicy`** — bind a Gateway Guardrails policy. See [Guardrails](/sdk-reference/guardrails).

## Bring your own models

Register your own OpenAI-compatible endpoint, then reference it by its `custom:<providerId>:<model>` id in `setModelPlan` (`single_model`) or on a graph Model node. You can declare each model's **context window** so oversize requests are caught before they reach your endpoint. See the [changelog](/changelog) and the [custom providers REST endpoint](/api-reference/overview). Prompts to your own models are free and never draw credits.

## Billing helpers

Top up credits or open the billing portal from code (top-level, not under `keys`):

```ts theme={null}
const { checkout_url } = await theo.billingCheckout({ amount_cents: 2000, scope: "personal" });
const { portal_url } = await theo.billingPortal();
```

`scope: "org"` funds / manages the team credit pool.
