Skip to content

Exceptions

All library exceptions inherit from MixpanelHeadlessError, enabling callers to catch all library errors with a single except clause.

Explore on DeepWiki

🤖 Error Handling Guide →

Ask questions about specific exceptions, error recovery patterns, or debugging strategies.

Exception Hierarchy

MixpanelHeadlessError
├── ConfigError
│   ├── AccountNotFoundError
│   ├── AccountExistsError
│   ├── AccountInUseError
│   ├── InvalidArgumentError
│   └── ProjectNotFoundError
├── APIError
│   ├── AuthenticationError
│   ├── RateLimitError
│   ├── QueryError
│   └── ServerError
├── OAuthError
│   └── RegionProbeError
│       └── RegionProbeNetworkError
├── WorkspaceScopeError
└── BusinessContextValidationError

Catching Errors

import mixpanel_headless as mp

try:
    ws = mp.Workspace()
    result = ws.segmentation(event="Purchase", from_date="2025-01-01", to_date="2025-01-31")
except mp.AuthenticationError as e:
    print(f"Auth failed: {e.message}")
except mp.RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after}s")
except mp.OAuthError as e:
    print(f"OAuth error [{e.code}]: {e.message}")
except mp.WorkspaceScopeError as e:
    print(f"Workspace error [{e.code}]: {e.message}")
except mp.AccountInUseError as e:
    print(f"Account '{e.account_name}' referenced by targets: {e.referenced_by}")
except mp.MixpanelHeadlessError as e:
    print(f"Error [{e.code}]: {e.message}")

Base Exception

mixpanel_headless.MixpanelHeadlessError

MixpanelHeadlessError(
    message: str,
    code: str = "UNKNOWN_ERROR",
    details: dict[str, Any] | None = None,
)

Bases: Exception

Base exception for all mixpanel_headless errors.

All library exceptions inherit from this class, allowing callers to: - Catch all library errors: except MixpanelHeadlessError - Handle specific errors: except AccountNotFoundError - Serialize errors: error.to_dict()

Initialize exception.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

code

Machine-readable error code for programmatic handling.

TYPE: str DEFAULT: 'UNKNOWN_ERROR'

details

Additional structured data about the error.

TYPE: dict[str, Any] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    code: str = "UNKNOWN_ERROR",
    details: dict[str, Any] | None = None,
) -> None:
    """Initialize exception.

    Args:
        message: Human-readable error message.
        code: Machine-readable error code for programmatic handling.
        details: Additional structured data about the error.
    """
    super().__init__(message)
    self._message = message
    self._code = code
    self._details = details or {}

code property

code: str

Machine-readable error code.

message property

message: str

Human-readable error message.

details property

details: dict[str, Any]

Additional structured error data.

to_dict

to_dict() -> dict[str, Any]

Serialize exception for logging/JSON output.

RETURNS DESCRIPTION
dict[str, Any]

Dictionary with keys: code, message, details.

dict[str, Any]

All values are JSON-serializable.

Source code in src/mixpanel_headless/exceptions.py
def to_dict(self) -> dict[str, Any]:
    """Serialize exception for logging/JSON output.

    Returns:
        Dictionary with keys: code, message, details.
        All values are JSON-serializable.
    """
    return {
        "code": self._code,
        "message": self._message,
        "details": self._details,
    }

__str__

__str__() -> str

Return human-readable error message.

Source code in src/mixpanel_headless/exceptions.py
def __str__(self) -> str:
    """Return human-readable error message."""
    return self._message

__repr__

__repr__() -> str

Return detailed string representation.

Source code in src/mixpanel_headless/exceptions.py
def __repr__(self) -> str:
    """Return detailed string representation."""
    return (
        f"{self.__class__.__name__}(message={self._message!r}, code={self._code!r})"
    )

API Exceptions

mixpanel_headless.APIError

APIError(
    message: str,
    *,
    status_code: int,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str = "API_ERROR",
)

Bases: MixpanelHeadlessError

Base class for Mixpanel API HTTP errors.

Provides structured access to HTTP request/response context for debugging and automated recovery by AI agents. All API-related exceptions inherit from this class, enabling agents to:

  • Understand what went wrong (status code, error message)
  • See exactly what was sent (request method, URL, params, body)
  • See exactly what came back (response body, headers)
  • Modify their approach and retry autonomously
Example
try:
    result = client.segmentation(event="signup", ...)
except APIError as e:
    print(f"Status: {e.status_code}")
    print(f"Response: {e.response_body}")
    print(f"Request URL: {e.request_url}")
    print(f"Request params: {e.request_params}")

Initialize APIError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

status_code

HTTP status code from response.

TYPE: int

response_body

Raw response body (string or parsed dict).

TYPE: str | dict[str, Any] | None DEFAULT: None

request_method

HTTP method used (GET, POST).

TYPE: str | None DEFAULT: None

request_url

Full request URL.

TYPE: str | None DEFAULT: None

request_params

Query parameters sent.

TYPE: dict[str, Any] | None DEFAULT: None

request_body

Request body sent (for POST requests).

TYPE: dict[str, Any] | None DEFAULT: None

code

Machine-readable error code.

TYPE: str DEFAULT: 'API_ERROR'

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    *,
    status_code: int,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str = "API_ERROR",
) -> None:
    """Initialize APIError.

    Args:
        message: Human-readable error message.
        status_code: HTTP status code from response.
        response_body: Raw response body (string or parsed dict).
        request_method: HTTP method used (GET, POST).
        request_url: Full request URL.
        request_params: Query parameters sent.
        request_body: Request body sent (for POST requests).
        code: Machine-readable error code.
    """
    self._status_code = status_code
    self._response_body = response_body
    self._request_method = request_method
    self._request_url = request_url
    self._request_params = request_params
    self._request_body = request_body

    details: dict[str, Any] = {
        "status_code": status_code,
    }
    if response_body is not None:
        details["response_body"] = response_body
    if request_method is not None:
        details["request_method"] = request_method
    if request_url is not None:
        details["request_url"] = request_url
    if request_params is not None:
        details["request_params"] = request_params
    if request_body is not None:
        details["request_body"] = request_body

    super().__init__(message, code=code, details=details)

