Custom Auth
You can now override the default api key auth.
By default, custom auth enforces only the rate limits you set on the returned object. Budgets and model-access require a flag. The table below shows, for each control, where to configure it and which flags it needs.
What gets enforced​
| Goal | Set it on | Flags required |
|---|---|---|
| Key / user / team / end-user rate limits | returned object (rpm_limit, team_tpm_limit, …) | none |
| Per-model rate limits, key / team scoped | metadata / team_metadata on the returned object | none |
| Per-model rate limits, project scoped | the project record (model_tpm_limit / model_rpm_limit) | custom_auth_run_common_checks |
| Team / user / project budget | the team / user / project record | custom_auth_run_common_checks |
| Team / user / project model allowlist | the team / user / project record | custom_auth_run_common_checks |
| End-user budget | the end-user record | custom_auth_run_common_checks or enable_post_custom_auth_checks |
Key model allowlist (models) | returned object | both flags |
Key per-model budget (model_max_budget) | returned object | enable_post_custom_auth_checks |
Key expiry (expires) | returned object | enable_post_custom_auth_checks |
Key scalar budget (max_budget / soft_budget) | not supported; use a per-scope budget | n/a |
Note: Project per-model limits go on the object's project_metadata with the flag off, but the DB project record overrides it once the flag is on, so set them there. (Team per-model via team_metadata always stays on the object.)
See Enforce budgets and model access and Key-level enforcement for examples.
Usage​
1. Create a custom auth file.​
Make sure the response type follows the UserAPIKeyAuth pydantic object. This is used by for logging usage specific to that user key.
from fastapi import Request
from litellm.proxy._types import UserAPIKeyAuth
async def user_api_key_auth(request: Request, api_key: str) -> UserAPIKeyAuth:
try:
modified_master_key = "sk-my-master-key"
if api_key == modified_master_key:
return UserAPIKeyAuth(api_key=api_key)
raise Exception
except:
raise Exception
2. Pass the filepath (relative to the config.yaml)​
Pass the filepath to the config.yaml
e.g. if they're both in the same dir - ./config.yaml and ./custom_auth.py, this is what it looks like:
model_list:
- model_name: "openai-model"
litellm_params:
model: "gpt-3.5-turbo"
litellm_settings:
drop_params: True
set_verbose: True
general_settings:
custom_auth: custom_auth.user_api_key_auth
3. Start the proxy​
$ litellm --config /path/to/config.yaml
UserAPIKeyAuth Fields Reference​
These fields are read straight off the returned object and enforced with no flag. Budgets and model-access are enforced behind flags (see below).
Identity​
Who the request belongs to. The *_id fields also tell LiteLLM which DB records to load when custom_auth_run_common_checks: true.
UserAPIKeyAuth(
api_key: Optional[str] = None, # The API key (will be hashed automatically)
token: Optional[str] = None, # Hashed token for internal use
key_alias: Optional[str] = None, # Key alias for identification
user_id: Optional[str] = None, # User identifier (also used to load the user record)
user_email: Optional[str] = None, # User email address
user_role: Optional[LitellmUserRoles] = None, # User role (PROXY_ADMIN, INTERNAL_USER, etc.)
team_id: Optional[str] = None, # Team identifier (also used to load the team record)
org_id: Optional[str] = None, # Organization identifier (also used to load the org record)
end_user_id: Optional[str] = None, # End-user identifier (also used to load the end-user record)
)
Rate limits​
All scopes below are enforced directly off the returned object, with no flag.
UserAPIKeyAuth(
# Key
tpm_limit: Optional[int] = None,
rpm_limit: Optional[int] = None,
# User
user_tpm_limit: Optional[int] = None,
user_rpm_limit: Optional[int] = None,
# Team
team_tpm_limit: Optional[int] = None,
team_rpm_limit: Optional[int] = None,
# Per team-member
team_member_tpm_limit: Optional[int] = None,
team_member_rpm_limit: Optional[int] = None,
# Per end-user
end_user_tpm_limit: Optional[int] = None,
end_user_rpm_limit: Optional[int] = None,
# Per-model (key / team scoped)
metadata: Dict = {}, # e.g. {"model_tpm_limit": {...}, "model_rpm_limit": {...}}
team_metadata: Optional[Dict] = None, # same keys, team scoped
)
Per-model rate limits are read from metadata (key) and team_metadata (team), keyed by model name. The model key must equal the request's model string exactly, or the limit is skipped silently.
rpm_limit_per_model / tpm_limit_per_model exist on the object but are inert; use metadata / team_metadata instead, or the project record (see below).
Advanced​
UserAPIKeyAuth(
max_parallel_requests: Optional[int] = None, # Concurrent request limit
allowed_model_region: Optional[AllowedModelRegion] = None, # Geographic restrictions
blocked: Optional[bool] = None, # Whether the key is blocked
config: Dict = {}, # Configuration settings
)
Object permission (MCP, agents, etc.)​
from litellm.proxy._experimental.mcp_server.mcp_server_manager import (
global_mcp_server_manager,
)
def _server_id(name: str) -> str:
server = global_mcp_server_manager.get_mcp_server_by_name(name)
if not server:
raise ValueError(f"Unknown MCP server '{name}'")
return server.server_id
object_permission = LiteLLM_ObjectPermissionTable(
mcp_servers=[_server_id("deepwiki"), _server_id("everything")], # MCP servers this key is allowed to use
mcp_tool_permissions={"deepwiki": ["search", "read_doc"]}, # optional per-server tool allow-list
)
UserAPIKeyAuth(
object_permission=object_permission,
)
Enforce budgets and model access​
Set custom_auth_run_common_checks: true to enforce budgets and model-access alongside custom auth:
general_settings:
custom_auth: custom_auth.user_api_key_auth
custom_auth_run_common_checks: true
Your handler returns the IDs; the budgets and allowlists live on the matching DB records (/team/new, /user/new, /project/new, /customer/new, or the UI), which LiteLLM loads and enforces.
For example, a team with a budget and model allowlist:
curl -X POST 'http://0.0.0.0:4000/team/new' \
-H 'Authorization: Bearer sk-master-key' \
-H 'Content-Type: application/json' \
-d '{
"team_id": "eng-team",
"max_budget": 100,
"models": ["gpt-4o-mini", "claude-3-haiku"]
}'
# ...then return that team_id from custom auth:
return UserAPIKeyAuth(api_key=api_key, team_id="eng-team")
For project per-model rate limits, set model_tpm_limit / model_rpm_limit on the project record (keyed by model name) and return that project_id:
# On the project record (via /project/new or the UI):
# model_tpm_limit = {"gpt-4o": 100000, "claude-3-haiku": 50000}
# model_rpm_limit = {"gpt-4o": 100, "claude-3-haiku": 200}
- The project record's metadata replaces any
project_metadatayou set on the returned object, so configure project per-model limits on the project record, not on the object. - For per-model rate limits, the model key must equal the request's
modelstring exactly, or the limit is skipped silently. This was the actual Expedia failure mode.
Key models vs project models​
These are separate controls:
| Field | Where it is enforced | Source of truth |
|---|---|---|
models on UserAPIKeyAuth | Key-level allowlist | Value you return from custom auth |
project_id on UserAPIKeyAuth | Project-level allowlist | models on the project record in LiteLLM's DB |
An empty models list ([]) means no restriction. Names must match the model group in your config (wildcards supported). See Project Management and Config Settings.
Key-level enforcement​
The following are enforced from the returned object, but only when litellm.enable_post_custom_auth_checks: true is also set:
general_settings:
custom_auth: custom_auth.user_api_key_auth
custom_auth_run_common_checks: true # required for the key models allowlist
litellm_settings:
enable_post_custom_auth_checks: true
from datetime import datetime, timedelta, timezone
return UserAPIKeyAuth(
api_key=api_key,
models=["gpt-4o-mini"], # key model allowlist (needs both flags)
model_max_budget={"gpt-4o": {"budget_limit": 100, "time_period": "30d"}}, # key per-model budget
expires=datetime.now(timezone.utc) + timedelta(days=30), # key expiry
)
This path also enforces end-user budgets and per-model end-user budgets when end_user_id is set.
✨ Support LiteLLM Virtual Keys + Custom Auth​
Supported from v1.72.2+
✨ Supporting Custom Auth + LiteLLM Virtual Keys is on LiteLLM Enterprise
Usage​
- Setup custom auth file
"""
Example custom auth function.
This will allow all keys starting with "my-custom-key" to pass through.
"""
from typing import Union
from fastapi import Request
from litellm.proxy._types import UserAPIKeyAuth
async def user_api_key_auth(
request: Request, api_key: str
) -> Union[UserAPIKeyAuth, str]:
try:
if api_key.startswith("my-custom-key"):
return "sk-P1zJMdsqCPNN54alZd_ETw"
else:
raise Exception("Invalid API key")
except Exception:
raise Exception("Invalid API key")
- Setup config.yaml
Key change set mode: auto. This will check both litellm api key auth + custom auth.
model_list:
- model_name: "openai-model"
litellm_params:
model: "gpt-3.5-turbo"
api_key: os.environ/OPENAI_API_KEY
general_settings:
custom_auth: custom_auth_auto.user_api_key_auth
custom_auth_settings:
mode: "auto" # can be 'on', 'off', 'auto' - 'auto' checks both litellm api key auth + custom auth
Flow:
-
Checks custom auth first
-
If custom auth fails, checks litellm api key auth
-
If both fail, returns 401
-
Test it!
curl -L -X POST 'http://0.0.0.0:4000/v1/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-P1zJMdsqCPNN54alZd_ETw' \
-d '{
"model": "openai-model",
"messages": [
{
"role": "user",
"content": "Hey! My name is John"
}
]
}'
Bubble up custom exceptions​
If you want to bubble up custom exceptions, you can do so by raising a ProxyException.
"""
Example custom auth function.
This will allow all keys starting with "my-custom-key" to pass through.
"""
from typing import Union
from fastapi import Request
from litellm.proxy._types import UserAPIKeyAuth, ProxyException
async def user_api_key_auth(
request: Request, api_key: str
) -> Union[UserAPIKeyAuth, str]:
try:
if api_key.startswith("my-custom-key"):
return "sk-P1zJMdsqCPNN54alZd_ETw"
if api_key == "invalid-api-key":
# raise a custom exception back to the client
raise ProxyException(
message="Invalid API key",
type="invalid_request_error",
param="api_key",
code=401,
)
else:
raise Exception("Invalid API key")
except Exception:
raise Exception("Invalid API key")