---
title: "Preview Auto Router Routing"
method: POST
path: "/auto_router/test_routing"
tags: ["model management"]
---

# Preview Auto Router Routing

`POST /auto_router/test_routing`

Route a single request through a complexity-router config and report where it landed.

Answers "which model would this request get?" for a config that only exists in a form,
so an auto router can be checked before it is created. The request is classified by the
same pre-routing hook a live request runs, over the same messages, system prompt and tool
definitions, then dropped: nothing is sent to the model it routed to, and no auto router is
created. A heuristic config therefore spends nothing, while an `llm` classifier or semantic
keyword matching bills its classifier/embedding call to the calling key, like Test Connection
does.

Send `messages` to classify a real turn, with `system` and `tools` beside it when the surface
carries them top level, as Anthropic /v1/messages does. `prompt` is the single-ask shorthand and
routes as one user turn with nothing around it.

**Example Request:**
```json
{
    "messages": [
        {"role": "system", "content": "You are a database migration assistant"},
        {"role": "user", "content": "the index is not unique"},
        {"role": "assistant", "content": "Then two workers can both insert. Add a unique index"},
        {"role": "user", "content": "ok do it"}
    ],
    "tools": [{"type": "function", "function": {"name": "Bash", "description": "Run a command"}}],
    "complexity_router_config": {
        "tiers": {"SIMPLE": ["gpt-4o-mini"], "REASONING": ["o3"]},
        "classifier_type": "heuristic"
    }
}
```

## Request body

