Overview
Model aliasing lets you decouple the model name your application sends from the identifier Bifrost actually uses when calling a provider. You can:
- Send
"best-model" and have Bifrost resolve it to whatever model you’ve decided is best - without touching your application code
- Map a single logical name like
"gpt-4o" to a provider-specific deployment name, inference profile ARN, or fine-tuned model ID
- Give different teams different underlying models behind the same name
There are two aliasing mechanisms, and they operate at different layers:
| Static Aliases | Dynamic Aliases (Routing Rules) |
|---|
| Where configured | On a provider key | On routing rules, scoped to VK / Team / Customer / Global |
| When applied | After key selection, before the provider API call | At request time, before key selection |
| Scope | Per-key | Per-VK, per-team, per-customer, or global |
| Condition-based | No - always resolves | Yes - CEL expression controls when it fires |
Static Aliasing
Static aliasing is available in Bifrost v1.5.0-prerelease2 and above.
Static aliases are configured directly on a provider key. Every request that is served by that key will have its model name resolved through the alias map before the request reaches the provider API.
How it works
- Your application sends a request with
model: "best-model"
- Bifrost selects a key that supports
"best-model" (alias names are treated as model identifiers for key selection and allowlists)
- Before calling the provider, Bifrost resolves
"best-model" → "gpt-4o-2024-11-20" using that key’s aliases map
- The provider receives
"gpt-4o-2024-11-20" - your application never needs to know
Configuration
Add an aliases object to any key in config.json:
{
"providers": {
"openai": {
"keys": [
{
"value": "env.OPENAI_API_KEY",
"models": ["*"],
"aliases": {
"best-model": "gpt-4o-2024-11-20",
"fast-model": "gpt-4o-mini",
"embedder": "text-embedding-3-large"
}
}
]
}
}
}
You can also add aliases via the provider keys API:
curl -X POST http://localhost:8080/api/providers/openai/keys \
-H "Content-Type: application/json" \
-d '{
"value": "env.OPENAI_API_KEY",
"models": ["*"],
"aliases": {
"best-model": "gpt-4o-2024-11-20",
"fast-model": "gpt-4o-mini"
}
}'
Each alias maps the name your application sends to either a plain target string (the simplest form) or an object that additionally tags the alias with the canonical name used for pricing/logs, the model family used for provider routing, and any provider-specific overrides. The shorthand and the rich object form are interchangeable - both are accepted on the wire.
{
"aliases": {
"fast-model": "gpt-4o-mini",
"best-model": {
"model_id": "12345-azure-deployment",
"model_name": "claude-sonnet-4-5",
"model_family": "anthropic",
"description": "Claude Sonnet 4.5 on our Azure tenant",
"api_version": "2024-10-21",
"endpoint": "env.AZURE_SECONDARY_ENDPOINT"
}
}
}
| Field | Type | Description |
|---|
model_id | string | Required. The wire identifier forwarded to the provider — deployment name, inference profile ARN, fine-tune ID, anything. |
model_name | string | Canonical model name used by pricing and logs. Set this when model_id is opaque (e.g. an Azure deployment ID) so cost attribution still hits the catalog. |
model_family | enum | Forces the family used for provider routing decisions (request shape, response parsing, etc.). One of: anthropic, openai, mistral, cohere, gemini, gemma, llama, imagen, veo, nova, titan. When unset, the family is auto-detected by substring matching against model_name, model_id, and the alias name. |
description | string | Free-form note. Surfaced in the UI; not used for routing. |
region | string / EnvVar | Per-alias region override (Bedrock + Vertex). Falls back to the key-level region when unset. |
Provider-specific overrides (only set on aliases whose owning key matches the provider; validation rejects mismatches):
| Provider | Field | Description |
|---|
| Azure | api_version | Overrides the api-version query parameter |
| Azure | anthropic_version | Overrides the anthropic-version header for Claude-on-Azure deployments |
| Azure | endpoint | Overrides the key-level Azure endpoint — useful when one credential spans deployments on multiple Azure resources |
| Vertex | project_id | Per-alias GCP project ID |
| Vertex | project_number | Per-alias GCP project number (required for fine-tuned models) |
| Bedrock | inference_profile_arn | Cross-region inference profile ARN invoked instead of the model ID |
| Replicate | use_deployments_endpoint | Routes the alias through Replicate’s deployments endpoint |
Validation rules
Bifrost rejects an aliases map that violates any of these:
- No empty strings - both the alias name and
model_id must be non-empty
- No leading or trailing whitespace on either side
- No duplicate alias names (checked case-insensitively) -
"GPT-4o" and "gpt-4o" cannot both be keys in the same map
- No provider mismatch on sub-configs - an Azure-specific override (
api_version, endpoint, …) can only appear on an alias whose owning key is an Azure key, and likewise for Vertex / Bedrock / Replicate
Case-insensitive matching
Alias lookup is case-insensitive. If your map has "GPT-4O": "gpt-4o-2024-11-20" and a request comes in with model: "gpt-4o", it resolves correctly. Aliases are stored as-is but matched without regard to case.
Tracking in responses
Every response includes a routing_info block in extra_fields describing the routing decisions Bifrost made:
{
"extra_fields": {
"routing_info": {
"provider": "openai",
"model": "best-model",
"key": "production-key",
"resolved_key_alias": {
"model_id": "gpt-4o-2024-11-20",
"model_name": "gpt-4o"
}
}
}
}
| Field | Description |
|---|
routing_info.provider | Provider that handled the request |
routing_info.model | Model name the caller sent (the LHS of the alias map when an alias matched) |
routing_info.key | Human-friendly name of the key used |
routing_info.resolved_key_alias | Present only when an alias matched. Carries the wire model_id, the canonical model_name (if set), and the resolved model_family (if set). |
routing_info.is_fallback | true when the request was served by a fallback attempt rather than the primary |
routing_info.primary_provider / routing_info.primary_model | Populated on fallback attempts with the primary attempt’s provider/model |
When no alias matches, resolved_key_alias is omitted and model carries the wire identifier directly.
extra_fields.provider, extra_fields.original_model_requested, and extra_fields.resolved_model_used are deprecated but still populated for backward compatibility. New consumers should read from routing_info.
Dynamic Aliasing
Dynamic aliasing uses Routing Rules to rewrite the model at request time based on a CEL expression. Unlike static aliases (which are fixed to a key), dynamic aliases fire conditionally and are scoped - so the same model name can resolve differently depending on who is making the request.
How scopes make it dynamic
Routing rules are organized into four scopes, evaluated in priority order:
Virtual Key scope → Team scope → Customer scope → Global scope
This means you can configure aliasing at any level of your org hierarchy. For example:
- Global scope aliases
"best-model" → "gpt-4o-mini" (cost-effective default for everyone)
- Team scope for the AI team overrides
"best-model" → "claude-3-5-sonnet-20241022" (more capable)
- Virtual Key scope for a specific VK overrides
"best-model" → "o1" (highest capability, specific use case)
Each requester gets the right model behind the same name, with zero changes to the application.
Example: alias based on request type
{
"name": "route-embeddings-to-fast-model",
"cel_expression": "request_type == 'embedding' && model == 'embedder'",
"targets": [
{ "model": "text-embedding-3-small", "weight": 1.0 }
],
"scope": "global"
}
Any request with model: "embedder" that is an embedding request gets routed to "text-embedding-3-small".
Example: alias with provider switch
{
"name": "premium-tier-routing",
"cel_expression": "headers['x-tier'] == 'premium'",
"targets": [
{ "provider": "anthropic", "model": "claude-3-5-sonnet-20241022", "weight": 1.0 }
],
"scope": "global"
}
Premium-tier requests get routed to Anthropic’s Sonnet regardless of what model the client sent.
Multi-step rewrites with chaining
Setting chain_rule: true on a rule causes Bifrost to re-evaluate the full scope chain with the new provider/model as the new context. This lets you build layered alias resolution where a global rule establishes provider intent and a VK-scoped rule applies the final key selection.
Scenario: All clients send model: "best-model". Premium VKs should get gpt-5 via a high-tier key; standard VKs should get gpt-4.1 via a lower-tier key.
Rule 1 - Global scope (chain_rule: true):
{
"name": "resolve-best-model-provider",
"cel_expression": "model == 'best-model'",
"targets": [
{ "provider": "openai", "model": "best-model", "weight": 1.0 }
],
"scope": "global",
"chain_rule": true
}
This establishes that best-model resolves to OpenAI and re-evaluates the scope chain with provider="openai", model="best-model".
Rule 2a - VK scope on premium-vk (chain_rule: false):
{
"name": "premium-model-selection",
"cel_expression": "provider == 'openai' && model == 'best-model'",
"targets": [
{ "provider": "openai", "model": "gpt-5", "weight": 1.0 }
],
"scope": "virtual_key",
"scope_id": "premium-vk"
}
Rule 2b - VK scope on standard-vk (chain_rule: false):
{
"name": "standard-model-selection",
"cel_expression": "provider == 'openai' && model == 'best-model'",
"targets": [
{ "provider": "openai", "model": "gpt-4.1", "weight": 1.0 }
],
"scope": "virtual_key",
"scope_id": "standard-vk"
}
What happens for a premium-vk request:
model="best-model" via premium-vk
↓ Rule 1 (global, chain_rule: true)
provider="openai", model="best-model" - re-evaluate scope chain
↓ Rule 2a (premium-vk scope, chain_rule: false)
provider="openai", model="gpt-5" - done
OpenAI receives model="gpt-5"
What happens for a standard-vk request:
model="best-model" via standard-vk
↓ Rule 1 (global, chain_rule: true)
provider="openai", model="best-model" - re-evaluate scope chain
↓ Rule 2b (standard-vk scope, chain_rule: false)
provider="openai", model="gpt-4.1" - done
OpenAI receives model="gpt-4.1"
Each step in the chain can change provider, model, or both. Cycle detection prevents infinite loops.
See the Routing Rules documentation for the full CEL expression reference, priority configuration, and chaining details.
Advanced: Combining Both Layers
Static and dynamic aliasing compose naturally - routing rules fire first (at the HTTP layer), then key-level aliases resolve second (inside the inference worker, after key selection). This lets you separate concerns across two distinct layers:
- Routing rules decide which provider and which key tier to use, based on who is making the request
- Key aliases handle the final model identifier forwarded to the provider
Example
Setup: Two OpenAI keys with different tiers, each with their own best-model alias:
{
"providers": {
"openai": {
"keys": [
{
"id": "high-tier-key",
"value": "env.OPENAI_HIGH_TIER_KEY",
"models": ["*"],
"aliases": { "best-model": "gpt-5" }
},
{
"id": "low-tier-key",
"value": "env.OPENAI_LOW_TIER_KEY",
"models": ["*"],
"aliases": { "best-model": "gpt-4o" }
}
]
},
"anthropic": {
"keys": [
{
"id": "anthropic-key",
"value": "env.ANTHROPIC_KEY",
"models": ["*"],
"aliases": { "best-model": "claude-3-5-sonnet-20241022" }
}
]
}
}
}
Routing rules: Two team-scoped rules handle provider selection, and two VK-scoped rules handle key tier selection.
[
{
"name": "tech-team-provider",
"cel_expression": "model == 'best-model'",
"targets": [{ "provider": "openai", "model": "best-model", "weight": 1.0 }],
"scope": "team",
"scope_id": "tech-team",
"chain_rule": true
},
{
"name": "ml-team-provider",
"cel_expression": "model == 'best-model'",
"targets": [{ "provider": "anthropic", "model": "best-model", "weight": 1.0 }],
"scope": "team",
"scope_id": "ml-team",
"chain_rule": true
},
{
"name": "premium-vk-key-selection",
"cel_expression": "provider == 'openai' && model == 'best-model'",
"targets": [{ "provider": "openai", "model": "best-model", "key_id": "high-tier-key", "weight": 1.0 }],
"scope": "virtual_key",
"scope_id": "premium-vk"
},
{
"name": "standard-vk-key-selection",
"cel_expression": "provider == 'openai' && model == 'best-model'",
"targets": [{ "provider": "openai", "model": "best-model", "key_id": "low-tier-key", "weight": 1.0 }],
"scope": "virtual_key",
"scope_id": "standard-vk"
}
]
Resolution paths:
tech-team + premium-vk → model="best-model"
↓ Team rule: provider="openai", model="best-model" (chain)
↓ VK rule: key=high-tier-key
↓ Alias: "best-model" → "gpt-5"
→ OpenAI receives model="gpt-5"
tech-team + standard-vk → model="best-model"
↓ Team rule: provider="openai", model="best-model" (chain)
↓ VK rule: key=low-tier-key
↓ Alias: "best-model" → "gpt-4o"
→ OpenAI receives model="gpt-4o"
ml-team → model="best-model"
↓ Team rule: provider="anthropic", model="best-model" (chain)
↓ No VK rule matches anthropic - chain terminates
↓ Alias: "best-model" → "claude-3-5-sonnet-20241022"
→ Anthropic receives model="claude-3-5-sonnet-20241022"
Response extra_fields.routing_info for tech-team + premium-vk:
{
"routing_info": {
"provider": "openai",
"model": "best-model",
"key": "high-tier-key",
"resolved_key_alias": {
"model_id": "gpt-5"
}
}
}
routing_info.model is what the client sent (after any routing rule rewrites). routing_info.resolved_key_alias.model_id is the final identifier that reached the provider API - after both routing and key-level alias resolution. When no key-level alias matches, resolved_key_alias is omitted and the wire model equals routing_info.model.