status_code property

status_code: int

HTTP status code from response.

response_body property

response_body: str | dict[str, Any] | None

Raw response body (string or parsed dict).

request_method property

request_method: str | None

HTTP method used (GET, POST).

request_url property

request_url: str | None

Full request URL.

request_params property

request_params: dict[str, Any] | None

Query parameters sent.

request_body property

request_body: dict[str, Any] | None

Request body sent (for POST requests).

mixpanel_headless.AuthenticationError

AuthenticationError(
    message: str = "Authentication failed",
    *,
    status_code: int = 401,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
)

Bases: APIError

Authentication with Mixpanel API failed (HTTP 401).

Raised when credentials are invalid, expired, or lack required permissions. Inherits from APIError to provide full request/response context.

Example
try:
    client.segmentation(...)
except AuthenticationError as e:
    print(f"Auth failed: {e.message}")
    print(f"Request URL: {e.request_url}")
    # Check if project_id is correct, credentials are valid, etc.

Initialize AuthenticationError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str DEFAULT: 'Authentication failed'

status_code

HTTP status code (default 401).

TYPE: int DEFAULT: 401

response_body

Raw response body.

TYPE: str | dict[str, Any] | None DEFAULT: None

request_method

HTTP method used.

TYPE: str | None DEFAULT: None

request_url

Full request URL.

TYPE: str | None DEFAULT: None

request_params

Query parameters sent.

TYPE: dict[str, Any] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str = "Authentication failed",
    *,
    status_code: int = 401,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
) -> None:
    """Initialize AuthenticationError.

    Args:
        message: Human-readable error message.
        status_code: HTTP status code (default 401).
        response_body: Raw response body.
        request_method: HTTP method used.
        request_url: Full request URL.
        request_params: Query parameters sent.
    """
    super().__init__(
        message,
        status_code=status_code,
        response_body=response_body,
        request_method=request_method,
        request_url=request_url,
        request_params=request_params,
        code="AUTH_FAILED",
    )

mixpanel_headless.RateLimitError

RateLimitError(
    message: str = "Rate limit exceeded",
    *,
    retry_after: int | None = None,
    status_code: int = 429,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    project_id: str | None = None,
)

Bases: APIError

Mixpanel API rate limit exceeded (HTTP 429).

Raised when the API returns a 429 status. The retry_after property indicates when the request can be retried. Inherits from APIError to provide full request context for debugging.

Example
try:
    for _ in range(1000):
        client.segmentation(...)
except RateLimitError as e:
    print(f"Rate limited! Retry after {e.retry_after}s")
    print(f"Request: {e.request_method} {e.request_url}")
    print(f"Request a higher limit: {e.rate_limit_form_url}")
    time.sleep(e.retry_after or 60)

Initialize RateLimitError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str DEFAULT: 'Rate limit exceeded'

retry_after

Seconds until retry is allowed (from Retry-After header).

TYPE: int | None DEFAULT: None

status_code

HTTP status code (default 429).

TYPE: int DEFAULT: 429

response_body

Raw response body.

TYPE: str | dict[str, Any] | None DEFAULT: None

request_method

HTTP method used.

TYPE: str | None DEFAULT: None

request_url

Full request URL.

TYPE: str | None DEFAULT: None

request_params

Query parameters sent.

TYPE: dict[str, Any] | None DEFAULT: None

project_id

Mixpanel project id active when the limit was hit, used to prefill the rate-limit-increase request form. None when unknown.

TYPE: str | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str = "Rate limit exceeded",
    *,
    retry_after: int | None = None,
    status_code: int = 429,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    project_id: str | None = None,
) -> None:
    """Initialize RateLimitError.

    Args:
        message: Human-readable error message.
        retry_after: Seconds until retry is allowed (from Retry-After header).
        status_code: HTTP status code (default 429).
        response_body: Raw response body.
        request_method: HTTP method used.
        request_url: Full request URL.
        request_params: Query parameters sent.
        project_id: Mixpanel project id active when the limit was hit, used
            to prefill the rate-limit-increase request form. ``None`` when
            unknown.
    """
    self._retry_after = retry_after
    self._project_id = project_id
    if retry_after is not None:
        message = f"{message}. Retry after {retry_after} seconds."

    super().__init__(
        message,
        status_code=status_code,
        response_body=response_body,
        request_method=request_method,
        request_url=request_url,
        request_params=request_params,
        code="RATE_LIMITED",
    )
    # Add retry_after to details
    if retry_after is not None:
        self._details["retry_after"] = retry_after
    # Add project_id to details so JSON consumers (to_dict) can attribute it.
    if project_id is not None:
        self._details["project_id"] = project_id

retry_after property

retry_after: int | None

Seconds until retry is allowed, or None if unknown.

project_id property

project_id: str | None

Mixpanel project id active when the rate limit was hit, if known.

rate_limit_form_url property

rate_limit_form_url: str

URL to request a rate-limit increase.

Returns the project-prefilled Google Form URL when the project id is known (so the lead is attributed to a project), otherwise the short form link. Handy for surfacing in scripts or notebooks that catch this error.

RETURNS DESCRIPTION
str

The rate-limit-increase request form URL.

mixpanel_headless.QueryError

QueryError(
    message: str = "Query execution failed",
    *,
    status_code: int = 400,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
)

Bases: APIError

Query execution failed (HTTP 400 or query-specific error).

Raised when an API query fails due to invalid parameters, syntax errors, or other query-specific issues. Inherits from APIError to provide full request/response context for debugging.

Example
try:
    client.segmentation(event="nonexistent", ...)
except QueryError as e:
    print(f"Query failed: {e.message}")
    print(f"Response: {e.response_body}")
    print(f"Request params: {e.request_params}")

Initialize QueryError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str DEFAULT: 'Query execution failed'

status_code

HTTP status code (default 400).

TYPE: int DEFAULT: 400

response_body

Raw response body with error details.

TYPE: str | dict[str, Any] | None DEFAULT: None

request_method