- AutoRouterRoutingTestRequest — A single request to classify against a complexity-router config that need not be saved yet. Carries the same fields the serving path carries, so a dry run classifies what a real turn would classify. `messages`, `system` and `tools` are forwarded to the routing hook untranslated, which is why they are typed loosely: the hook reads whatever dialect the surface produced, and validating them against one surface's schema would reject the others.
  - `prompt` string, nullable — A single ask to route, as an end user would send it. Mutually exclusive with messages
  - `messages` object[], nullable — The full message list to route, exactly as the serving path would receive it. Mutually exclusive with prompt
  - `system` union — The top-level system prompt an Anthropic /v1/messages body carries beside its messages
    - string
    - object[]
  - `tools` object[], nullable — The tool definitions the request advertises, which decide whether the plan-mode floor applies
  - `complexity_router_config` RequestComplexityRouterConfig, required — The part of a complexity-router config a request can carry. `plugins` holds live RoutingPlugin objects, which no JSON body can express and which have no OpenAPI schema, so it is closed off here rather than left as an arbitrary-type field.
    - `tiers` object — Mapping of complexity tiers to a model or model pool. A list is randomly picked from when adaptive=False, and used as a soft-floor home pool when adaptive=True
    - `tier_model_configs` object
    - `tier_definitions` TierDefinition[], nullable — Operator-defined tier set replacing the built-in SIMPLE/MEDIUM/COMPLEX/REASONING. Each entry's name becomes a value the LLM classifier can return and its description becomes that tier's rubric bullet; entries named after a built-in tier may omit the description and inherit the built-in criteria. List order is ascending severity and decides which tier wins when several keyword_tier_rules match. Requires classifier_type 'llm' or 'custom', a fallback_tier, and `tiers` keys matching the defined names exactly. Escalation, adaptive selection, session affinity, plugins, tier_labels, and the calibration-example rubric presets are unavailable with a custom tier set: the first four are built on the built-in tier ladder, and the last two rename or exemplify tiers the set replaces.
      - `name` string, required — Tier name; becomes a value the LLM classifier can return and a key of `tiers`
      - `description` string, nullable — What belongs in this tier; rendered as this tier's bullet in the classifier rubric. Required unless the name is a built-in tier (SIMPLE/MEDIUM/COMPLEX/REASONING), which inherits the built-in criteria when omitted
    - `fallback_tier` string, nullable — Tier routed to when the LLM classifier fails (timeout, provider error, or an unparseable reply). Required with tier_definitions and must name a defined tier; the heuristic scorer cannot produce custom tiers, so this replaces the heuristic fallback for custom tier sets.
    - `classification_prompt` string, nullable — Replaces the classification instructions that open the LLM classifier rubric, and nothing else. The per-tier bullets follow it, the calibration examples follow those, and the trust-boundary paragraph telling the classifier to ignore tier requests embedded in quoted caller text is always appended after them and cannot be overridden. Requires an LLM classifier and cannot be combined with classifier_llm_config.system_prompt. With built-in tiers the rubric preset still supplies the tier criteria and, unless classification_examples replaces them, the calibration examples.
    - `classification_examples` string, nullable — Replaces the calibration examples of the LLM classifier rubric, and nothing else. Written as example lines only: the router renders the 'Calibration examples:' heading above them, after the per-tier bullets. Requires an LLM classifier and cannot be combined with classifier_llm_config.system_prompt. With built-in tiers the rubric preset still supplies the tier criteria and, unless classification_prompt replaces them, the classification instructions; a custom tier set ships no examples of its own, so the section renders only when this is set.
    - `tier_labels` object — Display names for the complexity tiers, so a deployment can use its own vocabulary (e.g. Cheap/Standard/Premium/Deep) in the dashboard, spend logs, and the LLM classifier rubric. Purely operator-facing: config keys stay canonical (tiers, keyword_tier_rules[].tier, tier_boundaries), API callers never see these names, and the heuristic scorer never reads them. Unlisted tiers keep their canonical name. Partial maps are allowed.
    - `tier_boundaries` object — Score boundaries between tiers. These keys (simple_medium, medium_complex, complex_reasoning) name the gaps between the default tier names and are not renameable by tier_labels; they are scorer knobs persisted by name on every routing decision
    - `reasoning_override_min_score` number, nullable — Minimum weighted score a request must reach before 2+ reasoning markers may promote it to the reasoning tier. Unset tracks tier_boundaries.simple_medium, so the override never rescues a request the scorer placed in the cheapest tier; 0 restores the unconditional override
    - `token_thresholds` object — Token count thresholds for simple/complex classification
    - `dimension_weights` object — Weights for each scoring dimension
    - `code_keywords` string[], nullable — Keywords indicating code-related content
    - `reasoning_keywords` string[], nullable — Keywords indicating reasoning-required content
    - `technical_keywords` string[], nullable — Keywords indicating technical content
    - `custom_technical_keywords` string[], nullable — Domain-specific technical keywords appended to the effective base list (technical_keywords if set, otherwise DEFAULT_TECHNICAL_KEYWORDS). Order is preserved; duplicates are removed case-insensitively against the base list and within this list.
    - `simple_keywords` string[], nullable — Keywords indicating simple/basic queries
    - `default_model` string, nullable — Default model to use if tier cannot be determined
    - `return_raw_model_name` boolean — Return the resolved raw model name in the response model field instead of the client-requested complexity-router alias
    - `classifier_type` 'heuristic' | 'heuristic_v2' | 'llm' | 'custom' | 'heuristic_first' | 'hybrid' — Classification strategy: local regex/keyword scoring, the bundled trained four-tier heuristic, an LLM call, a custom classifier plugin, 'heuristic_first', which scores locally and only pays for the LLM classifier when the local scorer does not confidently land a cheap tier, or 'hybrid', which trusts the local scorer everywhere except when its score lands near a tier boundary
    - `heuristic_v2_artifact` union — Success-probability artifact used by classifier_type 'heuristic_v2'. The bundled UltraFeedback artifact is selected by default; an inline trained artifact may replace it
      - TrainedTierArtifact
        - `schema_version` 1
        - `global_statistics` TierGlobalStatistic[], required
          - `tier` integer, required
          - `successes` number, required
          - `observations` number, required
        - `domain_statistics` TierDomainStatistic[]
          - `tier` integer, required
          - `successes` number, required
          - `observations` number, required
          - `request_type` 'code_generation' | 'code_understanding' | 'technical_design' | 'analytical_reasoning' | 'writing' | 'factual_lookup' | 'general', required — Fixed v0 taxonomy. User-extensible types come in v1.
        - `cohort_statistics` TierCohortStatistic[]
          - `tier` integer, required
          - `successes` number, required
          - `observations` number, required
          - `cohort` string, required
        - `domain_prior_mass` number
        - `cohort_prior_mass` number
        - `routing_threshold` number
        - `datasets` TierDataset[]
          - `name` string, required
          - `url` string, required
          - `license` string, required
          - `rows` integer, required
          - `success_definition` string
        - `success_definition` string
        - `split_method` string
      - 'ultrafeedback'
    - `classifier_llm_config` ClassifierLLMConfig — Configuration for the LLM-based complexity classifier.
      - `model` string, required — Model name (from the router's model_list) to call for classification
      - `vision` ClassifierVisionConfig — Whether the LLM classifier sees the images on the request it is classifying. Off by default because images cost far more than the text ask they arrive with, and the classifier runs on every request. A turn whose complexity lives in the image ("what is wrong in this stack trace screenshot") is invisible to a text-only classifier, which is what this buys.
        - `enabled` boolean — Forward image content to the classifier. Requires a classifier model declared supports_vision, on the deployment's model_info or in the model cost map; images stay stripped otherwise, so a classifier that cannot read them is never sent one. Declare model_info.supports_vision on the deployment to enable a model the cost map does not describe. Only inline data: URIs are forwarded. A request whose images are http(s) URLs still classifies on its text alone, because some providers fetch such a URL from the proxy rather than the provider, which would let a caller aim a proxy-side request at an address of their choosing.
        - `max_images` integer — How many images from the newest user turn to forward, in wire order. Bounds the added cost of a turn that attaches many images. Images on earlier turns are never forwarded.
      - `reasoning_effort` 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max', nullable — Reasoning effort override for classifier calls. Leave unset to use the classifier deployment or provider default.
      - `timeout_ms` integer — Timeout budget for the classification call, in milliseconds
      - `circuit_breaker_enabled` boolean — Whether one classifier timeout temporarily sends requests through classifier_fallback. Enabled by default so an unhealthy classifier cannot repeat its timeout across sessions.
      - `circuit_breaker_cooldown_seconds` number — How long to skip this router's LLM classifier after a classification call times out. Requests use classifier_fallback during the cooldown. When it expires, one request probes the classifier while concurrent requests keep using the fallback; a successful probe closes the circuit and a failed probe restarts the cooldown.
      - `classification_rubric` 'legacy' | 'agentic' | 'chat' | 'business' — Which calibration examples, and for BUSINESS which tier criteria, the built-in classifier rubric carries.
      - `system_prompt` string, nullable — Replaces the built-in complexity rubric as the classifier's entire system role. When set, neither the default rubric nor the context-window closing line is appended, so the prompt owns the whole taxonomy and the tier names SIMPLE/MEDIUM/COMPLEX/REASONING become whatever buckets it defines: a prompt that classifies data sensitivity routes on that instead of on difficulty. Two consequences of full replacement. The default rubric's closing paragraph is the classifier's prompt-injection defense, telling it that the caller's quoted system prompt and prior turns are material to judge and never instructions; a replacement that omits it lets a caller ask for a tier and get it. And the heuristic fallback still scores complexity, so a router on some other taxonomy wants classifier_fallback='default_model'. Leave unset for the built-in rubric. Only applies when classifier_type is 'llm'.
    - `heuristic_first_max_tier` string, nullable — The highest tier the local scorer may decide on its own; required when classifier_type is 'heuristic_first' and rejected otherwise. A request whose heuristic tier is at or below this one skips the LLM classifier and routes straight to that heuristic tier, so the classifier call is only paid for on traffic the scorer could not place cheaply. The scorer must also have produced at least one signal: a prompt where no dimension fired scores 0.0 and would otherwise land SIMPLE by default rather than by evidence, which is how a chained router would silently send unclassified traffic to the cheapest model. Names a built-in tier, and may not name the highest one, since that would make the LLM classifier unreachable.
    - `hybrid_boundary_margin` number, nullable — How close to a tier boundary a heuristic score has to land before the LLM classifier breaks the tie; required when classifier_type is 'hybrid' and rejected otherwise. Everything further than this from every active boundary routes on the scorer's own tier with no classifier call, at any tier, which is what separates 'hybrid' from 'heuristic_first' and its cheap-tier ceiling. A prompt where no dimension fired still goes to the classifier, since the scorer has no opinion to be near a boundary with. 0 escalates only scores sitting exactly on a boundary.
    - `classifier_plugin` unknown
    - `classifier_plugin_timeout_ms` integer — Timeout budget for the classifier plugin call, in milliseconds. On expiry the fallback path decides the tier. Only applies when classifier_type is 'custom'.
    - `classifier_fallback` 'heuristic' | 'default_model' — What classifies the request when the LLM classifier errors, times out, or returns an unparseable response. 'heuristic' runs the local complexity scorer, which is right when the classifier grades complexity too. 'default_model' skips scoring and routes to default_model, which is what a classifier on some other taxonomy wants: a prompt that grades data sensitivity has no use for a complexity score, and scoring one produces a tier unrelated to what the operator configured. Requires default_model when set to 'default_model'. Only applies when classifier_type is 'llm', 'custom', or 'heuristic_first'.
    - `classifier_context_window_size` integer — Number of prior user turns (tool output and harness reminders excluded) to include as context in the LLM classifier prompt, so a follow-up like 'now do the same for the streaming path' is classified against what it refers to. Counts turns of both roles when classifier_context_include_assistant_turns is enabled. These turns are sent to the classifier model, which may be a different deployment or provider than the routed completion model; that call already carries the current user ask and the caller's system prompt in full. Set to 0 to send neither prior turns nor any conversation context beyond the current ask. Only applies when classifier_type is 'llm'.
    - `classifier_context_budget_chars` integer — Maximum characters of prior-turn text quoted to the LLM classifier, across the whole context window, per classification call. Turns are taken newest first and quoted whole while they fit, so a conversation small enough to quote entirely is never cut; once the budget runs out the older turns are dropped whole and only the turn straddling the boundary is truncated, into whatever space is left. The current ask and the caller's system prompt sit outside this budget and are always sent in full, as does the numbering each quoted turn carries. A budget under 120 leaves no room to quote a turn and suppresses the block; set classifier_context_window_size to 0 to turn context off deliberately. Only applies when classifier_type is 'llm'.
    - `classifier_context_per_turn_chars` integer, nullable — Optional cap on each individual prior turn's text, applied before classifier_context_budget_chars bounds the block. Unset by default, so one long turn may spend the whole budget, which is usually what a follow-up needs; set it when no single turn should dominate the context the classifier sees. A capped turn keeps its opening and its ending with the middle elided. Only applies when classifier_type is 'llm'.
    - `classifier_context_include_assistant_turns` boolean — Include assistant turns in the classifier context window, so difficulty stated by the model rather than by the user stays visible: a plan the assistant calls complex, which the user approves with 'yes', is classified on the work being approved instead of on the word 'yes'. When enabled, classifier_context_window_size counts the last N turns of the conversation across both roles rather than the last N user turns, and assistant text is sent to the classifier model, which may be a different deployment or provider than the routed completion model. Assistant replies spend classifier_context_budget_chars alongside user turns, so raise it if the oldest turns stop being quoted once replies join the window. Off by default because enabling it shifts tier decisions, and therefore spend, for an already-deployed router. Only applies when classifier_type is 'llm'.
    - `adaptive` boolean — Enable adaptive bandit selection with soft complexity floors
    - `adaptive_weights` AdaptiveRouterWeights
      - `quality` number
      - `cost` number
    - `tier_distance_penalty` number — Score penalty per tier-step away from the classified tier when adaptive=True
    - `adaptive_eligible` 'all' | 'classified_tier' — When adaptive=True: 'all' scores every pool model with a tier-distance penalty (soft floors); 'classified_tier' Thompson-samples only inside the classified tier's pool
    - `escalation_keywords` string[], nullable — Case-sensitive phrases a user can include to force a bump to the next-higher complexity tier when they aren't satisfied with results (they can force a stronger model, but not choose which one). Defaults to ['LITELLM ESCALATE'] when unset; set to an empty list to disable.
    - `keyword_tier_rules` KeywordTierRule[], nullable — Rules that force a specific tier when their keywords match the prompt
      - `keywords` string[], required — Keywords/phrases that trigger this rule (lexical or semantic match)
      - `tier` string, required — Tier to route to when this rule matches: a built-in tier name, or with tier_definitions set, one of the defined tier names
    - `stall_escalation_enabled` boolean — Escalate mid-task to the next-higher configured tier when the assistant's own recent tool calls look stuck: the newest tool call repeats, or errors, at least stall_escalation_repeat_threshold times across the last stall_escalation_window calls. Both tests are anchored on the newest call, so a task that tried the same thing a few times and then moved on is not escalated on the strength of those older calls alone, while a retry loop broken up by an unrelated lookup still counts. One tier at most, on the same ladder escalation_keywords bumps along, and never above the highest configured tier. Detection re-runs on every classified turn from the tool calls visible in that request, so it needs no state and nothing survives past the task. Mutually exclusive with session_affinity and classification_mode='user_turn', which both replay a held routing decision instead of classifying most turns, so this would never see the tool calls to look at. Off by default.
    - `stall_escalation_window` integer — How many of the assistant's most recent tool calls stall detection looks at, oldest ones dropped as new calls happen. Counted across the whole visible conversation rather than reset at the newest human ask, so evidence from before a plain follow-up message like 'try again' is still visible on the turn after it.
    - `stall_escalation_repeat_threshold` integer — How many of the last stall_escalation_window tool calls must repeat the newest call, or must have errored alongside it, before the task counts as stalled. Must not exceed stall_escalation_window, or the condition could never be reached.
    - `plan_mode_min_tier` string, nullable — When set, requests carrying a coding-agent plan-mode sentinel (Claude Code plan mode, VS Code Copilot Plan mode, Copilot CLI's exit_plan_mode tool) are routed to at least this tier: the classified tier still wins when it is higher, and the floor also overrides a session-affinity pin to a lower tier for exactly the turns carrying the sentinel, without rewriting the pin -- the first turn after plan mode exits routes as if plan mode had never happened. Names a built-in tier, or with tier_definitions set, one of the defined tier names (list order is ascending severity, same as keyword_tier_rules). Unset disables detection entirely. The sentinels ride in client-injected prompt text, so a caller who pastes one can spend up to this tier's models -- never down, and never outside the configured pools.
    - `plan_mode_patterns` string[], nullable — Additional case-sensitive literal sentinels that mark a request as plan mode, on top of the built-in Claude Code and Copilot ones. For clients whose plan-mode wording the built-ins don't cover, or after a client release changes its strings.
    - `route_housekeeping_to_cheapest_tier` boolean — Route a coding agent's own housekeeping calls to the cheapest configured tier without classifying them. A client names the conversation by quoting the whole session and asking for a title, so the ask reads as the session's engineering work and lands on the most expensive tier, which is the reverse of what the call is worth. Detection is a literal match against client-owned sentinels on the newest ask only, so it cannot fire on an earlier turn, and it never lowers what anyone else asked for: a keyword_tier_rule or a session pin still decides instead, and an escalation keyword or the plan-mode floor still raises the tier from here. Only the classifier is displaced, and its call is skipped, so a matched request costs nothing to route. Set false to classify these calls like any other.
    - `housekeeping_patterns` string[], nullable — Additional case-sensitive literal sentinels that mark a request as client housekeeping, on top of the built-in conversation-title ones. For clients whose wording the built-ins don't cover, or after a client release changes its strings.
    - `enable_context_window_escalation` boolean — Escalate a request off a tier whose models provably cannot hold its prompt, before dispatch. The classifier scores complexity and never prompt size, so a long agentic session whose newest ask is trivial lands on a small-window tier and the provider rejects it with a context-window 400 that nothing retries. When every model of the decided tier has a declared window smaller than the estimated prompt, the request moves to the lowest configured tier with a model whose declared window fits; when only some of the tier's models fit, the pick is restricted to those and the tier keeps the request. Models with no resolvable window are never escalated away from and never escalated onto. Set false to dispatch on complexity alone, as before.
    - `context_window_escalation_buffer` number — Fraction of a model's declared context window the estimated prompt must fit within. The token count is an estimate, so fitting against the full window would dispatch prompts that the provider's own tokenizer then rejects; 0.95 leaves room for that drift plus the response tokens.
    - `modality_routing` boolean — Route image-bearing requests only to models that can accept image input. The classifier reads text alone, so an image request whose text classifies cheap otherwise lands on a text-only model and fails with a provider 400. When enabled, a routed model explicitly declared supports_vision false (deployment model_info or the model cost map; unmapped names stay routable) is replaced by the nearest HIGHER tier holding a capable model, then default_model, else a clear 400. A kept session-affinity pin still wins even when an image arrives, unless modality_pin_override is also enabled.
    - `modality_pin_override` boolean — Let modality_routing replace a kept session-affinity pin on the turns that carry an image. Without this, a session pinned to a text-only model fails every image turn with a provider 400, since the pin is exempt from the modality gate. When enabled, such a turn routes to a capable model for that request only and the stored pin is left untouched, so the next text turn replays the session's own model; the override is reported as cause modality_pin_override and is never itself pinned. Inert unless modality_routing is also enabled.
    - `semantic_keyword_matching` boolean — Match keyword_tier_rules by embedding similarity instead of literal text
    - `embedding_model` string, nullable — Embedding model (LiteLLM model name) used when semantic_keyword_matching is enabled
    - `match_threshold` number — Minimum cosine similarity for a semantic keyword match
    - `classification_mode` 'every_request' | 'user_turn' — When to run the complexity classifier. 'every_request' (the default) classifies every inference request, including the tool-result continuation turns of an agentic loop. 'user_turn' classifies only requests whose newest turn is a new human ask and replays the session's held routing decision on continuation turns, which cuts classifier spend and eliminates mid-loop model switches. Continuations with no held decision to replay (no resolvable session_id, expired pin, fresh restart) still classify. Unlike session_affinity, a new human ask always re-classifies, so a session can still move tiers between asks. Suppressed when plugins are configured, for the same reason session_affinity is: a replayed decision would bypass the plugin pipeline.
    - `session_affinity` boolean — When True and a session_id is resolvable on the request, pin the model chosen on the session's first turn and reuse it for every later turn, skipping re-classification. Off by default so every turn is classified on its own merits and routed to the cheapest adequate tier. Set True to keep a multi-turn session on one model, which preserves provider prompt caches and avoids cross-model conversation-history errors. Always implies the deployment pin regardless of deployment_affinity: the session sticks to one deployment of the pinned model, since freezing the model while re-shuffling its deployments would still go cache-cold.
    - `deployment_affinity` boolean — When True and a session_id is resolvable on the request, pin the deployment chosen inside each routed model group and reuse it whenever the session returns to that group, without pinning which group the session routes to. Independent of session_affinity, which pins the model group instead (and always carries this deployment pin with it): with session_affinity off, every turn is still classified on its own merits while a session that escalates to a stronger tier and comes back still lands on the deployment it used before, which is what keeps a provider prompt cache warm. Pins are held per model group, so switching tiers does not disturb the pin left behind in the previous group. On by default because re-shuffling a conversation across deployments of the same model discards that cache for no benefit; set False to keep every turn load-balanced across the group, which is what a deployment set with tight per-deployment rate limits wants. Inert when no session_id is resolvable, since there is nothing to key a pin on, and suppressed when plugins are configured, for the same reason session_affinity is.
    - `session_affinity_ttl_seconds` integer — TTL for the session affinity pin; refreshed on every cache hit. Bounds both the session_affinity model pin and the deployment_affinity deployment pin, so it measures idle time for the session's routing decisions rather than total session length
    - `plugins` unknown
    - `reminder_markers` ReminderMarkerPair[], nullable — Override the delimiter pairs used to recognize and strip harness-injected reminder blocks before classification. A harness that wraps injected context differently per agent type (main, subagent, cron) lists every pair it emits. Replaces, rather than adds to, the built-in default of ('<system-reminder>', '</system-reminder>'), so a harness that also emits that pair lists it too. Matching is case-insensitive.
      - `open` string, required — Opening delimiter, e.g. '<system-reminder>'
      - `close` string, required — Closing delimiter, e.g. '</system-reminder>'
  - `default_model` string, nullable — Model to route to when no tier resolves, i.e. complexity_router_default_model
  - `router_name` string — Name reported as the router in the routing decision. Display only
  - `team_id` string, nullable — Team the router is being created for. Required for a team admin, who may only test their own team's routers

## Response `200`

Successful Response

- AutoRouterRoutingTestResponse — Where one prompt would have been routed, and why.
  - `routed_model` string, required — The model group the router picked
  - `routed_model_configured` boolean, required — Whether routed_model is a model group available to the caller, scoped to team_id when given. Never confirms models the caller could not use
  - `routing_decision` StandardLoggingRoutingDecision, required — Per-request provenance for a pre-routing strategy (auto-router) decision.
    - `router_model_name` string
    - `router_type` 'complexity' | 'adaptive' | 'quality'
    - `routed_model` string
    - `cause` 'heuristic_scorer' | 'heuristic_v2' | 'reasoning_override' | 'llm_classifier' | 'heuristic_first_short_circuit' | 'hybrid_short_circuit' | 'classifier_plugin' | 'classifier_fallback' | 'default_model_fallback' | 'literal_keyword_match' | 'semantic_keyword_match' | 'plan_mode' | 'housekeeping' | 'modality_escalation' | 'modality_pin_override' | 'health_failover' | 'session_affinity_pin' | 'session_affinity_escalation' | 'user_turn_continuation' | 'default_fallback' | 'keyword' | 'quality_tier' | 'bandit'
    - `tier` string
    - `tier_label` string
    - `request_type` string
    - `score` number
    - `signals` string[]
    - `matched_keyword` string
    - `escalation_keyword` string
    - `classifier_model` string
    - `classifier_cost` number
    - `escalated` boolean
    - `context_escalated` boolean
    - `context_escalation_original_tier` string
    - `tier_boundaries` StandardLoggingRoutingDecisionTierBoundaries — Snapshot of the complexity scorer's tier boundaries at decision time, so a historical spend log row stays explainable after the router config changes.
      - `simple_medium` number, required
      - `medium_complex` number, required
      - `complex_reasoning` number, required
    - `reasoning_override_min_score` number
    - `conversation_continuing` boolean
    - `savings_baseline_model` string
    - `savings_baseline_deployment_id` string
    - `tier_litellm_params` object

## Other responses

- `422` — Validation Error

## Changes

- **2026-09-18** `082b5fabd909` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/flock/apis/litellm-api/changes/auto_router/test_routing/post.md)

---

[API](https://skmtc.dev/flock/apis/litellm-api.md) · [All operations](https://skmtc.dev/flock/apis/litellm-api/llms.txt) · [OpenAPI document](https://skmtc.dev/flock/apis/litellm-api/revisions/731afbea6a1b?raw)
