# Auto router

Send the model `routerplus/auto`, and RouterPlus selects a model for each request. The router
reads what the request asks for and stays in the cost tier that you select. It keeps one
conversation on one model.

## Request

```json
{
  "model": "routerplus/auto",
  "messages": [{ "role": "user", "content": "Why does this throw?\n```js\nnull.x\n```" }],
  "auto": { "tier": "economy", "prefer": "cost", "models": ["deepseek/deepseek-v4.1-flash", "claude-haiku-5-5"] }
}
```

The `auto` object and all of its fields are optional.

| Field | Rule |
|---|---|
| `auto.tier` | `economy`, `balanced` (default) or `premium`. |
| `auto.prefer` | `quality` (default) or `cost`. With `cost`, the router can also select models in cheaper tiers, and price counts for more. |
| `auto.models` | The models that the router can select. Leave it out to use the full list below. |

## Tiers

| Tier | Models |
|---|---|
| `premium` | `claude-opus-5-5`, `gpt-6-astra` |
| `balanced` | `claude-sonnet-5-5`, `gpt-6-luna`, `moonshotai/kimi-k3`, `z-ai/glm-5.3` |
| `economy` | `claude-haiku-5-5`, `deepseek/deepseek-v4.1-flash`, `openai/gpt-oss-120b`, `z-ai/glm-5.3-flash` |

The router selects from your tier. When no model in your tier can serve the request, it selects
from the next tier up.

## How it selects

1. The router reads the conversation and names the task:
   - `code`: code fences, stack traces, file paths or SQL.
   - `reasoning`: proofs, math or requests for step-by-step work.
   - `extraction`: `response_format` with a JSON schema.
   - `vision`: images in the latest message.
   - `long_context`: 120,000 or more characters of input.
   - `chat` or `creative` otherwise.
2. It removes the models that cannot serve the request:
   - models without image input, when you send images
   - models without tools, when you send tools
   - models with a context window too small for your input
3. It removes the models that your key's guardrails or `auto.models` do not allow.
4. It ranks the remaining models by how well each one does the task, then by price.

A short follow-up such as "thanks, now make it faster" keeps the conversation's task.

## One model per conversation

The router keeps a conversation on the model that it selected first. The router identifies a
conversation by your system prompt plus your first user message. It selects again only in these
cases:

- The latest message asks for a different task, for example a code conversation that now asks
  for a poem.
- The model can no longer serve the request.

## Fallback

The router also ranks up to 4 more models. When the selected model fails before its answer
starts, RouterPlus tries the next model, as in [Model fallback](/docs/model-fallback).

## Response

The response is the selected model's answer, and `model` names that model. Two headers
tell what the router did:

| Header | Meaning |
|---|---|
| `x-tm-auto-model` | The model that the router selected. |
| `x-tm-auto-reason` | A short reason, for example `task code; tier economy; strong at code`. |

## Billing

RouterPlus bills the request as a direct call to the model that answered. The router adds no
charge.

## Custom routers

Your organization can store its own router and call it as the model `routerplus/auto@<slug>`.
A router has a list of models that it can select, rules that it tries in order, and a default
model. In the console, open Build > Routers.

```json
{
  "model": "routerplus/auto@support",
  "messages": [{ "role": "user", "content": "Where is my invoice for October?" }]
}
```

The router selects a model in this order:

1. The trained classifier, if the router has one and its prediction is above the threshold. See
   [Training](#training).
2. The rules, in order. The first rule that matches selects its model.
3. The built-in ranking above, over the router's candidates.
4. The default model.

- The router never selects a model that your key's guardrails or `auto.models` do not
  allow. It goes to the next step instead.
- `auto.tier` and `auto.prefer` in the request apply to step 3.
- In step 3, one conversation keeps its model, as above. The classifier and the rules decide
  again on each request.

The response has the `x-tm-auto-model` and `x-tm-auto-reason` headers. It also has
`x-tm-router` with the router's slug and version, for example `support@3`. The reason
names the step: `classifier: claude-haiku-5-5 at 94%`, `rule 2`, a ranking reason, or `default`.
An unknown slug returns 404 `model_unavailable`.

### Spec

```json
{
  "candidates": [
    { "model": "claude-haiku-5-5", "tier": "economy" },
    { "model": "claude-sonnet-5-5", "strengths": ["code", "creative"] }
  ],
  "rules": [
    { "when": { "contains": ["\\binvoice\\b", "\\brefund\\b"] }, "use": "claude-haiku-5-5" },
    { "when": { "task": ["code"], "min_input_chars": 20000 }, "use": "claude-sonnet-5-5" }
  ],
  "default": "claude-haiku-5-5",
  "prefer": "cost"
}
```

| Field | Rule |
|---|---|
| `candidates` | Up to 20 models for step 3. `tier` and `strengths` are optional. A model from the tiers table keeps its tier, strengths and context window. Any other model is `balanced`, and the router assumes that it takes images and tools. |
| `rules` | Up to 50 rules. Each rule has `when` and `use` (a model id). |
| `default` | The model for step 4. Required. |
| `prefer` | `cost` or `quality`, for step 3. `auto.prefer` in the request wins. |

Every condition in `when` must be true. A rule with an empty `when` always matches.

| Condition | Matches when |
|---|---|
| `task` | The task is one of these: `code`, `reasoning`, `extraction`, `chat`, `creative`, `vision`, `long_context`. |
| `contains` | One of these regular expressions matches the first 20,000 characters of the latest user message. Case is ignored. 1 to 10 patterns of up to 512 characters. |
| `min_input_chars` | The input, with the system prompt and the tool definitions, has at least this many characters. |
| `has_images` | The conversation has an image (`true`), or has none (`false`). |
| `has_tools` | The request sends tools (`true`), or sends none (`false`). |

`contains` patterns follow the same safety rules as guardrail regex rules. RouterPlus refuses
these patterns with 400 `invalid_router`:

- a lookahead or a lookbehind
- a backreference
- a repeated group that holds a quantifier or an alternation, such as `(a+)+`

### Manage routers

Use a management key, under `https://demo-api.routerplus.com/v1` or `https://demo-api.routerplus.com/api/v1`.

| Method and path | Body | Result |
|---|---|---|
| `GET /v1/routers` | | The latest version of each router. |
| `POST /v1/routers` | `{ "slug": "support", "spec": { ... } }` | `201`. A slug that exists returns `409`. |
| `GET /v1/routers/:slug` | | The latest version. `?version=2` reads an older version. |
| `PUT /v1/routers/:slug` | `{ "spec": { ... }, "expected_version": 3 }` | A new version. `expected_version` is optional. When it is not the latest version, the response is `409`. |
| `DELETE /v1/routers/:slug` | | The router stops working. |
| `POST /v1/routers/:slug/train` | `{ "examples": [ ... ], "threshold": 0.7 }` | A new version with a trained classifier. |

A slug is 1 to 80 characters from `a-z`, `0-9`, `_` and `-`, and starts with a letter or a
digit. Each change writes a new version. Requests use the latest version within 5 seconds.

```bash
curl -s https://demo-api.routerplus.com/v1/routers \
  -H "Authorization: Bearer $TM_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"slug":"support","spec":{"rules":[{"when":{"contains":["\\binvoice\\b"]},"use":"claude-haiku-5-5"}],"default":"claude-sonnet-5-5"}}'
```

## Training

A router can learn from your labeled requests. Send examples of text, each with the model that
must answer it:

```bash
curl -s https://demo-api.routerplus.com/v1/routers/support/train \
  -H "Authorization: Bearer $TM_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"threshold":0.8,"examples":[
        {"text":"I was charged twice this month","model":"claude-haiku-5-5"},
        {"text":"The export to CSV crashes with error 500","model":"claude-sonnet-5-5"}
      ]}'
```

- Send 10 to 5,000 examples. Each `text` has up to 10,000 characters.
- Label at least two models. Each label must be a candidate or the router's default.
- The gateway fits a naive Bayes text classifier on words and word pairs. It keeps the 20,000
  most frequent features.
- `threshold` is optional, above 0 and below 1. Default 0.7.

At request time, the classifier reads the latest user message. When its top model has a
probability above the threshold, that model answers, before the rules. Otherwise the rules
decide.

The response has the new version and a `training` object with these fields: `examples`,
`labels`, `features`, `threshold` and `training_accuracy`. `training_accuracy` is the share of
your examples that the classifier labels correctly. Router objects show a summary of the
classifier: its labels, feature count, example count, threshold and training time.

To replace the classifier, train again. A `PUT` without `model_classifier` keeps the current
classifier. To remove it, send `"model_classifier": null`.

## Errors

| Status | Cause |
|---|---|
| 400 `invalid_request` | The `auto` object is invalid. The message names the field. |
| 400 `invalid_request` | No allowed model can serve the request. The message names the need: image input, tools or context length. |
| 400 `invalid_router` | A custom router's spec or training request is invalid. The message names the field. |
| 400 `invalid_request` | No step of a custom router can select an allowed model. |
| 404 `model_unavailable` | The custom router slug is unknown. |