HTTP method used.

TYPE: str | None DEFAULT: None

request_url

Full request URL.

TYPE: str | None DEFAULT: None

request_params

Query parameters sent.

TYPE: dict[str, Any] | None DEFAULT: None

request_body

Request body sent (for POST).

TYPE: dict[str, Any] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str = "Query execution failed",
    *,
    status_code: int = 400,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
) -> None:
    """Initialize QueryError.

    Args:
        message: Human-readable error message.
        status_code: HTTP status code (default 400).
        response_body: Raw response body with error details.
        request_method: HTTP method used.
        request_url: Full request URL.
        request_params: Query parameters sent.
        request_body: Request body sent (for POST).
    """
    super().__init__(
        message,
        status_code=status_code,
        response_body=response_body,
        request_method=request_method,
        request_url=request_url,
        request_params=request_params,
        request_body=request_body,
        code="QUERY_FAILED",
    )

mixpanel_headless.ServerError

ServerError(
    message: str = "Server error",
    *,
    status_code: int = 500,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
)

Bases: APIError

Mixpanel server error (HTTP 5xx).

Raised when the Mixpanel API returns a server error. These are typically transient issues that may succeed on retry. The response_body property contains the full error details from Mixpanel, which often include actionable information (e.g., "unit and interval both specified").

Example
try:
    client.retention(born_event="signup", ...)
except ServerError as e:
    print(f"Server error {e.status_code}: {e.message}")
    print(f"Response: {e.response_body}")
    print(f"Request params: {e.request_params}")
    # AI agent can analyze response_body to fix the request

Initialize ServerError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str DEFAULT: 'Server error'

status_code

HTTP status code (5xx).

TYPE: int DEFAULT: 500

response_body

Raw response body with error details.

TYPE: str | dict[str, Any] | None DEFAULT: None

request_method

HTTP method used.

TYPE: str | None DEFAULT: None

request_url

Full request URL.

TYPE: str | None DEFAULT: None

request_params

Query parameters sent.

TYPE: dict[str, Any] | None DEFAULT: None

request_body

Request body sent (for POST).

TYPE: dict[str, Any] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str = "Server error",
    *,
    status_code: int = 500,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
) -> None:
    """Initialize ServerError.

    Args:
        message: Human-readable error message.
        status_code: HTTP status code (5xx).
        response_body: Raw response body with error details.
        request_method: HTTP method used.
        request_url: Full request URL.
        request_params: Query parameters sent.
        request_body: Request body sent (for POST).
    """
    super().__init__(
        message,
        status_code=status_code,
        response_body=response_body,
        request_method=request_method,
        request_url=request_url,
        request_params=request_params,
        request_body=request_body,
        code="SERVER_ERROR",
    )

Configuration Exceptions

mixpanel_headless.ConfigError

ConfigError(message: str, details: dict[str, Any] | None = None)

Bases: MixpanelHeadlessError

Base for configuration-related errors.

Raised when there's a problem with configuration files, environment variables, or credential resolution.

Initialize ConfigError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

details

Additional structured data.

TYPE: dict[str, Any] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    details: dict[str, Any] | None = None,
) -> None:
    """Initialize ConfigError.

    Args:
        message: Human-readable error message.
        details: Additional structured data.
    """
    super().__init__(message, code="CONFIG_ERROR", details=details)

mixpanel_headless.AccountNotFoundError

AccountNotFoundError(
    account_name: str, available_accounts: list[str] | None = None
)

Bases: ConfigError

Named account does not exist in configuration.

Raised when attempting to access an account that hasn't been configured. The available_accounts property lists valid account names to help users.

Initialize AccountNotFoundError.

PARAMETER DESCRIPTION
account_name

The requested account name that wasn't found.

TYPE: str

available_accounts

List of valid account names for suggestions.

TYPE: list[str] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    account_name: str,
    available_accounts: list[str] | None = None,
) -> None:
    """Initialize AccountNotFoundError.

    Args:
        account_name: The requested account name that wasn't found.
        available_accounts: List of valid account names for suggestions.
    """
    available = available_accounts or []
    if available:
        available_str = ", ".join(f"'{a}'" for a in available)
        message = (
            f"Account '{account_name}' not found. "
            f"Available accounts: {available_str}"
        )
    else:
        message = f"Account '{account_name}' not found. No accounts configured."

    details = {
        "account_name": account_name,
        "available_accounts": available,
    }
    super().__init__(message, details=details)
    self._code = "ACCOUNT_NOT_FOUND"

account_name property

account_name: str

The requested account name that wasn't found.

available_accounts property

available_accounts: list[str]

List of valid account names.

mixpanel_headless.AccountExistsError

AccountExistsError(account_name: str)

Bases: ConfigError

Account name already exists in configuration.

Raised when attempting to add an account with a name that's already in use.

Initialize AccountExistsError.

PARAMETER DESCRIPTION
account_name

The conflicting account name.

TYPE: str

Source code in src/mixpanel_headless/exceptions.py
def __init__(self, account_name: str) -> None:
    """Initialize AccountExistsError.

    Args:
        account_name: The conflicting account name.
    """
    message = f"Account '{account_name}' already exists."
    details = {"account_name": account_name}
    super().__init__(message, details=details)
    self._code = "ACCOUNT_EXISTS"

account_name property

account_name: str

The conflicting account name.

mixpanel_headless.AccountInUseError

AccountInUseError(account_name: str, referenced_by: list[str] | None = None)

Bases: ConfigError

Account is referenced by one or more targets and cannot be removed.

Raised by mp.accounts.remove(name) when the account is referenced by one or more [targets.NAME] blocks and the caller did not pass force=True. The list of dependent target names is available in referenced_by so callers can show a helpful error message or pass force=True to delete the account and orphan the targets.

Initialize AccountInUseError.

PARAMETER DESCRIPTION
account_name

The account that callers tried to remove.

TYPE: str

referenced_by

Names of targets that reference the account.

