Routers
Create and manage AI Gateway routers, read what they did and learned, and score routed responses. A router is a virtual model: use its id or name as the model of a chat, completion or Responses request (/api/v1/ai-gateway/chat/completions, ...) and the gateway sends each request to one of its member models by its strategy. See Routers for every strategy.
All endpoints require authentication via the X-API-Key header and the scope shown.
Router Object
{
"_id": "Tn3fcPqCPdFjjBoF5",
"name": "support-chat",
"description": "Customer support",
"strategy": "epsilon_greedy",
"members": [
{ "modelId": "6a502004c1c6457701e39093", "weight": 1, "priority": 1, "enabled": true,
"capabilities": [] },
{ "modelId": "2aed3be8ea864b6ef78ddf93", "weight": 1, "priority": 2, "enabled": true,
"capabilities": [] }
],
"fallbackBehavior": "next",
"stickyRouting": false,
"healthCheck": { "enabled": true, "intervalSeconds": 60, "failureThreshold": 3, "recoveryThreshold": 2 },
"banditConfig": { "rewardMetric": "feedback", "epsilon": 0.1 },
"organizationId": "org-default-1mgi3vfz8",
"owner": "FsbHXu34LHShtmb9L",
"createdAt": "2026-09-27T10:00:00.000Z",
"updatedAt": "2026-09-27T10:00:00.000Z"
}
| Field | Description |
|---|---|
strategy | priority, weighted, round-robin, latency-optimized, thompson_sampling, ucb1, epsilon_greedy, linucb, complexity, content-type, token-budget, semantic, routellm, canary, custom |
members[].priority | Lower is preferred (priority strategy, failover order, "most capable") |
members[].weight | Relative share of traffic (weighted) |
members[].capabilities | Content types the member handles: code, data, creative, ops, general (content-type) |
fallbackBehavior | next: a failed member hands the request to the next by priority; error: never fail over |
stickyRouting | Weighted: the same caller always gets the same member |
Strategy settings
| Field | Strategy | Shape |
|---|---|---|
banditConfig | thompson_sampling, ucb1, epsilon_greedy, linucb | { reward_metric: success | latency | feedback, epsilon (epsilon_greedy, 0-1), exploration_bonus (ucb1 c, linucb alpha), max_latency_ms (latency), linucb_features (linucb) } |
canaryConfig | canary | { control_model_id, canary_model_id, canary_percent } |
semantic_config | semantic | { embedding_model_id, confidence_threshold, default_model_id, routes: [{ name, model_id, examples: [...] }] } |
complexityConfig | complexity | { simple_model_id, complex_model_id? } (unset complex: the most capable) |
contentTypeConfig | content-type | { type_model_map: { code | data | creative | ops | general: model_id }, default_model_id } |
token_budget_config | token-budget | { long_response_tokens, short_model_id } |
routellm_config | routellm | { embedding_model_id (a text-embedding-3-small model), threshold }; exactly two members |
customStrategy | custom | { code }: Python that sets result to a member's model id |
linucbFeatures are among prompt_length, last_message_length, conversation_turns, has_system_prompt, has_tools, json_output, has_images, complex_prompt, content_code, content_data, content_creative, content_ops.
A setting a strategy needs and is not given takes its default on save (epsilon 0.1, UCB1 exploration 1.41, LinUCB alpha 1.0, similarity threshold 0.75, long answers 500 tokens, RouteLLM threshold 0.11593, max latency 10000 ms). A router its strategy cannot act on is refused with a 400 that names what to fix.
GET /api/v1/ai-gateway/routers
List routers the caller can use.
Scope: ai-gateway:read
POST /api/v1/ai-gateway/routers
Create a router and return it (Router object).
Scope: ai-gateway:write
Body: name and strategy (required), description, members, fallbackBehavior, stickyRouting, healthCheck and the strategy's settings object.
{
"name": "support-chat",
"strategy": "linucb",
"members": [
{ "modelId": "6a502004c1c6457701e39093", "priority": 1 },
{ "modelId": "2aed3be8ea864b6ef78ddf93", "priority": 2 }
],
"banditConfig": {
"rewardMetric": "feedback",
"explorationBonus": 1.0,
"linucbFeatures": ["prompt_length", "has_tools", "content_code"]
}
}
GET /api/v1/ai-gateway/routers/:id
Get a router.
Scope: ai-gateway:read
PATCH /api/v1/ai-gateway/routers/:id
Update a router and return it. Send only the fields to change; the result is validated as the whole router. What a learning router has learned is kept (except that new LinUCB features start its learning afresh).
Scope: ai-gateway:write
DELETE /api/v1/ai-gateway/routers/:id
Delete a router.
Scope: ai-gateway:write
POST /api/v1/ai-gateway/routers/:id/members
Add a member and return the router: modelId (required), weight, priority, enabled, capabilities.
Scope: ai-gateway:write
DELETE /api/v1/ai-gateway/routers/:routerId/members/:id
Remove a member. Refused when the router's strategy settings still name it.
Scope: ai-gateway:write
GET /api/v1/ai-gateway/routers/:id/stats
What the router did and learned.
Scope: ai-gateway:read
| Name | In | Type | Required | Description |
|---|---|---|---|---|
periodHours | query | integer | No | Window in hours (default 24) |
Response 200 OK
{
"data": {
"routerId": "Tn3fcPqCPdFjjBoF5",
"strategy": "epsilon_greedy",
"learns": true,
"rewardMetric": "feedback",
"periodHours": 24,
"totalDecisions": 40,
"members": [
{
"modelId": "6a502004c1c6457701e39093",
"decisions": 31, "share": 0.775,
"answered": 31, "failed": 0, "failedOver": 0,
"avgLatencyMs": 1840.2,
"rewarded": 28, "avgReward": 0.86, "pendingFeedback": 3,
"allTime": { "decisions": 120, "pulls": 110, "totalReward": 93.5, "avgReward": 0.85,
"lastReward": 1.0, "lastRewardAt": "2026-09-27T10:41:00+00:00" }
}
]
},
"meta": { "requestId": "req_abc123" }
}
decisions and share are the router's decisions in the window; all_time.pulls counts the rewards a member received. LinUCB routers add linucb: { features, weights } per member: the reward weight learned for each request feature.
GET /api/v1/ai-gateway/routers/:id/decisions
The router's decisions, newest first.
Scope: ai-gateway:read
| Name | In | Type | Required | Description |
|---|---|---|---|---|
cursor | query | string | No | meta.nextCursor of the previous page; omit for the first page |
limit | query | integer | No | Decisions per page (default 25) |
Response 200 OK
{
"data": {
"total": 40, "offset": 0, "limit": 25,
"decisions": [
{
"id": "0f5c3a9e7d2b4c81a6e2b3d4f5a6b7c8",
"createdAt": "2026-09-27T10:41:00+00:00",
"selectedModelId": "6a502004c1c6457701e39093",
"reason": "Epsilon-greedy exploit: best average reward 0.860",
"success": true,
"servedModelId": "6a502004c1c6457701e39093",
"attempts": [{ "modelId": "6a502004c1c6457701e39093", "ok": true, "reason": null }],
"latencyMs": 1712.4,
"reward": 1.0, "rewardSource": "feedback", "rewardPending": false,
"feedback": { "reward": 1.0, "label": "resolved", "source": "api", "at": "2026-09-27T10:42:10+00:00" }
}
]
},
"meta": { "requestId": "req_abc123" }
}
POST /api/v1/ai-gateway/routers/:id/feedback
Score a routed response of a router whose reward is feedback. The reward updates the chosen member's statistics, which the router's next decisions read.
Scope: ai-gateway:write
| Field | Type | Required | Description |
|---|---|---|---|
decisionId | string | Yes | router_decision.id of the routed response (also its X-Router-Decision-Id header) |
reward | number | Yes | From 0 (bad) to 1 (good) |
label | string | No | A note kept with the feedback |
Response 200 OK
{
"data": {
"decisionId": "0f5c3a9e7d2b4c81a6e2b3d4f5a6b7c8",
"routerId": "Tn3fcPqCPdFjjBoF5",
"modelId": "6a502004c1c6457701e39093",
"reward": 1.0,
"memberStats": { "pulls": 111, "totalReward": 94.5, "avgReward": 0.851 }
},
"meta": { "requestId": "req_abc123" }
}
| Status | When |
|---|---|
| 400 | No reward, or a reward outside 0 to 1 |
| 404 | No such decision for this router |
| 409 | The router's reward is computed by the gateway (success, latency), the decision already has its reward, or its chosen member did not answer |