diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260702120000_add_logging_exporters_columns/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260702120000_add_logging_exporters_columns/migration.sql
index bed03a36455..e9f559aa83e 100644
--- a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260702120000_add_logging_exporters_columns/migration.sql
+++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260702120000_add_logging_exporters_columns/migration.sql
@@ -4,11 +4,5 @@ ALTER TABLE "LiteLLM_TeamTable" ADD COLUMN IF NOT EXISTS "logging_exporters" TEX
-- AlterTable
ALTER TABLE "LiteLLM_DeletedTeamTable" ADD COLUMN IF NOT EXISTS "logging_exporters" TEXT[] DEFAULT ARRAY[]::TEXT[];
--- AlterTable
-ALTER TABLE "LiteLLM_VerificationToken" ADD COLUMN IF NOT EXISTS "logging_exporters" TEXT[] DEFAULT ARRAY[]::TEXT[];
-
--- AlterTable
-ALTER TABLE "LiteLLM_DeletedVerificationToken" ADD COLUMN IF NOT EXISTS "logging_exporters" TEXT[] DEFAULT ARRAY[]::TEXT[];
-
-- AlterTable
ALTER TABLE "LiteLLM_OrganizationTable" ADD COLUMN IF NOT EXISTS "logging_exporters" TEXT[] DEFAULT ARRAY[]::TEXT[];
diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma
index ed4f7d4f0a0..60de5dc5470 100644
--- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma
+++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma
@@ -445,7 +445,6 @@ model LiteLLM_VerificationToken {
key_type String?
policies String[] @default([])
access_group_ids String[] @default([])
- logging_exporters String[] @default([]) // admin-owned OTEL trace destinations assigned to this key (credential names)
model_spend Json @default("{}")
model_max_budget Json @default("{}")
budget_fallbacks Json @default("{}")
@@ -541,7 +540,6 @@ model LiteLLM_DeletedVerificationToken {
key_type String?
policies String[] @default([])
access_group_ids String[] @default([])
- logging_exporters String[] @default([])
model_spend Json @default("{}")
model_max_budget Json @default("{}")
budget_fallbacks Json @default("{}")
diff --git a/litellm/models/verification_token.py b/litellm/models/verification_token.py
index 86e9da613f5..519066b8266 100644
--- a/litellm/models/verification_token.py
+++ b/litellm/models/verification_token.py
@@ -5,7 +5,6 @@ Canonical definition for ``litellm_verificationtoken``. Re-exported from
``litellm.proxy._types`` for backwards compatibility.
"""
-from collections.abc import Sequence
from datetime import datetime
from typing import Dict, List, Optional, Union
@@ -55,7 +54,6 @@ class LiteLLM_VerificationToken(LiteLLMPydanticObjectBase):
object_permission_id: Optional[str] = None
object_permission: Optional[LiteLLM_ObjectPermissionTable] = None
access_group_ids: Optional[List[str]] = None
- logging_exporters: Sequence[str] | None = None
rotation_count: Optional[int] = 0
auto_rotate: Optional[bool] = False
rotation_interval: Optional[str] = None
diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py
index 63d5bd19081..15922c9fbb4 100644
--- a/litellm/proxy/_types.py
+++ b/litellm/proxy/_types.py
@@ -1084,7 +1084,6 @@ class AllowedVectorStoreIndexItem(LiteLLMPydanticObjectBase):
class KeyRequestBase(GenerateRequestBase):
key: Optional[str] = None
budget_id: Optional[str] = None
- logging_exporters: Sequence[str] | None = None
tags: Optional[List[str]] = None
disable_global_guardrails: Optional[bool] = None
throttle_on_budget_exceeded: Optional[bool] = None
diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py
index 91c15489f72..08766ebae71 100644
--- a/litellm/proxy/litellm_pre_call_utils.py
+++ b/litellm/proxy/litellm_pre_call_utils.py
@@ -625,10 +625,9 @@ async def _effective_org_id(user_api_key_dict: UserAPIKeyAuth) -> str | None:
async def _union_logging_exporter_names(user_api_key_dict: UserAPIKeyAuth, org_id: str | None) -> frozenset[str]:
"""The union of admin-assigned exporter names across the request's identity chain.
- Each level is read from its own ``logging_exporters`` column: the key via
- ``get_key_object`` (the auth object's fields are the team's shadow, so it is
- fetched fresh), the team via ``get_team_object``, the org via ``get_org_object``
- on the effective ``org_id`` (token org or team fallback). Internal-user is
+ Each level is read from its own ``logging_exporters`` column: the team via
+ ``get_team_object``, the org via ``get_org_object`` on the effective ``org_id``
+ (token org or team fallback). Keys inherit from their team and org. Internal-user is
intentionally not a routing dimension. The assignment is an admin-owned column;
the request never supplies it. Needs a DB connection: in SDK mode there is no
identity to resolve against, so this is empty (admin-owned destinations do not
@@ -636,7 +635,6 @@ async def _union_logging_exporter_names(user_api_key_dict: UserAPIKeyAuth, org_i
"""
from litellm.proxy import proxy_server
from litellm.proxy.auth.auth_checks import (
- get_key_object,
get_org_object,
get_team_object,
)
@@ -659,19 +657,6 @@ async def _union_logging_exporter_names(user_api_key_dict: UserAPIKeyAuth, org_i
except Exception: # noqa: BLE001 # best-effort identity enrichment; a failed lookup must not block the request
return ()
- key_names = (
- await _level(
- get_key_object(
- hashed_token=user_api_key_dict.token,
- prisma_client=prisma_client,
- user_api_key_cache=cache,
- parent_otel_span=span,
- proxy_logging_obj=proxy_server.proxy_logging_obj,
- )
- )
- if user_api_key_dict.token
- else ()
- )
team_names = (
await _level(
get_team_object(
@@ -698,7 +683,7 @@ async def _union_logging_exporter_names(user_api_key_dict: UserAPIKeyAuth, org_i
if org_id
else ()
)
- return frozenset((*key_names, *team_names, *org_names))
+ return frozenset((*team_names, *org_names))
async def _resolve_logging_exporters(
diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py
index 7bd7cf0e174..63140689097 100644
--- a/litellm/proxy/management_endpoints/key_management_endpoints.py
+++ b/litellm/proxy/management_endpoints/key_management_endpoints.py
@@ -18,7 +18,7 @@ import os
import re
import secrets
import traceback
-from collections.abc import Mapping, Sequence
+from collections.abc import Mapping
from datetime import datetime, timedelta, timezone
from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, cast
@@ -1504,7 +1504,6 @@ async def generate_key_fn(
- metadata: Optional[dict] - Metadata for key, store information for key. Example metadata = {"team": "core-infra", "app": "app2", "email": "ishaan@berri.ai" }
- guardrails: Optional[List[str]] - List of active guardrails for the key
- policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules.
- - logging_exporters: Optional[List[str]] - Names of admin-owned logging destinations (credential names) this key exports its traces to.
- disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key.
- throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely.
- permissions: Optional[dict] - key-specific permissions. Currently just used for turning off pii masking (if connected). Example - {"pii": false}
@@ -1557,9 +1556,6 @@ async def generate_key_fn(
"""
try:
from litellm.proxy._types import CommonProxyErrors
- from litellm.proxy.management_endpoints.logging_exporter_validation import (
- validate_logging_exporter_field,
- )
from litellm.proxy.proxy_server import (
prisma_client,
user_api_key_cache,
@@ -1648,11 +1644,6 @@ async def generate_key_fn(
route=KeyManagementRoutes.KEY_GENERATE,
)
- # logging_exporters on a key is proxy-admin only. Skip the check when the
- # field isn't in the payload to keep /key/generate cheap for the common case.
- if data.logging_exporters is not None:
- validate_logging_exporter_field(data.logging_exporters, user_api_key_dict)
-
if team_table is not None:
await _check_team_key_limits(
team_table=team_table,
@@ -1830,15 +1821,6 @@ async def generate_service_account_key_fn(
route=KeyManagementRoutes.KEY_GENERATE_SERVICE_ACCOUNT,
)
- # Same logging_exporters gate as /key/generate: proxy-admin only. Skip the
- # check unless the field is being written.
- from litellm.proxy.management_endpoints.logging_exporter_validation import (
- validate_logging_exporter_field,
- )
-
- if data.logging_exporters is not None:
- validate_logging_exporter_field(data.logging_exporters, user_api_key_dict)
-
data.user_id = None # do not allow user_id to be set for service account keys
return await _common_key_generation_helper(
@@ -2559,7 +2541,6 @@ async def update_key_fn( # noqa: C901 # single endpoint handling many optional
- send_invite_email: Optional[bool] - Send invite email to user_id
- guardrails: Optional[List[str]] - List of active guardrails for the key
- policies: Optional[List[str]] - List of policy names to apply to the key. Policies define guardrails, conditions, and inheritance rules.
- - logging_exporters: Optional[List[str]] - Names of admin-owned logging destinations (credential names) this key exports its traces to.
- disable_global_guardrails: Optional[bool] - Whether to disable global guardrails for the key.
- throttle_on_budget_exceeded: Optional[bool] - When the key exceeds its max_budget, throttle its tpm/rpm to the global budget_exceeded_throttle_percentage instead of blocking the key entirely.
- prompts: Optional[List[str]] - List of prompts that the key is allowed to use.
@@ -2594,9 +2575,6 @@ async def update_key_fn( # noqa: C901 # single endpoint handling many optional
}'
```
"""
- from litellm.proxy.management_endpoints.logging_exporter_validation import (
- validate_logging_exporter_field,
- )
from litellm.proxy.proxy_server import (
llm_router,
premium_user,
@@ -2623,16 +2601,6 @@ async def update_key_fn( # noqa: C901 # single endpoint handling many optional
prisma_client=prisma_client,
)
- # logging_exporters is proxy-admin only. The validator no-ops when the
- # effective value doesn't change; pass the stored column value so a
- # non-admin cannot clear an admin-assigned one.
- if data.logging_exporters is not None:
- validate_logging_exporter_field(
- data.logging_exporters,
- user_api_key_dict,
- existing_exporters=getattr(existing_key_row, "logging_exporters", None),
- )
-
await _validate_update_key_data(
data=data,
existing_key_row=existing_key_row,
@@ -3621,7 +3589,6 @@ async def generate_key_helper_fn(
rotation_interval: Optional[str] = None,
router_settings: Optional[dict] = None,
access_group_ids: Optional[list] = None,
- logging_exporters: Sequence[str] | None = None, # admin-owned OTEL destinations (credential names)
budget_limits: Optional[list] = None, # multiple concurrent budget windows
):
from litellm.proxy.proxy_server import premium_user, prisma_client
@@ -3759,7 +3726,6 @@ async def generate_key_helper_fn(
"object_permission_id": object_permission_id,
"router_settings": router_settings_json,
"access_group_ids": access_group_ids or [],
- "logging_exporters": logging_exporters or [],
}
# Add rotation fields if auto_rotate is enabled
@@ -4826,23 +4792,6 @@ async def regenerate_key_fn( # noqa: C901 # single endpoint handling many opti
is_proxy_admin=_regen_is_proxy_admin,
)
- # logging_exporters gate on regenerate matches /key/generate and
- # /key/update. Without this, a key owner could set logging_exporters on
- # /key/{id}/regenerate and route future traces to a destination they
- # aren't allowed to assign (Veria F3). The validator no-ops when the
- # effective value doesn't change; pass the stored column value so a
- # non-admin cannot clear an admin-assigned one.
- if data is not None and data.logging_exporters is not None:
- from litellm.proxy.management_endpoints.logging_exporter_validation import (
- validate_logging_exporter_field,
- )
-
- validate_logging_exporter_field(
- data.logging_exporters,
- user_api_key_dict,
- existing_exporters=getattr(_key_in_db, "logging_exporters", None),
- )
-
verbose_proxy_logger.info(
"Key regeneration requested: key_alias=%s",
getattr(_key_in_db, "key_alias", None),
diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma
index ed4f7d4f0a0..60de5dc5470 100644
--- a/litellm/proxy/schema.prisma
+++ b/litellm/proxy/schema.prisma
@@ -445,7 +445,6 @@ model LiteLLM_VerificationToken {
key_type String?
policies String[] @default([])
access_group_ids String[] @default([])
- logging_exporters String[] @default([]) // admin-owned OTEL trace destinations assigned to this key (credential names)
model_spend Json @default("{}")
model_max_budget Json @default("{}")
budget_fallbacks Json @default("{}")
@@ -541,7 +540,6 @@ model LiteLLM_DeletedVerificationToken {
key_type String?
policies String[] @default([])
access_group_ids String[] @default([])
- logging_exporters String[] @default([])
model_spend Json @default("{}")
model_max_budget Json @default("{}")
budget_fallbacks Json @default("{}")
diff --git a/schema.prisma b/schema.prisma
index ed4f7d4f0a0..60de5dc5470 100644
--- a/schema.prisma
+++ b/schema.prisma
@@ -445,7 +445,6 @@ model LiteLLM_VerificationToken {
key_type String?
policies String[] @default([])
access_group_ids String[] @default([])
- logging_exporters String[] @default([]) // admin-owned OTEL trace destinations assigned to this key (credential names)
model_spend Json @default("{}")
model_max_budget Json @default("{}")
budget_fallbacks Json @default("{}")
@@ -541,7 +540,6 @@ model LiteLLM_DeletedVerificationToken {
key_type String?
policies String[] @default([])
access_group_ids String[] @default([])
- logging_exporters String[] @default([])
model_spend Json @default("{}")
model_max_budget Json @default("{}")
budget_fallbacks Json @default("{}")
diff --git a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py
index 367bda5801c..bd7a8814c28 100644
--- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py
+++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py
@@ -5232,11 +5232,12 @@ async def test_resolve_logging_exporters_team_level(_seeded_logging_credentials,
@pytest.mark.asyncio
-async def test_resolve_logging_exporters_unions_key_team_org(
+async def test_resolve_logging_exporters_unions_team_org_keys_inherit(
_seeded_logging_credentials, monkeypatch
):
- # key, team, and org are each read from their OWN logging_exporters column.
- # All three union, deduped.
+ # team and org are each read from their OWN logging_exporters column and union,
+ # deduped. Keys have no assignment column: they inherit from team/org, so a
+ # key-level value (patched below) must contribute nothing.
from litellm.proxy.litellm_pre_call_utils import _resolve_logging_exporters
_patch_identity(monkeypatch, key=["arize-prod"], team=["langfuse-eu"], org=["langfuse-eu"])
@@ -5245,9 +5246,8 @@ async def test_resolve_logging_exporters_unions_key_team_org(
assert {d["endpoint"] for d in destinations} == {
"https://cloud.langfuse.com/api/public/otel", # team + org (deduped)
- "https://otlp.arize.com/v1", # key
}
- assert set(backends) == {"langfuse_otel", "arize"}
+ assert set(backends) == {"langfuse_otel"}
@pytest.mark.asyncio
diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx
index 7a376cc1b4f..468e6f10a9f 100644
--- a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx
@@ -36,8 +36,6 @@ import { fetchTeamModels } from "../organisms/create_key_button";
import NumericalInput from "../shared/numerical_input";
import { Tag } from "../tag_management/types";
import EditLoggingSettings from "../team/EditLoggingSettings";
-import { LoggingExportersFormItem } from "../logging_credentials/LoggingExportersSelect";
-import { loggingExportersOf } from "../logging_credentials/loggingExportersOf";
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
interface KeyEditViewProps {
@@ -210,7 +208,6 @@ export function KeyEditView({
accessGroups: keyData.object_permission?.agent_access_groups || [],
},
logging_settings: extractLoggingSettings(keyData.metadata),
- logging_exporters: loggingExportersOf(keyData),
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
: [],
@@ -242,8 +239,7 @@ export function KeyEditView({
mcp_tool_permissions: keyData.object_permission?.mcp_tool_permissions || {},
throttle_on_budget_exceeded: keyData.metadata?.throttle_on_budget_exceeded || false,
logging_settings: extractLoggingSettings(keyData.metadata),
- logging_exporters: loggingExportersOf(keyData),
- disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
+ disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
: [],
access_group_ids: keyData.access_group_ids || [],
@@ -834,7 +830,6 @@ export function KeyEditView({
)}
-