TYPE: list[str] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self, account_name: str, referenced_by: list[str] | None = None
) -> None:
    """Initialize AccountInUseError.

    Args:
        account_name: The account that callers tried to remove.
        referenced_by: Names of targets that reference the account.
    """
    targets = referenced_by or []
    if targets:
        target_str = ", ".join(f"'{t}'" for t in targets)
        message = (
            f"Account '{account_name}' is referenced by target(s): {target_str}. "
            f"Pass `force=True` to remove anyway."
        )
    else:
        message = (
            f"Account '{account_name}' is in use. Pass `force=True` to remove."
        )

    details: dict[str, Any] = {
        "account_name": account_name,
        "referenced_by": list(targets),
    }
    super().__init__(message, details=details)
    self._code = "ACCOUNT_IN_USE"

account_name property

account_name: str

The account name that callers tried to remove.

referenced_by property

referenced_by: list[str]

Target names that reference the account.

mixpanel_headless.ProjectNotFoundError

ProjectNotFoundError(
    project_id: str, available_projects: list[str] | None = None
)

Bases: ConfigError

Raised when a specified project is not accessible.

Includes the requested project ID and optionally a list of accessible project IDs to help the user correct their selection.

Example
try:
    projects = ws.projects()
    match = [p for p in projects if p.id == target_id]
    if not match:
        raise ProjectNotFoundError(
            target_id,
            available_projects=[p.id for p in projects],
        )
except ProjectNotFoundError as e:
    print(f"Project '{e.project_id}' not found.")
    if e.available_projects:
        print(f"Available: {', '.join(e.available_projects)}")

Initialize ProjectNotFoundError.

PARAMETER DESCRIPTION
project_id

The requested project ID that wasn't found.

TYPE: str

available_projects

List of accessible project IDs for suggestions.

TYPE: list[str] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    project_id: str,
    available_projects: list[str] | None = None,
) -> None:
    """Initialize ProjectNotFoundError.

    Args:
        project_id: The requested project ID that wasn't found.
        available_projects: List of accessible project IDs for suggestions.
    """
    available = available_projects or []
    if available:
        available_str = ", ".join(f"'{p}'" for p in available)
        message = (
            f"Project '{project_id}' not found. Available projects: {available_str}"
        )
    else:
        message = (
            f"Project '{project_id}' not found. No accessible projects discovered."
        )

    details: dict[str, Any] = {
        "project_id": project_id,
        "available_projects": available,
    }
    super().__init__(message, details=details)
    self._code = "PROJECT_NOT_FOUND"

project_id property

project_id: str

The requested project ID that wasn't found.

available_projects property

available_projects: list[str]

List of accessible project IDs.

InvalidArgumentError

