BerriAI/litellm · error · ValueError

Invalid identifier : contains disallowed characters

Error message

Invalid identifier {identifier!r}: contains disallowed characters

What it means

ArizePhoenixClient sanitizes every identifier (prompt_id, prompt_version_id) before placing it into a request URL path. `_sanitize_id` rejects identifiers containing '/', '\\', '#', or '?' because those characters would alter the URL path structure or query, then URL-encodes everything else. This is a guard against malformed or injected identifiers.

Solutions

  1. Use the bare prompt/prompt-version identifier with no '/', '\\', '#', '?' characters (e.g. 'UHJvbXB0OjE' style ID from the Phoenix UI)
  2. If the ID came from a URL, strip everything except the last path segment and any query/fragment before passing it
  3. Validate user-provided prompt IDs at your API boundary before they reach litellm's prompt manager

Example fix

# before
prompt_id = "https://app.phoenix.arize.com/prompts/UHJvbXB0OjE"  # contains '/' -> ValueError

# after
prompt_id = "UHJvbXB0OjE"  # bare identifier only
Defensive patterns

Strategy: type-guard

Validate before calling

import re

def safe_prompt_id(identifier: str) -> str:
    if not re.fullmatch(r"[A-Za-z0-9_\-.]+", identifier):
        raise ValueError(f"prompt_id {identifier!r} contains path/query characters")
    return identifier

prompt_id = safe_prompt_id(user_supplied_prompt_id)

Type guard

def is_sanitizable_identifier(identifier: str) -> bool:
    return isinstance(identifier, str) and not any(c in identifier for c in ("/", "\\", "#", "?")) and ".." not in identifier

Try / catch

try:
    client.get_prompt_version(prompt_version_id)
except ValueError as e:
    if "disallowed characters" in str(e):
        # strip to last path segment and retry once with the bare ID
        bare = prompt_version_id.rstrip("/#?").rsplit("/", 1)[-1]
        if bare and is_sanitizable_identifier(bare):
            return client.get_prompt_version(bare)
    raise

Prevention

When it happens

Trigger: Calling prompt operations with an identifier that contains a path separator or query character, e.g. prompt_id='workspace/prompts/123', 'my\\prompt', 'prompt#v2', or 'prompt?version=1'. The client is constructed via the Arize Phoenix prompt integration when loading or rendering that prompt.

Common situations: Pasting a full Phoenix URL or a prompt 'slug path' as the prompt_id instead of the bare ID; copy-pasting IDs with trailing URL fragments; user-supplied prompt IDs from an API request being passed through unvalidated.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/b47626248f053f8d. Report an issue: GitHub.

Appendix: source

Thrown at litellm/integrations/arize/arize_phoenix_client.py:14

"""
Arize Phoenix API client for fetching prompt versions from Arize Phoenix.
"""

import urllib.parse
from typing import Any, Final

from litellm.llms.custom_httpx.http_handler import HTTPHandler


def _sanitize_id(identifier: str) -> str:
    """Reject path traversal characters and URL-encode the identifier."""
    if any(c in identifier for c in ("/", "\\", "#", "?")):
        raise ValueError(f"Invalid identifier {identifier!r}: contains disallowed characters")
    if ".." in identifier:
        raise ValueError(f"Invalid identifier {identifier!r}: path traversal detected")
    return urllib.parse.quote(identifier, safe="")


class ArizePhoenixClient:
    """
    Client for interacting with Arize Phoenix API to fetch prompt versions.

    Supports:
    - Authentication with Bearer tokens
    - Fetching prompt versions
    - Direct API base URL configuration
    """

    def __init__(self, api_key: str | None = None, api_base: str | None = None):
        """
        Initialize the Arize Phoenix client.

View on GitHub (pinned to 77b7c6c40c)