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

# Dynamic routes

> Build a graph that picks the model per request, deploy it, and call it like a model.

A dynamic route is a graph you author that decides, per request, which model
answers. Instead of naming one model in your code, you name the route. The
graph can branch on the request, split traffic, ask a classifier, or rank a
set of models on live numbers — and you change it without touching code.

<Frame>
  <img src="https://mintcdn.com/opper/iLUNiwNORpxT-ruB/images/routes/route-editor.png?fit=max&auto=format&n=iLUNiwNORpxT-ruB&q=85&s=6c4b1ae93b3a0069f96028c409f59735" alt="The route editor: Start, a Pool node and End on the canvas, with the pool's inspector on the right" width="1512" height="798" data-path="images/routes/route-editor.png" />
</Frame>

## Call a route

A deployed route is a model name. Pass `dynamic/<name>` wherever a model
goes — the chat completions and Messages endpoints, `/v3/call`, the SDKs:

```bash theme={null}
curl https://api.opper.ai/v3/compat/chat/completions \
  -H "Authorization: Bearer $OPPER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "dynamic/support-router",
    "messages": [{"role": "user", "content": "My invoice is wrong."}]
  }'
```

The response's `meta.routing` says what happened: `requested` is the route,
`route` names the version that ran and the node that answered, and `served`
is the model that produced the answer.

## Build one

Open **Models → Routes** in [platform.opper.ai](https://platform.opper.ai)
and create a route. A new route is three nodes — Start, a Model and End —
which already works: the model answers and the walk ends.

* Click the **+** on an edge to add a node there. Under **Split traffic** you
  get **If / Else**, **Traffic split** and **Classifier**; below them,
  **Add Model** and **Add Pool**.
* Select a node to edit it in the inspector on the right.
* **Save draft** keeps your work without changing traffic. **Publish**
  deploys it.

| Node                      | What it does                                                                                                                                                   |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Model**                 | Calls one model. Add fallback models to try, in a fixed order, when it fails.                                                                                  |
| **Pool**                  | Holds several models and ranks them on every request — cheapest, fastest, highest throughput, or a weighted blend. See [Pool node](/capabilities/routes/pool). |
| **Branch** (If / Else)    | Evaluates conditions in order; the first one that is true wins, and a default branch catches the rest.                                                         |
| **Split** (Traffic split) | Sends a percentage of requests down each branch. The weights must add up to 100.                                                                               |
| **Classifier**            | Asks a model which of your labelled cases the request belongs to, up to ten, and takes that branch.                                                            |
| **Observe**               | Scores the answer with an LLM judge after the response has been sent, and attaches the score to the trace.                                                     |

Model and Pool are the nodes that answer: a request stops at the first one
that produces an answer. Each also has a **fallback** edge, taken when every
attempt on it failed, so a route can say "try this pool, then that model".
Observe nodes can only follow an answer.

## Conditions

Branch conditions are [CEL](https://cel.dev) expressions over the request.
`metadata` is the request's metadata map, `auth` the caller, and `input` the
request itself:

```
metadata.tier == "enterprise"
has(metadata.locale) && metadata.locale.startsWith("sv")
```

Anything that does not compile is rejected when you publish, never at
request time.

## Draft and deployed

Editing changes the draft. Publishing snapshots it as a numbered version and
points the route at it. Requests only ever run the active version, so a
half-finished edit cannot reach traffic, and a bad deploy is undone by
pointing the route back at the previous version.

**Simulate** sends a sample request through the draft and shows the path it
takes — which branch matched, which model answered and, for a pool, the order
its models would be tried in.

## What's next

<CardGroup cols={2}>
  <Card title="Pool node" icon="layer-group" href="/capabilities/routes/pool">
    Rank several models per request on price, latency or throughput.
  </Card>

  <Card title="Routing rule" icon="route" href="/control-plane/rules/routing">
    The same ordering for the pool behind a bare model name, no graph needed.
  </Card>
</CardGroup>