Raised by accounts.login_unified (and the CLI's mp login) when a public-API call combines mutually incompatible arguments. Subclass of ConfigError. The CLI maps this to exit code 3 (INVALID_ARGS) instead of the generic 1.

violation Raised When
mutually_exclusive --service-account + --token-env (or equivalent kwargs)
no_browser_misuse --no-browser against a non-browser auth type
secret_stdin_misuse --secret-stdin against a non-SA auth type

The details dict carries violation and (when detection ran) detected_auth_type. Pattern-match by class so non-CLI callers (Cowork's auth_manager.py, JSON consumers) can dispatch without parsing the human message.

mixpanel_headless.InvalidArgumentError

InvalidArgumentError(
    message: str,
    *,
    violation: Literal[
        "mutually_exclusive", "no_browser_misuse", "secret_stdin_misuse"
    ],
    detected_auth_type: str | None = None,
)

Bases: ConfigError

Raised when a public API call combines mutually incompatible arguments.

Carries a violation discriminator and the resolved detected_auth_type so non-CLI callers (Cowork's auth_manager.py, JSON consumers) can dispatch programmatically without parsing the human message. The CLI handle_errors decorator maps this subclass to ExitCode.INVALID_ARGS (3) instead of the generic GENERAL_ERROR (1) that ConfigError would otherwise produce.

Used by accounts.login_unified for the three documented flag-combination rejections (043 contract, cli-commands.md §5): --service-account + --token-env, --no-browser against a non-browser auth type, and --secret-stdin against a non-SA auth type.

Example
try:
    accounts.login_unified(service_account=True, token_env="X")
except InvalidArgumentError as exc:
    assert exc.violation == "mutually_exclusive"
    assert exc.detected_auth_type == "service_account"

Initialize InvalidArgumentError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

violation

Discriminator for the kind of misuse. One of "mutually_exclusive", "no_browser_misuse", "secret_stdin_misuse".

TYPE: Literal['mutually_exclusive', 'no_browser_misuse', 'secret_stdin_misuse']

detected_auth_type

The auth type the orchestrator resolved from the supplied flags / env. None only when the violation was caught BEFORE detection ran (currently no such case, but kept optional for future-proofing).

TYPE: str | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    *,
    violation: Literal[
        "mutually_exclusive", "no_browser_misuse", "secret_stdin_misuse"
    ],
    detected_auth_type: str | None = None,
) -> None:
    """Initialize InvalidArgumentError.

    Args:
        message: Human-readable error message.
        violation: Discriminator for the kind of misuse. One of
            ``"mutually_exclusive"``, ``"no_browser_misuse"``,
            ``"secret_stdin_misuse"``.
        detected_auth_type: The auth type the orchestrator resolved
            from the supplied flags / env. ``None`` only when the
            violation was caught BEFORE detection ran (currently
            no such case, but kept optional for future-proofing).
    """
    if violation not in self._VALID_VIOLATIONS:
        raise ValueError(
            f"Invalid violation {violation!r}; must be one of "
            f"{self._VALID_VIOLATIONS}."
        )
    details: dict[str, Any] = {"violation": violation}
    if detected_auth_type is not None:
        details["detected_auth_type"] = detected_auth_type
    super().__init__(message, details=details)
    self._code = "INVALID_ARGUMENT"

violation property

violation: str

The kind of misuse — see _VALID_VIOLATIONS.

detected_auth_type property

detected_auth_type: str | None

The auth type the orchestrator resolved (or None if pre-detection).

OAuth Exceptions

Raised during OAuth 2.0 PKCE authentication flows and the mp login region probe.

Error Code Raised When
OAUTH_TOKEN_ERROR Token exchange fails
OAUTH_REFRESH_ERROR Token refresh fails (transient)
OAUTH_REFRESH_REVOKED Refresh token rejected by IdP as invalid_grant (re-run mp login --name NAME)
OAUTH_REGISTRATION_ERROR Dynamic client registration fails
OAUTH_TIMEOUT Callback server times out waiting for authorization
OAUTH_PORT_ERROR Cannot bind to a local port for the callback server
OAUTH_BROWSER_ERROR Cannot open the authorization URL in the browser
OAUTH_REGION_PROBE_FAILED mp login probed every region and none accepted the credential — see RegionProbeError below
OAUTH_NETWORK_UNREACHABLE Every region probe failed at the network layer (DNS / TLS / connect refused) — see RegionProbeNetworkError below

mixpanel_headless.OAuthError

OAuthError(
    message: str,
    code: str = "OAUTH_TOKEN_ERROR",
    details: dict[str, Any] | None = None,
)

Bases: MixpanelHeadlessError

OAuth authentication flow error.

Raised for failures during the OAuth 2.0 PKCE flow, including token exchange, token refresh, client registration, callback timeout, port unavailability, and browser launch failures.

Error codes: - OAUTH_TOKEN_ERROR: Token exchange or validation failed - OAUTH_REFRESH_ERROR: Token refresh failed - OAUTH_REGISTRATION_ERROR: Dynamic Client Registration failed - OAUTH_TIMEOUT: Callback server timed out waiting for authorization - OAUTH_PORT_ERROR: All callback ports are occupied - OAUTH_BROWSER_ERROR: Could not open browser for authorization

Example
try:
    flow = OAuthFlow(region="us")
    tokens = flow.login()
except OAuthError as e:
    print(f"OAuth failed: {e.message} (code: {e.code})")

Initialize OAuthError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

code

Machine-readable error code. One of: OAUTH_TOKEN_ERROR, OAUTH_REFRESH_ERROR, OAUTH_REGISTRATION_ERROR, OAUTH_TIMEOUT, OAUTH_PORT_ERROR, OAUTH_BROWSER_ERROR.

TYPE: str DEFAULT: 'OAUTH_TOKEN_ERROR'

details

Additional structured data about the error.

TYPE: dict[str, Any] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    code: str = "OAUTH_TOKEN_ERROR",
    details: dict[str, Any] | None = None,
) -> None:
    """Initialize OAuthError.

    Args:
        message: Human-readable error message.
        code: Machine-readable error code. One of: OAUTH_TOKEN_ERROR,
            OAUTH_REFRESH_ERROR, OAUTH_REGISTRATION_ERROR, OAUTH_TIMEOUT,
            OAUTH_PORT_ERROR, OAUTH_BROWSER_ERROR.
        details: Additional structured data about the error.
    """
    super().__init__(message, code=code, details=details)

RegionProbeError

Raised by mp login (and accounts.login_unified) when the us → eu → in region probe fails for every region. Subclass of OAuthError. The attempts attribute carries the full (region, status_code, error_body) list; status 0 indicates a network-layer failure (DNS / TLS / connect refused) — those cases raise RegionProbeNetworkError (subclass) so the CLI can render a different remediation hint.

import mixpanel_headless as mp

try:
    mp.accounts.login_unified()
except mp.RegionProbeNetworkError as exc:
    print("Could not reach any Mixpanel region. Check connectivity.")
    for region, status, body in exc.attempts:
        print(f"  {region}: {body}")
except mp.RegionProbeError as exc:
    print("Credential not valid in any region.")
    for region, status, body in exc.attempts:
        print(f"  {region}: {status} {body}")

mixpanel_headless.RegionProbeError

RegionProbeError(
    message: str,
    *,
    attempts: list[tuple[Region, int, str]],
    code: str = "OAUTH_REGION_PROBE_FAILED",
)

Bases: OAuthError

Raised when no region accepts the credential during region probing.

The region probe walks a configured order (default useuin) against /api/app/me, returning the first 200. When every probe attempt fails, this exception is raised carrying the full attempt list for diagnostic and telemetry use.

A status code of 0 indicates the request never reached the server (network error); the third tuple element carries the failure detail (HTTP response text or the network error reason).

See :class:RegionProbeNetworkError for the all-network-error subclass — the probe distinguishes "credential rejected" from "could not reach any region" so the CLI can render different remediation hints.

Example
try:
    result = probe_region(client_factory, headers)
except RegionProbeError as exc:
    for region, status, body in exc.attempts:
        print(f"{region}: {status} {body}")

Initialize RegionProbeError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

attempts

Ordered list of (region, status_code, error_body) tuples for every probed region. status_code is 0 for network errors; error_body carries the failure detail.

TYPE: list[tuple[Region, int, str]]

code

Machine-readable error code. Defaults to OAUTH_REGION_PROBE_FAILED for the generic case; :class:RegionProbeNetworkError overrides to OAUTH_NETWORK_UNREACHABLE.

TYPE: str DEFAULT: 'OAUTH_REGION_PROBE_FAILED'

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    *,
    attempts: list[tuple[Region, int, str]],
    code: str = "OAUTH_REGION_PROBE_FAILED",
) -> None:
    """Initialize RegionProbeError.

    Args:
        message: Human-readable error message.
        attempts: Ordered list of ``(region, status_code, error_body)``
            tuples for every probed region. ``status_code`` is ``0``
            for network errors; ``error_body`` carries the failure
            detail.
        code: Machine-readable error code. Defaults to
            ``OAUTH_REGION_PROBE_FAILED`` for the generic case;
            :class:`RegionProbeNetworkError` overrides to
            ``OAUTH_NETWORK_UNREACHABLE``.
    """
    self._attempts: list[tuple[Region, int, str]] = list(attempts)
    super().__init__(
        message,
        code=code,
        details={"attempts": [list(a) for a in self._attempts]},
    )

attempts property

attempts: list[tuple[Region, int, str]]

Ordered list of (region, status_code, error_body) tuples.

to_dict

to_dict() -> dict[str, Any]

Serialize the exception to a JSON-friendly dict.

Includes attempts at the top level so consumers can inspect the per-region probe outcomes without unpacking details.

RETURNS DESCRIPTION
dict[str, Any]

Dictionary with keys code, message, details, and

dict[str, Any]

attempts. Each attempts entry is a 3-element list

dict[str, Any]

[region, status_code, error_body].

Source code in src/mixpanel_headless/exceptions.py
def to_dict(self) -> dict[str, Any]:
    """Serialize the exception to a JSON-friendly dict.

    Includes ``attempts`` at the top level so consumers can inspect
    the per-region probe outcomes without unpacking ``details``.

    Returns:
        Dictionary with keys ``code``, ``message``, ``details``, and
        ``attempts``. Each ``attempts`` entry is a 3-element list
        ``[region, status_code, error_body]``.
    """
    base = super().to_dict()
    base["attempts"] = [list(a) for a in self._attempts]
    return base

mixpanel_headless.RegionProbeNetworkError

RegionProbeNetworkError(
    message: str, *, attempts: list[tuple[Region, int, str]]
)

Bases: RegionProbeError

Raised when every region probe attempt failed at the network layer.

Subclass of :class:RegionProbeError used when ALL recorded attempts have status_code == 0 — i.e. the credential was never actually evaluated because no region was reachable (DNS failure, TLS rejection, captive portal, no internet). The CLI catches this before the generic RegionProbeError so it can render "could not reach any Mixpanel region" instead of "credential not valid", which would mislead a user who is actually offline.

Carries the same attempts shape as the parent so existing consumers can render the per-region detail without changes.

Initialize RegionProbeNetworkError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

attempts

Ordered list of (region, 0, error_body) tuples — every entry must have status 0 by construction (the probe loop only raises this subclass when that invariant holds).

TYPE: list[tuple[Region, int, str]]

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    *,
    attempts: list[tuple[Region, int, str]],
) -> None:
    """Initialize RegionProbeNetworkError.

    Args:
        message: Human-readable error message.
        attempts: Ordered list of ``(region, 0, error_body)``
            tuples — every entry must have status 0 by construction
            (the probe loop only raises this subclass when that
            invariant holds).
    """
    super().__init__(
        message,
        attempts=attempts,
        code="OAUTH_NETWORK_UNREACHABLE",
    )

Workspace / Organization Scope Exceptions

Raised when an auth-axis identifier (workspace or organization) cannot be resolved during App API requests.

Error Code Raised When
NO_WORKSPACES No workspaces found for the project
AMBIGUOUS_WORKSPACE Multiple workspaces found and none is marked as default
WORKSPACE_NOT_FOUND Specified workspace ID does not exist
ORGANIZATION_AMBIGUOUS An org-scoped business-context call could not auto-resolve the organization (active project absent from /me AND >1 accessible organization). details carries project_id and available_organizations. Pass organization_id=N explicitly to bypass auto-resolution.

mixpanel_headless.WorkspaceScopeError

WorkspaceScopeError(
    message: str,
    code: str = "NO_WORKSPACES",
    details: dict[str, Any] | None = None,
)

Bases: MixpanelHeadlessError

Scope resolution error (workspace or organization).

Raised when an auth-axis identifier cannot be resolved during App API requests. Originally introduced for workspace resolution; also raised when the organization ID for an org-scoped business-context call cannot be auto-derived from the cached /me response.

Error codes: - NO_WORKSPACES: Project has no accessible workspaces - AMBIGUOUS_WORKSPACE: Multiple workspaces, none default; must specify --workspace-id - WORKSPACE_NOT_FOUND: Explicit workspace ID doesn't match any workspace - ORGANIZATION_AMBIGUOUS: Cannot auto-resolve the organization for an org-scoped call (active project absent from /me AND >1 accessible organization). The details dict carries project_id and available_organizations. Pass organization_id=N explicitly to bypass auto-resolution.

Example
try:
    workspace_id = ws.resolve_workspace_id()
except WorkspaceScopeError as e:
    print(f"Scope issue: {e.message} (code: {e.code})")

Initialize WorkspaceScopeError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

code

Machine-readable error code. One of: NO_WORKSPACES, AMBIGUOUS_WORKSPACE, WORKSPACE_NOT_FOUND, ORGANIZATION_AMBIGUOUS.

TYPE: str DEFAULT: 'NO_WORKSPACES'

details

Additional structured data about the error.

TYPE: dict[str, Any] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    code: str = "NO_WORKSPACES",
    details: dict[str, Any] | None = None,
) -> None:
    """Initialize WorkspaceScopeError.

    Args:
        message: Human-readable error message.
        code: Machine-readable error code. One of: NO_WORKSPACES,
            AMBIGUOUS_WORKSPACE, WORKSPACE_NOT_FOUND,
            ORGANIZATION_AMBIGUOUS.
        details: Additional structured data about the error.
    """
    super().__init__(message, code=code, details=details)

Business Context Exceptions

Raised by Workspace.set_business_context() when content exceeds the 50,000-character cap. The check runs before the HTTP call, so callers fail fast and don't waste a round-trip; the server enforces the same limit and would otherwise return QueryError (HTTP 400). See the Business Context guide for usage.

Error Code Raised When
BUSINESS_CONTEXT_TOO_LONG len(content) > BUSINESS_CONTEXT_MAX_CHARS (50,000)

The details dict carries length (the actual content length) and max (the configured limit) for programmatic recovery.

mixpanel_headless.BusinessContextValidationError

BusinessContextValidationError(
    message: str, details: dict[str, Any] | None = None
)

Bases: MixpanelHeadlessError

Business context content failed client-side validation.

Raised by Workspace.set_business_context() when the supplied content exceeds BUSINESS_CONTEXT_MAX_CHARS (50,000 characters). The check runs before the HTTP call so callers can fail fast and avoid a wasted round-trip — the server enforces the same limit server-side and would otherwise return QueryError (HTTP 400).

The details dict carries length (the actual content length) and max (the configured limit) for programmatic recovery.

Example
try:
    ws.set_business_context("x" * 60_000, level="project")
except BusinessContextValidationError as e:
    print(f"Too long: {e.details['length']} > {e.details['max']}")

Initialize BusinessContextValidationError.

PARAMETER DESCRIPTION
message

Human-readable error message.

TYPE: str

details

Additional structured data — typically length and max.

TYPE: dict[str, Any] | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    details: dict[str, Any] | None = None,
) -> None:
    """Initialize BusinessContextValidationError.

    Args:
        message: Human-readable error message.
        details: Additional structured data — typically ``length``
            and ``max``.
    """
    super().__init__(
        message,
        code="BUSINESS_CONTEXT_TOO_LONG",
        details=details,
    )

Session Replay Exceptions

Raised by the session-replay surface (fetch_replay(), sign_replay(), stream_replay()). SessionReplayError is the base — catch it to handle any replay failure. See the Session Replay guide.

Exception Raised When
SessionReplayAccessError The project has the SESSION_RECORDING_SENSITIVE_DATA flag set and the caller lacks the sensitive_data_replay permission (HTTP 403). details carries project_id, flag, and permission_required.
SignedURLExpiredError A signed CDN URL expired mid-fetch (~5-minute TTL) and re-signing was disabled or also failed.
ReplayNotFoundError The replay's first CDN file returned 404 — it aged out of its retention window, was never recorded, or was deleted.

mixpanel_headless.SessionReplayError

SessionReplayError(
    message: str,
    *,
    details: dict[str, Any] | None = None,
    status_code: int | None = None,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str | None = None,
)

Bases: APIError

Base class for session-replay-specific failures.

Subclasses cover the three replay-specific failure modes: :class:SessionReplayAccessError (sensitive-data permission denied), :class:SignedURLExpiredError (5-minute signed-URL TTL elapsed), and :class:ReplayNotFoundError (CDN walker found no bytes for the replay). All carry the standard :class:APIError HTTP context (status_code, response_body, request_url, etc.) plus a replay-specific details dict that the subclass merges in on top (replay_id, project_id, flag, retention_days, …).

Catch this base class to handle any replay failure uniformly:

Example
try:
    replay = ws.fetch_replay("r-19221")
except SessionReplayError as exc:
    log.warning("replay fetch failed: %s", exc.to_dict())

Because :class:SessionReplayError is an :class:APIError, existing except APIError: handlers — including the CLI handle_errors decorator that maps HTTP failures to exit codes — continue to catch these without modification.

Initialize SessionReplayError.

PARAMETER DESCRIPTION
message

Human-readable error message; see error-messages.md for the catalog of stable wording per subclass.

TYPE: str

details

Replay-specific structured context (replay_id, project_id, flag, retention_days, cdn_url_prefix, signed_at, expired_at — whichever keys the subclass documents). Merged into the base :class:APIError details dict so consumers see both HTTP context and replay context in one place.

TYPE: dict[str, Any] | None DEFAULT: None

status_code

HTTP status that triggered the error. Defaults to the subclass's _DEFAULT_STATUS (403 for access / expiry, 404 for not-found, 500 for the base class).

TYPE: int | None DEFAULT: None

response_body

Raw response body for debugging.

TYPE: str | dict[str, Any] | None DEFAULT: None

request_method

HTTP method (GET, POST, …).

TYPE: str | None DEFAULT: None

request_url

Full request URL.

TYPE: str | None DEFAULT: None

request_params

Query parameters sent on the failing request.

TYPE: dict[str, Any] | None DEFAULT: None

request_body

Request body sent on the failing request.

TYPE: dict[str, Any] | None DEFAULT: None

code

Machine-readable error code. Defaults to the subclass's _DEFAULT_CODE.

TYPE: str | None DEFAULT: None

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    *,
    details: dict[str, Any] | None = None,
    status_code: int | None = None,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str | None = None,
) -> None:
    """Initialize SessionReplayError.

    Args:
        message: Human-readable error message; see error-messages.md for
            the catalog of stable wording per subclass.
        details: Replay-specific structured context (``replay_id``,
            ``project_id``, ``flag``, ``retention_days``,
            ``cdn_url_prefix``, ``signed_at``, ``expired_at`` —
            whichever keys the subclass documents). Merged into the
            base :class:`APIError` ``details`` dict so consumers see
            both HTTP context and replay context in one place.
        status_code: HTTP status that triggered the error. Defaults
            to the subclass's ``_DEFAULT_STATUS`` (403 for access /
            expiry, 404 for not-found, 500 for the base class).
        response_body: Raw response body for debugging.
        request_method: HTTP method (GET, POST, …).
        request_url: Full request URL.
        request_params: Query parameters sent on the failing request.
        request_body: Request body sent on the failing request.
        code: Machine-readable error code. Defaults to the subclass's
            ``_DEFAULT_CODE``.
    """
    super().__init__(
        message,
        status_code=status_code
        if status_code is not None
        else self._DEFAULT_STATUS,
        response_body=response_body,
        request_method=request_method,
        request_url=request_url,
        request_params=request_params,
        request_body=request_body,
        code=code if code is not None else self._DEFAULT_CODE,
    )
    if details:
        self._details.update(details)

mixpanel_headless.SessionReplayAccessError

SessionReplayAccessError(
    message: str,
    *,
    details: dict[str, Any] | None = None,
    status_code: int | None = None,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str | None = None,
)

Bases: SessionReplayError

Project has SESSION_RECORDING_SENSITIVE_DATA enabled and caller lacks access.

Raised when the bulk-sign endpoint returns 403 with a body that mentions the SESSION_RECORDING_SENSITIVE_DATA project flag. The project owner can grant the sensitive_data_replay permission to unblock the caller; a service account with that permission works too.

Details

project_id (int): The project that gated the call. flag (str): Always "SESSION_RECORDING_SENSITIVE_DATA". permission_required (str): Always "sensitive_data_replay".

See error-messages.md §1 for the canonical message wording.

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    *,
    details: dict[str, Any] | None = None,
    status_code: int | None = None,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str | None = None,
) -> None:
    """Initialize SessionReplayError.

    Args:
        message: Human-readable error message; see error-messages.md for
            the catalog of stable wording per subclass.
        details: Replay-specific structured context (``replay_id``,
            ``project_id``, ``flag``, ``retention_days``,
            ``cdn_url_prefix``, ``signed_at``, ``expired_at`` —
            whichever keys the subclass documents). Merged into the
            base :class:`APIError` ``details`` dict so consumers see
            both HTTP context and replay context in one place.
        status_code: HTTP status that triggered the error. Defaults
            to the subclass's ``_DEFAULT_STATUS`` (403 for access /
            expiry, 404 for not-found, 500 for the base class).
        response_body: Raw response body for debugging.
        request_method: HTTP method (GET, POST, …).
        request_url: Full request URL.
        request_params: Query parameters sent on the failing request.
        request_body: Request body sent on the failing request.
        code: Machine-readable error code. Defaults to the subclass's
            ``_DEFAULT_CODE``.
    """
    super().__init__(
        message,
        status_code=status_code
        if status_code is not None
        else self._DEFAULT_STATUS,
        response_body=response_body,
        request_method=request_method,
        request_url=request_url,
        request_params=request_params,
        request_body=request_body,
        code=code if code is not None else self._DEFAULT_CODE,
    )
    if details:
        self._details.update(details)

mixpanel_headless.SignedURLExpiredError

SignedURLExpiredError(
    message: str,
    *,
    details: dict[str, Any] | None = None,
    status_code: int | None = None,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str | None = None,
)

Bases: SessionReplayError

Signed CDN URL passed to a fetch has expired (5-minute TTL).

Raised when a CDN fetch returns 403 with an expiration body AND the caller opted out of automatic re-signing (stream_replay with re_sign_on_expiry=False). Re-sign via :meth:Workspace.sign_replay and retry, or pass re_sign_on_expiry=True (the default) to let the library re-sign transparently.

Details

replay_id (str): The replay whose URL expired. signed_at (float): Unix seconds when the original URL was signed. expired_at (float): Unix seconds when the URL expired (typically signed_at + 300).

See error-messages.md §2 for the canonical message wording.

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    *,
    details: dict[str, Any] | None = None,
    status_code: int | None = None,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str | None = None,
) -> None:
    """Initialize SessionReplayError.

    Args:
        message: Human-readable error message; see error-messages.md for
            the catalog of stable wording per subclass.
        details: Replay-specific structured context (``replay_id``,
            ``project_id``, ``flag``, ``retention_days``,
            ``cdn_url_prefix``, ``signed_at``, ``expired_at`` —
            whichever keys the subclass documents). Merged into the
            base :class:`APIError` ``details`` dict so consumers see
            both HTTP context and replay context in one place.
        status_code: HTTP status that triggered the error. Defaults
            to the subclass's ``_DEFAULT_STATUS`` (403 for access /
            expiry, 404 for not-found, 500 for the base class).
        response_body: Raw response body for debugging.
        request_method: HTTP method (GET, POST, …).
        request_url: Full request URL.
        request_params: Query parameters sent on the failing request.
        request_body: Request body sent on the failing request.
        code: Machine-readable error code. Defaults to the subclass's
            ``_DEFAULT_CODE``.
    """
    super().__init__(
        message,
        status_code=status_code
        if status_code is not None
        else self._DEFAULT_STATUS,
        response_body=response_body,
        request_method=request_method,
        request_url=request_url,
        request_params=request_params,
        request_body=request_body,
        code=code if code is not None else self._DEFAULT_CODE,
    )
    if details:
        self._details.update(details)

mixpanel_headless.ReplayNotFoundError

ReplayNotFoundError(
    message: str,
    *,
    details: dict[str, Any] | None = None,
    status_code: int | None = None,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str | None = None,
)

Bases: SessionReplayError

No CDN bytes found for a requested replay.

Raised when the CDN walker hits a 404 on the very first file (0000-N.json). The replay either aged out of its retention window, was never recorded, or has been deleted. Mid-walk 404s are treated as the end-of-replay sentinel and do NOT raise.

Details

replay_id (str): The replay that returned no bytes. retention_days (int): The retention window that was assumed (1, 7, 30, or 90). cdn_url_prefix (str): The CDN prefix that was walked.

See error-messages.md §3 for the canonical message wording.

Source code in src/mixpanel_headless/exceptions.py
def __init__(
    self,
    message: str,
    *,
    details: dict[str, Any] | None = None,
    status_code: int | None = None,
    response_body: str | dict[str, Any] | None = None,
    request_method: str | None = None,
    request_url: str | None = None,
    request_params: dict[str, Any] | None = None,
    request_body: dict[str, Any] | None = None,
    code: str | None = None,
) -> None:
    """Initialize SessionReplayError.

    Args:
        message: Human-readable error message; see error-messages.md for
            the catalog of stable wording per subclass.
        details: Replay-specific structured context (``replay_id``,
            ``project_id``, ``flag``, ``retention_days``,
            ``cdn_url_prefix``, ``signed_at``, ``expired_at`` —
            whichever keys the subclass documents). Merged into the
            base :class:`APIError` ``details`` dict so consumers see
            both HTTP context and replay context in one place.
        status_code: HTTP status that triggered the error. Defaults
            to the subclass's ``_DEFAULT_STATUS`` (403 for access /
            expiry, 404 for not-found, 500 for the base class).
        response_body: Raw response body for debugging.
        request_method: HTTP method (GET, POST, …).
        request_url: Full request URL.
        request_params: Query parameters sent on the failing request.
        request_body: Request body sent on the failing request.
        code: Machine-readable error code. Defaults to the subclass's
            ``_DEFAULT_CODE``.
    """
    super().__init__(
        message,
        status_code=status_code
        if status_code is not None
        else self._DEFAULT_STATUS,
        response_body=response_body,
        request_method=request_method,
        request_url=request_url,
        request_params=request_params,
        request_body=request_body,
        code=code if code is not None else self._DEFAULT_CODE,
    )
    if details:
        self._details.update(details)