Skip to content

Workspace

The Workspace class is the unified entry point for all Mixpanel data operations.

Explore on DeepWiki

🤖 Workspace Class Deep Dive →

Ask questions about Workspace methods, explore usage patterns, or understand how services are orchestrated.

Overview

Workspace orchestrates internal services and provides direct App API access:

  • DiscoveryService — Schema exploration (events, properties, funnels, cohorts)
  • LiveQueryService — Real-time analytics queries (legacy) and Insights engine queries
  • Streaming — Stream events and profiles directly from Mixpanel
  • Entity CRUD — Create, read, update, delete dashboards, reports, and cohorts via Mixpanel App API (workspace-scoped)
  • Feature Management — Create, read, update, delete feature flags and experiments via Mixpanel App API (project-scoped)
  • Operational Tooling — Manage alerts, annotations, and webhooks via Mixpanel App API (workspace-scoped)
  • Data Governance — Manage Lexicon definitions, drop filters, custom properties, custom events, lookup tables, schema registry, schema enforcement, data auditing, volume anomalies, and event deletion requests via Mixpanel App API (workspace-scoped)

Key Features

Entity CRUD

Manage dashboards, reports (bookmarks), and cohorts programmatically via the Mixpanel App API (workspace-scoped):

import mixpanel_data as mp

ws = mp.Workspace()

# Dashboards
dashboards = ws.list_dashboards()
new_dash = ws.create_dashboard(mp.CreateDashboardParams(title="Q1 Metrics"))
ws.update_dashboard(new_dash.id, mp.UpdateDashboardParams(title="Q1 Metrics v2"))
ws.favorite_dashboard(new_dash.id)

# Reports (Bookmarks)
reports = ws.list_bookmarks_v2()
report = ws.create_bookmark(mp.CreateBookmarkParams(
    name="Daily Signups",
    bookmark_type="insights"
))

# Cohorts
cohorts = ws.list_cohorts_full()
cohort = ws.create_cohort(mp.CreateCohortParams(name="Power Users"))
ws.update_cohort(cohort.id, mp.UpdateCohortParams(name="Super Users"))

Dashboard, report, and cohort operations require a workspace ID, set via MP_WORKSPACE_ID environment variable, --workspace / -w CLI flag, Workspace(workspace=N), or ws.use(workspace=N). List available workspaces with mp workspace list or ws.workspaces().

Feature Flags & Experiments

Manage feature flags and experiments programmatically. Unlike dashboards/reports/cohorts, these are project-scoped and do not require a workspace ID.

import mixpanel_data as mp

ws = mp.Workspace()

# Feature Flags
flags = ws.list_feature_flags()
flag = ws.create_feature_flag(mp.CreateFeatureFlagParams(
    name="Dark Mode", key="dark_mode"
))
ws.update_feature_flag(flag.id, mp.UpdateFeatureFlagParams(
    name="Dark Mode", key="dark_mode",
    status=mp.FeatureFlagStatus.ENABLED,
    ruleset=flag.ruleset,
))

# Experiments (full lifecycle)
exp = ws.create_experiment(mp.CreateExperimentParams(name="Checkout Flow Test"))
launched = ws.launch_experiment(exp.id)
concluded = ws.conclude_experiment(exp.id)
decided = ws.decide_experiment(exp.id, mp.ExperimentDecideParams(success=True))

Feature flag update uses PUT semantics (all required fields must be provided). Experiment update uses PATCH semantics (only changed fields needed). See the Entity Management guide for complete coverage.

Data Governance

Manage Lexicon definitions, drop filters, custom properties, custom events, and lookup tables programmatically. All operations are workspace-scoped.

import mixpanel_data as mp

ws = mp.Workspace()

# Lexicon — Event and property definitions
defs = ws.get_event_definitions(names=["Signup", "Login"])
ws.update_event_definition("Signup", mp.UpdateEventDefinitionParams(verified=True))
tags = ws.list_lexicon_tags()

# Drop filters
filters = ws.list_drop_filters()
ws.create_drop_filter(mp.CreateDropFilterParams(
    event_name="Debug Event", filters={"property": "env", "value": "test"},
))

# Custom properties
props = ws.list_custom_properties()
prop = ws.get_custom_property("abc123")

# Lookup tables
tables = ws.list_lookup_tables()
table = ws.upload_lookup_table(mp.UploadLookupTableParams(
    name="Countries", file_path="/path/to/countries.csv",
))

# Custom events
events = ws.list_custom_events()

See the Data Governance guide for complete coverage.

workspaces() vs list_workspaces()

Both methods are exposed. workspaces() (recommended) returns list[WorkspaceRef] from the cached /me response — fast, typed, and consistent with events() / properties() / funnels() / cohorts(). list_workspaces() is a lower-level escape hatch that calls GET /api/app/projects/{pid}/workspaces/public directly and returns list[PublicWorkspace].

In-Session Switching

Workspace.use() swaps the active account, project, workspace, or target without rebuilding the underlying httpx.Client or per-account /me cache. It returns self for fluent chaining, so cross-project iteration is O(1) per swap.

import mixpanel_data as mp

ws = mp.Workspace()
ws.use(account="team")                              # implicitly clears workspace
ws.use(project="3018488")
ws.use(workspace=3448414)
ws.use(target="ecom")                               # apply all three at once

# Persist the new state
ws.use(project="3018488", persist=True)             # writes [active]

# Read the resolved state
print(ws.account.name, ws.project.id, ws.workspace.id if ws.workspace else None)
print(ws.session)                                   # full Session snapshot

See Auth → Workspace.use() for the resolution semantics and parallel-snapshot patterns.

Class Reference

mixpanel_data.Workspace

Workspace(
    *,
    account: str | None = None,
    project: str | None = None,
    workspace: int | None = None,
    target: str | None = None,
    session: Session | None = None,
    _api_client: MixpanelAPIClient | None = None,
)

Unified entry point for Mixpanel data operations.

The Workspace class is a facade that orchestrates: - DiscoveryService for schema exploration - LiveQueryService for real-time analytics - App API client for CRUD and data governance operations

Examples:

Basic usage with credentials from config:

ws = Workspace()
events = ws.events()  # discover schema
result = ws.segmentation(event="login", from_date="2024-01-01", to_date="2024-01-31")
ws.close()

Stream events for external processing:

ws = Workspace()
for event in ws.stream_events(from_date="2024-01-01", to_date="2024-01-31"):
    process(event)
ws.close()

Create a new Workspace bound to a resolved :class:Session.

Resolution priority follows FR-017: env vars > kwargs > target > bridge > [active] > Account.default_project. Pass session= to bypass the resolver and use a pre-built :class:Session directly.

PARAMETER DESCRIPTION
account

Named account from ~/.mp/config.toml.

TYPE: str | None DEFAULT: None

project

Project ID override (digit string).

TYPE: str | None DEFAULT: None

workspace

Workspace ID override (positive int).

TYPE: int | None DEFAULT: None

target

Apply all three axes from [targets.NAME]. Mutually exclusive with account/project/workspace.

TYPE: str | None DEFAULT: None

session

Pre-built :class:Session (full resolver bypass).

TYPE: Session | None DEFAULT: None

_api_client

Injected :class:MixpanelAPIClient for testing.

TYPE: MixpanelAPIClient | None DEFAULT: None

RAISES DESCRIPTION
ValueError

target= combined with any axis kwarg.

ConfigError

Account or project axis cannot be resolved.

OAuthError

Auth header construction fails.

Source code in src/mixpanel_data/workspace.py
def __init__(
    self,
    *,
    account: str | None = None,
    project: str | None = None,
    workspace: int | None = None,
    target: str | None = None,
    session: _Session | None = None,
    _api_client: MixpanelAPIClient | None = None,
) -> None:
    """Create a new Workspace bound to a resolved :class:`Session`.

    Resolution priority follows FR-017: env vars > kwargs > target >
    bridge > ``[active]`` > ``Account.default_project``. Pass
    ``session=`` to bypass the resolver and use a pre-built
    :class:`Session` directly.

    Args:
        account: Named account from ``~/.mp/config.toml``.
        project: Project ID override (digit string).
        workspace: Workspace ID override (positive int).
        target: Apply all three axes from ``[targets.NAME]``. Mutually
            exclusive with ``account``/``project``/``workspace``.
        session: Pre-built :class:`Session` (full resolver bypass).
        _api_client: Injected :class:`MixpanelAPIClient` for testing.

    Raises:
        ValueError: ``target=`` combined with any axis kwarg.
        ConfigError: Account or project axis cannot be resolved.
        OAuthError: Auth header construction fails.
    """
    if target is not None and (
        account is not None or project is not None or workspace is not None
    ):
        raise ValueError(
            "`target=` is mutually exclusive with "
            "`account=`/`project=`/`workspace=`."
        )

    self._discovery: DiscoveryService | None = None
    self._live_query: LiveQueryService | None = None
    self._me_service: MeService | None = None

    if session is not None:
        sess = session
    else:
        from mixpanel_data._internal.auth.bridge import load_bridge

        br = load_bridge()
        # If the bridge has oauth_browser tokens embedded, materialize them
        # to the per-account on-disk path so the OnDiskTokenResolver can
        # serve them downstream. This is the Cowork credential-courier
        # contract: the bridge is the source of truth at startup.
        if (
            br is not None
            and br.tokens is not None
            and br.account.type == "oauth_browser"
        ):
            from mixpanel_data._internal.auth.storage import (
                ensure_account_dir,
            )
            from mixpanel_data._internal.auth.token import token_payload_bytes
            from mixpanel_data._internal.io_utils import atomic_write_bytes

            tokens_path = ensure_account_dir(br.account.name) / "tokens.json"
            # Always overwrite — the bridge is the authoritative
            # source of truth at startup, so a refreshed payload from
            # the host must replace any stale on-disk cache here.
            # ``OAuthTokens.expires_at`` is always set (required, tz-aware
            # per Fix 25) — no fall-through to None which would trip the
            # OnDiskTokenResolver expiry check. Empty scopes from the
            # bridge get a ``"read"`` default so the cached file matches
            # what `mp account login` would have written.
            tokens_to_persist = br.tokens
            if not tokens_to_persist.scope:
                tokens_to_persist = tokens_to_persist.model_copy(
                    update={"scope": "read"}
                )
            # atomic_write_bytes creates the file with 0o600 via O_EXCL
            # before any data is written, eliminating the umask-derived
            # permission window left open by write_text + chmod.
            atomic_write_bytes(tokens_path, token_payload_bytes(tokens_to_persist))
        sess = _resolve_session(
            account=account,
            project=project,
            workspace=workspace,
            target=target,
            config=ConfigManager(),
            bridge=br,
        )
    self._session = sess
    self._account_name: str = sess.account.name
    self._initial_workspace_id = sess.workspace.id if sess.workspace else None
    if _api_client is not None:
        self._api_client: MixpanelAPIClient | None = _api_client
    else:
        self._api_client = MixpanelAPIClient(session=sess)

account property

account: Account

Return the resolved :class:Account for the current session.

project property

project: Project

Return the resolved :class:Project for the current session.

workspace property

workspace: WorkspaceRef | None

Return the resolved :class:WorkspaceRef (or None for lazy).

session property

session: Session

Return the bound :class:Session.

api property

api: MixpanelAPIClient

Direct access to the Mixpanel API client.

Use this escape hatch for Mixpanel API operations not covered by the Workspace class. The client handles authentication automatically.

The client provides
  • request(method, url, **kwargs): Make authenticated requests to any Mixpanel API endpoint.
  • project_id: The configured project ID for constructing URLs.
  • region: The configured region ('us', 'eu', or 'in').
RETURNS DESCRIPTION
MixpanelAPIClient

The underlying MixpanelAPIClient.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Example

Fetch event schema from the Lexicon Schemas API::

import mixpanel_data as mp
from urllib.parse import quote

ws = mp.Workspace()
client = ws.api

# Build the URL with proper encoding
event_name = quote("Added To Cart", safe="")
url = f"https://mixpanel.com/api/app/projects/{client.project_id}/schemas/event/{event_name}"

# Make the authenticated request
schema = client.request("GET", url)
print(schema)

close

close() -> None

Close all resources (HTTP client).

This method is idempotent and safe to call multiple times.

Source code in src/mixpanel_data/workspace.py
def close(self) -> None:
    """Close all resources (HTTP client).

    This method is idempotent and safe to call multiple times.
    """
    # Close API client if we created one
    if self._api_client is not None:
        self._api_client.close()
        self._api_client = None

use

use(
    *,
    account: str | None = None,
    project: str | None = None,
    workspace: int | None = None,
    target: str | None = None,
    persist: bool = False,
) -> Workspace

Swap one or more session axes in place; return self for chaining.

target= is mutually exclusive with account=/project=/ workspace=. The HTTP transport is preserved across all switches (per Research R5).

When account= is supplied, the project axis re-resolves through the FR-017 chain ending at the new account's default_project (env MP_PROJECT_ID > explicit project= > new account's default_project). If no source provides a project, the call raises :class:ConfigError per FR-033 — the prior session's project is NEVER carried forward across an account swap because cross-account project access is not guaranteed. The workspace axis is cleared on account swap (workspaces are project-scoped; the prior workspace doesn't apply to the new project) — explicit workspace= or MP_WORKSPACE_ID env override is honored.

PARAMETER DESCRIPTION
account

Replacement account name.

TYPE: str | None DEFAULT: None

project

Replacement project ID.

TYPE: str | None DEFAULT: None

workspace

Replacement workspace ID.

TYPE: int | None DEFAULT: None

target

Apply this target's three axes atomically.

TYPE: str | None DEFAULT: None

persist

When True, also write the new state to [active].

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Workspace

self for fluent chaining.

RAISES DESCRIPTION
ValueError

Mutually exclusive args, or referenced name missing.

OAuthError

New auth header construction fails (atomic on success).

ConfigError

account= swap cannot resolve a project axis.

Source code in src/mixpanel_data/workspace.py
def use(
    self,
    *,
    account: str | None = None,
    project: str | None = None,
    workspace: int | None = None,
    target: str | None = None,
    persist: bool = False,
) -> Workspace:
    """Swap one or more session axes in place; return ``self`` for chaining.

    ``target=`` is mutually exclusive with ``account=``/``project=``/
    ``workspace=``. The HTTP transport is preserved across all switches
    (per Research R5).

    When ``account=`` is supplied, the project axis re-resolves through
    the FR-017 chain ending at the new account's ``default_project``
    (env ``MP_PROJECT_ID`` > explicit ``project=`` > new account's
    ``default_project``). If no source provides a project, the call
    raises :class:`ConfigError` per FR-033 — the prior session's
    project is NEVER carried forward across an account swap because
    cross-account project access is not guaranteed. The workspace
    axis is cleared on account swap (workspaces are project-scoped;
    the prior workspace doesn't apply to the new project) — explicit
    ``workspace=`` or ``MP_WORKSPACE_ID`` env override is honored.

    Args:
        account: Replacement account name.
        project: Replacement project ID.
        workspace: Replacement workspace ID.
        target: Apply this target's three axes atomically.
        persist: When ``True``, also write the new state to ``[active]``.

    Returns:
        ``self`` for fluent chaining.

    Raises:
        ValueError: Mutually exclusive args, or referenced name missing.
        OAuthError: New auth header construction fails (atomic on success).
        ConfigError: ``account=`` swap cannot resolve a project axis.
    """
    if target is not None and (
        account is not None or project is not None or workspace is not None
    ):
        raise ValueError(
            "`target=` is mutually exclusive with `account=`/`project=`/`workspace=`."
        )

    cm = ConfigManager()
    client = self._require_api_client()
    new_account_obj: _AccountUnion | None = None
    new_project_obj: _Project | None = None
    new_workspace_obj: _WorkspaceRef | None = None
    if target is not None:
        # Route through the same resolver as Workspace() construction so
        # env > param > target > bridge > config ordering applies (FR-017).
        # Without this, mid-process env-var overrides would be honored at
        # construction but silently ignored on `ws.use(target=...)`.
        sess = _resolve_session(
            target=target,
            config=cm,
            bridge=_load_bridge(),
        )
        new_account_obj = sess.account
        new_project_obj = sess.project
        new_workspace_obj = sess.workspace
    elif account is not None:
        # Explicit account swap: the user told us which account to use,
        # so the env-vars-override-param rule (FR-017) on the account
        # axis doesn't apply here — load the requested account directly.
        # Project re-resolves through the FR-017 chain ending at the
        # NEW account's default_project (env > explicit > new account's
        # default); raises ConfigError if nothing resolves (per FR-033,
        # cross-account project access is not guaranteed).
        # Workspace is cleared (workspaces are project-scoped; the
        # prior workspace is meaningless under the new account/project)
        # — explicit `workspace=` overrides the clear, and env override
        # via MP_WORKSPACE_ID still applies for parity with FR-017.
        new_account_obj = cm.get_account(account)
        br = _load_bridge()
        project_id = _resolve_project_axis(
            explicit=project,
            target_project=None,
            bridge=br,
            account=new_account_obj,
        )
        if project_id is None:
            raise ConfigError(_format_no_project_error(new_account_obj))
        new_project_obj = _Project(id=project_id)
        # Account-swap intentionally clears workspace per FR-033 (workspaces
        # are project-scoped; the prior workspace doesn't apply to the new
        # project). Only an explicit ``workspace=`` kwarg or a validated
        # ``MP_WORKSPACE_ID`` env var can populate it. We bypass
        # ``resolve_workspace_axis`` because that consults ``[active].workspace``
        # — which is exactly the fallback we need to skip here.
        if workspace is not None:
            new_workspace_obj = _WorkspaceRef(id=workspace)
        else:
            env_ws = _env_workspace_id()
            new_workspace_obj = (
                _WorkspaceRef(id=env_ws) if env_ws is not None else None
            )
    else:
        new_project_obj = _Project(id=project) if project is not None else None
        new_workspace_obj = (
            _WorkspaceRef(id=workspace) if workspace is not None else None
        )
    client.use(
        account=new_account_obj,
        project=new_project_obj,
        workspace=new_workspace_obj,
    )
    self._session = client.session

    # Clear lazy services so subsequent reads of `project` / `account` /
    # `workspaces()` / `_me_svc` observe the new session rather than the
    # prior one.
    self._account_name = self._session.account.name
    self._initial_workspace_id = (
        self._session.workspace.id if self._session.workspace else None
    )
    self._discovery = None
    self._live_query = None
    self._me_service = None

    if persist:
        self._persist_active()
    return self

me

me(*, force_refresh: bool = False) -> Any

Get /me response for current credentials (cached 24h).

Returns the authenticated user's profile including all accessible organizations, projects, and workspaces.

PARAMETER DESCRIPTION
force_refresh

If True, bypass cache and call the API.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
Any

MeResponse with user profile, projects, and workspaces.

RAISES DESCRIPTION
ConfigError

If credentials lack /me access (401 or 403).

QueryError

If the API returns a non-403 error.

Example
ws = Workspace()
me = ws.me()
print(me.user_email)
for pid, proj in me.projects.items():
    print(f"  {pid}: {proj.name}")
Source code in src/mixpanel_data/workspace.py
def me(self, *, force_refresh: bool = False) -> Any:
    """Get /me response for current credentials (cached 24h).

    Returns the authenticated user's profile including all accessible
    organizations, projects, and workspaces.

    Args:
        force_refresh: If True, bypass cache and call the API.

    Returns:
        MeResponse with user profile, projects, and workspaces.

    Raises:
        ConfigError: If credentials lack /me access (401 or 403).
        QueryError: If the API returns a non-403 error.

    Example:
        ```python
        ws = Workspace()
        me = ws.me()
        print(me.user_email)
        for pid, proj in me.projects.items():
            print(f"  {pid}: {proj.name}")
        ```
    """
    return self._me_svc.fetch(force_refresh=force_refresh)

projects

projects(*, refresh: bool = False) -> list[_Project]

List all accessible projects via the /me API (FR-035).

Returns projects from the cached /me response, sorted by name. Each entry is a v3 :class:Project (id + name + organization_id + timezone), built from the underlying MeProjectInfo payload — callers iterate for project in ws.projects(): ws.use(project=project.id) per the documented cross-project iteration pattern.

Replaces the deprecated discover_projects() (which returned list[tuple[str, MeProjectInfo]]) — for the raw /me shape with extra fields (has_workspaces, domain, type, ...), call self._me_svc.list_projects() directly from internal code.

PARAMETER DESCRIPTION
refresh

When True, bypass the on-disk and in-memory /me caches and refetch from the API. Default False uses the 24h cache.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[Project]

List of :class:Project records sorted by name.

RAISES DESCRIPTION
ConfigError

If credentials lack /me access.

Example
ws = Workspace()
for project in ws.projects():
    ws.use(project=project.id)
    print(project.id, project.name, len(ws.events()))
Source code in src/mixpanel_data/workspace.py
def projects(self, *, refresh: bool = False) -> list[_Project]:
    """List all accessible projects via the /me API (FR-035).

    Returns projects from the cached /me response, sorted by name. Each
    entry is a v3 :class:`Project` (id + name + organization_id +
    timezone), built from the underlying ``MeProjectInfo`` payload —
    callers iterate ``for project in ws.projects(): ws.use(project=project.id)``
    per the documented cross-project iteration pattern.

    Replaces the deprecated ``discover_projects()`` (which returned
    ``list[tuple[str, MeProjectInfo]]``) — for the raw ``/me`` shape
    with extra fields (``has_workspaces``, ``domain``, ``type``, ...),
    call ``self._me_svc.list_projects()`` directly from internal code.

    Args:
        refresh: When True, bypass the on-disk and in-memory ``/me``
            caches and refetch from the API. Default False uses the
            24h cache.

    Returns:
        List of :class:`Project` records sorted by name.

    Raises:
        ConfigError: If credentials lack /me access.

    Example:
        ```python
        ws = Workspace()
        for project in ws.projects():
            ws.use(project=project.id)
            print(project.id, project.name, len(ws.events()))
        ```
    """
    if refresh:
        self._me_svc.fetch(force_refresh=True)
    return [
        _Project(
            id=pid,
            name=info.name,
            organization_id=info.organization_id,
            timezone=info.timezone,
        )
        for pid, info in self._me_svc.list_projects()
    ]

workspaces

workspaces(
    *, project_id: str | None = None, refresh: bool = False
) -> list[_WorkspaceRef]

List workspaces for a project via the /me API (FR-036).

Returns workspaces from the cached /me response, sorted by name. Defaults to the current project if project_id is not provided.

Replaces the deprecated discover_workspaces() (which returned list[MeWorkspaceInfo]) — for the raw /me shape with extra fields (is_global, is_restricted, description, ...), call self._me_svc.list_workspaces(project_id=) directly from internal code.

PARAMETER DESCRIPTION
project_id

Project ID to list workspaces for. Defaults to the current project.

TYPE: str | None DEFAULT: None

refresh

When True, bypass the on-disk and in-memory /me caches and refetch from the API. Default False uses the 24h cache. Mirrors :meth:projects(refresh=) (FR-047).

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[WorkspaceRef]

List of :class:WorkspaceRef records sorted by name.

RAISES DESCRIPTION
ConfigError

If credentials lack /me access.

Example
ws = Workspace()
for workspace in ws.workspaces():
    print(workspace.id, workspace.name, workspace.is_default)
Source code in src/mixpanel_data/workspace.py
def workspaces(
    self,
    *,
    project_id: str | None = None,
    refresh: bool = False,
) -> list[_WorkspaceRef]:
    """List workspaces for a project via the /me API (FR-036).

    Returns workspaces from the cached /me response, sorted by name.
    Defaults to the current project if ``project_id`` is not provided.

    Replaces the deprecated ``discover_workspaces()`` (which returned
    ``list[MeWorkspaceInfo]``) — for the raw ``/me`` shape with extra
    fields (``is_global``, ``is_restricted``, ``description``, ...),
    call ``self._me_svc.list_workspaces(project_id=)`` directly from
    internal code.

    Args:
        project_id: Project ID to list workspaces for. Defaults to
            the current project.
        refresh: When True, bypass the on-disk and in-memory ``/me``
            caches and refetch from the API. Default False uses the
            24h cache. Mirrors :meth:`projects(refresh=)` (FR-047).

    Returns:
        List of :class:`WorkspaceRef` records sorted by name.

    Raises:
        ConfigError: If credentials lack /me access.

    Example:
        ```python
        ws = Workspace()
        for workspace in ws.workspaces():
            print(workspace.id, workspace.name, workspace.is_default)
        ```
    """
    if refresh:
        self._me_svc.fetch(force_refresh=True)
    pid = project_id
    if pid is None:
        pid = self._session.project.id
    return [
        _WorkspaceRef(id=info.id, name=info.name, is_default=info.is_default)
        for info in self._me_svc.list_workspaces(project_id=pid)
    ]

list_workspaces

list_workspaces() -> list[PublicWorkspace]

List all public workspaces for the current project.

Delegates to the API client's list_workspaces() method, which calls GET /api/app/projects/{pid}/workspaces/public.

RETURNS DESCRIPTION
list[PublicWorkspace]

List of PublicWorkspace models for the project.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
workspaces = ws.list_workspaces()
for w in workspaces:
    print(f"{w.name} (id={w.id}, default={w.is_default})")
Source code in src/mixpanel_data/workspace.py
def list_workspaces(self) -> list[PublicWorkspace]:
    """List all public workspaces for the current project.

    Delegates to the API client's ``list_workspaces()`` method, which
    calls ``GET /api/app/projects/{pid}/workspaces/public``.

    Returns:
        List of ``PublicWorkspace`` models for the project.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        workspaces = ws.list_workspaces()
        for w in workspaces:
            print(f"{w.name} (id={w.id}, default={w.is_default})")
        ```
    """
    client = self._require_api_client()
    return client.list_workspaces()

resolve_workspace_id

resolve_workspace_id() -> int

Resolve the workspace ID for scoped requests.

Resolution order: 1. Workspace ID already pinned on the resolved session (for example via Workspace(workspace=N), Workspace.use(workspace=N), MP_WORKSPACE_ID, saved targets, bridge pins, or persisted [active].workspace state) 2. Cached auto-discovered workspace ID 3. Auto-discover by listing workspaces and finding the default

RETURNS DESCRIPTION
int

The resolved workspace ID.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

WorkspaceScopeError

If no workspaces are found for the project.

Example
ws = Workspace()
ws_id = ws.resolve_workspace_id()
print(f"Using workspace {ws_id}")
Source code in src/mixpanel_data/workspace.py
def resolve_workspace_id(self) -> int:
    """Resolve the workspace ID for scoped requests.

    Resolution order:
    1. Workspace ID already pinned on the resolved session (for example
       via ``Workspace(workspace=N)``, ``Workspace.use(workspace=N)``,
       ``MP_WORKSPACE_ID``, saved targets, bridge pins, or persisted
       ``[active].workspace`` state)
    2. Cached auto-discovered workspace ID
    3. Auto-discover by listing workspaces and finding the default

    Returns:
        The resolved workspace ID.

    Raises:
        ConfigError: If credentials are not available.
        WorkspaceScopeError: If no workspaces are found for the project.

    Example:
        ```python
        ws = Workspace()
        ws_id = ws.resolve_workspace_id()
        print(f"Using workspace {ws_id}")
        ```
    """
    client = self._require_api_client()
    return client.resolve_workspace_id()

events

events() -> list[str]

List all event names in the Mixpanel project.

Results are cached for the lifetime of the Workspace.

RETURNS DESCRIPTION
list[str]

Alphabetically sorted list of event names.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

AuthenticationError

If credentials are invalid.

Source code in src/mixpanel_data/workspace.py
def events(self) -> list[str]:
    """List all event names in the Mixpanel project.

    Results are cached for the lifetime of the Workspace.

    Returns:
        Alphabetically sorted list of event names.

    Raises:
        ConfigError: If API credentials not available.
        AuthenticationError: If credentials are invalid.
    """
    return self._discovery_service.list_events()

properties

properties(event: str) -> list[str]

List all property names for an event.

Results are cached per event for the lifetime of the Workspace.

PARAMETER DESCRIPTION
event

Event name to get properties for.

TYPE: str

RETURNS DESCRIPTION
list[str]

Alphabetically sorted list of property names.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def properties(self, event: str) -> list[str]:
    """List all property names for an event.

    Results are cached per event for the lifetime of the Workspace.

    Args:
        event: Event name to get properties for.

    Returns:
        Alphabetically sorted list of property names.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._discovery_service.list_properties(event)

property_values

property_values(
    property_name: str, *, event: str | None = None, limit: int = 100
) -> list[str]

Get sample values for a property.

Results are cached per (property, event, limit) for the lifetime of the Workspace.

PARAMETER DESCRIPTION
property_name

Property to get values for.

TYPE: str

event

Optional event to filter by.

TYPE: str | None DEFAULT: None

limit

Maximum number of values to return.

TYPE: int DEFAULT: 100

RETURNS DESCRIPTION
list[str]

List of sample property values as strings.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def property_values(
    self,
    property_name: str,
    *,
    event: str | None = None,
    limit: int = 100,
) -> list[str]:
    """Get sample values for a property.

    Results are cached per (property, event, limit) for the lifetime of the Workspace.

    Args:
        property_name: Property to get values for.
        event: Optional event to filter by.
        limit: Maximum number of values to return.

    Returns:
        List of sample property values as strings.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._discovery_service.list_property_values(
        property_name, event=event, limit=limit
    )

subproperties

subproperties(
    property_name: str, *, event: str | None = None, sample_size: int = 50
) -> list[SubPropertyInfo]

List inferred subproperties of a list-of-object event property.

Samples values via :meth:property_values, parses each as JSON, and returns one :class:SubPropertyInfo per discovered scalar subproperty. Designed for properties like cart whose values are objects with subkeys (Brand, Category, Price, Item ID). The returned name and type plug directly into :meth:GroupBy.list_item and :meth:Filter.list_contains.

Scope: only scalar subproperty values (string / number / boolean / ISO datetime string) are reported. Subproperties whose values are themselves dicts or lists are silently skipped — they cannot be used by GroupBy.list_item or Filter.list_contains anyway.

PARAMETER DESCRIPTION
property_name

Top-level list-of-object property name (e.g. "cart").

TYPE: str

event

Optional event name to scope the sample. Strongly recommended; without it the API may return values from across all events.

TYPE: str | None DEFAULT: None

sample_size

Number of raw values to sample. Default: 50.

TYPE: int DEFAULT: 50

RETURNS DESCRIPTION
list[SubPropertyInfo]

Alphabetically sorted list of :class:SubPropertyInfo.

list[SubPropertyInfo]

Empty list if no parseable dict values were found.

RAISES DESCRIPTION
ConfigError

If API credentials cannot be resolved.

AuthenticationError

If credentials are configured but rejected by Mixpanel.

WARNS DESCRIPTION
UserWarning

When a subproperty has values of mixed scalar types across rows (collapses to "string"); when a sub-key is observed with both scalar and nested-object shapes (reports the scalar form); or when a sub-key is observed but all sampled values were null (excluded from output).

Example
for sp in ws.subproperties("cart", event="Cart Viewed"):
    print(sp.name, sp.type, sp.sample_values)
# Brand string ('nike', 'puma', 'h&m')
# Category string ('hats', 'jeans')
# Item ID number (35317, 35318)
# Price number (51, 87, 102)
Source code in src/mixpanel_data/workspace.py
def subproperties(
    self,
    property_name: str,
    *,
    event: str | None = None,
    sample_size: int = 50,
) -> list[SubPropertyInfo]:
    """List inferred subproperties of a list-of-object event property.

    Samples values via :meth:`property_values`, parses each as JSON,
    and returns one :class:`SubPropertyInfo` per discovered scalar
    subproperty. Designed for properties like ``cart`` whose values
    are objects with subkeys (``Brand``, ``Category``, ``Price``,
    ``Item ID``). The returned ``name`` and ``type`` plug directly
    into :meth:`GroupBy.list_item` and :meth:`Filter.list_contains`.

    Scope: only **scalar** subproperty values (string / number /
    boolean / ISO datetime string) are reported. Subproperties whose
    values are themselves dicts or lists are silently skipped — they
    cannot be used by ``GroupBy.list_item`` or
    ``Filter.list_contains`` anyway.

    Args:
        property_name: Top-level list-of-object property name (e.g.
            ``"cart"``).
        event: Optional event name to scope the sample. Strongly
            recommended; without it the API may return values from
            across all events.
        sample_size: Number of raw values to sample. Default: 50.

    Returns:
        Alphabetically sorted list of :class:`SubPropertyInfo`.
        Empty list if no parseable dict values were found.

    Raises:
        ConfigError: If API credentials cannot be resolved.
        AuthenticationError: If credentials are configured but
            rejected by Mixpanel.

    Warns:
        UserWarning: When a subproperty has values of mixed scalar
            types across rows (collapses to ``"string"``); when a
            sub-key is observed with both scalar and nested-object
            shapes (reports the scalar form); or when a sub-key
            is observed but all sampled values were ``null``
            (excluded from output).

    Example:
        ```python
        for sp in ws.subproperties("cart", event="Cart Viewed"):
            print(sp.name, sp.type, sp.sample_values)
        # Brand string ('nike', 'puma', 'h&m')
        # Category string ('hats', 'jeans')
        # Item ID number (35317, 35318)
        # Price number (51, 87, 102)
        ```
    """
    return self._discovery_service.list_subproperties(
        property_name, event=event, sample_size=sample_size
    )

funnels

funnels() -> list[FunnelInfo]

List saved funnels in the Mixpanel project.

Results are cached for the lifetime of the Workspace.

RETURNS DESCRIPTION
list[FunnelInfo]

List of FunnelInfo objects (funnel_id, name).

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def funnels(self) -> list[FunnelInfo]:
    """List saved funnels in the Mixpanel project.

    Results are cached for the lifetime of the Workspace.

    Returns:
        List of FunnelInfo objects (funnel_id, name).

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._discovery_service.list_funnels()

cohorts

cohorts() -> list[SavedCohort]

List saved cohorts in the Mixpanel project.

Results are cached for the lifetime of the Workspace.

RETURNS DESCRIPTION
list[SavedCohort]

List of SavedCohort objects.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def cohorts(self) -> list[SavedCohort]:
    """List saved cohorts in the Mixpanel project.

    Results are cached for the lifetime of the Workspace.

    Returns:
        List of SavedCohort objects.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._discovery_service.list_cohorts()

list_bookmarks

list_bookmarks(bookmark_type: BookmarkType | None = None) -> list[BookmarkInfo]

List all saved reports (bookmarks) in the project.

Retrieves metadata for all saved Insights, Funnel, Retention, and Flows reports in the project.

PARAMETER DESCRIPTION
bookmark_type

Optional filter by report type. Valid values are 'insights', 'funnels', 'retention', 'flows', 'launch-analysis'. If None, returns all bookmark types.

TYPE: BookmarkType | None DEFAULT: None

RETURNS DESCRIPTION
list[BookmarkInfo]

List of BookmarkInfo objects with report metadata.

list[BookmarkInfo]

Empty list if no bookmarks exist.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

QueryError

Permission denied or invalid type parameter.

Source code in src/mixpanel_data/workspace.py
def list_bookmarks(
    self,
    bookmark_type: BookmarkType | None = None,
) -> list[BookmarkInfo]:
    """List all saved reports (bookmarks) in the project.

    Retrieves metadata for all saved Insights, Funnel, Retention, and
    Flows reports in the project.

    Args:
        bookmark_type: Optional filter by report type. Valid values are
            'insights', 'funnels', 'retention', 'flows', 'launch-analysis'.
            If None, returns all bookmark types.

    Returns:
        List of BookmarkInfo objects with report metadata.
        Empty list if no bookmarks exist.

    Raises:
        ConfigError: If API credentials not available.
        QueryError: Permission denied or invalid type parameter.
    """
    return self._discovery_service.list_bookmarks(bookmark_type=bookmark_type)

top_events

top_events(
    *,
    type: Literal["general", "average", "unique"] = "general",
    limit: int | None = None,
) -> list[TopEvent]

Get today's most active events.

This method is NOT cached (returns real-time data).

PARAMETER DESCRIPTION
type

Counting method (general, average, unique).

TYPE: Literal['general', 'average', 'unique'] DEFAULT: 'general'

limit

Maximum number of events to return.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
list[TopEvent]

List of TopEvent objects with event, count, and

list[TopEvent]

percent_change fields.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Example
top = ws.top_events(limit=10)
for t in top:
    print(f"{t.event}: {t.count:,} ({t.percent_change:+.1%})")
Source code in src/mixpanel_data/workspace.py
def top_events(
    self,
    *,
    type: Literal["general", "average", "unique"] = "general",
    limit: int | None = None,
) -> list[TopEvent]:
    """Get today's most active events.

    This method is NOT cached (returns real-time data).

    Args:
        type: Counting method (general, average, unique).
        limit: Maximum number of events to return.

    Returns:
        List of TopEvent objects with ``event``, ``count``, and
        ``percent_change`` fields.

    Raises:
        ConfigError: If API credentials not available.

    Example:
        ```python
        top = ws.top_events(limit=10)
        for t in top:
            print(f"{t.event}: {t.count:,} ({t.percent_change:+.1%})")
        ```
    """
    return self._discovery_service.list_top_events(type=type, limit=limit)

lexicon_schemas

lexicon_schemas(
    *, entity_type: EntityType | None = None
) -> list[LexiconSchema]

List Lexicon schemas in the project.

Retrieves documented event and profile property schemas from the Mixpanel Lexicon (data dictionary).

Results are cached for the lifetime of the Workspace.

PARAMETER DESCRIPTION
entity_type

Optional filter by type ("event" or "profile"). If None, returns all schemas.

TYPE: EntityType | None DEFAULT: None

RETURNS DESCRIPTION
list[LexiconSchema]

Alphabetically sorted list of LexiconSchema objects.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

AuthenticationError

If credentials are invalid.

Note

The Lexicon API has a strict 5 requests/minute rate limit. Caching helps avoid hitting this limit; call clear_discovery_cache() only when fresh data is needed.

Source code in src/mixpanel_data/workspace.py
def lexicon_schemas(
    self,
    *,
    entity_type: EntityType | None = None,
) -> list[LexiconSchema]:
    """List Lexicon schemas in the project.

    Retrieves documented event and profile property schemas from the
    Mixpanel Lexicon (data dictionary).

    Results are cached for the lifetime of the Workspace.

    Args:
        entity_type: Optional filter by type ("event" or "profile").
            If None, returns all schemas.

    Returns:
        Alphabetically sorted list of LexiconSchema objects.

    Raises:
        ConfigError: If API credentials not available.
        AuthenticationError: If credentials are invalid.

    Note:
        The Lexicon API has a strict 5 requests/minute rate limit.
        Caching helps avoid hitting this limit; call clear_discovery_cache()
        only when fresh data is needed.
    """
    return self._discovery_service.list_schemas(entity_type=entity_type)

lexicon_schema

lexicon_schema(entity_type: EntityType, name: str) -> LexiconSchema

Get a single Lexicon schema by entity type and name.

Retrieves a documented schema for a specific event or profile property from the Mixpanel Lexicon (data dictionary).

Results are cached for the lifetime of the Workspace.

PARAMETER DESCRIPTION
entity_type

Entity type ("event" or "profile").

TYPE: EntityType

name

Entity name.

TYPE: str

RETURNS DESCRIPTION
LexiconSchema

LexiconSchema for the specified entity.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

AuthenticationError

If credentials are invalid.

QueryError

If schema not found.

Note

The Lexicon API has a strict 5 requests/minute rate limit. Caching helps avoid hitting this limit; call clear_discovery_cache() only when fresh data is needed.

Source code in src/mixpanel_data/workspace.py
def lexicon_schema(
    self,
    entity_type: EntityType,
    name: str,
) -> LexiconSchema:
    """Get a single Lexicon schema by entity type and name.

    Retrieves a documented schema for a specific event or profile property
    from the Mixpanel Lexicon (data dictionary).

    Results are cached for the lifetime of the Workspace.

    Args:
        entity_type: Entity type ("event" or "profile").
        name: Entity name.

    Returns:
        LexiconSchema for the specified entity.

    Raises:
        ConfigError: If API credentials not available.
        AuthenticationError: If credentials are invalid.
        QueryError: If schema not found.

    Note:
        The Lexicon API has a strict 5 requests/minute rate limit.
        Caching helps avoid hitting this limit; call clear_discovery_cache()
        only when fresh data is needed.
    """
    return self._discovery_service.get_schema(entity_type, name)

clear_discovery_cache

clear_discovery_cache() -> None

Clear cached discovery results.

Subsequent discovery calls will fetch fresh data from the API.

Source code in src/mixpanel_data/workspace.py
def clear_discovery_cache(self) -> None:
    """Clear cached discovery results.

    Subsequent discovery calls will fetch fresh data from the API.
    """
    if self._discovery is not None:
        self._discovery.clear_cache()

stream_events

stream_events(
    *,
    from_date: str,
    to_date: str,
    events: list[str] | None = None,
    where: str | None = None,
    limit: int | None = None,
    raw: bool = False,
) -> Iterator[dict[str, Any]]

Stream events directly from Mixpanel API without storing.

Yields events one at a time as they are received from the API. No database files or tables are created.

PARAMETER DESCRIPTION
from_date

Start date inclusive (YYYY-MM-DD format).

TYPE: str

to_date

End date inclusive (YYYY-MM-DD format).

TYPE: str

events

Optional list of event names to filter. If None, all events returned.

TYPE: list[str] | None DEFAULT: None

where

Optional Mixpanel filter expression (e.g., 'properties["country"]=="US"').

TYPE: str | None DEFAULT: None

limit

Optional maximum number of events to return (max 100000).

TYPE: int | None DEFAULT: None

raw

If True, return events in raw Mixpanel API format. If False (default), return normalized format with datetime objects.

TYPE: bool DEFAULT: False

YIELDS DESCRIPTION
dict[str, Any]

dict[str, Any]: Event dictionaries in normalized or raw format.

RAISES DESCRIPTION
ConfigError

If API credentials are not available.

AuthenticationError

If credentials are invalid.

RateLimitError

If rate limit exceeded after max retries.

QueryError

If filter expression is invalid.

ValueError

If limit is outside valid range (1-100000).

Example
ws = Workspace()
for event in ws.stream_events(from_date="2024-01-01", to_date="2024-01-31"):
    process(event)
ws.close()

With raw format:

for event in ws.stream_events(
    from_date="2024-01-01", to_date="2024-01-31", raw=True
):
    legacy_system.ingest(event)
Source code in src/mixpanel_data/workspace.py
def stream_events(
    self,
    *,
    from_date: str,
    to_date: str,
    events: list[str] | None = None,
    where: str | None = None,
    limit: int | None = None,
    raw: bool = False,
) -> Iterator[dict[str, Any]]:
    """Stream events directly from Mixpanel API without storing.

    Yields events one at a time as they are received from the API.
    No database files or tables are created.

    Args:
        from_date: Start date inclusive (YYYY-MM-DD format).
        to_date: End date inclusive (YYYY-MM-DD format).
        events: Optional list of event names to filter. If None, all events returned.
        where: Optional Mixpanel filter expression (e.g., 'properties["country"]=="US"').
        limit: Optional maximum number of events to return (max 100000).
        raw: If True, return events in raw Mixpanel API format.
             If False (default), return normalized format with datetime objects.

    Yields:
        dict[str, Any]: Event dictionaries in normalized or raw format.

    Raises:
        ConfigError: If API credentials are not available.
        AuthenticationError: If credentials are invalid.
        RateLimitError: If rate limit exceeded after max retries.
        QueryError: If filter expression is invalid.
        ValueError: If limit is outside valid range (1-100000).

    Example:
        ```python
        ws = Workspace()
        for event in ws.stream_events(from_date="2024-01-01", to_date="2024-01-31"):
            process(event)
        ws.close()
        ```

        With raw format:

        ```python
        for event in ws.stream_events(
            from_date="2024-01-01", to_date="2024-01-31", raw=True
        ):
            legacy_system.ingest(event)
        ```
    """
    # Validate limit early to avoid wasted API calls
    _validate_limit(limit)

    api_client = self._require_api_client()
    event_iterator = api_client.export_events(
        from_date=from_date,
        to_date=to_date,
        events=events,
        where=where,
        limit=limit,
    )

    if raw:
        yield from event_iterator
    else:
        for event in event_iterator:
            yield transform_event(event)

stream_profiles

stream_profiles(
    *,
    where: str | None = None,
    cohort_id: str | None = None,
    output_properties: list[str] | None = None,
    raw: bool = False,
    distinct_id: str | None = None,
    distinct_ids: list[str] | None = None,
    group_id: str | None = None,
    behaviors: list[dict[str, Any]] | None = None,
    as_of_timestamp: int | None = None,
    include_all_users: bool = False,
) -> Iterator[dict[str, Any]]

Stream user profiles directly from Mixpanel API without storing.

Yields profiles one at a time as they are received from the API. No database files or tables are created.

PARAMETER DESCRIPTION
where

Optional Mixpanel filter expression for profile properties.

TYPE: str | None DEFAULT: None

cohort_id

Optional cohort ID to filter by. Only profiles that are members of this cohort will be returned.

TYPE: str | None DEFAULT: None

output_properties

Optional list of property names to include in the response. If None, all properties are returned.

TYPE: list[str] | None DEFAULT: None

raw

If True, return profiles in raw Mixpanel API format. If False (default), return normalized format.

TYPE: bool DEFAULT: False

distinct_id

Optional single user ID to fetch. Mutually exclusive with distinct_ids.

TYPE: str | None DEFAULT: None

distinct_ids

Optional list of user IDs to fetch. Mutually exclusive with distinct_id. Duplicates are automatically removed.

TYPE: list[str] | None DEFAULT: None

group_id

Optional group type identifier (e.g., "companies") to fetch group profiles instead of user profiles.

TYPE: str | None DEFAULT: None

behaviors

Optional list of behavioral filters. Each dict should have 'window' (e.g., "30d"), 'name' (identifier), and 'event_selectors' (list of {"event": "Name"}). Use with where parameter to filter, e.g., where='(behaviors["name"] > 0)'. Mutually exclusive with cohort_id.

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

as_of_timestamp

Optional Unix timestamp to query profile state at a specific point in time. Must be in the past.

TYPE: int | None DEFAULT: None

include_all_users

If True, include all users and mark cohort membership. Only valid when cohort_id is provided.

TYPE: bool DEFAULT: False

YIELDS DESCRIPTION
dict[str, Any]

dict[str, Any]: Profile dictionaries in normalized or raw format.

RAISES DESCRIPTION
ConfigError

If API credentials are not available.

AuthenticationError

If credentials are invalid.

RateLimitError

If rate limit exceeded after max retries.

ValueError

If mutually exclusive parameters are provided.

Example
ws = Workspace()
for profile in ws.stream_profiles():
    sync_to_crm(profile)
ws.close()

Filter to premium users:

for profile in ws.stream_profiles(where='properties["plan"]=="premium"'):
    send_survey(profile)

Filter by cohort and select specific properties:

for profile in ws.stream_profiles(
    cohort_id="12345",
    output_properties=["$email", "$name"]
):
    send_email(profile)

Fetch specific users by ID:

for profile in ws.stream_profiles(distinct_ids=["user_1", "user_2"]):
    print(profile)

Fetch group profiles:

for company in ws.stream_profiles(group_id="companies"):
    print(company)
Source code in src/mixpanel_data/workspace.py
def stream_profiles(
    self,
    *,
    where: str | None = None,
    cohort_id: str | None = None,
    output_properties: list[str] | None = None,
    raw: bool = False,
    distinct_id: str | None = None,
    distinct_ids: list[str] | None = None,
    group_id: str | None = None,
    behaviors: list[dict[str, Any]] | None = None,
    as_of_timestamp: int | None = None,
    include_all_users: bool = False,
) -> Iterator[dict[str, Any]]:
    """Stream user profiles directly from Mixpanel API without storing.

    Yields profiles one at a time as they are received from the API.
    No database files or tables are created.

    Args:
        where: Optional Mixpanel filter expression for profile properties.
        cohort_id: Optional cohort ID to filter by. Only profiles that are
            members of this cohort will be returned.
        output_properties: Optional list of property names to include in
            the response. If None, all properties are returned.
        raw: If True, return profiles in raw Mixpanel API format.
             If False (default), return normalized format.
        distinct_id: Optional single user ID to fetch. Mutually exclusive
            with distinct_ids.
        distinct_ids: Optional list of user IDs to fetch. Mutually exclusive
            with distinct_id. Duplicates are automatically removed.
        group_id: Optional group type identifier (e.g., "companies") to fetch
            group profiles instead of user profiles.
        behaviors: Optional list of behavioral filters. Each dict should have
            'window' (e.g., "30d"), 'name' (identifier), and 'event_selectors'
            (list of {"event": "Name"}). Use with `where` parameter to filter,
            e.g., where='(behaviors["name"] > 0)'. Mutually exclusive with
            cohort_id.
        as_of_timestamp: Optional Unix timestamp to query profile state at
            a specific point in time. Must be in the past.
        include_all_users: If True, include all users and mark cohort membership.
            Only valid when cohort_id is provided.

    Yields:
        dict[str, Any]: Profile dictionaries in normalized or raw format.

    Raises:
        ConfigError: If API credentials are not available.
        AuthenticationError: If credentials are invalid.
        RateLimitError: If rate limit exceeded after max retries.
        ValueError: If mutually exclusive parameters are provided.

    Example:
        ```python
        ws = Workspace()
        for profile in ws.stream_profiles():
            sync_to_crm(profile)
        ws.close()
        ```

        Filter to premium users:

        ```python
        for profile in ws.stream_profiles(where='properties["plan"]=="premium"'):
            send_survey(profile)
        ```

        Filter by cohort and select specific properties:

        ```python
        for profile in ws.stream_profiles(
            cohort_id="12345",
            output_properties=["$email", "$name"]
        ):
            send_email(profile)
        ```

        Fetch specific users by ID:

        ```python
        for profile in ws.stream_profiles(distinct_ids=["user_1", "user_2"]):
            print(profile)
        ```

        Fetch group profiles:

        ```python
        for company in ws.stream_profiles(group_id="companies"):
            print(company)
        ```
    """
    api_client = self._require_api_client()
    profile_iterator = api_client.export_profiles(
        where=where,
        cohort_id=cohort_id,
        output_properties=output_properties,
        distinct_id=distinct_id,
        distinct_ids=distinct_ids,
        group_id=group_id,
        behaviors=behaviors,
        as_of_timestamp=as_of_timestamp,
        include_all_users=include_all_users,
    )

    if raw:
        yield from profile_iterator
    else:
        for profile in profile_iterator:
            yield transform_profile(profile)

query

query(
    events: str
    | Metric
    | CohortMetric
    | Formula
    | Sequence[str | Metric | CohortMetric | Formula],
    *,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: MathType = "total",
    math_property: str | None = None,
    per_user: PerUserAggregation | None = None,
    percentile_value: int | float | None = None,
    group_by: str
    | GroupBy
    | CohortBreakdown
    | FrequencyBreakdown
    | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown]
    | None = None,
    where: Filter
    | FrequencyFilter
    | list[Filter | FrequencyFilter]
    | None = None,
    formula: str | None = None,
    formula_label: str | None = None,
    rolling: int | None = None,
    cumulative: bool = False,
    mode: Literal["timeseries", "total", "table"] = "timeseries",
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> QueryResult

Run a typed insights query against the Mixpanel API.

Generates bookmark params from keyword arguments, POSTs them inline to /api/query/insights, and returns a structured QueryResult with lazy DataFrame conversion.

PARAMETER DESCRIPTION
events

Event name(s) to query. Accepts a single string, a Metric object, a CohortMetric object, a Formula object, or a sequence mixing strings, Metrics, CohortMetrics, and Formulas. Formula objects in the list are extracted and appended as formula show clauses. When events includes a CohortMetric, math, math_property, and per_user are silently ignored for that entry — cohort size is always counted as unique users (CM3).

TYPE: str | Metric | CohortMetric | Formula | Sequence[str | Metric | CohortMetric | Formula]

from_date

Start date (YYYY-MM-DD). If set, overrides last.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD). Requires from_date.

TYPE: str | None DEFAULT: None

last

Relative time range in days. Default: 30. Ignored if from_date is set.

TYPE: int DEFAULT: 30

unit

Time aggregation unit. Default: "day".

TYPE: QueryTimeUnit DEFAULT: 'day'

math

Aggregation function for plain-string events. Default: "total".

TYPE: MathType DEFAULT: 'total'

math_property

Property name for property-based math (average, sum, percentiles).

TYPE: str | None DEFAULT: None

per_user

Per-user pre-aggregation (average, total, min, max).

TYPE: PerUserAggregation | None DEFAULT: None

percentile_value

Custom percentile value (e.g. 95 for p95). Required when math="percentile". Maps to percentile in bookmark measurement. Ignored for other math types.

TYPE: int | float | None DEFAULT: None

group_by

Break down results by property or cohort membership. Accepts a string, GroupBy, CohortBreakdown, or list of any mix.

TYPE: str | GroupBy | CohortBreakdown | FrequencyBreakdown | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown] | None DEFAULT: None

where

Filter results by conditions. Accepts a Filter or list of Filters.

TYPE: Filter | FrequencyFilter | list[Filter | FrequencyFilter] | None DEFAULT: None

formula

Formula expression referencing events by position (A, B, C...). Requires 2+ events. Cannot be combined with Formula objects in events.

TYPE: str | None DEFAULT: None

formula_label

Display label for formula result.

TYPE: str | None DEFAULT: None

rolling

Rolling window size in periods. Mutually exclusive with cumulative.

TYPE: int | None DEFAULT: None

cumulative

Enable cumulative analysis mode. Mutually exclusive with rolling.

TYPE: bool DEFAULT: False

mode

Result shape. "timeseries" returns per-period data, "total" returns a single aggregate, "table" returns tabular data. Default: "timeseries".

TYPE: Literal['timeseries', 'total', 'table'] DEFAULT: 'timeseries'

time_comparison

Optional period-over-period comparison. Use TimeComparison.relative("month") for previous month, TimeComparison.absolute_start("2026-01-01") for a fixed start date, etc. Default: None.

TYPE: TimeComparison | None DEFAULT: None

data_group_id

Optional data group ID for group-level analytics. Scopes the query to a specific data group. Default: None.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
QueryResult

QueryResult with series data, DataFrame, and metadata.

RAISES DESCRIPTION
ValueError

If arguments violate validation rules.

ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials.

QueryError

Invalid query parameters.

RateLimitError

Rate limit exceeded.

Example
ws = Workspace()

# Simple event query
result = ws.query("Login")
print(result.df.head())

# With aggregation and time range
result = ws.query("Login", math="unique", last=7, unit="day")

# Multi-event with formula (top-level parameter)
result = ws.query(
    [Metric("Signup", math="unique"), Metric("Purchase", math="unique")],
    formula="(B / A) * 100",
    formula_label="Conversion Rate",
)

# Multi-event with formula (Formula in list)
result = ws.query(
    [Metric("Signup", math="unique"),
     Metric("Purchase", math="unique"),
     Formula("(B / A) * 100", label="Conversion Rate")],
)
Source code in src/mixpanel_data/workspace.py
def query(
    self,
    events: str
    | Metric
    | CohortMetric
    | Formula
    | Sequence[str | Metric | CohortMetric | Formula],
    *,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: MathType = "total",
    math_property: str | None = None,
    per_user: PerUserAggregation | None = None,
    percentile_value: int | float | None = None,
    group_by: str
    | GroupBy
    | CohortBreakdown
    | FrequencyBreakdown
    | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown]
    | None = None,
    where: Filter | FrequencyFilter | list[Filter | FrequencyFilter] | None = None,
    formula: str | None = None,
    formula_label: str | None = None,
    rolling: int | None = None,
    cumulative: bool = False,
    mode: Literal["timeseries", "total", "table"] = "timeseries",
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> QueryResult:
    """Run a typed insights query against the Mixpanel API.

    Generates bookmark params from keyword arguments, POSTs them inline
    to ``/api/query/insights``, and returns a structured QueryResult
    with lazy DataFrame conversion.

    Args:
        events: Event name(s) to query. Accepts a single string,
            a Metric object, a CohortMetric object, a Formula
            object, or a sequence mixing strings, Metrics,
            CohortMetrics, and Formulas. Formula objects in the
            list are extracted and appended as formula show clauses.
            When events includes a CohortMetric, ``math``,
            ``math_property``, and ``per_user`` are silently
            ignored for that entry — cohort size is always counted
            as unique users (CM3).
        from_date: Start date (YYYY-MM-DD). If set, overrides ``last``.
        to_date: End date (YYYY-MM-DD). Requires ``from_date``.
        last: Relative time range in days. Default: 30.
            Ignored if ``from_date`` is set.
        unit: Time aggregation unit. Default: ``"day"``.
        math: Aggregation function for plain-string events.
            Default: ``"total"``.
        math_property: Property name for property-based math
            (average, sum, percentiles).
        per_user: Per-user pre-aggregation (average, total, min, max).
        percentile_value: Custom percentile value (e.g. 95 for p95).
            Required when ``math="percentile"``. Maps to ``percentile``
            in bookmark measurement. Ignored for other math types.
        group_by: Break down results by property or cohort membership.
            Accepts a string, ``GroupBy``, ``CohortBreakdown``, or
            list of any mix.
        where: Filter results by conditions. Accepts a Filter
            or list of Filters.
        formula: Formula expression referencing events by position
            (A, B, C...). Requires 2+ events. Cannot be combined
            with Formula objects in ``events``.
        formula_label: Display label for formula result.
        rolling: Rolling window size in periods.
            Mutually exclusive with ``cumulative``.
        cumulative: Enable cumulative analysis mode.
            Mutually exclusive with ``rolling``.
        mode: Result shape. ``"timeseries"`` returns per-period data,
            ``"total"`` returns a single aggregate, ``"table"`` returns
            tabular data. Default: ``"timeseries"``.
        time_comparison: Optional period-over-period comparison.
            Use ``TimeComparison.relative("month")`` for previous
            month, ``TimeComparison.absolute_start("2026-01-01")``
            for a fixed start date, etc. Default: ``None``.
        data_group_id: Optional data group ID for group-level
            analytics. Scopes the query to a specific data group.
            Default: ``None``.

    Returns:
        QueryResult with series data, DataFrame, and metadata.

    Raises:
        ValueError: If arguments violate validation rules.
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials.
        QueryError: Invalid query parameters.
        RateLimitError: Rate limit exceeded.

    Example:
        ```python
        ws = Workspace()

        # Simple event query
        result = ws.query("Login")
        print(result.df.head())

        # With aggregation and time range
        result = ws.query("Login", math="unique", last=7, unit="day")

        # Multi-event with formula (top-level parameter)
        result = ws.query(
            [Metric("Signup", math="unique"), Metric("Purchase", math="unique")],
            formula="(B / A) * 100",
            formula_label="Conversion Rate",
        )

        # Multi-event with formula (Formula in list)
        result = ws.query(
            [Metric("Signup", math="unique"),
             Metric("Purchase", math="unique"),
             Formula("(B / A) * 100", label="Conversion Rate")],
        )
        ```
    """
    params = self._resolve_and_build_params(
        events=events,
        from_date=from_date,
        to_date=to_date,
        last=last,
        unit=unit,
        math=math,
        math_property=math_property,
        per_user=per_user,
        percentile_value=percentile_value,
        group_by=group_by,
        where=where,
        formula=formula,
        formula_label=formula_label,
        rolling=rolling,
        cumulative=cumulative,
        mode=mode,
        time_comparison=time_comparison,
        data_group_id=data_group_id,
    )

    return self._live_query_service.query(
        bookmark_params=params,
        project_id=int(self._session.project.id),
    )

build_params

build_params(
    events: str
    | Metric
    | CohortMetric
    | Formula
    | Sequence[str | Metric | CohortMetric | Formula],
    *,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: MathType = "total",
    math_property: str | None = None,
    per_user: PerUserAggregation | None = None,
    percentile_value: int | float | None = None,
    group_by: str
    | GroupBy
    | CohortBreakdown
    | FrequencyBreakdown
    | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown]
    | None = None,
    where: Filter
    | FrequencyFilter
    | list[Filter | FrequencyFilter]
    | None = None,
    formula: str | None = None,
    formula_label: str | None = None,
    rolling: int | None = None,
    cumulative: bool = False,
    mode: Literal["timeseries", "total", "table"] = "timeseries",
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> dict[str, Any]

Build validated bookmark params without executing the API call.

Has the same signature as :meth:query but returns the generated bookmark params dict instead of querying the Mixpanel API. Useful for debugging, inspecting generated JSON, persisting via :meth:create_bookmark, or testing.

PARAMETER DESCRIPTION
events

Event name(s) to query. Accepts a single string, a Metric, CohortMetric, Formula, or a sequence mixing strings, Metrics, CohortMetrics, and Formulas.

TYPE: str | Metric | CohortMetric | Formula | Sequence[str | Metric | CohortMetric | Formula]

from_date

Start date (YYYY-MM-DD). If set, overrides last.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD). Requires from_date.

TYPE: str | None DEFAULT: None

last

Relative time range in days. Default: 30.

TYPE: int DEFAULT: 30

unit

Time aggregation unit. Default: "day".

TYPE: QueryTimeUnit DEFAULT: 'day'

math

Aggregation function for plain-string events. Default: "total".

TYPE: MathType DEFAULT: 'total'

math_property

Property name for property-based math.

TYPE: str | None DEFAULT: None

per_user

Per-user pre-aggregation.

TYPE: PerUserAggregation | None DEFAULT: None

percentile_value

Custom percentile value (e.g. 95). Required when math="percentile".

TYPE: int | float | None DEFAULT: None

group_by

Break down results by property or cohort membership. Accepts a string, GroupBy, CohortBreakdown, or list of any mix.

TYPE: str | GroupBy | CohortBreakdown | FrequencyBreakdown | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown] | None DEFAULT: None

where

Filter results by conditions.

TYPE: Filter | FrequencyFilter | list[Filter | FrequencyFilter] | None DEFAULT: None

formula

Formula expression referencing events by position.

TYPE: str | None DEFAULT: None

formula_label

Display label for formula result.

TYPE: str | None DEFAULT: None

rolling

Rolling window size in periods.

TYPE: int | None DEFAULT: None

cumulative

Enable cumulative analysis mode.

TYPE: bool DEFAULT: False

mode

Result shape. Default: "timeseries".

TYPE: Literal['timeseries', 'total', 'table'] DEFAULT: 'timeseries'

time_comparison

Optional period-over-period comparison. Use TimeComparison.relative("month") for previous month, etc. Default: None.

TYPE: TimeComparison | None DEFAULT: None

data_group_id

Optional data group ID for group-level analytics. Scopes the query to a specific data group. Default: None.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
dict[str, Any]

Bookmark params dict with sections and displayOptions

dict[str, Any]

keys, ready for use with the insights API or

dict[str, Any]

meth:create_bookmark.

RAISES DESCRIPTION
BookmarkValidationError

If arguments violate validation rules.

Example
ws = Workspace()

# Inspect generated bookmark JSON
params = ws.build_params("Login", math="unique", last=7)
print(json.dumps(params, indent=2))

# Save as a bookmark (dashboard_id required)
ws.create_bookmark(CreateBookmarkParams(
    name="Daily Unique Logins",
    bookmark_type="insights",
    params=params,
    dashboard_id=12345,
))
Source code in src/mixpanel_data/workspace.py
def build_params(
    self,
    events: str
    | Metric
    | CohortMetric
    | Formula
    | Sequence[str | Metric | CohortMetric | Formula],
    *,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: MathType = "total",
    math_property: str | None = None,
    per_user: PerUserAggregation | None = None,
    percentile_value: int | float | None = None,
    group_by: str
    | GroupBy
    | CohortBreakdown
    | FrequencyBreakdown
    | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown]
    | None = None,
    where: Filter | FrequencyFilter | list[Filter | FrequencyFilter] | None = None,
    formula: str | None = None,
    formula_label: str | None = None,
    rolling: int | None = None,
    cumulative: bool = False,
    mode: Literal["timeseries", "total", "table"] = "timeseries",
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> dict[str, Any]:
    """Build validated bookmark params without executing the API call.

    Has the same signature as :meth:`query` but returns the generated
    bookmark params dict instead of querying the Mixpanel API. Useful
    for debugging, inspecting generated JSON, persisting via
    :meth:`create_bookmark`, or testing.

    Args:
        events: Event name(s) to query. Accepts a single string,
            a ``Metric``, ``CohortMetric``, ``Formula``, or a
            sequence mixing strings, ``Metric``s, ``CohortMetric``s,
            and ``Formula``s.
        from_date: Start date (YYYY-MM-DD). If set, overrides ``last``.
        to_date: End date (YYYY-MM-DD). Requires ``from_date``.
        last: Relative time range in days. Default: 30.
        unit: Time aggregation unit. Default: ``"day"``.
        math: Aggregation function for plain-string events.
            Default: ``"total"``.
        math_property: Property name for property-based math.
        per_user: Per-user pre-aggregation.
        percentile_value: Custom percentile value (e.g. 95).
            Required when ``math="percentile"``.
        group_by: Break down results by property or cohort membership.
            Accepts a string, ``GroupBy``, ``CohortBreakdown``, or
            list of any mix.
        where: Filter results by conditions.
        formula: Formula expression referencing events by position.
        formula_label: Display label for formula result.
        rolling: Rolling window size in periods.
        cumulative: Enable cumulative analysis mode.
        mode: Result shape. Default: ``"timeseries"``.
        time_comparison: Optional period-over-period comparison.
            Use ``TimeComparison.relative("month")`` for previous
            month, etc. Default: ``None``.
        data_group_id: Optional data group ID for group-level
            analytics. Scopes the query to a specific data group.
            Default: ``None``.

    Returns:
        Bookmark params dict with ``sections`` and ``displayOptions``
        keys, ready for use with the insights API or
        :meth:`create_bookmark`.

    Raises:
        BookmarkValidationError: If arguments violate validation rules.

    Example:
        ```python
        ws = Workspace()

        # Inspect generated bookmark JSON
        params = ws.build_params("Login", math="unique", last=7)
        print(json.dumps(params, indent=2))

        # Save as a bookmark (dashboard_id required)
        ws.create_bookmark(CreateBookmarkParams(
            name="Daily Unique Logins",
            bookmark_type="insights",
            params=params,
            dashboard_id=12345,
        ))
        ```
    """
    return self._resolve_and_build_params(
        events=events,
        from_date=from_date,
        to_date=to_date,
        last=last,
        unit=unit,
        math=math,
        math_property=math_property,
        per_user=per_user,
        percentile_value=percentile_value,
        group_by=group_by,
        where=where,
        formula=formula,
        formula_label=formula_label,
        rolling=rolling,
        cumulative=cumulative,
        mode=mode,
        time_comparison=time_comparison,
        data_group_id=data_group_id,
    )

query_funnel

query_funnel(
    steps: list[str | FunnelStep],
    *,
    conversion_window: int = 14,
    conversion_window_unit: Literal[
        "second", "minute", "hour", "day", "week", "month", "session"
    ] = "day",
    order: Literal["loose", "any"] = "loose",
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: FunnelMathType = "conversion_rate_unique",
    math_property: str | None = None,
    group_by: str
    | GroupBy
    | CohortBreakdown
    | list[str | GroupBy | CohortBreakdown]
    | None = None,
    where: Filter | list[Filter] | None = None,
    exclusions: list[str | Exclusion] | None = None,
    holding_constant: str
    | HoldingConstant
    | list[str | HoldingConstant]
    | None = None,
    mode: Literal["steps", "trends", "table"] = "steps",
    reentry_mode: FunnelReentryMode | None = None,
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> FunnelQueryResult

Run a typed funnel query against the Mixpanel API.

Generates funnel bookmark params from keyword arguments, POSTs them inline to /api/query/insights, and returns a structured FunnelQueryResult with lazy DataFrame conversion.

PARAMETER DESCRIPTION
steps

Funnel step specifications. At least 2 required. Accepts event name strings or FunnelStep objects for per-step filters, labels, and ordering.

TYPE: list[str | FunnelStep]

conversion_window

How long users have to complete the funnel. Default: 14.

TYPE: int DEFAULT: 14

conversion_window_unit

Time unit for conversion window. Default: "day".

TYPE: Literal['second', 'minute', 'hour', 'day', 'week', 'month', 'session'] DEFAULT: 'day'

order

Step ordering mode. "loose" requires steps in order but allows other events between. "any" allows steps in any order. Default: "loose".

TYPE: Literal['loose', 'any'] DEFAULT: 'loose'

from_date

Start date (YYYY-MM-DD). If set, overrides last.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD). Requires from_date.

TYPE: str | None DEFAULT: None

last

Relative time range in days. Default: 30.

TYPE: int DEFAULT: 30

unit

Time aggregation unit. Default: "day".

TYPE: QueryTimeUnit DEFAULT: 'day'

math

Funnel aggregation function. Default: "conversion_rate_unique".

TYPE: FunnelMathType DEFAULT: 'conversion_rate_unique'

math_property

Numeric property name for property-aggregation math types ("average", "median", "min", "max", "p25", "p75", "p90", "p99"). Required when using those math types; must be None for count/rate math types. Default: None.

TYPE: str | None DEFAULT: None

group_by

Break down results by property or cohort membership. Accepts a string, GroupBy, CohortBreakdown, or list of any mix.

TYPE: str | GroupBy | CohortBreakdown | list[str | GroupBy | CohortBreakdown] | None DEFAULT: None

where

Filter results by conditions.

TYPE: Filter | list[Filter] | None DEFAULT: None

exclusions

Events to exclude between steps. Accepts event name strings or Exclusion objects.

TYPE: list[str | Exclusion] | None DEFAULT: None

holding_constant

Properties to hold constant across steps. Accepts strings, HoldingConstant objects, or a list mixing both.

TYPE: str | HoldingConstant | list[str | HoldingConstant] | None DEFAULT: None

mode

Result display mode. "steps" shows step-level data, "trends" shows conversion over time, "table" shows tabular breakdown. Default: "steps".

TYPE: Literal['steps', 'trends', 'table'] DEFAULT: 'steps'

reentry_mode

Funnel reentry mode controlling how users re-enter the funnel after conversion. One of "default", "basic", "aggressive", or "optimized". Default: None (server default).

TYPE: FunnelReentryMode | None DEFAULT: None

time_comparison

Optional period-over-period comparison. Use TimeComparison.relative("month") for previous month, etc. Default: None.

TYPE: TimeComparison | None DEFAULT: None

data_group_id

Optional data group ID for group-level analytics. Scopes the query to a specific data group. Default: None.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
FunnelQueryResult

FunnelQueryResult with step data, DataFrame, and metadata.

RAISES DESCRIPTION
BookmarkValidationError

If arguments violate validation rules (before API call).

ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials.

QueryError

Invalid query parameters.

RateLimitError

Rate limit exceeded.

Example
ws = Workspace()

# Simple two-step funnel
result = ws.query_funnel(["Signup", "Purchase"])
print(result.overall_conversion_rate)

# Configured funnel
result = ws.query_funnel(
    ["Signup", "Add to Cart", "Checkout", "Purchase"],
    conversion_window=7,
    order="loose",
    last=90,
)
print(result.df)
Source code in src/mixpanel_data/workspace.py
def query_funnel(
    self,
    steps: list[str | FunnelStep],
    *,
    conversion_window: int = 14,
    conversion_window_unit: Literal[
        "second", "minute", "hour", "day", "week", "month", "session"
    ] = "day",
    order: Literal["loose", "any"] = "loose",
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: FunnelMathType = "conversion_rate_unique",
    math_property: str | None = None,
    group_by: str
    | GroupBy
    | CohortBreakdown
    | list[str | GroupBy | CohortBreakdown]
    | None = None,
    where: Filter | list[Filter] | None = None,
    exclusions: list[str | Exclusion] | None = None,
    holding_constant: (
        str | HoldingConstant | list[str | HoldingConstant] | None
    ) = None,
    mode: Literal["steps", "trends", "table"] = "steps",
    reentry_mode: FunnelReentryMode | None = None,
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> FunnelQueryResult:
    """Run a typed funnel query against the Mixpanel API.

    Generates funnel bookmark params from keyword arguments, POSTs
    them inline to ``/api/query/insights``, and returns a structured
    FunnelQueryResult with lazy DataFrame conversion.

    Args:
        steps: Funnel step specifications. At least 2 required.
            Accepts event name strings or ``FunnelStep`` objects
            for per-step filters, labels, and ordering.
        conversion_window: How long users have to complete the
            funnel. Default: 14.
        conversion_window_unit: Time unit for conversion window.
            Default: ``"day"``.
        order: Step ordering mode. ``"loose"`` requires steps in
            order but allows other events between. ``"any"`` allows
            steps in any order. Default: ``"loose"``.
        from_date: Start date (YYYY-MM-DD). If set, overrides
            ``last``.
        to_date: End date (YYYY-MM-DD). Requires ``from_date``.
        last: Relative time range in days. Default: 30.
        unit: Time aggregation unit. Default: ``"day"``.
        math: Funnel aggregation function. Default:
            ``"conversion_rate_unique"``.
        math_property: Numeric property name for property-aggregation
            math types (``"average"``, ``"median"``, ``"min"``,
            ``"max"``, ``"p25"``, ``"p75"``, ``"p90"``, ``"p99"``).
            Required when using those math types; must be ``None``
            for count/rate math types. Default: ``None``.
        group_by: Break down results by property or cohort
            membership. Accepts a string, ``GroupBy``,
            ``CohortBreakdown``, or list of any mix.
        where: Filter results by conditions.
        exclusions: Events to exclude between steps. Accepts
            event name strings or ``Exclusion`` objects.
        holding_constant: Properties to hold constant across
            steps. Accepts strings, ``HoldingConstant`` objects,
            or a list mixing both.
        mode: Result display mode. ``"steps"`` shows step-level
            data, ``"trends"`` shows conversion over time,
            ``"table"`` shows tabular breakdown. Default:
            ``"steps"``.
        reentry_mode: Funnel reentry mode controlling how users
            re-enter the funnel after conversion. One of
            ``"default"``, ``"basic"``, ``"aggressive"``, or
            ``"optimized"``. Default: ``None`` (server default).
        time_comparison: Optional period-over-period comparison.
            Use ``TimeComparison.relative("month")`` for previous
            month, etc. Default: ``None``.
        data_group_id: Optional data group ID for group-level
            analytics. Scopes the query to a specific data group.
            Default: ``None``.

    Returns:
        FunnelQueryResult with step data, DataFrame, and metadata.

    Raises:
        BookmarkValidationError: If arguments violate validation
            rules (before API call).
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials.
        QueryError: Invalid query parameters.
        RateLimitError: Rate limit exceeded.

    Example:
        ```python
        ws = Workspace()

        # Simple two-step funnel
        result = ws.query_funnel(["Signup", "Purchase"])
        print(result.overall_conversion_rate)

        # Configured funnel
        result = ws.query_funnel(
            ["Signup", "Add to Cart", "Checkout", "Purchase"],
            conversion_window=7,
            order="loose",
            last=90,
        )
        print(result.df)
        ```
    """
    params = self._resolve_and_build_funnel_params(
        steps=steps,
        conversion_window=conversion_window,
        conversion_window_unit=conversion_window_unit,
        order=order,
        math=math,
        math_property=math_property,
        from_date=from_date,
        to_date=to_date,
        last=last,
        unit=unit,
        group_by=group_by,
        where=where,
        exclusions=exclusions,
        holding_constant=holding_constant,
        mode=mode,
        reentry_mode=reentry_mode,
        time_comparison=time_comparison,
        data_group_id=data_group_id,
    )

    return self._live_query_service.query_funnel(
        bookmark_params=params,
        project_id=int(self._session.project.id),
    )

build_funnel_params

build_funnel_params(
    steps: list[str | FunnelStep],
    *,
    conversion_window: int = 14,
    conversion_window_unit: Literal[
        "second", "minute", "hour", "day", "week", "month", "session"
    ] = "day",
    order: Literal["loose", "any"] = "loose",
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: FunnelMathType = "conversion_rate_unique",
    math_property: str | None = None,
    group_by: str
    | GroupBy
    | CohortBreakdown
    | list[str | GroupBy | CohortBreakdown]
    | None = None,
    where: Filter | list[Filter] | None = None,
    exclusions: list[str | Exclusion] | None = None,
    holding_constant: str
    | HoldingConstant
    | list[str | HoldingConstant]
    | None = None,
    mode: Literal["steps", "trends", "table"] = "steps",
    reentry_mode: FunnelReentryMode | None = None,
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> dict[str, Any]

Build validated funnel bookmark params without executing.

Has the same signature as :meth:query_funnel but returns the generated bookmark params dict instead of querying the API. Useful for debugging, inspecting generated JSON, persisting via :meth:create_bookmark, or testing.

PARAMETER DESCRIPTION
steps

Funnel step specifications. At least 2 required.

TYPE: list[str | FunnelStep]

conversion_window

Conversion window size. Default: 14.

TYPE: int DEFAULT: 14

conversion_window_unit

Time unit. Default: "day".

TYPE: Literal['second', 'minute', 'hour', 'day', 'week', 'month', 'session'] DEFAULT: 'day'

order

Step ordering mode. Default: "loose".

TYPE: Literal['loose', 'any'] DEFAULT: 'loose'

from_date

Start date (YYYY-MM-DD) or None.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD) or None.

TYPE: str | None DEFAULT: None

last

Relative time range in days. Default: 30.

TYPE: int DEFAULT: 30

unit

Time aggregation unit. Default: "day".

TYPE: QueryTimeUnit DEFAULT: 'day'

math

Aggregation function. Default: "conversion_rate_unique".

TYPE: FunnelMathType DEFAULT: 'conversion_rate_unique'

math_property

Numeric property name for property-aggregation math types. Required for "average", "median", etc. Default: None.

TYPE: str | None DEFAULT: None

group_by

Break down results by property or cohort membership. Accepts a string, GroupBy, CohortBreakdown, or list of any mix.

TYPE: str | GroupBy | CohortBreakdown | list[str | GroupBy | CohortBreakdown] | None DEFAULT: None

where

Filter results by conditions.

TYPE: Filter | list[Filter] | None DEFAULT: None

exclusions

Events to exclude between steps.

TYPE: list[str | Exclusion] | None DEFAULT: None

holding_constant

Properties to hold constant.

TYPE: str | HoldingConstant | list[str | HoldingConstant] | None DEFAULT: None

mode

Display mode. Default: "steps".

TYPE: Literal['steps', 'trends', 'table'] DEFAULT: 'steps'

reentry_mode

Funnel reentry mode controlling how users re-enter the funnel after conversion. One of "default", "basic", "aggressive", or "optimized". Default: None (server default).

TYPE: FunnelReentryMode | None DEFAULT: None

time_comparison

Optional period-over-period comparison. Use TimeComparison.relative("month") for previous month, etc. Default: None.

TYPE: TimeComparison | None DEFAULT: None

data_group_id

Optional data group ID for group-level analytics. Scopes the query to a specific data group. Default: None.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
dict[str, Any]

Bookmark params dict with sections and

dict[str, Any]

displayOptions keys.

RAISES DESCRIPTION
BookmarkValidationError

If arguments violate validation rules.

Example
ws = Workspace()

# Inspect generated JSON
params = ws.build_funnel_params(["Signup", "Purchase"])
print(json.dumps(params, indent=2))

# Save as a report (dashboard_id required)
ws.create_bookmark(CreateBookmarkParams(
    name="Signup → Purchase Funnel",
    bookmark_type="funnels",
    params=params,
    dashboard_id=12345,
))
Source code in src/mixpanel_data/workspace.py
def build_funnel_params(
    self,
    steps: list[str | FunnelStep],
    *,
    conversion_window: int = 14,
    conversion_window_unit: Literal[
        "second", "minute", "hour", "day", "week", "month", "session"
    ] = "day",
    order: Literal["loose", "any"] = "loose",
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: FunnelMathType = "conversion_rate_unique",
    math_property: str | None = None,
    group_by: str
    | GroupBy
    | CohortBreakdown
    | list[str | GroupBy | CohortBreakdown]
    | None = None,
    where: Filter | list[Filter] | None = None,
    exclusions: list[str | Exclusion] | None = None,
    holding_constant: (
        str | HoldingConstant | list[str | HoldingConstant] | None
    ) = None,
    mode: Literal["steps", "trends", "table"] = "steps",
    reentry_mode: FunnelReentryMode | None = None,
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> dict[str, Any]:
    """Build validated funnel bookmark params without executing.

    Has the same signature as :meth:`query_funnel` but returns the
    generated bookmark params dict instead of querying the API.
    Useful for debugging, inspecting generated JSON, persisting
    via :meth:`create_bookmark`, or testing.

    Args:
        steps: Funnel step specifications. At least 2 required.
        conversion_window: Conversion window size. Default: 14.
        conversion_window_unit: Time unit. Default: ``"day"``.
        order: Step ordering mode. Default: ``"loose"``.
        from_date: Start date (YYYY-MM-DD) or None.
        to_date: End date (YYYY-MM-DD) or None.
        last: Relative time range in days. Default: 30.
        unit: Time aggregation unit. Default: ``"day"``.
        math: Aggregation function. Default:
            ``"conversion_rate_unique"``.
        math_property: Numeric property name for property-aggregation
            math types. Required for ``"average"``, ``"median"``,
            etc. Default: ``None``.
        group_by: Break down results by property or cohort
            membership. Accepts a string, ``GroupBy``,
            ``CohortBreakdown``, or list of any mix.
        where: Filter results by conditions.
        exclusions: Events to exclude between steps.
        holding_constant: Properties to hold constant.
        mode: Display mode. Default: ``"steps"``.
        reentry_mode: Funnel reentry mode controlling how users
            re-enter the funnel after conversion. One of
            ``"default"``, ``"basic"``, ``"aggressive"``, or
            ``"optimized"``. Default: ``None`` (server default).
        time_comparison: Optional period-over-period comparison.
            Use ``TimeComparison.relative("month")`` for previous
            month, etc. Default: ``None``.
        data_group_id: Optional data group ID for group-level
            analytics. Scopes the query to a specific data group.
            Default: ``None``.

    Returns:
        Bookmark params dict with ``sections`` and
        ``displayOptions`` keys.

    Raises:
        BookmarkValidationError: If arguments violate validation
            rules.

    Example:
        ```python
        ws = Workspace()

        # Inspect generated JSON
        params = ws.build_funnel_params(["Signup", "Purchase"])
        print(json.dumps(params, indent=2))

        # Save as a report (dashboard_id required)
        ws.create_bookmark(CreateBookmarkParams(
            name="Signup → Purchase Funnel",
            bookmark_type="funnels",
            params=params,
            dashboard_id=12345,
        ))
        ```
    """
    return self._resolve_and_build_funnel_params(
        steps=steps,
        conversion_window=conversion_window,
        conversion_window_unit=conversion_window_unit,
        order=order,
        math=math,
        math_property=math_property,
        from_date=from_date,
        to_date=to_date,
        last=last,
        unit=unit,
        group_by=group_by,
        where=where,
        exclusions=exclusions,
        holding_constant=holding_constant,
        mode=mode,
        reentry_mode=reentry_mode,
        time_comparison=time_comparison,
        data_group_id=data_group_id,
    )

query_retention

query_retention(
    born_event: str | RetentionEvent,
    return_event: str | RetentionEvent,
    *,
    retention_unit: TimeUnit = "week",
    alignment: RetentionAlignment = "birth",
    bucket_sizes: list[int] | None = None,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: RetentionMathType = "retention_rate",
    group_by: str
    | GroupBy
    | CohortBreakdown
    | list[str | GroupBy | CohortBreakdown]
    | None = None,
    where: Filter | list[Filter] | None = None,
    mode: RetentionMode = "curve",
    unbounded_mode: RetentionUnboundedMode | None = None,
    retention_cumulative: bool = False,
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> RetentionQueryResult

Run a typed retention query against the Mixpanel API.

Generates retention bookmark params from keyword arguments, POSTs them inline to /api/query/insights, and returns a structured RetentionQueryResult with lazy DataFrame conversion.

PARAMETER DESCRIPTION
born_event

Event that defines cohort membership. Accepts an event name string or a RetentionEvent object for per-event filters.

TYPE: str | RetentionEvent

return_event

Event that defines return. Accepts an event name string or a RetentionEvent object.

TYPE: str | RetentionEvent

retention_unit

Retention period unit. Default: "week".

TYPE: TimeUnit DEFAULT: 'week'

alignment

Retention alignment mode. Default: "birth".

TYPE: RetentionAlignment DEFAULT: 'birth'

bucket_sizes

Custom bucket sizes (positive ints in ascending order). Default: None (uniform buckets).

TYPE: list[int] | None DEFAULT: None

from_date

Start date (YYYY-MM-DD). If set, overrides last.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD). Requires from_date.

TYPE: str | None DEFAULT: None

last

Relative time range in days. Default: 30.

TYPE: int DEFAULT: 30

unit

Time aggregation unit (day, week, or monthhour is not supported for retention). Default: "day".

TYPE: QueryTimeUnit DEFAULT: 'day'

math

Retention aggregation function. Default: "retention_rate".

TYPE: RetentionMathType DEFAULT: 'retention_rate'

group_by

Break down results by property or cohort membership. Accepts a string, GroupBy, CohortBreakdown, or list of any mix.

TYPE: str | GroupBy | CohortBreakdown | list[str | GroupBy | CohortBreakdown] | None DEFAULT: None

where

Filter results by conditions.

TYPE: Filter | list[Filter] | None DEFAULT: None

mode

Result display mode. Default: "curve".

TYPE: RetentionMode DEFAULT: 'curve'

unbounded_mode

Retention unbounded mode controlling how retention is counted in unbounded periods. One of "none", "carry_back", "carry_forward", or "consecutive_forward". Default: None (server default).

TYPE: RetentionUnboundedMode | None DEFAULT: None

retention_cumulative

Whether to use cumulative retention counting. Default: False.

TYPE: bool DEFAULT: False

time_comparison

Optional period-over-period comparison. Use TimeComparison.relative("month") for previous month, etc. Default: None.

TYPE: TimeComparison | None DEFAULT: None

data_group_id

Optional data group ID for group-level analytics. Scopes the query to a specific data group. Default: None.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
RetentionQueryResult

RetentionQueryResult with cohort data, DataFrame, and

RetentionQueryResult

metadata.

RAISES DESCRIPTION
BookmarkValidationError

If arguments violate validation rules (before API call).

ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials.

QueryError

Invalid query parameters.

RateLimitError

Rate limit exceeded.

Example
ws = Workspace()

# Simple retention query
result = ws.query_retention("Signup", "Login")
print(result.average)

# With configuration
result = ws.query_retention(
    "Signup", "Login",
    retention_unit="day",
    bucket_sizes=[1, 3, 7, 14, 30],
    last=90,
)
print(result.df)
Source code in src/mixpanel_data/workspace.py
def query_retention(
    self,
    born_event: str | RetentionEvent,
    return_event: str | RetentionEvent,
    *,
    retention_unit: TimeUnit = "week",
    alignment: RetentionAlignment = "birth",
    bucket_sizes: list[int] | None = None,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: RetentionMathType = "retention_rate",
    group_by: str
    | GroupBy
    | CohortBreakdown
    | list[str | GroupBy | CohortBreakdown]
    | None = None,
    where: Filter | list[Filter] | None = None,
    mode: RetentionMode = "curve",
    unbounded_mode: RetentionUnboundedMode | None = None,
    retention_cumulative: bool = False,
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> RetentionQueryResult:
    """Run a typed retention query against the Mixpanel API.

    Generates retention bookmark params from keyword arguments, POSTs
    them inline to ``/api/query/insights``, and returns a structured
    RetentionQueryResult with lazy DataFrame conversion.

    Args:
        born_event: Event that defines cohort membership. Accepts
            an event name string or a ``RetentionEvent`` object
            for per-event filters.
        return_event: Event that defines return. Accepts an event
            name string or a ``RetentionEvent`` object.
        retention_unit: Retention period unit. Default: ``"week"``.
        alignment: Retention alignment mode. Default: ``"birth"``.
        bucket_sizes: Custom bucket sizes (positive ints in
            ascending order). Default: ``None`` (uniform buckets).
        from_date: Start date (YYYY-MM-DD). If set, overrides
            ``last``.
        to_date: End date (YYYY-MM-DD). Requires ``from_date``.
        last: Relative time range in days. Default: 30.
        unit: Time aggregation unit (``day``, ``week``, or
            ``month`` — ``hour`` is not supported for retention).
            Default: ``"day"``.
        math: Retention aggregation function. Default:
            ``"retention_rate"``.
        group_by: Break down results by property or cohort
            membership. Accepts a string, ``GroupBy``,
            ``CohortBreakdown``, or list of any mix.
        where: Filter results by conditions.
        mode: Result display mode. Default: ``"curve"``.
        unbounded_mode: Retention unbounded mode controlling how
            retention is counted in unbounded periods. One of
            ``"none"``, ``"carry_back"``, ``"carry_forward"``, or
            ``"consecutive_forward"``. Default: ``None``
            (server default).
        retention_cumulative: Whether to use cumulative retention
            counting. Default: ``False``.
        time_comparison: Optional period-over-period comparison.
            Use ``TimeComparison.relative("month")`` for previous
            month, etc. Default: ``None``.
        data_group_id: Optional data group ID for group-level
            analytics. Scopes the query to a specific data group.
            Default: ``None``.

    Returns:
        RetentionQueryResult with cohort data, DataFrame, and
        metadata.

    Raises:
        BookmarkValidationError: If arguments violate validation
            rules (before API call).
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials.
        QueryError: Invalid query parameters.
        RateLimitError: Rate limit exceeded.

    Example:
        ```python
        ws = Workspace()

        # Simple retention query
        result = ws.query_retention("Signup", "Login")
        print(result.average)

        # With configuration
        result = ws.query_retention(
            "Signup", "Login",
            retention_unit="day",
            bucket_sizes=[1, 3, 7, 14, 30],
            last=90,
        )
        print(result.df)
        ```
    """
    params = self._resolve_and_build_retention_params(
        born_event=born_event,
        return_event=return_event,
        retention_unit=retention_unit,
        alignment=alignment,
        bucket_sizes=bucket_sizes,
        math=math,
        from_date=from_date,
        to_date=to_date,
        last=last,
        unit=unit,
        group_by=group_by,
        where=where,
        mode=mode,
        unbounded_mode=unbounded_mode,
        retention_cumulative=retention_cumulative,
        time_comparison=time_comparison,
        data_group_id=data_group_id,
    )

    return self._live_query_service.query_retention(
        bookmark_params=params,
        project_id=int(self._session.project.id),
    )

build_retention_params

build_retention_params(
    born_event: str | RetentionEvent,
    return_event: str | RetentionEvent,
    *,
    retention_unit: TimeUnit = "week",
    alignment: RetentionAlignment = "birth",
    bucket_sizes: list[int] | None = None,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: RetentionMathType = "retention_rate",
    group_by: str
    | GroupBy
    | CohortBreakdown
    | list[str | GroupBy | CohortBreakdown]
    | None = None,
    where: Filter | list[Filter] | None = None,
    mode: RetentionMode = "curve",
    unbounded_mode: RetentionUnboundedMode | None = None,
    retention_cumulative: bool = False,
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> dict[str, Any]

Build validated retention bookmark params without executing.

Accepts the same arguments as :meth:query_retention but returns the generated bookmark params dict (not a RetentionQueryResult) instead of querying the API. Useful for debugging, inspecting generated JSON, persisting via :meth:create_bookmark, or testing.

PARAMETER DESCRIPTION
born_event

Event that defines cohort membership.

TYPE: str | RetentionEvent

return_event

Event that defines return.

TYPE: str | RetentionEvent

retention_unit

Retention period unit. Default: "week".

TYPE: TimeUnit DEFAULT: 'week'

alignment

Retention alignment mode. Default: "birth".

TYPE: RetentionAlignment DEFAULT: 'birth'

bucket_sizes

Custom bucket sizes. Default: None.

TYPE: list[int] | None DEFAULT: None

from_date

Start date (YYYY-MM-DD) or None.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD) or None.

TYPE: str | None DEFAULT: None

last

Relative time range in days. Default: 30.

TYPE: int DEFAULT: 30

unit

Time aggregation unit (day, week, or monthhour is not supported for retention). Default: "day".

TYPE: QueryTimeUnit DEFAULT: 'day'

math

Aggregation function. Default: "retention_rate".

TYPE: RetentionMathType DEFAULT: 'retention_rate'

group_by

Break down results by property or cohort membership. Accepts a string, GroupBy, CohortBreakdown, or list of any mix.

TYPE: str | GroupBy | CohortBreakdown | list[str | GroupBy | CohortBreakdown] | None DEFAULT: None

where

Filter results by conditions.

TYPE: Filter | list[Filter] | None DEFAULT: None

mode

Display mode. Default: "curve".

TYPE: RetentionMode DEFAULT: 'curve'

unbounded_mode

Retention unbounded mode controlling how retention is counted in unbounded periods. One of "none", "carry_back", "carry_forward", or "consecutive_forward". Default: None (server default).

TYPE: RetentionUnboundedMode | None DEFAULT: None

retention_cumulative

Whether to use cumulative retention counting. Default: False.

TYPE: bool DEFAULT: False

time_comparison

Optional period-over-period comparison. Use TimeComparison.relative("month") for previous month, etc. Default: None.

TYPE: TimeComparison | None DEFAULT: None

data_group_id

Optional data group ID for group-level analytics. Scopes the query to a specific data group. Default: None.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
dict[str, Any]

Bookmark params dict with sections and

dict[str, Any]

displayOptions keys.

RAISES DESCRIPTION
BookmarkValidationError

If arguments violate validation rules.

Example
ws = Workspace()

# Inspect generated JSON
params = ws.build_retention_params("Signup", "Login")
print(json.dumps(params, indent=2))

# Save as a report (dashboard_id required)
ws.create_bookmark(CreateBookmarkParams(
    name="Signup → Login Retention",
    bookmark_type="retention",
    params=params,
    dashboard_id=12345,
))
Source code in src/mixpanel_data/workspace.py
def build_retention_params(
    self,
    born_event: str | RetentionEvent,
    return_event: str | RetentionEvent,
    *,
    retention_unit: TimeUnit = "week",
    alignment: RetentionAlignment = "birth",
    bucket_sizes: list[int] | None = None,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    unit: QueryTimeUnit = "day",
    math: RetentionMathType = "retention_rate",
    group_by: str
    | GroupBy
    | CohortBreakdown
    | list[str | GroupBy | CohortBreakdown]
    | None = None,
    where: Filter | list[Filter] | None = None,
    mode: RetentionMode = "curve",
    unbounded_mode: RetentionUnboundedMode | None = None,
    retention_cumulative: bool = False,
    time_comparison: TimeComparison | None = None,
    data_group_id: int | None = None,
) -> dict[str, Any]:
    """Build validated retention bookmark params without executing.

    Accepts the same arguments as :meth:`query_retention` but returns
    the generated bookmark params ``dict`` (not a
    ``RetentionQueryResult``) instead of querying the API. Useful for
    debugging, inspecting generated JSON, persisting via
    :meth:`create_bookmark`, or testing.

    Args:
        born_event: Event that defines cohort membership.
        return_event: Event that defines return.
        retention_unit: Retention period unit. Default: ``"week"``.
        alignment: Retention alignment mode. Default: ``"birth"``.
        bucket_sizes: Custom bucket sizes. Default: ``None``.
        from_date: Start date (YYYY-MM-DD) or None.
        to_date: End date (YYYY-MM-DD) or None.
        last: Relative time range in days. Default: 30.
        unit: Time aggregation unit (``day``, ``week``, or
            ``month`` — ``hour`` is not supported for retention).
            Default: ``"day"``.
        math: Aggregation function. Default:
            ``"retention_rate"``.
        group_by: Break down results by property or cohort
            membership. Accepts a string, ``GroupBy``,
            ``CohortBreakdown``, or list of any mix.
        where: Filter results by conditions.
        mode: Display mode. Default: ``"curve"``.
        unbounded_mode: Retention unbounded mode controlling how
            retention is counted in unbounded periods. One of
            ``"none"``, ``"carry_back"``, ``"carry_forward"``, or
            ``"consecutive_forward"``. Default: ``None``
            (server default).
        retention_cumulative: Whether to use cumulative retention
            counting. Default: ``False``.
        time_comparison: Optional period-over-period comparison.
            Use ``TimeComparison.relative("month")`` for previous
            month, etc. Default: ``None``.
        data_group_id: Optional data group ID for group-level
            analytics. Scopes the query to a specific data group.
            Default: ``None``.

    Returns:
        Bookmark params dict with ``sections`` and
        ``displayOptions`` keys.

    Raises:
        BookmarkValidationError: If arguments violate validation
            rules.

    Example:
        ```python
        ws = Workspace()

        # Inspect generated JSON
        params = ws.build_retention_params("Signup", "Login")
        print(json.dumps(params, indent=2))

        # Save as a report (dashboard_id required)
        ws.create_bookmark(CreateBookmarkParams(
            name="Signup → Login Retention",
            bookmark_type="retention",
            params=params,
            dashboard_id=12345,
        ))
        ```
    """
    return self._resolve_and_build_retention_params(
        born_event=born_event,
        return_event=return_event,
        retention_unit=retention_unit,
        alignment=alignment,
        bucket_sizes=bucket_sizes,
        math=math,
        from_date=from_date,
        to_date=to_date,
        last=last,
        unit=unit,
        group_by=group_by,
        where=where,
        mode=mode,
        unbounded_mode=unbounded_mode,
        retention_cumulative=retention_cumulative,
        time_comparison=time_comparison,
        data_group_id=data_group_id,
    )

query_flow

query_flow(
    event: str | FlowStep | Sequence[str | FlowStep],
    *,
    forward: int = 3,
    reverse: int = 0,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    conversion_window: int = 7,
    conversion_window_unit: Literal["day", "week", "month", "session"] = "day",
    count_type: Literal["unique", "total", "session"] = "unique",
    cardinality: int = 3,
    collapse_repeated: bool = False,
    hidden_events: list[str] | None = None,
    mode: Literal["sankey", "paths", "tree"] = "sankey",
    where: Filter | list[Filter] | None = None,
    data_group_id: int | None = None,
    segments: str
    | GroupBy
    | CohortBreakdown
    | FrequencyBreakdown
    | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown]
    | None = None,
    exclusions: list[str] | None = None,
) -> FlowQueryResult

Run a typed flow query against the Mixpanel API.

Generates flow bookmark params from keyword arguments, POSTs them inline to /arb_funnels, and returns a structured FlowQueryResult with lazy DataFrame conversion.

PARAMETER DESCRIPTION
event

Event specification. Accepts an event name string, a FlowStep object for per-step configuration, or a list of strings/FlowStep objects for multi-step flows.

TYPE: str | FlowStep | Sequence[str | FlowStep]

forward

Default number of forward steps to trace from each anchor event. Overridden by per-step values. Default: 3.

TYPE: int DEFAULT: 3

reverse

Default number of reverse steps to trace from each anchor event. Overridden by per-step values. Default: 0.

TYPE: int DEFAULT: 0

from_date

Start date (YYYY-MM-DD). If set, overrides last.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD). Requires from_date.

TYPE: str | None DEFAULT: None

last

Relative time range in days. Default: 30.

TYPE: int DEFAULT: 30

conversion_window

Conversion window size. Default: 7.

TYPE: int DEFAULT: 7

conversion_window_unit

Conversion window unit. Default: "day".

TYPE: Literal['day', 'week', 'month', 'session'] DEFAULT: 'day'

count_type

Counting method for flow analysis. Default: "unique".

TYPE: Literal['unique', 'total', 'session'] DEFAULT: 'unique'

cardinality

Number of top paths to display. Default: 3.

TYPE: int DEFAULT: 3

collapse_repeated

Whether to merge consecutive repeated events. Default: False.

TYPE: bool DEFAULT: False

hidden_events

Events to hide from the flow visualization. Default: None.

TYPE: list[str] | None DEFAULT: None

mode

Flow visualization mode. Default: "sankey".

TYPE: Literal['sankey', 'paths', 'tree'] DEFAULT: 'sankey'

where

Filter results by cohort membership or property conditions. Cohort filters (Filter.in_cohort / Filter.not_in_cohort) produce filter_by_cohort. Property filters (Filter.equals, etc.) produce filter_by_event. Default: None.

TYPE: Filter | list[Filter] | None DEFAULT: None

data_group_id

Optional data group ID for group-level analytics. Scopes the query to a specific data group. Default: None.

TYPE: int | None DEFAULT: None

segments

Segment (breakdown) specification for flow results. Accepts a string, GroupBy, or list of strings/GroupBy objects. Default: None.

TYPE: str | GroupBy | CohortBreakdown | FrequencyBreakdown | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown] | None DEFAULT: None

exclusions

List of event names to exclude from flow paths. Default: None.

TYPE: list[str] | None DEFAULT: None

RETURNS DESCRIPTION
FlowQueryResult

FlowQueryResult with steps, flows, breakdowns, and

FlowQueryResult

metadata.

RAISES DESCRIPTION
BookmarkValidationError

If arguments violate validation rules (before API call).

ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials.

QueryError

Invalid query parameters.

RateLimitError

Rate limit exceeded.

Example
ws = Workspace()

# Simple flow query
result = ws.query_flow("Login")
print(result.overall_conversion_rate)

# With configuration
result = ws.query_flow(
    FlowStep("Login", forward=5, reverse=2),
    mode="paths",
    last=90,
)
print(result.df)

# With property filter and segments
result = ws.query_flow(
    "Login",
    where=Filter.equals("country", "US"),
    segments=GroupBy("platform"),
    exclusions=["Error Event"],
)
Source code in src/mixpanel_data/workspace.py
def query_flow(
    self,
    event: str | FlowStep | Sequence[str | FlowStep],
    *,
    forward: int = 3,
    reverse: int = 0,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    conversion_window: int = 7,
    conversion_window_unit: Literal["day", "week", "month", "session"] = "day",
    count_type: Literal["unique", "total", "session"] = "unique",
    cardinality: int = 3,
    collapse_repeated: bool = False,
    hidden_events: list[str] | None = None,
    mode: Literal["sankey", "paths", "tree"] = "sankey",
    where: Filter | list[Filter] | None = None,
    data_group_id: int | None = None,
    segments: str
    | GroupBy
    | CohortBreakdown
    | FrequencyBreakdown
    | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown]
    | None = None,
    exclusions: list[str] | None = None,
) -> FlowQueryResult:
    """Run a typed flow query against the Mixpanel API.

    Generates flow bookmark params from keyword arguments, POSTs
    them inline to ``/arb_funnels``, and returns a structured
    ``FlowQueryResult`` with lazy DataFrame conversion.

    Args:
        event: Event specification. Accepts an event name string,
            a ``FlowStep`` object for per-step configuration, or
            a list of strings/``FlowStep`` objects for multi-step
            flows.
        forward: Default number of forward steps to trace from
            each anchor event. Overridden by per-step values.
            Default: ``3``.
        reverse: Default number of reverse steps to trace from
            each anchor event. Overridden by per-step values.
            Default: ``0``.
        from_date: Start date (YYYY-MM-DD). If set, overrides
            ``last``.
        to_date: End date (YYYY-MM-DD). Requires ``from_date``.
        last: Relative time range in days. Default: 30.
        conversion_window: Conversion window size. Default: 7.
        conversion_window_unit: Conversion window unit.
            Default: ``"day"``.
        count_type: Counting method for flow analysis.
            Default: ``"unique"``.
        cardinality: Number of top paths to display.
            Default: ``3``.
        collapse_repeated: Whether to merge consecutive repeated
            events. Default: ``False``.
        hidden_events: Events to hide from the flow visualization.
            Default: ``None``.
        mode: Flow visualization mode. Default: ``"sankey"``.
        where: Filter results by cohort membership or property
            conditions. Cohort filters (``Filter.in_cohort`` /
            ``Filter.not_in_cohort``) produce ``filter_by_cohort``.
            Property filters (``Filter.equals``, etc.) produce
            ``filter_by_event``. Default: ``None``.
        data_group_id: Optional data group ID for group-level
            analytics. Scopes the query to a specific data group.
            Default: ``None``.
        segments: Segment (breakdown) specification for flow
            results. Accepts a string, ``GroupBy``, or list of
            strings/``GroupBy`` objects. Default: ``None``.
        exclusions: List of event names to exclude from flow
            paths. Default: ``None``.

    Returns:
        FlowQueryResult with steps, flows, breakdowns, and
        metadata.

    Raises:
        BookmarkValidationError: If arguments violate validation
            rules (before API call).
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials.
        QueryError: Invalid query parameters.
        RateLimitError: Rate limit exceeded.

    Example:
        ```python
        ws = Workspace()

        # Simple flow query
        result = ws.query_flow("Login")
        print(result.overall_conversion_rate)

        # With configuration
        result = ws.query_flow(
            FlowStep("Login", forward=5, reverse=2),
            mode="paths",
            last=90,
        )
        print(result.df)

        # With property filter and segments
        result = ws.query_flow(
            "Login",
            where=Filter.equals("country", "US"),
            segments=GroupBy("platform"),
            exclusions=["Error Event"],
        )
        ```
    """
    params = self._resolve_and_build_flow_params(
        event=event,
        forward=forward,
        reverse=reverse,
        from_date=from_date,
        to_date=to_date,
        last=last,
        conversion_window=conversion_window,
        conversion_window_unit=conversion_window_unit,
        count_type=count_type,
        cardinality=cardinality,
        collapse_repeated=collapse_repeated,
        hidden_events=hidden_events,
        mode=mode,
        where=where,
        data_group_id=data_group_id,
        segments=segments,
        exclusions=exclusions,
    )

    return self._live_query_service.query_flow(
        bookmark_params=params,
        project_id=int(self._session.project.id),
        mode=mode,
    )

build_flow_params

build_flow_params(
    event: str | FlowStep | Sequence[str | FlowStep],
    *,
    forward: int = 3,
    reverse: int = 0,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    conversion_window: int = 7,
    conversion_window_unit: Literal["day", "week", "month", "session"] = "day",
    count_type: Literal["unique", "total", "session"] = "unique",
    cardinality: int = 3,
    collapse_repeated: bool = False,
    hidden_events: list[str] | None = None,
    mode: Literal["sankey", "paths", "tree"] = "sankey",
    where: Filter | list[Filter] | None = None,
    data_group_id: int | None = None,
    segments: str
    | GroupBy
    | CohortBreakdown
    | FrequencyBreakdown
    | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown]
    | None = None,
    exclusions: list[str] | None = None,
) -> dict[str, Any]

Build validated flow bookmark params without executing.

Accepts the same arguments as :meth:query_flow but returns the generated bookmark params dict instead of querying the API. Useful for debugging, inspecting generated JSON, persisting via :meth:create_bookmark, or testing.

PARAMETER DESCRIPTION
event

Event specification. Accepts an event name string, a FlowStep object, or a list of strings/FlowStep objects.

TYPE: str | FlowStep | Sequence[str | FlowStep]

forward

Default forward step count. Default: 3.

TYPE: int DEFAULT: 3

reverse

Default reverse step count. Default: 0.

TYPE: int DEFAULT: 0

from_date

Start date (YYYY-MM-DD) or None.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD) or None.

TYPE: str | None DEFAULT: None

last

Relative time range in days. Default: 30.

TYPE: int DEFAULT: 30

conversion_window

Conversion window size. Default: 7.

TYPE: int DEFAULT: 7

conversion_window_unit

Conversion window unit. Default: "day".

TYPE: Literal['day', 'week', 'month', 'session'] DEFAULT: 'day'

count_type

Counting method. Default: "unique".

TYPE: Literal['unique', 'total', 'session'] DEFAULT: 'unique'

cardinality

Number of top paths. Default: 3.

TYPE: int DEFAULT: 3

collapse_repeated

Merge repeated events. Default: False.

TYPE: bool DEFAULT: False

hidden_events

Events to hide. Default: None.

TYPE: list[str] | None DEFAULT: None

mode

Display mode. Default: "sankey".

TYPE: Literal['sankey', 'paths', 'tree'] DEFAULT: 'sankey'

where

Filter results by cohort membership or property conditions. Cohort filters produce filter_by_cohort, property filters produce filter_by_event. Default: None.

TYPE: Filter | list[Filter] | None DEFAULT: None

data_group_id

Optional data group ID for group-level analytics. Scopes the query to a specific data group. Default: None.

TYPE: int | None DEFAULT: None

segments

Segment (breakdown) specification for flow results. Accepts a string, GroupBy, or list of strings/GroupBy objects. Default: None.

TYPE: str | GroupBy | CohortBreakdown | FrequencyBreakdown | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown] | None DEFAULT: None

exclusions

List of event names to exclude from flow paths. Default: None.

TYPE: list[str] | None DEFAULT: None

RETURNS DESCRIPTION
dict[str, Any]

Flat bookmark params dict with steps, date_range,

dict[str, Any]

chartType, count_type, and version keys.

RAISES DESCRIPTION
BookmarkValidationError

If arguments violate validation rules.

Example
ws = Workspace()

# Inspect generated JSON
params = ws.build_flow_params("Login")
print(json.dumps(params, indent=2))

# With segments and exclusions
params = ws.build_flow_params(
    "Login",
    segments=GroupBy("country"),
    exclusions=["Error Event"],
    where=Filter.equals("platform", "iOS"),
)
Source code in src/mixpanel_data/workspace.py
def build_flow_params(
    self,
    event: str | FlowStep | Sequence[str | FlowStep],
    *,
    forward: int = 3,
    reverse: int = 0,
    from_date: str | None = None,
    to_date: str | None = None,
    last: int = 30,
    conversion_window: int = 7,
    conversion_window_unit: Literal["day", "week", "month", "session"] = "day",
    count_type: Literal["unique", "total", "session"] = "unique",
    cardinality: int = 3,
    collapse_repeated: bool = False,
    hidden_events: list[str] | None = None,
    mode: Literal["sankey", "paths", "tree"] = "sankey",
    where: Filter | list[Filter] | None = None,
    data_group_id: int | None = None,
    segments: str
    | GroupBy
    | CohortBreakdown
    | FrequencyBreakdown
    | list[str | GroupBy | CohortBreakdown | FrequencyBreakdown]
    | None = None,
    exclusions: list[str] | None = None,
) -> dict[str, Any]:
    """Build validated flow bookmark params without executing.

    Accepts the same arguments as :meth:`query_flow` but returns
    the generated bookmark params ``dict`` instead of querying
    the API. Useful for debugging, inspecting generated JSON,
    persisting via :meth:`create_bookmark`, or testing.

    Args:
        event: Event specification. Accepts an event name string,
            a ``FlowStep`` object, or a list of strings/``FlowStep``
            objects.
        forward: Default forward step count. Default: ``3``.
        reverse: Default reverse step count. Default: ``0``.
        from_date: Start date (YYYY-MM-DD) or ``None``.
        to_date: End date (YYYY-MM-DD) or ``None``.
        last: Relative time range in days. Default: 30.
        conversion_window: Conversion window size. Default: 7.
        conversion_window_unit: Conversion window unit.
            Default: ``"day"``.
        count_type: Counting method. Default: ``"unique"``.
        cardinality: Number of top paths. Default: ``3``.
        collapse_repeated: Merge repeated events. Default: ``False``.
        hidden_events: Events to hide. Default: ``None``.
        mode: Display mode. Default: ``"sankey"``.
        where: Filter results by cohort membership or property
            conditions. Cohort filters produce ``filter_by_cohort``,
            property filters produce ``filter_by_event``.
            Default: ``None``.
        data_group_id: Optional data group ID for group-level
            analytics. Scopes the query to a specific data group.
            Default: ``None``.
        segments: Segment (breakdown) specification for flow
            results. Accepts a string, ``GroupBy``, or list of
            strings/``GroupBy`` objects. Default: ``None``.
        exclusions: List of event names to exclude from flow
            paths. Default: ``None``.

    Returns:
        Flat bookmark params dict with ``steps``, ``date_range``,
        ``chartType``, ``count_type``, and ``version`` keys.

    Raises:
        BookmarkValidationError: If arguments violate validation
            rules.

    Example:
        ```python
        ws = Workspace()

        # Inspect generated JSON
        params = ws.build_flow_params("Login")
        print(json.dumps(params, indent=2))

        # With segments and exclusions
        params = ws.build_flow_params(
            "Login",
            segments=GroupBy("country"),
            exclusions=["Error Event"],
            where=Filter.equals("platform", "iOS"),
        )
        ```
    """
    return self._resolve_and_build_flow_params(
        event=event,
        forward=forward,
        reverse=reverse,
        from_date=from_date,
        to_date=to_date,
        last=last,
        conversion_window=conversion_window,
        conversion_window_unit=conversion_window_unit,
        count_type=count_type,
        cardinality=cardinality,
        collapse_repeated=collapse_repeated,
        hidden_events=hidden_events,
        mode=mode,
        where=where,
        data_group_id=data_group_id,
        segments=segments,
        exclusions=exclusions,
    )

query_user

query_user(
    *,
    where: Filter | list[Filter] | str | None = None,
    cohort: int | CohortDefinition | None = None,
    properties: list[str] | None = None,
    sort_by: str | None = None,
    sort_order: Literal["ascending", "descending"] = "descending",
    limit: int | None = 1,
    search: str | None = None,
    distinct_id: str | None = None,
    distinct_ids: list[str] | None = None,
    group_id: str | None = None,
    as_of: str | int | None = None,
    mode: Literal["profiles", "aggregate"] = "aggregate",
    aggregate: Literal[
        "count", "extremes", "percentile", "numeric_summary"
    ] = "count",
    aggregate_property: str | None = None,
    percentile: float | None = None,
    segment_by: list[int] | None = None,
    parallel: bool = False,
    workers: int = 5,
    include_all_users: bool = False,
) -> UserQueryResult

Query user profiles from Mixpanel's Engage API.

Provides a high-level interface to Mixpanel's Engage API for querying user profiles with typed filters, cohort membership, sorting, and pagination. Results are returned as a structured UserQueryResult with lazy DataFrame conversion.

PARAMETER DESCRIPTION
where

Filter profiles by property values. Accepts a single Filter, a list of Filter objects (AND-combined), a raw selector string, or None.

TYPE: Filter | list[Filter] | str | None DEFAULT: None

cohort

Filter by cohort membership. An int for a saved cohort ID, or a CohortDefinition for an inline cohort definition.

TYPE: int | CohortDefinition | None DEFAULT: None

properties

Output properties to include in results.

TYPE: list[str] | None DEFAULT: None

sort_by

Property name to sort results by.

TYPE: str | None DEFAULT: None

sort_order

Sort direction ("ascending" or "descending").

TYPE: Literal['ascending', 'descending'] DEFAULT: 'descending'

limit

Maximum profiles to return. Defaults to 1 for quick exploration. Use None to fetch all matching profiles.

TYPE: int | None DEFAULT: 1

search

Full-text search term applied to profile properties.

TYPE: str | None DEFAULT: None

distinct_id

Look up a single user by distinct ID.

TYPE: str | None DEFAULT: None

distinct_ids

Batch look up multiple users by distinct IDs.

TYPE: list[str] | None DEFAULT: None

group_id

Query group profiles instead of user profiles.

TYPE: str | None DEFAULT: None

as_of

Point-in-time query. An ISO date string (YYYY-MM-DD) is converted to a Unix timestamp; an int is passed through directly.

TYPE: str | int | None DEFAULT: None

mode

Output mode ("profiles" or "aggregate").

TYPE: Literal['profiles', 'aggregate'] DEFAULT: 'aggregate'

aggregate

Aggregation function for aggregate mode. One of "count" (profile count), "extremes" (min/max), "percentile" (Nth percentile), or "numeric_summary" (count/mean/var/sum_of_squares).

TYPE: Literal['count', 'extremes', 'percentile', 'numeric_summary'] DEFAULT: 'count'

aggregate_property

Property to aggregate on (required for non-count aggregations).

TYPE: str | None DEFAULT: None

percentile

Percentile value (0-100 exclusive). Required when aggregate="percentile".

TYPE: float | None DEFAULT: None

segment_by

Cohort IDs for segmented aggregation.

TYPE: list[int] | None DEFAULT: None

parallel

Whether to enable concurrent page fetching.

TYPE: bool DEFAULT: False

workers

Maximum concurrent workers for parallel fetching.

TYPE: int DEFAULT: 5

include_all_users

Include non-members in cohort query results.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
UserQueryResult

UserQueryResult with profiles, total count, DataFrame,

UserQueryResult

and execution metadata.

RAISES DESCRIPTION
BookmarkValidationError

If any validation rule fails.

ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

RateLimitError

API rate limit exceeded (429).

APIError

Other API communication errors.

Example
ws = Workspace()

# Quick peek at one profile
result = ws.query_user()
print(result.df)

# Filter premium users, sorted by LTV
result = ws.query_user(
    where=Filter.equals("plan", "premium"),
    sort_by="ltv",
    sort_order="descending",
    limit=100,
)
print(f"Total premium users: {result.total}")
print(result.df.head())

# Batch lookup specific users
result = ws.query_user(
    distinct_ids=["user_001", "user_002"],
    limit=None,
)
Source code in src/mixpanel_data/workspace.py
def query_user(
    self,
    *,
    where: Filter | list[Filter] | str | None = None,
    cohort: int | CohortDefinition | None = None,
    properties: list[str] | None = None,
    sort_by: str | None = None,
    sort_order: Literal["ascending", "descending"] = "descending",
    limit: int | None = 1,
    search: str | None = None,
    distinct_id: str | None = None,
    distinct_ids: list[str] | None = None,
    group_id: str | None = None,
    as_of: str | int | None = None,
    mode: Literal["profiles", "aggregate"] = "aggregate",
    aggregate: Literal[
        "count", "extremes", "percentile", "numeric_summary"
    ] = "count",
    aggregate_property: str | None = None,
    percentile: float | None = None,
    segment_by: list[int] | None = None,
    parallel: bool = False,
    workers: int = 5,
    include_all_users: bool = False,
) -> UserQueryResult:
    """Query user profiles from Mixpanel's Engage API.

    Provides a high-level interface to Mixpanel's Engage API for
    querying user profiles with typed filters, cohort membership,
    sorting, and pagination. Results are returned as a structured
    ``UserQueryResult`` with lazy DataFrame conversion.

    Args:
        where: Filter profiles by property values. Accepts a single
            ``Filter``, a list of ``Filter`` objects (AND-combined),
            a raw selector string, or ``None``.
        cohort: Filter by cohort membership. An ``int`` for a saved
            cohort ID, or a ``CohortDefinition`` for an inline
            cohort definition.
        properties: Output properties to include in results.
        sort_by: Property name to sort results by.
        sort_order: Sort direction (``"ascending"`` or ``"descending"``).
        limit: Maximum profiles to return. Defaults to ``1`` for
            quick exploration. Use ``None`` to fetch all matching
            profiles.
        search: Full-text search term applied to profile properties.
        distinct_id: Look up a single user by distinct ID.
        distinct_ids: Batch look up multiple users by distinct IDs.
        group_id: Query group profiles instead of user profiles.
        as_of: Point-in-time query. An ISO date string (``YYYY-MM-DD``)
            is converted to a Unix timestamp; an ``int`` is passed
            through directly.
        mode: Output mode (``"profiles"`` or ``"aggregate"``).
        aggregate: Aggregation function for aggregate mode. One of
            ``"count"`` (profile count), ``"extremes"`` (min/max),
            ``"percentile"`` (Nth percentile), or
            ``"numeric_summary"`` (count/mean/var/sum_of_squares).
        aggregate_property: Property to aggregate on (required for
            non-count aggregations).
        percentile: Percentile value (0-100 exclusive). Required
            when ``aggregate="percentile"``.
        segment_by: Cohort IDs for segmented aggregation.
        parallel: Whether to enable concurrent page fetching.
        workers: Maximum concurrent workers for parallel fetching.
        include_all_users: Include non-members in cohort query results.

    Returns:
        ``UserQueryResult`` with profiles, total count, DataFrame,
        and execution metadata.

    Raises:
        BookmarkValidationError: If any validation rule fails.
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        RateLimitError: API rate limit exceeded (429).
        APIError: Other API communication errors.

    Example:
        ```python
        ws = Workspace()

        # Quick peek at one profile
        result = ws.query_user()
        print(result.df)

        # Filter premium users, sorted by LTV
        result = ws.query_user(
            where=Filter.equals("plan", "premium"),
            sort_by="ltv",
            sort_order="descending",
            limit=100,
        )
        print(f"Total premium users: {result.total}")
        print(result.df.head())

        # Batch lookup specific users
        result = ws.query_user(
            distinct_ids=["user_001", "user_002"],
            limit=None,
        )
        ```
    """
    params = self._resolve_and_build_user_params(
        where=where,
        cohort=cohort,
        properties=properties,
        sort_by=sort_by,
        sort_order=sort_order,
        limit=limit,
        search=search,
        distinct_id=distinct_id,
        distinct_ids=distinct_ids,
        group_id=group_id,
        as_of=as_of,
        mode=mode,
        aggregate=aggregate,
        aggregate_property=aggregate_property,
        percentile=percentile,
        segment_by=segment_by,
        parallel=parallel,
        workers=workers,
        include_all_users=include_all_users,
    )

    # Route by mode
    if mode == "aggregate":
        aggregate_data, total, computed_at, meta = self._execute_user_aggregate(
            params
        )
        return UserQueryResult(
            computed_at=computed_at,
            total=total,
            profiles=[],
            params=params,
            meta=meta,
            mode="aggregate",
            aggregate_data=aggregate_data,
        )

    # Profiles mode — choose sequential or parallel
    if parallel and limit != 1:
        profiles, total, computed_at, meta = self._execute_user_query_parallel(
            params, limit, workers
        )
    else:
        if parallel and limit == 1:
            logger.debug("parallel=True ignored: limit=1 uses sequential path")
        profiles, total, computed_at, meta = self._execute_user_query_sequential(
            params, limit
        )

    return UserQueryResult(
        computed_at=computed_at,
        total=total,
        profiles=profiles,
        params=params,
        meta=meta,
        mode="profiles",
        aggregate_data=None,
    )

build_user_params

build_user_params(
    *,
    where: Filter | list[Filter] | str | None = None,
    cohort: int | CohortDefinition | None = None,
    properties: list[str] | None = None,
    sort_by: str | None = None,
    sort_order: Literal["ascending", "descending"] = "descending",
    search: str | None = None,
    distinct_id: str | None = None,
    distinct_ids: list[str] | None = None,
    group_id: str | None = None,
    as_of: str | int | None = None,
    mode: Literal["profiles", "aggregate"] = "aggregate",
    aggregate: Literal[
        "count", "extremes", "percentile", "numeric_summary"
    ] = "count",
    aggregate_property: str | None = None,
    percentile: float | None = None,
    segment_by: list[int] | None = None,
    limit: int | None = 1,
    parallel: bool = False,
    workers: int = 5,
    include_all_users: bool = False,
) -> dict[str, Any]

Build engage API params without executing a query.

Validates arguments and constructs the params dict that would be sent to the Engage API, without actually making an API call. Useful for debugging, testing, and inspecting the generated params before execution.

PARAMETER DESCRIPTION
where

Filter profiles by property values. Accepts a single Filter, a list of Filter objects (AND-combined), a raw selector string, or None.

TYPE: Filter | list[Filter] | str | None DEFAULT: None

cohort

Filter by cohort membership. An int for a saved cohort ID, or a CohortDefinition for an inline cohort definition.

TYPE: int | CohortDefinition | None DEFAULT: None

properties

Output properties to include in results.

TYPE: list[str] | None DEFAULT: None

sort_by

Property name to sort results by.

TYPE: str | None DEFAULT: None

sort_order

Sort direction ("ascending" or "descending").

TYPE: Literal['ascending', 'descending'] DEFAULT: 'descending'

search

Full-text search term applied to profile properties.

TYPE: str | None DEFAULT: None

distinct_id

Look up a single user by distinct ID.

TYPE: str | None DEFAULT: None

distinct_ids

Batch look up multiple users by distinct IDs.

TYPE: list[str] | None DEFAULT: None

group_id

Query group profiles instead of user profiles.

TYPE: str | None DEFAULT: None

as_of

Point-in-time query. An ISO date string (YYYY-MM-DD) is converted to a Unix timestamp; an int is passed through directly.

TYPE: str | int | None DEFAULT: None

mode

Output mode ("profiles" or "aggregate").

TYPE: Literal['profiles', 'aggregate'] DEFAULT: 'aggregate'

aggregate

Aggregation function for aggregate mode. One of "count" (profile count), "extremes" (min/max), "percentile" (Nth percentile), or "numeric_summary" (count/mean/var/sum_of_squares).

TYPE: Literal['count', 'extremes', 'percentile', 'numeric_summary'] DEFAULT: 'count'

aggregate_property

Property to aggregate on (required for non-count aggregations).

TYPE: str | None DEFAULT: None

percentile

Percentile value (0-100 exclusive). Required when aggregate="percentile".

TYPE: float | None DEFAULT: None

segment_by

Cohort IDs for segmented aggregation.

TYPE: list[int] | None DEFAULT: None

limit

Maximum profiles to return. Defaults to 1. Used for argument-level validation (U3); not included in the returned params dict.

TYPE: int | None DEFAULT: 1

parallel

Whether to enable concurrent page fetching. Accepted for signature compatibility with query_user() but has no effect on the returned params dict.

TYPE: bool DEFAULT: False

workers

Maximum concurrent workers for parallel fetching. Accepted for signature compatibility with query_user() but has no effect on the returned params dict.

TYPE: int DEFAULT: 5

include_all_users

Include non-members in cohort query results.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
dict[str, Any]

Engage API params dict. Does not include pagination params

dict[str, Any]

(page, session_id) or limit, which are added at

dict[str, Any]

execution time by query_user().

RAISES DESCRIPTION
BookmarkValidationError

If any validation rule fails at either the argument level (U1-U28) or the param level (UP1-UP4).

Example
ws = Workspace()
params = ws.build_user_params(
    where=Filter.equals("plan", "premium"),
    sort_by="ltv",
)
print(params)
# {"where": 'properties["plan"] == "premium"',
#  "sort_key": 'properties["ltv"]',
#  "sort_order": "descending"}
Source code in src/mixpanel_data/workspace.py
def build_user_params(
    self,
    *,
    where: Filter | list[Filter] | str | None = None,
    cohort: int | CohortDefinition | None = None,
    properties: list[str] | None = None,
    sort_by: str | None = None,
    sort_order: Literal["ascending", "descending"] = "descending",
    search: str | None = None,
    distinct_id: str | None = None,
    distinct_ids: list[str] | None = None,
    group_id: str | None = None,
    as_of: str | int | None = None,
    mode: Literal["profiles", "aggregate"] = "aggregate",
    aggregate: Literal[
        "count", "extremes", "percentile", "numeric_summary"
    ] = "count",
    aggregate_property: str | None = None,
    percentile: float | None = None,
    segment_by: list[int] | None = None,
    limit: int | None = 1,
    parallel: bool = False,
    workers: int = 5,
    include_all_users: bool = False,
) -> dict[str, Any]:
    """Build engage API params without executing a query.

    Validates arguments and constructs the params dict that would be
    sent to the Engage API, without actually making an API call.
    Useful for debugging, testing, and inspecting the generated
    params before execution.

    Args:
        where: Filter profiles by property values. Accepts a single
            ``Filter``, a list of ``Filter`` objects (AND-combined),
            a raw selector string, or ``None``.
        cohort: Filter by cohort membership. An ``int`` for a saved
            cohort ID, or a ``CohortDefinition`` for an inline
            cohort definition.
        properties: Output properties to include in results.
        sort_by: Property name to sort results by.
        sort_order: Sort direction (``"ascending"`` or ``"descending"``).
        search: Full-text search term applied to profile properties.
        distinct_id: Look up a single user by distinct ID.
        distinct_ids: Batch look up multiple users by distinct IDs.
        group_id: Query group profiles instead of user profiles.
        as_of: Point-in-time query. An ISO date string (``YYYY-MM-DD``)
            is converted to a Unix timestamp; an ``int`` is passed
            through directly.
        mode: Output mode (``"profiles"`` or ``"aggregate"``).
        aggregate: Aggregation function for aggregate mode. One of
            ``"count"`` (profile count), ``"extremes"`` (min/max),
            ``"percentile"`` (Nth percentile), or
            ``"numeric_summary"`` (count/mean/var/sum_of_squares).
        aggregate_property: Property to aggregate on (required for
            non-count aggregations).
        percentile: Percentile value (0-100 exclusive). Required
            when ``aggregate="percentile"``.
        segment_by: Cohort IDs for segmented aggregation.
        limit: Maximum profiles to return. Defaults to ``1``. Used
            for argument-level validation (U3); not included in the
            returned params dict.
        parallel: Whether to enable concurrent page fetching.
            Accepted for signature compatibility with ``query_user()``
            but has no effect on the returned params dict.
        workers: Maximum concurrent workers for parallel fetching.
            Accepted for signature compatibility with ``query_user()``
            but has no effect on the returned params dict.
        include_all_users: Include non-members in cohort query results.

    Returns:
        Engage API params dict. Does not include pagination params
        (``page``, ``session_id``) or ``limit``, which are added at
        execution time by ``query_user()``.

    Raises:
        BookmarkValidationError: If any validation rule fails at
            either the argument level (U1-U28) or the param level
            (UP1-UP4).

    Example:
        ```python
        ws = Workspace()
        params = ws.build_user_params(
            where=Filter.equals("plan", "premium"),
            sort_by="ltv",
        )
        print(params)
        # {"where": 'properties["plan"] == "premium"',
        #  "sort_key": 'properties["ltv"]',
        #  "sort_order": "descending"}
        ```
    """
    return self._resolve_and_build_user_params(
        where=where,
        cohort=cohort,
        properties=properties,
        sort_by=sort_by,
        sort_order=sort_order,
        search=search,
        distinct_id=distinct_id,
        distinct_ids=distinct_ids,
        group_id=group_id,
        as_of=as_of,
        mode=mode,
        aggregate=aggregate,
        aggregate_property=aggregate_property,
        percentile=percentile,
        segment_by=segment_by,
        limit=limit,
        parallel=parallel,
        workers=workers,
        include_all_users=include_all_users,
    )

segmentation

segmentation(
    event: str,
    *,
    from_date: str,
    to_date: str,
    on: str | None = None,
    unit: Literal["day", "week", "month"] = "day",
    where: str | None = None,
) -> SegmentationResult

Run a segmentation query against Mixpanel API.

PARAMETER DESCRIPTION
event

Event name to query.

TYPE: str

from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

on

Optional property to segment by.

TYPE: str | None DEFAULT: None

unit

Time unit for aggregation.

TYPE: Literal['day', 'week', 'month'] DEFAULT: 'day'

where

Optional WHERE clause.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
SegmentationResult

SegmentationResult with time-series data.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def segmentation(
    self,
    event: str,
    *,
    from_date: str,
    to_date: str,
    on: str | None = None,
    unit: Literal["day", "week", "month"] = "day",
    where: str | None = None,
) -> SegmentationResult:
    """Run a segmentation query against Mixpanel API.

    Args:
        event: Event name to query.
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        on: Optional property to segment by.
        unit: Time unit for aggregation.
        where: Optional WHERE clause.

    Returns:
        SegmentationResult with time-series data.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.segmentation(
        event=event,
        from_date=from_date,
        to_date=to_date,
        on=on,
        unit=unit,
        where=where,
    )

funnel

funnel(
    funnel_id: int,
    *,
    from_date: str,
    to_date: str,
    unit: str | None = None,
    on: str | None = None,
) -> FunnelResult

Run a funnel analysis query.

PARAMETER DESCRIPTION
funnel_id

ID of saved funnel.

TYPE: int

from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

unit

Optional time unit.

TYPE: str | None DEFAULT: None

on

Optional property to segment by.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
FunnelResult

FunnelResult with step conversion rates.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def funnel(
    self,
    funnel_id: int,
    *,
    from_date: str,
    to_date: str,
    unit: str | None = None,
    on: str | None = None,
) -> FunnelResult:
    """Run a funnel analysis query.

    Args:
        funnel_id: ID of saved funnel.
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        unit: Optional time unit.
        on: Optional property to segment by.

    Returns:
        FunnelResult with step conversion rates.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.funnel(
        funnel_id=funnel_id,
        from_date=from_date,
        to_date=to_date,
        unit=unit,
        on=on,
    )

retention

retention(
    *,
    born_event: str,
    return_event: str,
    from_date: str,
    to_date: str,
    born_where: str | None = None,
    return_where: str | None = None,
    interval: int = 1,
    interval_count: int = 10,
    unit: Literal["day", "week", "month"] = "day",
) -> RetentionResult

Run a retention analysis query.

PARAMETER DESCRIPTION
born_event

Event that defines cohort entry.

TYPE: str

return_event

Event that defines return.

TYPE: str

from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

born_where

Optional filter for born event.

TYPE: str | None DEFAULT: None

return_where

Optional filter for return event.

TYPE: str | None DEFAULT: None

interval

Retention interval.

TYPE: int DEFAULT: 1

interval_count

Number of intervals.

TYPE: int DEFAULT: 10

unit

Time unit.

TYPE: Literal['day', 'week', 'month'] DEFAULT: 'day'

RETURNS DESCRIPTION
RetentionResult

RetentionResult with cohort retention data.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def retention(
    self,
    *,
    born_event: str,
    return_event: str,
    from_date: str,
    to_date: str,
    born_where: str | None = None,
    return_where: str | None = None,
    interval: int = 1,
    interval_count: int = 10,
    unit: Literal["day", "week", "month"] = "day",
) -> RetentionResult:
    """Run a retention analysis query.

    Args:
        born_event: Event that defines cohort entry.
        return_event: Event that defines return.
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        born_where: Optional filter for born event.
        return_where: Optional filter for return event.
        interval: Retention interval.
        interval_count: Number of intervals.
        unit: Time unit.

    Returns:
        RetentionResult with cohort retention data.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.retention(
        born_event=born_event,
        return_event=return_event,
        from_date=from_date,
        to_date=to_date,
        born_where=born_where,
        return_where=return_where,
        interval=interval,
        interval_count=interval_count,
        unit=unit,
    )

jql

jql(script: str, params: dict[str, Any] | None = None) -> JQLResult

Execute a custom JQL script.

PARAMETER DESCRIPTION
script

JQL script code.

TYPE: str

params

Optional parameters to pass to script.

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

RETURNS DESCRIPTION
JQLResult

JQLResult with raw query results.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

JQLSyntaxError

If script has syntax errors.

Source code in src/mixpanel_data/workspace.py
def jql(self, script: str, params: dict[str, Any] | None = None) -> JQLResult:
    """Execute a custom JQL script.

    Args:
        script: JQL script code.
        params: Optional parameters to pass to script.

    Returns:
        JQLResult with raw query results.

    Raises:
        ConfigError: If API credentials not available.
        JQLSyntaxError: If script has syntax errors.
    """
    return self._live_query_service.jql(script=script, params=params)

event_counts

event_counts(
    events: list[str],
    *,
    from_date: str,
    to_date: str,
    type: Literal["general", "unique", "average"] = "general",
    unit: Literal["day", "week", "month"] = "day",
) -> EventCountsResult

Get event counts for multiple events.

PARAMETER DESCRIPTION
events

List of event names.

TYPE: list[str]

from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

type

Counting method.

TYPE: Literal['general', 'unique', 'average'] DEFAULT: 'general'

unit

Time unit.

TYPE: Literal['day', 'week', 'month'] DEFAULT: 'day'

RETURNS DESCRIPTION
EventCountsResult

EventCountsResult with time-series per event.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def event_counts(
    self,
    events: list[str],
    *,
    from_date: str,
    to_date: str,
    type: Literal["general", "unique", "average"] = "general",
    unit: Literal["day", "week", "month"] = "day",
) -> EventCountsResult:
    """Get event counts for multiple events.

    Args:
        events: List of event names.
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        type: Counting method.
        unit: Time unit.

    Returns:
        EventCountsResult with time-series per event.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.event_counts(
        events=events,
        from_date=from_date,
        to_date=to_date,
        type=type,
        unit=unit,
    )

property_counts

property_counts(
    event: str,
    property_name: str,
    *,
    from_date: str,
    to_date: str,
    type: Literal["general", "unique", "average"] = "general",
    unit: Literal["day", "week", "month"] = "day",
    values: list[str] | None = None,
    limit: int | None = None,
) -> PropertyCountsResult

Get event counts broken down by property values.

PARAMETER DESCRIPTION
event

Event name.

TYPE: str

property_name

Property to break down by.

TYPE: str

from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

type

Counting method.

TYPE: Literal['general', 'unique', 'average'] DEFAULT: 'general'

unit

Time unit.

TYPE: Literal['day', 'week', 'month'] DEFAULT: 'day'

values

Optional list of property values to include.

TYPE: list[str] | None DEFAULT: None

limit

Maximum number of property values.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
PropertyCountsResult

PropertyCountsResult with time-series per property value.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def property_counts(
    self,
    event: str,
    property_name: str,
    *,
    from_date: str,
    to_date: str,
    type: Literal["general", "unique", "average"] = "general",
    unit: Literal["day", "week", "month"] = "day",
    values: list[str] | None = None,
    limit: int | None = None,
) -> PropertyCountsResult:
    """Get event counts broken down by property values.

    Args:
        event: Event name.
        property_name: Property to break down by.
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        type: Counting method.
        unit: Time unit.
        values: Optional list of property values to include.
        limit: Maximum number of property values.

    Returns:
        PropertyCountsResult with time-series per property value.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.property_counts(
        event=event,
        property_name=property_name,
        from_date=from_date,
        to_date=to_date,
        type=type,
        unit=unit,
        values=values,
        limit=limit,
    )

activity_feed

activity_feed(
    distinct_ids: list[str],
    *,
    from_date: str | None = None,
    to_date: str | None = None,
) -> ActivityFeedResult

Get activity feed for specific users.

PARAMETER DESCRIPTION
distinct_ids

List of user identifiers.

TYPE: list[str]

from_date

Optional start date filter.

TYPE: str | None DEFAULT: None

to_date

Optional end date filter.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
ActivityFeedResult

ActivityFeedResult with user events.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def activity_feed(
    self,
    distinct_ids: list[str],
    *,
    from_date: str | None = None,
    to_date: str | None = None,
) -> ActivityFeedResult:
    """Get activity feed for specific users.

    Args:
        distinct_ids: List of user identifiers.
        from_date: Optional start date filter.
        to_date: Optional end date filter.

    Returns:
        ActivityFeedResult with user events.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.activity_feed(
        distinct_ids=distinct_ids,
        from_date=from_date,
        to_date=to_date,
    )

query_saved_report

query_saved_report(
    bookmark_id: int,
    *,
    bookmark_type: Literal[
        "insights", "funnels", "retention", "flows"
    ] = "insights",
    from_date: str | None = None,
    to_date: str | None = None,
) -> SavedReportResult

Query a saved report by bookmark type.

Routes to the appropriate Mixpanel API endpoint based on bookmark_type and returns the normalized result.

PARAMETER DESCRIPTION
bookmark_id

ID of saved report (from list_bookmarks or Mixpanel URL).

TYPE: int

bookmark_type

Type of bookmark to query. Determines which API endpoint is called. Defaults to 'insights'.

TYPE: Literal['insights', 'funnels', 'retention', 'flows'] DEFAULT: 'insights'

from_date

Start date (YYYY-MM-DD). Required for funnels, optional otherwise.

TYPE: str | None DEFAULT: None

to_date

End date (YYYY-MM-DD). Required for funnels, optional otherwise.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
SavedReportResult

SavedReportResult with report data and report_type property.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

QueryError

If bookmark_id is invalid or report not found.

Source code in src/mixpanel_data/workspace.py
def query_saved_report(
    self,
    bookmark_id: int,
    *,
    bookmark_type: Literal[
        "insights", "funnels", "retention", "flows"
    ] = "insights",
    from_date: str | None = None,
    to_date: str | None = None,
) -> SavedReportResult:
    """Query a saved report by bookmark type.

    Routes to the appropriate Mixpanel API endpoint based on bookmark_type
    and returns the normalized result.

    Args:
        bookmark_id: ID of saved report (from list_bookmarks or Mixpanel URL).
        bookmark_type: Type of bookmark to query. Determines which API endpoint
            is called. Defaults to 'insights'.
        from_date: Start date (YYYY-MM-DD). Required for funnels, optional otherwise.
        to_date: End date (YYYY-MM-DD). Required for funnels, optional otherwise.

    Returns:
        SavedReportResult with report data and report_type property.

    Raises:
        ConfigError: If API credentials not available.
        QueryError: If bookmark_id is invalid or report not found.
    """
    return self._live_query_service.query_saved_report(
        bookmark_id=bookmark_id,
        bookmark_type=bookmark_type,
        from_date=from_date,
        to_date=to_date,
    )

query_saved_flows

query_saved_flows(bookmark_id: int) -> FlowsResult

Query a saved Flows report.

Executes a saved Flows report by its bookmark ID, returning step data, breakdowns, and conversion rates.

PARAMETER DESCRIPTION
bookmark_id

ID of saved flows report (from list_bookmarks or Mixpanel URL).

TYPE: int

RETURNS DESCRIPTION
FlowsResult

FlowsResult with steps, breakdowns, and conversion rate.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

QueryError

If bookmark_id is invalid or report not found.

Source code in src/mixpanel_data/workspace.py
def query_saved_flows(self, bookmark_id: int) -> FlowsResult:
    """Query a saved Flows report.

    Executes a saved Flows report by its bookmark ID, returning
    step data, breakdowns, and conversion rates.

    Args:
        bookmark_id: ID of saved flows report (from list_bookmarks or Mixpanel URL).

    Returns:
        FlowsResult with steps, breakdowns, and conversion rate.

    Raises:
        ConfigError: If API credentials not available.
        QueryError: If bookmark_id is invalid or report not found.
    """
    return self._live_query_service.query_saved_flows(bookmark_id=bookmark_id)

frequency

frequency(
    *,
    from_date: str,
    to_date: str,
    unit: Literal["day", "week", "month"] = "day",
    addiction_unit: Literal["hour", "day"] = "hour",
    event: str | None = None,
    where: str | None = None,
) -> FrequencyResult

Analyze event frequency distribution.

PARAMETER DESCRIPTION
from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

unit

Overall time unit.

TYPE: Literal['day', 'week', 'month'] DEFAULT: 'day'

addiction_unit

Measurement granularity.

TYPE: Literal['hour', 'day'] DEFAULT: 'hour'

event

Optional event filter.

TYPE: str | None DEFAULT: None

where

Optional WHERE clause.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
FrequencyResult

FrequencyResult with frequency distribution.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def frequency(
    self,
    *,
    from_date: str,
    to_date: str,
    unit: Literal["day", "week", "month"] = "day",
    addiction_unit: Literal["hour", "day"] = "hour",
    event: str | None = None,
    where: str | None = None,
) -> FrequencyResult:
    """Analyze event frequency distribution.

    Args:
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        unit: Overall time unit.
        addiction_unit: Measurement granularity.
        event: Optional event filter.
        where: Optional WHERE clause.

    Returns:
        FrequencyResult with frequency distribution.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.frequency(
        from_date=from_date,
        to_date=to_date,
        unit=unit,
        addiction_unit=addiction_unit,
        event=event,
        where=where,
    )

segmentation_numeric

segmentation_numeric(
    event: str,
    *,
    from_date: str,
    to_date: str,
    on: str,
    unit: Literal["hour", "day"] = "day",
    where: str | None = None,
    type: Literal["general", "unique", "average"] = "general",
) -> NumericBucketResult

Bucket events by numeric property ranges.

PARAMETER DESCRIPTION
event

Event name.

TYPE: str

from_date

Start date.

TYPE: str

to_date

End date.

TYPE: str

on

Numeric property expression.

TYPE: str

unit

Time unit.

TYPE: Literal['hour', 'day'] DEFAULT: 'day'

where

Optional filter.

TYPE: str | None DEFAULT: None

type

Counting method.

TYPE: Literal['general', 'unique', 'average'] DEFAULT: 'general'

RETURNS DESCRIPTION
NumericBucketResult

NumericBucketResult with bucketed data.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def segmentation_numeric(
    self,
    event: str,
    *,
    from_date: str,
    to_date: str,
    on: str,
    unit: Literal["hour", "day"] = "day",
    where: str | None = None,
    type: Literal["general", "unique", "average"] = "general",
) -> NumericBucketResult:
    """Bucket events by numeric property ranges.

    Args:
        event: Event name.
        from_date: Start date.
        to_date: End date.
        on: Numeric property expression.
        unit: Time unit.
        where: Optional filter.
        type: Counting method.

    Returns:
        NumericBucketResult with bucketed data.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.segmentation_numeric(
        event=event,
        from_date=from_date,
        to_date=to_date,
        on=on,
        unit=unit,
        where=where,
        type=type,
    )

segmentation_sum

segmentation_sum(
    event: str,
    *,
    from_date: str,
    to_date: str,
    on: str,
    unit: Literal["hour", "day"] = "day",
    where: str | None = None,
) -> NumericSumResult

Calculate sum of numeric property over time.

PARAMETER DESCRIPTION
event

Event name.

TYPE: str

from_date

Start date.

TYPE: str

to_date

End date.

TYPE: str

on

Numeric property expression.

TYPE: str

unit

Time unit.

TYPE: Literal['hour', 'day'] DEFAULT: 'day'

where

Optional filter.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
NumericSumResult

NumericSumResult with sum values per period.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def segmentation_sum(
    self,
    event: str,
    *,
    from_date: str,
    to_date: str,
    on: str,
    unit: Literal["hour", "day"] = "day",
    where: str | None = None,
) -> NumericSumResult:
    """Calculate sum of numeric property over time.

    Args:
        event: Event name.
        from_date: Start date.
        to_date: End date.
        on: Numeric property expression.
        unit: Time unit.
        where: Optional filter.

    Returns:
        NumericSumResult with sum values per period.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.segmentation_sum(
        event=event,
        from_date=from_date,
        to_date=to_date,
        on=on,
        unit=unit,
        where=where,
    )

segmentation_average

segmentation_average(
    event: str,
    *,
    from_date: str,
    to_date: str,
    on: str,
    unit: Literal["hour", "day"] = "day",
    where: str | None = None,
) -> NumericAverageResult

Calculate average of numeric property over time.

PARAMETER DESCRIPTION
event

Event name.

TYPE: str

from_date

Start date.

TYPE: str

to_date

End date.

TYPE: str

on

Numeric property expression.

TYPE: str

unit

Time unit.

TYPE: Literal['hour', 'day'] DEFAULT: 'day'

where

Optional filter.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
NumericAverageResult

NumericAverageResult with average values per period.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

Source code in src/mixpanel_data/workspace.py
def segmentation_average(
    self,
    event: str,
    *,
    from_date: str,
    to_date: str,
    on: str,
    unit: Literal["hour", "day"] = "day",
    where: str | None = None,
) -> NumericAverageResult:
    """Calculate average of numeric property over time.

    Args:
        event: Event name.
        from_date: Start date.
        to_date: End date.
        on: Numeric property expression.
        unit: Time unit.
        where: Optional filter.

    Returns:
        NumericAverageResult with average values per period.

    Raises:
        ConfigError: If API credentials not available.
    """
    return self._live_query_service.segmentation_average(
        event=event,
        from_date=from_date,
        to_date=to_date,
        on=on,
        unit=unit,
        where=where,
    )

property_distribution

property_distribution(
    event: str, property: str, *, from_date: str, to_date: str, limit: int = 20
) -> PropertyDistributionResult

Get distribution of values for a property.

Uses JQL to count occurrences of each property value, returning counts and percentages sorted by frequency.

PARAMETER DESCRIPTION
event

Event name to analyze.

TYPE: str

property

Property name to get distribution for.

TYPE: str

from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

limit

Maximum number of values to return. Default: 20.

TYPE: int DEFAULT: 20

RETURNS DESCRIPTION
PropertyDistributionResult

PropertyDistributionResult with value counts and percentages.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

QueryError

Script execution error.

Example
result = ws.property_distribution(
    event="Purchase",
    property="country",
    from_date="2024-01-01",
    to_date="2024-01-31",
)
for v in result.values:
    print(f"{v.value}: {v.count} ({v.percentage:.1f}%)")
Source code in src/mixpanel_data/workspace.py
def property_distribution(
    self,
    event: str,
    property: str,
    *,
    from_date: str,
    to_date: str,
    limit: int = 20,
) -> PropertyDistributionResult:
    """Get distribution of values for a property.

    Uses JQL to count occurrences of each property value, returning
    counts and percentages sorted by frequency.

    Args:
        event: Event name to analyze.
        property: Property name to get distribution for.
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        limit: Maximum number of values to return. Default: 20.

    Returns:
        PropertyDistributionResult with value counts and percentages.

    Raises:
        ConfigError: If API credentials not available.
        QueryError: Script execution error.

    Example:
        ```python
        result = ws.property_distribution(
            event="Purchase",
            property="country",
            from_date="2024-01-01",
            to_date="2024-01-31",
        )
        for v in result.values:
            print(f"{v.value}: {v.count} ({v.percentage:.1f}%)")
        ```
    """
    return self._live_query_service.property_distribution(
        event=event,
        property=property,
        from_date=from_date,
        to_date=to_date,
        limit=limit,
    )

numeric_summary

numeric_summary(
    event: str,
    property: str,
    *,
    from_date: str,
    to_date: str,
    percentiles: list[int] | None = None,
) -> NumericPropertySummaryResult

Get statistical summary for a numeric property.

Uses JQL to compute count, min, max, avg, stddev, and percentiles for a numeric property.

PARAMETER DESCRIPTION
event

Event name to analyze.

TYPE: str

property

Numeric property name.

TYPE: str

from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

percentiles

Percentiles to compute. Default: [25, 50, 75, 90, 95, 99].

TYPE: list[int] | None DEFAULT: None

RETURNS DESCRIPTION
NumericPropertySummaryResult

NumericPropertySummaryResult with statistics.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

QueryError

Script execution error or non-numeric property.

Example
result = ws.numeric_summary(
    event="Purchase",
    property="amount",
    from_date="2024-01-01",
    to_date="2024-01-31",
)
print(f"Avg: {result.avg}, Median: {result.percentiles[50]}")
Source code in src/mixpanel_data/workspace.py
def numeric_summary(
    self,
    event: str,
    property: str,
    *,
    from_date: str,
    to_date: str,
    percentiles: list[int] | None = None,
) -> NumericPropertySummaryResult:
    """Get statistical summary for a numeric property.

    Uses JQL to compute count, min, max, avg, stddev, and percentiles
    for a numeric property.

    Args:
        event: Event name to analyze.
        property: Numeric property name.
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        percentiles: Percentiles to compute. Default: [25, 50, 75, 90, 95, 99].

    Returns:
        NumericPropertySummaryResult with statistics.

    Raises:
        ConfigError: If API credentials not available.
        QueryError: Script execution error or non-numeric property.

    Example:
        ```python
        result = ws.numeric_summary(
            event="Purchase",
            property="amount",
            from_date="2024-01-01",
            to_date="2024-01-31",
        )
        print(f"Avg: {result.avg}, Median: {result.percentiles[50]}")
        ```
    """
    return self._live_query_service.numeric_summary(
        event=event,
        property=property,
        from_date=from_date,
        to_date=to_date,
        percentiles=percentiles,
    )

daily_counts

daily_counts(
    *, from_date: str, to_date: str, events: list[str] | None = None
) -> DailyCountsResult

Get daily event counts.

Uses JQL to count events by day, optionally filtered to specific events.

PARAMETER DESCRIPTION
from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

events

Optional list of events to count. None = all events.

TYPE: list[str] | None DEFAULT: None

RETURNS DESCRIPTION
DailyCountsResult

DailyCountsResult with date/event/count tuples.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

QueryError

Script execution error.

Example
result = ws.daily_counts(
    from_date="2024-01-01",
    to_date="2024-01-07",
    events=["Purchase", "Signup"],
)
for c in result.counts:
    print(f"{c.date} {c.event}: {c.count}")
Source code in src/mixpanel_data/workspace.py
def daily_counts(
    self,
    *,
    from_date: str,
    to_date: str,
    events: list[str] | None = None,
) -> DailyCountsResult:
    """Get daily event counts.

    Uses JQL to count events by day, optionally filtered to specific events.

    Args:
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        events: Optional list of events to count. None = all events.

    Returns:
        DailyCountsResult with date/event/count tuples.

    Raises:
        ConfigError: If API credentials not available.
        QueryError: Script execution error.

    Example:
        ```python
        result = ws.daily_counts(
            from_date="2024-01-01",
            to_date="2024-01-07",
            events=["Purchase", "Signup"],
        )
        for c in result.counts:
            print(f"{c.date} {c.event}: {c.count}")
        ```
    """
    return self._live_query_service.daily_counts(
        from_date=from_date,
        to_date=to_date,
        events=events,
    )

engagement_distribution

engagement_distribution(
    *,
    from_date: str,
    to_date: str,
    events: list[str] | None = None,
    buckets: list[int] | None = None,
) -> EngagementDistributionResult

Get user engagement distribution.

Uses JQL to bucket users by their event count, showing how many users performed N events.

PARAMETER DESCRIPTION
from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

events

Optional list of events to count. None = all events.

TYPE: list[str] | None DEFAULT: None

buckets

Bucket boundaries. Default: [1, 2, 5, 10, 25, 50, 100].

TYPE: list[int] | None DEFAULT: None

RETURNS DESCRIPTION
EngagementDistributionResult

EngagementDistributionResult with user counts per bucket.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

QueryError

Script execution error.

Example
result = ws.engagement_distribution(
    from_date="2024-01-01",
    to_date="2024-01-31",
)
for b in result.buckets:
    print(f"{b.bucket_label}: {b.user_count} ({b.percentage:.1f}%)")
Source code in src/mixpanel_data/workspace.py
def engagement_distribution(
    self,
    *,
    from_date: str,
    to_date: str,
    events: list[str] | None = None,
    buckets: list[int] | None = None,
) -> EngagementDistributionResult:
    """Get user engagement distribution.

    Uses JQL to bucket users by their event count, showing how many
    users performed N events.

    Args:
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).
        events: Optional list of events to count. None = all events.
        buckets: Bucket boundaries. Default: [1, 2, 5, 10, 25, 50, 100].

    Returns:
        EngagementDistributionResult with user counts per bucket.

    Raises:
        ConfigError: If API credentials not available.
        QueryError: Script execution error.

    Example:
        ```python
        result = ws.engagement_distribution(
            from_date="2024-01-01",
            to_date="2024-01-31",
        )
        for b in result.buckets:
            print(f"{b.bucket_label}: {b.user_count} ({b.percentage:.1f}%)")
        ```
    """
    return self._live_query_service.engagement_distribution(
        from_date=from_date,
        to_date=to_date,
        events=events,
        buckets=buckets,
    )

property_coverage

property_coverage(
    event: str, properties: list[str], *, from_date: str, to_date: str
) -> PropertyCoverageResult

Get property coverage statistics.

Uses JQL to count how often each property is defined (non-null) vs undefined for the specified event.

PARAMETER DESCRIPTION
event

Event name to analyze.

TYPE: str

properties

List of property names to check.

TYPE: list[str]

from_date

Start date (YYYY-MM-DD).

TYPE: str

to_date

End date (YYYY-MM-DD).

TYPE: str

RETURNS DESCRIPTION
PropertyCoverageResult

PropertyCoverageResult with coverage statistics per property.

RAISES DESCRIPTION
ConfigError

If API credentials not available.

QueryError

Script execution error.

Example
result = ws.property_coverage(
    event="Purchase",
    properties=["coupon_code", "referrer"],
    from_date="2024-01-01",
    to_date="2024-01-31",
)
for c in result.coverage:
    print(f"{c.property}: {c.coverage_percentage:.1f}% defined")
Source code in src/mixpanel_data/workspace.py
def property_coverage(
    self,
    event: str,
    properties: list[str],
    *,
    from_date: str,
    to_date: str,
) -> PropertyCoverageResult:
    """Get property coverage statistics.

    Uses JQL to count how often each property is defined (non-null)
    vs undefined for the specified event.

    Args:
        event: Event name to analyze.
        properties: List of property names to check.
        from_date: Start date (YYYY-MM-DD).
        to_date: End date (YYYY-MM-DD).

    Returns:
        PropertyCoverageResult with coverage statistics per property.

    Raises:
        ConfigError: If API credentials not available.
        QueryError: Script execution error.

    Example:
        ```python
        result = ws.property_coverage(
            event="Purchase",
            properties=["coupon_code", "referrer"],
            from_date="2024-01-01",
            to_date="2024-01-31",
        )
        for c in result.coverage:
            print(f"{c.property}: {c.coverage_percentage:.1f}% defined")
        ```
    """
    return self._live_query_service.property_coverage(
        event=event,
        properties=properties,
        from_date=from_date,
        to_date=to_date,
    )

list_dashboards

list_dashboards(*, ids: list[int] | None = None) -> list[Dashboard]

List dashboards for the current project/workspace.

Retrieves all dashboards visible to the authenticated user, optionally filtered by specific IDs.

PARAMETER DESCRIPTION
ids

Optional list of dashboard IDs to filter by.

TYPE: list[int] | None DEFAULT: None

RETURNS DESCRIPTION
list[Dashboard]

List of Dashboard objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dashboards = ws.list_dashboards()
for d in dashboards:
    print(f"{d.title} (id={d.id})")
Source code in src/mixpanel_data/workspace.py
def list_dashboards(self, *, ids: list[int] | None = None) -> list[Dashboard]:
    """List dashboards for the current project/workspace.

    Retrieves all dashboards visible to the authenticated user,
    optionally filtered by specific IDs.

    Args:
        ids: Optional list of dashboard IDs to filter by.

    Returns:
        List of ``Dashboard`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dashboards = ws.list_dashboards()
        for d in dashboards:
            print(f"{d.title} (id={d.id})")
        ```
    """
    client = self._require_api_client()
    raw = client.list_dashboards(ids=ids)
    return [Dashboard.model_validate(d) for d in raw]

create_dashboard

create_dashboard(params: CreateDashboardParams) -> Dashboard

Create a new dashboard.

PARAMETER DESCRIPTION
params

Dashboard creation parameters.

TYPE: CreateDashboardParams

RETURNS DESCRIPTION
Dashboard

The newly created Dashboard.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid parameters (400, 422).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dashboard = ws.create_dashboard(
    CreateDashboardParams(title="Q1 Metrics")
)
Source code in src/mixpanel_data/workspace.py
def create_dashboard(self, params: CreateDashboardParams) -> Dashboard:
    """Create a new dashboard.

    Args:
        params: Dashboard creation parameters.

    Returns:
        The newly created ``Dashboard``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid parameters (400, 422).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dashboard = ws.create_dashboard(
            CreateDashboardParams(title="Q1 Metrics")
        )
        ```
    """
    client = self._require_api_client()
    raw = client.create_dashboard(params.model_dump(exclude_none=True))
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for create_dashboard",
        )
    return Dashboard.model_validate(raw)

get_dashboard

get_dashboard(dashboard_id: int) -> Dashboard

Get a single dashboard by ID.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

RETURNS DESCRIPTION
Dashboard

The Dashboard object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dashboard = ws.get_dashboard(12345)
Source code in src/mixpanel_data/workspace.py
def get_dashboard(self, dashboard_id: int) -> Dashboard:
    """Get a single dashboard by ID.

    Args:
        dashboard_id: Dashboard identifier.

    Returns:
        The ``Dashboard`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dashboard = ws.get_dashboard(12345)
        ```
    """
    client = self._require_api_client()
    raw = client.get_dashboard(dashboard_id)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for get_dashboard",
        )
    return Dashboard.model_validate(raw)

update_dashboard

update_dashboard(dashboard_id: int, params: UpdateDashboardParams) -> Dashboard

Update an existing dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

params

Fields to update.

TYPE: UpdateDashboardParams

RETURNS DESCRIPTION
Dashboard

The updated Dashboard.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found or invalid params (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
updated = ws.update_dashboard(
    12345, UpdateDashboardParams(title="New Title")
)
Source code in src/mixpanel_data/workspace.py
def update_dashboard(
    self, dashboard_id: int, params: UpdateDashboardParams
) -> Dashboard:
    """Update an existing dashboard.

    Args:
        dashboard_id: Dashboard identifier.
        params: Fields to update.

    Returns:
        The updated ``Dashboard``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found or invalid params (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        updated = ws.update_dashboard(
            12345, UpdateDashboardParams(title="New Title")
        )
        ```
    """
    client = self._require_api_client()
    raw = client.update_dashboard(
        dashboard_id, params.model_dump(exclude_none=True)
    )
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for update_dashboard",
        )
    return Dashboard.model_validate(raw)

delete_dashboard

delete_dashboard(dashboard_id: int) -> None

Delete a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_dashboard(12345)
Source code in src/mixpanel_data/workspace.py
def delete_dashboard(self, dashboard_id: int) -> None:
    """Delete a dashboard.

    Args:
        dashboard_id: Dashboard identifier.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_dashboard(12345)
        ```
    """
    client = self._require_api_client()
    client.delete_dashboard(dashboard_id)

bulk_delete_dashboards

bulk_delete_dashboards(ids: list[int]) -> None

Delete multiple dashboards.

PARAMETER DESCRIPTION
ids

List of dashboard IDs to delete.

TYPE: list[int]

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

One or more IDs not found (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.bulk_delete_dashboards([1, 2, 3])
Source code in src/mixpanel_data/workspace.py
def bulk_delete_dashboards(self, ids: list[int]) -> None:
    """Delete multiple dashboards.

    Args:
        ids: List of dashboard IDs to delete.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: One or more IDs not found (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.bulk_delete_dashboards([1, 2, 3])
        ```
    """
    client = self._require_api_client()
    client.bulk_delete_dashboards(ids)

favorite_dashboard

favorite_dashboard(dashboard_id: int) -> None

Favorite a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.favorite_dashboard(12345)
Source code in src/mixpanel_data/workspace.py
def favorite_dashboard(self, dashboard_id: int) -> None:
    """Favorite a dashboard.

    Args:
        dashboard_id: Dashboard identifier.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.favorite_dashboard(12345)
        ```
    """
    client = self._require_api_client()
    client.favorite_dashboard(dashboard_id)

unfavorite_dashboard

unfavorite_dashboard(dashboard_id: int) -> None

Unfavorite a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.unfavorite_dashboard(12345)
Source code in src/mixpanel_data/workspace.py
def unfavorite_dashboard(self, dashboard_id: int) -> None:
    """Unfavorite a dashboard.

    Args:
        dashboard_id: Dashboard identifier.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.unfavorite_dashboard(12345)
        ```
    """
    client = self._require_api_client()
    client.unfavorite_dashboard(dashboard_id)

pin_dashboard

pin_dashboard(dashboard_id: int) -> None

Pin a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.pin_dashboard(12345)
Source code in src/mixpanel_data/workspace.py
def pin_dashboard(self, dashboard_id: int) -> None:
    """Pin a dashboard.

    Args:
        dashboard_id: Dashboard identifier.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.pin_dashboard(12345)
        ```
    """
    client = self._require_api_client()
    client.pin_dashboard(dashboard_id)

unpin_dashboard

unpin_dashboard(dashboard_id: int) -> None

Unpin a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.unpin_dashboard(12345)
Source code in src/mixpanel_data/workspace.py
def unpin_dashboard(self, dashboard_id: int) -> None:
    """Unpin a dashboard.

    Args:
        dashboard_id: Dashboard identifier.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.unpin_dashboard(12345)
        ```
    """
    client = self._require_api_client()
    client.unpin_dashboard(dashboard_id)

add_report_to_dashboard

add_report_to_dashboard(dashboard_id: int, bookmark_id: int) -> Dashboard

Add a report to a dashboard.

Clones the specified bookmark onto the dashboard. The cloned report appears as a new card in the dashboard layout.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

bookmark_id

Bookmark/report identifier to add.

TYPE: int

RETURNS DESCRIPTION
Dashboard

The updated Dashboard.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard or bookmark not found (404).

ServerError

Server-side errors (5xx).

MixpanelDataError

If the API response is not a valid dashboard dict.

Example
ws = Workspace()
updated = ws.add_report_to_dashboard(12345, 42)
Source code in src/mixpanel_data/workspace.py
def add_report_to_dashboard(self, dashboard_id: int, bookmark_id: int) -> Dashboard:
    """Add a report to a dashboard.

    Clones the specified bookmark onto the dashboard. The cloned report
    appears as a new card in the dashboard layout.

    Args:
        dashboard_id: Dashboard identifier.
        bookmark_id: Bookmark/report identifier to add.

    Returns:
        The updated ``Dashboard``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard or bookmark not found (404).
        ServerError: Server-side errors (5xx).
        MixpanelDataError: If the API response is not a valid dashboard dict.

    Example:
        ```python
        ws = Workspace()
        updated = ws.add_report_to_dashboard(12345, 42)
        ```
    """
    client = self._require_api_client()
    raw = client.add_report_to_dashboard(dashboard_id, bookmark_id)
    if not isinstance(raw, dict) or "id" not in raw:
        raise MixpanelDataError(
            "Unexpected response from add_report_to_dashboard: "
            f"expected dashboard dict with 'id', got {raw!r}",
        )
    return Dashboard.model_validate(raw)

remove_report_from_dashboard

remove_report_from_dashboard(dashboard_id: int, bookmark_id: int) -> Dashboard

Remove a report from a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

bookmark_id

Bookmark/report identifier to remove.

TYPE: int

RETURNS DESCRIPTION
Dashboard

The updated Dashboard.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard or bookmark not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
updated = ws.remove_report_from_dashboard(12345, 42)
Source code in src/mixpanel_data/workspace.py
def remove_report_from_dashboard(
    self, dashboard_id: int, bookmark_id: int
) -> Dashboard:
    """Remove a report from a dashboard.

    Args:
        dashboard_id: Dashboard identifier.
        bookmark_id: Bookmark/report identifier to remove.

    Returns:
        The updated ``Dashboard``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard or bookmark not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        updated = ws.remove_report_from_dashboard(12345, 42)
        ```
    """
    client = self._require_api_client()
    raw = client.remove_report_from_dashboard(dashboard_id, bookmark_id)
    return Dashboard.model_validate(raw)

list_blueprint_templates

list_blueprint_templates(
    *, include_reports: bool = False
) -> list[BlueprintTemplate]

List available dashboard blueprint templates.

PARAMETER DESCRIPTION
include_reports

Whether to include report details.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[BlueprintTemplate]

List of BlueprintTemplate objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
templates = ws.list_blueprint_templates()
Source code in src/mixpanel_data/workspace.py
def list_blueprint_templates(
    self, *, include_reports: bool = False
) -> list[BlueprintTemplate]:
    """List available dashboard blueprint templates.

    Args:
        include_reports: Whether to include report details.

    Returns:
        List of ``BlueprintTemplate`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        templates = ws.list_blueprint_templates()
        ```
    """
    client = self._require_api_client()
    raw = client.list_blueprint_templates(include_reports=include_reports)
    return [BlueprintTemplate.model_validate(t) for t in raw]

create_blueprint

create_blueprint(template_type: str) -> Dashboard

Create a dashboard from a blueprint template.

PARAMETER DESCRIPTION
template_type

Blueprint template type identifier.

TYPE: str

RETURNS DESCRIPTION
Dashboard

The newly created Dashboard.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid template type (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dashboard = ws.create_blueprint("onboarding")
Source code in src/mixpanel_data/workspace.py
def create_blueprint(self, template_type: str) -> Dashboard:
    """Create a dashboard from a blueprint template.

    Args:
        template_type: Blueprint template type identifier.

    Returns:
        The newly created ``Dashboard``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid template type (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dashboard = ws.create_blueprint("onboarding")
        ```
    """
    client = self._require_api_client()
    raw = client.create_blueprint(template_type)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for create_blueprint",
        )
    return Dashboard.model_validate(raw)

get_blueprint_config

get_blueprint_config(dashboard_id: int) -> BlueprintConfig

Get the blueprint configuration for a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

RETURNS DESCRIPTION
BlueprintConfig

BlueprintConfig with template variables.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
config = ws.get_blueprint_config(12345)
Source code in src/mixpanel_data/workspace.py
def get_blueprint_config(self, dashboard_id: int) -> BlueprintConfig:
    """Get the blueprint configuration for a dashboard.

    Args:
        dashboard_id: Dashboard identifier.

    Returns:
        ``BlueprintConfig`` with template variables.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        config = ws.get_blueprint_config(12345)
        ```
    """
    client = self._require_api_client()
    raw = client.get_blueprint_config(dashboard_id)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for get_blueprint_config",
        )
    return BlueprintConfig.model_validate(raw)

update_blueprint_cohorts

update_blueprint_cohorts(cohorts: list[dict[str, Any]]) -> None

Update cohorts for blueprint configuration.

PARAMETER DESCRIPTION
cohorts

List of cohort configuration dicts.

TYPE: list[dict[str, Any]]

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid cohort configuration (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.update_blueprint_cohorts([{"id": 1, "name": "Test"}])
Source code in src/mixpanel_data/workspace.py
def update_blueprint_cohorts(self, cohorts: list[dict[str, Any]]) -> None:
    """Update cohorts for blueprint configuration.

    Args:
        cohorts: List of cohort configuration dicts.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid cohort configuration (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.update_blueprint_cohorts([{"id": 1, "name": "Test"}])
        ```
    """
    client = self._require_api_client()
    client.update_blueprint_cohorts(cohorts)

finalize_blueprint

finalize_blueprint(params: BlueprintFinishParams) -> Dashboard

Finalize a blueprint dashboard with cards.

PARAMETER DESCRIPTION
params

Blueprint finalization parameters.

TYPE: BlueprintFinishParams

RETURNS DESCRIPTION
Dashboard

The finalized Dashboard.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid parameters (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dashboard = ws.finalize_blueprint(
    BlueprintFinishParams(
        dashboard_id=1,
        cards=[BlueprintCard(card_type="report", bookmark_id=42)],
    )
)
Source code in src/mixpanel_data/workspace.py
def finalize_blueprint(self, params: BlueprintFinishParams) -> Dashboard:
    """Finalize a blueprint dashboard with cards.

    Args:
        params: Blueprint finalization parameters.

    Returns:
        The finalized ``Dashboard``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid parameters (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dashboard = ws.finalize_blueprint(
            BlueprintFinishParams(
                dashboard_id=1,
                cards=[BlueprintCard(card_type="report", bookmark_id=42)],
            )
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True, by_alias=True)
    raw = client.finalize_blueprint(body)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for finalize_blueprint",
        )
    return Dashboard.model_validate(raw)

create_rca_dashboard

create_rca_dashboard(params: CreateRcaDashboardParams) -> Dashboard

Create an RCA (Root Cause Analysis) dashboard.

PARAMETER DESCRIPTION
params

RCA dashboard parameters.

TYPE: CreateRcaDashboardParams

RETURNS DESCRIPTION
Dashboard

The newly created Dashboard.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid parameters (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dashboard = ws.create_rca_dashboard(
    CreateRcaDashboardParams(
        rca_source_id=42,
        rca_source_data=RcaSourceData(source_type="anomaly"),
    )
)
Source code in src/mixpanel_data/workspace.py
def create_rca_dashboard(self, params: CreateRcaDashboardParams) -> Dashboard:
    """Create an RCA (Root Cause Analysis) dashboard.

    Args:
        params: RCA dashboard parameters.

    Returns:
        The newly created ``Dashboard``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid parameters (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dashboard = ws.create_rca_dashboard(
            CreateRcaDashboardParams(
                rca_source_id=42,
                rca_source_data=RcaSourceData(source_type="anomaly"),
            )
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True, by_alias=True)
    raw = client.create_rca_dashboard(body)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for create_rca_dashboard",
        )
    return Dashboard.model_validate(raw)

get_bookmark_dashboard_ids

get_bookmark_dashboard_ids(bookmark_id: int) -> list[int]

Get dashboard IDs containing a bookmark/report.

PARAMETER DESCRIPTION
bookmark_id

Bookmark identifier.

TYPE: int

RETURNS DESCRIPTION
list[int]

List of dashboard IDs.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Bookmark not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dash_ids = ws.get_bookmark_dashboard_ids(42)
Source code in src/mixpanel_data/workspace.py
def get_bookmark_dashboard_ids(self, bookmark_id: int) -> list[int]:
    """Get dashboard IDs containing a bookmark/report.

    Args:
        bookmark_id: Bookmark identifier.

    Returns:
        List of dashboard IDs.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Bookmark not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dash_ids = ws.get_bookmark_dashboard_ids(42)
        ```
    """
    client = self._require_api_client()
    return client.get_bookmark_dashboard_ids(bookmark_id)

get_dashboard_erf

get_dashboard_erf(dashboard_id: int) -> dict[str, Any]

Get ERF data for a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

RETURNS DESCRIPTION
dict[str, Any]

Dict with ERF metrics data.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
erf = ws.get_dashboard_erf(12345)
Source code in src/mixpanel_data/workspace.py
def get_dashboard_erf(self, dashboard_id: int) -> dict[str, Any]:
    """Get ERF data for a dashboard.

    Args:
        dashboard_id: Dashboard identifier.

    Returns:
        Dict with ERF metrics data.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        erf = ws.get_dashboard_erf(12345)
        ```
    """
    client = self._require_api_client()
    return client.get_dashboard_erf(dashboard_id)
update_report_link(
    dashboard_id: int, report_link_id: int, params: UpdateReportLinkParams
) -> None

Update a report link on a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

report_link_id

Report link identifier.

TYPE: int

params

Update parameters.

TYPE: UpdateReportLinkParams

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard or link not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.update_report_link(
    12345, 42, UpdateReportLinkParams(link_type="embedded")
)
Source code in src/mixpanel_data/workspace.py
def update_report_link(
    self,
    dashboard_id: int,
    report_link_id: int,
    params: UpdateReportLinkParams,
) -> None:
    """Update a report link on a dashboard.

    Args:
        dashboard_id: Dashboard identifier.
        report_link_id: Report link identifier.
        params: Update parameters.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard or link not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.update_report_link(
            12345, 42, UpdateReportLinkParams(link_type="embedded")
        )
        ```
    """
    client = self._require_api_client()
    client.update_report_link(
        dashboard_id,
        report_link_id,
        params.model_dump(by_alias=True, exclude_none=True),
    )

update_text_card

update_text_card(
    dashboard_id: int, text_card_id: int, params: UpdateTextCardParams
) -> None

Update a text card on a dashboard.

PARAMETER DESCRIPTION
dashboard_id

Dashboard identifier.

TYPE: int

text_card_id

Text card identifier.

TYPE: int

params

Update parameters.

TYPE: UpdateTextCardParams

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Dashboard or text card not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.update_text_card(
    12345, 99, UpdateTextCardParams(markdown="# Hello")
)
Source code in src/mixpanel_data/workspace.py
def update_text_card(
    self,
    dashboard_id: int,
    text_card_id: int,
    params: UpdateTextCardParams,
) -> None:
    """Update a text card on a dashboard.

    Args:
        dashboard_id: Dashboard identifier.
        text_card_id: Text card identifier.
        params: Update parameters.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Dashboard or text card not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.update_text_card(
            12345, 99, UpdateTextCardParams(markdown="# Hello")
        )
        ```
    """
    client = self._require_api_client()
    client.update_text_card(
        dashboard_id,
        text_card_id,
        params.model_dump(exclude_none=True),
    )

list_bookmarks_v2

list_bookmarks_v2(
    *, bookmark_type: BookmarkType | None = None, ids: list[int] | None = None
) -> list[Bookmark]

List bookmarks/reports via the App API v2 endpoint.

PARAMETER DESCRIPTION
bookmark_type

Optional report type filter (e.g., "funnels").

TYPE: BookmarkType | None DEFAULT: None

ids

Optional list of bookmark IDs to filter by.

TYPE: list[int] | None DEFAULT: None

RETURNS DESCRIPTION
list[Bookmark]

List of Bookmark objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
reports = ws.list_bookmarks_v2(bookmark_type="funnels")
for r in reports:
    print(f"{r.name} ({r.bookmark_type})")
Source code in src/mixpanel_data/workspace.py
def list_bookmarks_v2(
    self,
    *,
    bookmark_type: BookmarkType | None = None,
    ids: list[int] | None = None,
) -> list[Bookmark]:
    """List bookmarks/reports via the App API v2 endpoint.

    Args:
        bookmark_type: Optional report type filter (e.g., ``"funnels"``).
        ids: Optional list of bookmark IDs to filter by.

    Returns:
        List of ``Bookmark`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        reports = ws.list_bookmarks_v2(bookmark_type="funnels")
        for r in reports:
            print(f"{r.name} ({r.bookmark_type})")
        ```
    """
    client = self._require_api_client()
    raw = client.list_bookmarks_v2(bookmark_type=bookmark_type, ids=ids)
    return [Bookmark.model_validate(b) for b in raw]

create_bookmark

create_bookmark(params: CreateBookmarkParams) -> Bookmark

Create a new bookmark (saved report).

PARAMETER DESCRIPTION
params

Bookmark creation parameters. dashboard_id is required by the Mixpanel v2 API.

TYPE: CreateBookmarkParams

RETURNS DESCRIPTION
Bookmark

The newly created Bookmark.

RAISES DESCRIPTION
MixpanelDataError

If params.dashboard_id is None (required by the Mixpanel v2 API).

ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid parameters (400, 422).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dashboard = ws.create_dashboard(
    CreateDashboardParams(title="My Dashboard")
)
report = ws.create_bookmark(CreateBookmarkParams(
    name="Signup Funnel",
    bookmark_type="funnels",
    params={"events": [{"event": "Signup"}]},
    dashboard_id=dashboard.id,
))
Source code in src/mixpanel_data/workspace.py
def create_bookmark(self, params: CreateBookmarkParams) -> Bookmark:
    """Create a new bookmark (saved report).

    Args:
        params: Bookmark creation parameters.  ``dashboard_id``
            is required by the Mixpanel v2 API.

    Returns:
        The newly created ``Bookmark``.

    Raises:
        MixpanelDataError: If ``params.dashboard_id`` is ``None``
            (required by the Mixpanel v2 API).
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid parameters (400, 422).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dashboard = ws.create_dashboard(
            CreateDashboardParams(title="My Dashboard")
        )
        report = ws.create_bookmark(CreateBookmarkParams(
            name="Signup Funnel",
            bookmark_type="funnels",
            params={"events": [{"event": "Signup"}]},
            dashboard_id=dashboard.id,
        ))
        ```
    """
    if params.dashboard_id is None:
        raise MixpanelDataError(
            "dashboard_id is required when creating a bookmark. "
            "The Mixpanel v2 API requires every bookmark to be "
            "associated with a dashboard. Create a dashboard first "
            "with create_dashboard(), then pass its ID here.",
        )

    client = self._require_api_client()
    raw = client.create_bookmark(
        params.model_dump(by_alias=True, exclude_none=True)
    )
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for create_bookmark",
        )
    bookmark = Bookmark.model_validate(raw)

    # The v2 create endpoint associates the bookmark with the
    # dashboard in the database, but does NOT add it to the
    # dashboard's visual layout — that requires a separate
    # PATCH call.
    self.add_report_to_dashboard(params.dashboard_id, bookmark.id)

    return bookmark

get_bookmark

get_bookmark(bookmark_id: int) -> Bookmark

Get a single bookmark by ID.

PARAMETER DESCRIPTION
bookmark_id

Bookmark identifier.

TYPE: int

RETURNS DESCRIPTION
Bookmark

The Bookmark object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Bookmark not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
report = ws.get_bookmark(12345)
Source code in src/mixpanel_data/workspace.py
def get_bookmark(self, bookmark_id: int) -> Bookmark:
    """Get a single bookmark by ID.

    Args:
        bookmark_id: Bookmark identifier.

    Returns:
        The ``Bookmark`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Bookmark not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        report = ws.get_bookmark(12345)
        ```
    """
    client = self._require_api_client()
    raw = client.get_bookmark(bookmark_id)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for get_bookmark",
        )
    return Bookmark.model_validate(raw)

update_bookmark

update_bookmark(bookmark_id: int, params: UpdateBookmarkParams) -> Bookmark

Update an existing bookmark.

PARAMETER DESCRIPTION
bookmark_id

Bookmark identifier.

TYPE: int

params

Fields to update.

TYPE: UpdateBookmarkParams

RETURNS DESCRIPTION
Bookmark

The updated Bookmark.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Bookmark not found or invalid params (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
updated = ws.update_bookmark(
    12345, UpdateBookmarkParams(name="Renamed")
)
Source code in src/mixpanel_data/workspace.py
def update_bookmark(
    self, bookmark_id: int, params: UpdateBookmarkParams
) -> Bookmark:
    """Update an existing bookmark.

    Args:
        bookmark_id: Bookmark identifier.
        params: Fields to update.

    Returns:
        The updated ``Bookmark``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Bookmark not found or invalid params (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        updated = ws.update_bookmark(
            12345, UpdateBookmarkParams(name="Renamed")
        )
        ```
    """
    client = self._require_api_client()
    raw = client.update_bookmark(bookmark_id, params.model_dump(exclude_none=True))
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for update_bookmark",
        )
    return Bookmark.model_validate(raw)

delete_bookmark

delete_bookmark(bookmark_id: int) -> None

Delete a bookmark.

PARAMETER DESCRIPTION
bookmark_id

Bookmark identifier.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Bookmark not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_bookmark(12345)
Source code in src/mixpanel_data/workspace.py
def delete_bookmark(self, bookmark_id: int) -> None:
    """Delete a bookmark.

    Args:
        bookmark_id: Bookmark identifier.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Bookmark not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_bookmark(12345)
        ```
    """
    client = self._require_api_client()
    client.delete_bookmark(bookmark_id)

bulk_delete_bookmarks

bulk_delete_bookmarks(ids: list[int]) -> None

Delete multiple bookmarks.

PARAMETER DESCRIPTION
ids

List of bookmark IDs to delete.

TYPE: list[int]

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

One or more IDs not found (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.bulk_delete_bookmarks([1, 2, 3])
Source code in src/mixpanel_data/workspace.py
def bulk_delete_bookmarks(self, ids: list[int]) -> None:
    """Delete multiple bookmarks.

    Args:
        ids: List of bookmark IDs to delete.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: One or more IDs not found (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.bulk_delete_bookmarks([1, 2, 3])
        ```
    """
    client = self._require_api_client()
    client.bulk_delete_bookmarks(ids)

bulk_update_bookmarks

bulk_update_bookmarks(entries: list[BulkUpdateBookmarkEntry]) -> None

Update multiple bookmarks.

PARAMETER DESCRIPTION
entries

List of bookmark update entries.

TYPE: list[BulkUpdateBookmarkEntry]

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid entries or IDs not found (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.bulk_update_bookmarks([
    BulkUpdateBookmarkEntry(id=1, name="Renamed"),
])
Source code in src/mixpanel_data/workspace.py
def bulk_update_bookmarks(self, entries: list[BulkUpdateBookmarkEntry]) -> None:
    """Update multiple bookmarks.

    Args:
        entries: List of bookmark update entries.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid entries or IDs not found (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.bulk_update_bookmarks([
            BulkUpdateBookmarkEntry(id=1, name="Renamed"),
        ])
        ```
    """
    client = self._require_api_client()
    client.bulk_update_bookmarks([e.model_dump(exclude_none=True) for e in entries])

bookmark_linked_dashboard_ids

bookmark_linked_dashboard_ids(bookmark_id: int) -> list[int]

Get dashboard IDs linked to a bookmark.

PARAMETER DESCRIPTION
bookmark_id

Bookmark identifier.

TYPE: int

RETURNS DESCRIPTION
list[int]

List of dashboard IDs.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Bookmark not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dash_ids = ws.bookmark_linked_dashboard_ids(42)
Source code in src/mixpanel_data/workspace.py
def bookmark_linked_dashboard_ids(self, bookmark_id: int) -> list[int]:
    """Get dashboard IDs linked to a bookmark.

    Args:
        bookmark_id: Bookmark identifier.

    Returns:
        List of dashboard IDs.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Bookmark not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dash_ids = ws.bookmark_linked_dashboard_ids(42)
        ```
    """
    client = self._require_api_client()
    return client.bookmark_linked_dashboard_ids(bookmark_id)

get_bookmark_history

get_bookmark_history(
    bookmark_id: int, *, cursor: str | None = None, page_size: int | None = None
) -> BookmarkHistoryResponse

Get the change history for a bookmark.

PARAMETER DESCRIPTION
bookmark_id

Bookmark identifier.

TYPE: int

cursor

Opaque pagination cursor.

TYPE: str | None DEFAULT: None

page_size

Maximum entries per page.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
BookmarkHistoryResponse

BookmarkHistoryResponse with results and pagination.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Bookmark not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
history = ws.get_bookmark_history(12345, page_size=10)
Source code in src/mixpanel_data/workspace.py
def get_bookmark_history(
    self,
    bookmark_id: int,
    *,
    cursor: str | None = None,
    page_size: int | None = None,
) -> BookmarkHistoryResponse:
    """Get the change history for a bookmark.

    Args:
        bookmark_id: Bookmark identifier.
        cursor: Opaque pagination cursor.
        page_size: Maximum entries per page.

    Returns:
        ``BookmarkHistoryResponse`` with results and pagination.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Bookmark not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        history = ws.get_bookmark_history(12345, page_size=10)
        ```
    """
    client = self._require_api_client()
    raw = client.get_bookmark_history(
        bookmark_id, cursor=cursor, page_size=page_size
    )
    return BookmarkHistoryResponse.model_validate(raw)

list_cohorts_full

list_cohorts_full(
    *, data_group_id: str | None = None, ids: list[int] | None = None
) -> list[Cohort]

List cohorts via the App API (full detail).

Unlike cohorts() which uses the discovery endpoint, this method uses the App API and returns full Cohort objects with all metadata.

PARAMETER DESCRIPTION
data_group_id

Optional data group filter.

TYPE: str | None DEFAULT: None

ids

Optional list of cohort IDs to filter by.

TYPE: list[int] | None DEFAULT: None

RETURNS DESCRIPTION
list[Cohort]

List of Cohort objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
cohorts = ws.list_cohorts_full()
for c in cohorts:
    print(f"{c.name} ({c.count} users)")
Source code in src/mixpanel_data/workspace.py
def list_cohorts_full(
    self,
    *,
    data_group_id: str | None = None,
    ids: list[int] | None = None,
) -> list[Cohort]:
    """List cohorts via the App API (full detail).

    Unlike ``cohorts()`` which uses the discovery endpoint, this method
    uses the App API and returns full ``Cohort`` objects with all metadata.

    Args:
        data_group_id: Optional data group filter.
        ids: Optional list of cohort IDs to filter by.

    Returns:
        List of ``Cohort`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        cohorts = ws.list_cohorts_full()
        for c in cohorts:
            print(f"{c.name} ({c.count} users)")
        ```
    """
    client = self._require_api_client()
    raw = client.list_cohorts_app(data_group_id=data_group_id, ids=ids)
    return [Cohort.model_validate(c) for c in raw]

get_cohort

get_cohort(cohort_id: int) -> Cohort

Get a single cohort by ID via the App API.

PARAMETER DESCRIPTION
cohort_id

Cohort identifier.

TYPE: int

RETURNS DESCRIPTION
Cohort

The Cohort object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Cohort not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
cohort = ws.get_cohort(12345)
Source code in src/mixpanel_data/workspace.py
def get_cohort(self, cohort_id: int) -> Cohort:
    """Get a single cohort by ID via the App API.

    Args:
        cohort_id: Cohort identifier.

    Returns:
        The ``Cohort`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Cohort not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        cohort = ws.get_cohort(12345)
        ```
    """
    client = self._require_api_client()
    raw = client.get_cohort(cohort_id)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for get_cohort",
        )
    return Cohort.model_validate(raw)

create_cohort

create_cohort(params: CreateCohortParams) -> Cohort

Create a new cohort.

PARAMETER DESCRIPTION
params

Cohort creation parameters.

TYPE: CreateCohortParams

RETURNS DESCRIPTION
Cohort

The newly created Cohort.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid parameters (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
cohort = ws.create_cohort(
    CreateCohortParams(name="Power Users")
)
Source code in src/mixpanel_data/workspace.py
def create_cohort(self, params: CreateCohortParams) -> Cohort:
    """Create a new cohort.

    Args:
        params: Cohort creation parameters.

    Returns:
        The newly created ``Cohort``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid parameters (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        cohort = ws.create_cohort(
            CreateCohortParams(name="Power Users")
        )
        ```
    """
    client = self._require_api_client()
    raw = client.create_cohort(params.model_dump(exclude_none=True))
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for create_cohort",
        )
    return Cohort.model_validate(raw)

update_cohort

update_cohort(cohort_id: int, params: UpdateCohortParams) -> Cohort

Update an existing cohort.

PARAMETER DESCRIPTION
cohort_id

Cohort identifier.

TYPE: int

params

Fields to update.

TYPE: UpdateCohortParams

RETURNS DESCRIPTION
Cohort

The updated Cohort.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Cohort not found or invalid params (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
updated = ws.update_cohort(
    12345, UpdateCohortParams(name="Renamed")
)
Source code in src/mixpanel_data/workspace.py
def update_cohort(self, cohort_id: int, params: UpdateCohortParams) -> Cohort:
    """Update an existing cohort.

    Args:
        cohort_id: Cohort identifier.
        params: Fields to update.

    Returns:
        The updated ``Cohort``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Cohort not found or invalid params (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        updated = ws.update_cohort(
            12345, UpdateCohortParams(name="Renamed")
        )
        ```
    """
    client = self._require_api_client()
    raw = client.update_cohort(cohort_id, params.model_dump(exclude_none=True))
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for update_cohort",
        )
    return Cohort.model_validate(raw)

delete_cohort

delete_cohort(cohort_id: int) -> None

Delete a cohort.

PARAMETER DESCRIPTION
cohort_id

Cohort identifier.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Cohort not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_cohort(12345)
Source code in src/mixpanel_data/workspace.py
def delete_cohort(self, cohort_id: int) -> None:
    """Delete a cohort.

    Args:
        cohort_id: Cohort identifier.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Cohort not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_cohort(12345)
        ```
    """
    client = self._require_api_client()
    client.delete_cohort(cohort_id)

bulk_delete_cohorts

bulk_delete_cohorts(ids: list[int]) -> None

Delete multiple cohorts.

PARAMETER DESCRIPTION
ids

List of cohort IDs to delete.

TYPE: list[int]

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

One or more IDs not found (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.bulk_delete_cohorts([1, 2, 3])
Source code in src/mixpanel_data/workspace.py
def bulk_delete_cohorts(self, ids: list[int]) -> None:
    """Delete multiple cohorts.

    Args:
        ids: List of cohort IDs to delete.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: One or more IDs not found (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.bulk_delete_cohorts([1, 2, 3])
        ```
    """
    client = self._require_api_client()
    client.bulk_delete_cohorts(ids)

bulk_update_cohorts

bulk_update_cohorts(entries: list[BulkUpdateCohortEntry]) -> None

Update multiple cohorts.

PARAMETER DESCRIPTION
entries

List of cohort update entries.

TYPE: list[BulkUpdateCohortEntry]

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid entries or IDs not found (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.bulk_update_cohorts([
    BulkUpdateCohortEntry(id=1, name="Renamed"),
])
Source code in src/mixpanel_data/workspace.py
def bulk_update_cohorts(self, entries: list[BulkUpdateCohortEntry]) -> None:
    """Update multiple cohorts.

    Args:
        entries: List of cohort update entries.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid entries or IDs not found (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.bulk_update_cohorts([
            BulkUpdateCohortEntry(id=1, name="Renamed"),
        ])
        ```
    """
    client = self._require_api_client()
    client.bulk_update_cohorts([e.model_dump(exclude_none=True) for e in entries])

list_feature_flags

list_feature_flags(*, include_archived: bool = False) -> list[FeatureFlag]

List feature flags for the current project/workspace.

PARAMETER DESCRIPTION
include_archived

When True, include archived flags.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[FeatureFlag]

List of FeatureFlag objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
flags = ws.list_feature_flags()
for f in flags:
    print(f"{f.name} ({f.key})")
Source code in src/mixpanel_data/workspace.py
def list_feature_flags(
    self, *, include_archived: bool = False
) -> list[FeatureFlag]:
    """List feature flags for the current project/workspace.

    Args:
        include_archived: When True, include archived flags.

    Returns:
        List of ``FeatureFlag`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        flags = ws.list_feature_flags()
        for f in flags:
            print(f"{f.name} ({f.key})")
        ```
    """
    client = self._require_api_client()
    raw = client.list_feature_flags(include_archived=include_archived)
    return [FeatureFlag.model_validate(f) for f in raw]

create_feature_flag

create_feature_flag(params: CreateFeatureFlagParams) -> FeatureFlag

Create a new feature flag.

PARAMETER DESCRIPTION
params

Flag creation parameters.

TYPE: CreateFeatureFlagParams

RETURNS DESCRIPTION
FeatureFlag

The newly created FeatureFlag.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Duplicate key or invalid parameters (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
flag = ws.create_feature_flag(
    CreateFeatureFlagParams(name="Dark Mode", key="dark_mode")
)
Source code in src/mixpanel_data/workspace.py
def create_feature_flag(self, params: CreateFeatureFlagParams) -> FeatureFlag:
    """Create a new feature flag.

    Args:
        params: Flag creation parameters.

    Returns:
        The newly created ``FeatureFlag``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Duplicate key or invalid parameters (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        flag = ws.create_feature_flag(
            CreateFeatureFlagParams(name="Dark Mode", key="dark_mode")
        )
        ```
    """
    client = self._require_api_client()
    raw = client.create_feature_flag(params.model_dump(exclude_none=True))
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for create_feature_flag",
        )
    return FeatureFlag.model_validate(raw)

get_feature_flag

get_feature_flag(flag_id: str) -> FeatureFlag

Get a single feature flag by ID.

PARAMETER DESCRIPTION
flag_id

Feature flag UUID.

TYPE: str

RETURNS DESCRIPTION
FeatureFlag

The FeatureFlag object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Flag not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
flag = ws.get_feature_flag("abc-123-uuid")
Source code in src/mixpanel_data/workspace.py
def get_feature_flag(self, flag_id: str) -> FeatureFlag:
    """Get a single feature flag by ID.

    Args:
        flag_id: Feature flag UUID.

    Returns:
        The ``FeatureFlag`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Flag not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        flag = ws.get_feature_flag("abc-123-uuid")
        ```
    """
    client = self._require_api_client()
    raw = client.get_feature_flag(flag_id)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for get_feature_flag",
        )
    return FeatureFlag.model_validate(raw)

update_feature_flag

update_feature_flag(
    flag_id: str, params: UpdateFeatureFlagParams
) -> FeatureFlag

Update a feature flag (full replacement, PUT semantics).

PARAMETER DESCRIPTION
flag_id

Feature flag UUID.

TYPE: str

params

Complete flag configuration.

TYPE: UpdateFeatureFlagParams

RETURNS DESCRIPTION
FeatureFlag

The updated FeatureFlag.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Flag not found or invalid params (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
updated = ws.update_feature_flag(
    "abc-123", UpdateFeatureFlagParams(
        name="X", key="x",
        status=FeatureFlagStatus.ENABLED,
        ruleset={"variants": []},
    )
)
Source code in src/mixpanel_data/workspace.py
def update_feature_flag(
    self, flag_id: str, params: UpdateFeatureFlagParams
) -> FeatureFlag:
    """Update a feature flag (full replacement, PUT semantics).

    Args:
        flag_id: Feature flag UUID.
        params: Complete flag configuration.

    Returns:
        The updated ``FeatureFlag``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Flag not found or invalid params (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        updated = ws.update_feature_flag(
            "abc-123", UpdateFeatureFlagParams(
                name="X", key="x",
                status=FeatureFlagStatus.ENABLED,
                ruleset={"variants": []},
            )
        )
        ```
    """
    client = self._require_api_client()
    raw = client.update_feature_flag(flag_id, params.model_dump(exclude_none=True))
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for update_feature_flag",
        )
    return FeatureFlag.model_validate(raw)

delete_feature_flag

delete_feature_flag(flag_id: str) -> None

Delete a feature flag.

PARAMETER DESCRIPTION
flag_id

Feature flag UUID.

TYPE: str

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Flag not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_feature_flag("abc-123-uuid")
Source code in src/mixpanel_data/workspace.py
def delete_feature_flag(self, flag_id: str) -> None:
    """Delete a feature flag.

    Args:
        flag_id: Feature flag UUID.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Flag not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_feature_flag("abc-123-uuid")
        ```
    """
    client = self._require_api_client()
    client.delete_feature_flag(flag_id)

archive_feature_flag

archive_feature_flag(flag_id: str) -> None

Archive a feature flag (soft-delete).

PARAMETER DESCRIPTION
flag_id

Feature flag UUID.

TYPE: str

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Flag not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.archive_feature_flag("abc-123-uuid")
Source code in src/mixpanel_data/workspace.py
def archive_feature_flag(self, flag_id: str) -> None:
    """Archive a feature flag (soft-delete).

    Args:
        flag_id: Feature flag UUID.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Flag not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.archive_feature_flag("abc-123-uuid")
        ```
    """
    client = self._require_api_client()
    client.archive_feature_flag(flag_id)

restore_feature_flag

restore_feature_flag(flag_id: str) -> FeatureFlag

Restore an archived feature flag.

PARAMETER DESCRIPTION
flag_id

Feature flag UUID.

TYPE: str

RETURNS DESCRIPTION
FeatureFlag

The restored FeatureFlag.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Flag not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
restored = ws.restore_feature_flag("abc-123-uuid")
Source code in src/mixpanel_data/workspace.py
def restore_feature_flag(self, flag_id: str) -> FeatureFlag:
    """Restore an archived feature flag.

    Args:
        flag_id: Feature flag UUID.

    Returns:
        The restored ``FeatureFlag``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Flag not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        restored = ws.restore_feature_flag("abc-123-uuid")
        ```
    """
    client = self._require_api_client()
    raw = client.restore_feature_flag(flag_id)
    return FeatureFlag.model_validate(raw)

duplicate_feature_flag

duplicate_feature_flag(flag_id: str) -> FeatureFlag

Duplicate a feature flag.

PARAMETER DESCRIPTION
flag_id

Feature flag UUID.

TYPE: str

RETURNS DESCRIPTION
FeatureFlag

The newly created duplicate FeatureFlag.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Flag not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dup = ws.duplicate_feature_flag("abc-123-uuid")
Source code in src/mixpanel_data/workspace.py
def duplicate_feature_flag(self, flag_id: str) -> FeatureFlag:
    """Duplicate a feature flag.

    Args:
        flag_id: Feature flag UUID.

    Returns:
        The newly created duplicate ``FeatureFlag``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Flag not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dup = ws.duplicate_feature_flag("abc-123-uuid")
        ```
    """
    client = self._require_api_client()
    raw = client.duplicate_feature_flag(flag_id)
    return FeatureFlag.model_validate(raw)

set_flag_test_users

set_flag_test_users(flag_id: str, params: SetTestUsersParams) -> None

Set test user variant overrides for a feature flag.

PARAMETER DESCRIPTION
flag_id

Feature flag UUID.

TYPE: str

params

Test user mapping.

TYPE: SetTestUsersParams

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Flag not found (404) or invalid payload (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.set_flag_test_users(
    "abc-123",
    SetTestUsersParams(users={"on": "user-1"}),
)
Source code in src/mixpanel_data/workspace.py
def set_flag_test_users(self, flag_id: str, params: SetTestUsersParams) -> None:
    """Set test user variant overrides for a feature flag.

    Args:
        flag_id: Feature flag UUID.
        params: Test user mapping.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Flag not found (404) or invalid payload (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.set_flag_test_users(
            "abc-123",
            SetTestUsersParams(users={"on": "user-1"}),
        )
        ```
    """
    client = self._require_api_client()
    client.set_flag_test_users(flag_id, params.model_dump())

get_flag_history

get_flag_history(
    flag_id: str, *, page: str | None = None, page_size: int | None = None
) -> FlagHistoryResponse

Get paginated change history for a feature flag.

PARAMETER DESCRIPTION
flag_id

Feature flag UUID.

TYPE: str

page

Pagination cursor.

TYPE: str | None DEFAULT: None

page_size

Results per page.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
FlagHistoryResponse

FlagHistoryResponse with events and count.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Flag not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
history = ws.get_flag_history("abc-123", page_size=50)
Source code in src/mixpanel_data/workspace.py
def get_flag_history(
    self,
    flag_id: str,
    *,
    page: str | None = None,
    page_size: int | None = None,
) -> FlagHistoryResponse:
    """Get paginated change history for a feature flag.

    Args:
        flag_id: Feature flag UUID.
        page: Pagination cursor.
        page_size: Results per page.

    Returns:
        ``FlagHistoryResponse`` with events and count.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Flag not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        history = ws.get_flag_history("abc-123", page_size=50)
        ```
    """
    client = self._require_api_client()
    query_params: dict[str, str] = {}
    if page is not None:
        query_params["page"] = page
    if page_size is not None:
        query_params["page_size"] = str(page_size)
    raw = client.get_flag_history(
        flag_id, params=query_params if query_params else None
    )
    return FlagHistoryResponse.model_validate(raw)

get_flag_limits

get_flag_limits() -> FlagLimitsResponse

Get account-level feature flag limits and usage.

RETURNS DESCRIPTION
FlagLimitsResponse

FlagLimitsResponse with limit, usage, trial, and contract status.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
limits = ws.get_flag_limits()
print(f"{limits.current_usage}/{limits.limit}")
Source code in src/mixpanel_data/workspace.py
def get_flag_limits(self) -> FlagLimitsResponse:
    """Get account-level feature flag limits and usage.

    Returns:
        ``FlagLimitsResponse`` with limit, usage, trial, and contract status.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        limits = ws.get_flag_limits()
        print(f"{limits.current_usage}/{limits.limit}")
        ```
    """
    client = self._require_api_client()
    raw = client.get_flag_limits()
    return FlagLimitsResponse.model_validate(raw)

list_experiments

list_experiments(*, include_archived: bool = False) -> list[Experiment]

List experiments for the current project.

PARAMETER DESCRIPTION
include_archived

When True, include archived experiments.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
list[Experiment]

List of Experiment objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
experiments = ws.list_experiments()
for e in experiments:
    print(f"{e.name} (status={e.status})")
Source code in src/mixpanel_data/workspace.py
def list_experiments(self, *, include_archived: bool = False) -> list[Experiment]:
    """List experiments for the current project.

    Args:
        include_archived: When True, include archived experiments.

    Returns:
        List of ``Experiment`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        experiments = ws.list_experiments()
        for e in experiments:
            print(f"{e.name} (status={e.status})")
        ```
    """
    client = self._require_api_client()
    raw = client.list_experiments(include_archived=include_archived)
    return [Experiment.model_validate(e) for e in raw]

create_experiment

create_experiment(params: CreateExperimentParams) -> Experiment

Create a new experiment in Draft status.

PARAMETER DESCRIPTION
params

Experiment creation parameters.

TYPE: CreateExperimentParams

RETURNS DESCRIPTION
Experiment

The newly created Experiment.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid parameters (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
exp = ws.create_experiment(
    CreateExperimentParams(name="Checkout Flow Test")
)
Source code in src/mixpanel_data/workspace.py
def create_experiment(self, params: CreateExperimentParams) -> Experiment:
    """Create a new experiment in Draft status.

    Args:
        params: Experiment creation parameters.

    Returns:
        The newly created ``Experiment``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid parameters (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        exp = ws.create_experiment(
            CreateExperimentParams(name="Checkout Flow Test")
        )
        ```
    """
    client = self._require_api_client()
    raw = client.create_experiment(params.model_dump(exclude_none=True))
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for create_experiment",
        )
    return Experiment.model_validate(raw)

get_experiment

get_experiment(experiment_id: str) -> Experiment

Get a single experiment by ID.

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

RETURNS DESCRIPTION
Experiment

The Experiment object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Experiment not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
exp = ws.get_experiment("xyz-456-uuid")
Source code in src/mixpanel_data/workspace.py
def get_experiment(self, experiment_id: str) -> Experiment:
    """Get a single experiment by ID.

    Args:
        experiment_id: Experiment UUID.

    Returns:
        The ``Experiment`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Experiment not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        exp = ws.get_experiment("xyz-456-uuid")
        ```
    """
    client = self._require_api_client()
    raw = client.get_experiment(experiment_id)
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for get_experiment",
        )
    return Experiment.model_validate(raw)

update_experiment

update_experiment(
    experiment_id: str, params: UpdateExperimentParams
) -> Experiment

Update an experiment (PATCH semantics).

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

params

Fields to update.

TYPE: UpdateExperimentParams

RETURNS DESCRIPTION
Experiment

The updated Experiment.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Experiment not found or invalid params (400, 404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
updated = ws.update_experiment(
    "xyz-456", UpdateExperimentParams(description="Updated")
)
Source code in src/mixpanel_data/workspace.py
def update_experiment(
    self, experiment_id: str, params: UpdateExperimentParams
) -> Experiment:
    """Update an experiment (PATCH semantics).

    Args:
        experiment_id: Experiment UUID.
        params: Fields to update.

    Returns:
        The updated ``Experiment``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Experiment not found or invalid params (400, 404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        updated = ws.update_experiment(
            "xyz-456", UpdateExperimentParams(description="Updated")
        )
        ```
    """
    client = self._require_api_client()
    raw = client.update_experiment(
        experiment_id, params.model_dump(exclude_none=True)
    )
    if raw is None:
        raise MixpanelDataError(
            "API returned empty response for update_experiment",
        )
    return Experiment.model_validate(raw)

delete_experiment

delete_experiment(experiment_id: str) -> None

Delete an experiment.

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Experiment not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_experiment("xyz-456-uuid")
Source code in src/mixpanel_data/workspace.py
def delete_experiment(self, experiment_id: str) -> None:
    """Delete an experiment.

    Args:
        experiment_id: Experiment UUID.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Experiment not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_experiment("xyz-456-uuid")
        ```
    """
    client = self._require_api_client()
    client.delete_experiment(experiment_id)

launch_experiment

launch_experiment(experiment_id: str) -> Experiment

Launch an experiment (Draft → Active).

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

RETURNS DESCRIPTION
Experiment

The launched Experiment with updated status.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid state transition (400) or not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
launched = ws.launch_experiment("xyz-456-uuid")
Source code in src/mixpanel_data/workspace.py
def launch_experiment(self, experiment_id: str) -> Experiment:
    """Launch an experiment (Draft → Active).

    Args:
        experiment_id: Experiment UUID.

    Returns:
        The launched ``Experiment`` with updated status.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid state transition (400) or not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        launched = ws.launch_experiment("xyz-456-uuid")
        ```
    """
    client = self._require_api_client()
    raw = client.launch_experiment(experiment_id)
    return Experiment.model_validate(raw)

conclude_experiment

conclude_experiment(
    experiment_id: str, *, params: ExperimentConcludeParams | None = None
) -> Experiment

Conclude an experiment (Active → Concluded).

Always sends a JSON body (empty {} if no params).

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

params

Optional conclude parameters (e.g. end date override).

TYPE: ExperimentConcludeParams | None DEFAULT: None

RETURNS DESCRIPTION
Experiment

The concluded Experiment.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid state transition (400) or not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
concluded = ws.conclude_experiment("xyz-456-uuid")
Source code in src/mixpanel_data/workspace.py
def conclude_experiment(
    self,
    experiment_id: str,
    *,
    params: ExperimentConcludeParams | None = None,
) -> Experiment:
    """Conclude an experiment (Active → Concluded).

    Always sends a JSON body (empty ``{}`` if no params).

    Args:
        experiment_id: Experiment UUID.
        params: Optional conclude parameters (e.g. end date override).

    Returns:
        The concluded ``Experiment``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid state transition (400) or not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        concluded = ws.conclude_experiment("xyz-456-uuid")
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True) if params else {}
    raw = client.conclude_experiment(experiment_id, body)
    return Experiment.model_validate(raw)

decide_experiment

decide_experiment(
    experiment_id: str, params: ExperimentDecideParams
) -> Experiment

Record the experiment decision (Concluded → Success/Fail).

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

params

Decision parameters (success, variant, message).

TYPE: ExperimentDecideParams

RETURNS DESCRIPTION
Experiment

The decided Experiment with terminal status.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid state transition (400) or not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
decided = ws.decide_experiment(
    "xyz-456",
    ExperimentDecideParams(success=True, variant="simplified"),
)
Source code in src/mixpanel_data/workspace.py
def decide_experiment(
    self, experiment_id: str, params: ExperimentDecideParams
) -> Experiment:
    """Record the experiment decision (Concluded → Success/Fail).

    Args:
        experiment_id: Experiment UUID.
        params: Decision parameters (success, variant, message).

    Returns:
        The decided ``Experiment`` with terminal status.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid state transition (400) or not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        decided = ws.decide_experiment(
            "xyz-456",
            ExperimentDecideParams(success=True, variant="simplified"),
        )
        ```
    """
    client = self._require_api_client()
    raw = client.decide_experiment(
        experiment_id, params.model_dump(exclude_none=True)
    )
    return Experiment.model_validate(raw)

archive_experiment

archive_experiment(experiment_id: str) -> None

Archive an experiment.

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Experiment not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.archive_experiment("xyz-456-uuid")
Source code in src/mixpanel_data/workspace.py
def archive_experiment(self, experiment_id: str) -> None:
    """Archive an experiment.

    Args:
        experiment_id: Experiment UUID.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Experiment not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.archive_experiment("xyz-456-uuid")
        ```
    """
    client = self._require_api_client()
    client.archive_experiment(experiment_id)

restore_experiment

restore_experiment(experiment_id: str) -> Experiment

Restore an archived experiment.

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

RETURNS DESCRIPTION
Experiment

The restored Experiment.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Experiment not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
restored = ws.restore_experiment("xyz-456-uuid")
Source code in src/mixpanel_data/workspace.py
def restore_experiment(self, experiment_id: str) -> Experiment:
    """Restore an archived experiment.

    Args:
        experiment_id: Experiment UUID.

    Returns:
        The restored ``Experiment``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Experiment not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        restored = ws.restore_experiment("xyz-456-uuid")
        ```
    """
    client = self._require_api_client()
    raw = client.restore_experiment(experiment_id)
    return Experiment.model_validate(raw)

duplicate_experiment

duplicate_experiment(
    experiment_id: str, params: DuplicateExperimentParams
) -> Experiment

Duplicate an experiment.

A name is required because the Mixpanel API returns an empty response body when duplicating without one.

PARAMETER DESCRIPTION
experiment_id

Experiment UUID.

TYPE: str

params

Duplication parameters (name is required).

TYPE: DuplicateExperimentParams

RETURNS DESCRIPTION
Experiment

The newly created duplicate Experiment.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Experiment not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
dup = ws.duplicate_experiment(
    "xyz-456-uuid",
    DuplicateExperimentParams(name="Copy"),
)
Source code in src/mixpanel_data/workspace.py
def duplicate_experiment(
    self,
    experiment_id: str,
    params: DuplicateExperimentParams,
) -> Experiment:
    """Duplicate an experiment.

    A name is required because the Mixpanel API returns an empty
    response body when duplicating without one.

    Args:
        experiment_id: Experiment UUID.
        params: Duplication parameters (``name`` is required).

    Returns:
        The newly created duplicate ``Experiment``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Experiment not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        dup = ws.duplicate_experiment(
            "xyz-456-uuid",
            DuplicateExperimentParams(name="Copy"),
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.duplicate_experiment(experiment_id, body)
    return Experiment.model_validate(raw)

list_erf_experiments

list_erf_experiments() -> list[dict[str, Any]]

List experiments in ERF (Experiment Results Framework) format.

RETURNS DESCRIPTION
list[dict[str, Any]]

List of experiment dicts in ERF format.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
erf = ws.list_erf_experiments()
Source code in src/mixpanel_data/workspace.py
def list_erf_experiments(self) -> list[dict[str, Any]]:
    """List experiments in ERF (Experiment Results Framework) format.

    Returns:
        List of experiment dicts in ERF format.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        erf = ws.list_erf_experiments()
        ```
    """
    client = self._require_api_client()
    return client.list_erf_experiments()

list_alerts

list_alerts(
    *, bookmark_id: int | None = None, skip_user_filter: bool | None = None
) -> list[CustomAlert]

List custom alerts for the current project.

PARAMETER DESCRIPTION
bookmark_id

Filter alerts by linked bookmark ID.

TYPE: int | None DEFAULT: None

skip_user_filter

If True, list alerts for all users.

TYPE: bool | None DEFAULT: None

RETURNS DESCRIPTION
list[CustomAlert]

List of CustomAlert objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
alerts = ws.list_alerts()
for alert in alerts:
    print(f"{alert.name} (paused={alert.paused})")
Source code in src/mixpanel_data/workspace.py
def list_alerts(
    self,
    *,
    bookmark_id: int | None = None,
    skip_user_filter: bool | None = None,
) -> list[CustomAlert]:
    """List custom alerts for the current project.

    Args:
        bookmark_id: Filter alerts by linked bookmark ID.
        skip_user_filter: If True, list alerts for all users.

    Returns:
        List of ``CustomAlert`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        alerts = ws.list_alerts()
        for alert in alerts:
            print(f"{alert.name} (paused={alert.paused})")
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_alerts(
        bookmark_id=bookmark_id, skip_user_filter=skip_user_filter
    )
    return [CustomAlert.model_validate(item) for item in raw_list]

create_alert

create_alert(params: CreateAlertParams) -> CustomAlert

Create a new custom alert.

PARAMETER DESCRIPTION
params

Alert creation parameters (bookmark_id, name, condition, frequency, paused, and subscriptions are required).

TYPE: CreateAlertParams

RETURNS DESCRIPTION
CustomAlert

The created CustomAlert.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
alert = ws.create_alert(
    CreateAlertParams(
        bookmark_id=123,
        name="Daily signups drop",
        condition={"operator": "less_than", "value": 100},
        frequency=86400,
        paused=False,
        subscriptions=[{"type": "email", "value": "team@co.com"}],
    )
)
Source code in src/mixpanel_data/workspace.py
def create_alert(self, params: CreateAlertParams) -> CustomAlert:
    """Create a new custom alert.

    Args:
        params: Alert creation parameters (bookmark_id, name, condition,
            frequency, paused, and subscriptions are required).

    Returns:
        The created ``CustomAlert``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        alert = ws.create_alert(
            CreateAlertParams(
                bookmark_id=123,
                name="Daily signups drop",
                condition={"operator": "less_than", "value": 100},
                frequency=86400,
                paused=False,
                subscriptions=[{"type": "email", "value": "team@co.com"}],
            )
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.create_alert(body)
    return CustomAlert.model_validate(raw)

get_alert

get_alert(alert_id: int) -> CustomAlert

Get a single custom alert by ID.

PARAMETER DESCRIPTION
alert_id

Alert ID (integer).

TYPE: int

RETURNS DESCRIPTION
CustomAlert

The CustomAlert object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Alert not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
alert = ws.get_alert(42)
print(alert.name)
Source code in src/mixpanel_data/workspace.py
def get_alert(self, alert_id: int) -> CustomAlert:
    """Get a single custom alert by ID.

    Args:
        alert_id: Alert ID (integer).

    Returns:
        The ``CustomAlert`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Alert not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        alert = ws.get_alert(42)
        print(alert.name)
        ```
    """
    client = self._require_api_client()
    raw = client.get_alert(alert_id)
    return CustomAlert.model_validate(raw)

update_alert

update_alert(alert_id: int, params: UpdateAlertParams) -> CustomAlert

Update a custom alert (PATCH semantics).

PARAMETER DESCRIPTION
alert_id

Alert ID (integer).

TYPE: int

params

Fields to update.

TYPE: UpdateAlertParams

RETURNS DESCRIPTION
CustomAlert

The updated CustomAlert.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Alert not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
alert = ws.update_alert(
    42, UpdateAlertParams(name="Renamed alert")
)
Source code in src/mixpanel_data/workspace.py
def update_alert(self, alert_id: int, params: UpdateAlertParams) -> CustomAlert:
    """Update a custom alert (PATCH semantics).

    Args:
        alert_id: Alert ID (integer).
        params: Fields to update.

    Returns:
        The updated ``CustomAlert``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Alert not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        alert = ws.update_alert(
            42, UpdateAlertParams(name="Renamed alert")
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.update_alert(alert_id, body)
    return CustomAlert.model_validate(raw)

delete_alert

delete_alert(alert_id: int) -> None

Delete a custom alert.

PARAMETER DESCRIPTION
alert_id

Alert ID (integer).

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Alert not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_alert(42)
Source code in src/mixpanel_data/workspace.py
def delete_alert(self, alert_id: int) -> None:
    """Delete a custom alert.

    Args:
        alert_id: Alert ID (integer).

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Alert not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_alert(42)
        ```
    """
    client = self._require_api_client()
    client.delete_alert(alert_id)

bulk_delete_alerts

bulk_delete_alerts(ids: list[int]) -> None

Bulk-delete custom alerts.

PARAMETER DESCRIPTION
ids

List of alert IDs to delete.

TYPE: list[int]

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.bulk_delete_alerts([1, 2, 3])
Source code in src/mixpanel_data/workspace.py
def bulk_delete_alerts(self, ids: list[int]) -> None:
    """Bulk-delete custom alerts.

    Args:
        ids: List of alert IDs to delete.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.bulk_delete_alerts([1, 2, 3])
        ```
    """
    client = self._require_api_client()
    client.bulk_delete_alerts(ids)

get_alert_count

get_alert_count(*, alert_type: str | None = None) -> AlertCount

Get alert count and limits.

PARAMETER DESCRIPTION
alert_type

Optional filter by alert type.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
AlertCount

AlertCount with count, limit, and is_below_limit.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
count = ws.get_alert_count()
if count.is_below_limit:
    print(f"{count.anomaly_alerts_count}/{count.alert_limit}")
Source code in src/mixpanel_data/workspace.py
def get_alert_count(self, *, alert_type: str | None = None) -> AlertCount:
    """Get alert count and limits.

    Args:
        alert_type: Optional filter by alert type.

    Returns:
        ``AlertCount`` with count, limit, and is_below_limit.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        count = ws.get_alert_count()
        if count.is_below_limit:
            print(f"{count.anomaly_alerts_count}/{count.alert_limit}")
        ```
    """
    client = self._require_api_client()
    raw = client.get_alert_count(alert_type=alert_type)
    return AlertCount.model_validate(raw)

get_alert_history

get_alert_history(
    alert_id: int,
    *,
    page_size: int | None = None,
    next_cursor: str | None = None,
    previous_cursor: str | None = None,
) -> AlertHistoryResponse

Get alert trigger history (paginated).

PARAMETER DESCRIPTION
alert_id

Alert ID (integer).

TYPE: int

page_size

Number of results per page.

TYPE: int | None DEFAULT: None

next_cursor

Cursor for the next page.

TYPE: str | None DEFAULT: None

previous_cursor

Cursor for the previous page.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
AlertHistoryResponse

AlertHistoryResponse with results and pagination metadata.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Alert not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
history = ws.get_alert_history(42, page_size=10)
for entry in history.results:
    print(entry)
Source code in src/mixpanel_data/workspace.py
def get_alert_history(
    self,
    alert_id: int,
    *,
    page_size: int | None = None,
    next_cursor: str | None = None,
    previous_cursor: str | None = None,
) -> AlertHistoryResponse:
    """Get alert trigger history (paginated).

    Args:
        alert_id: Alert ID (integer).
        page_size: Number of results per page.
        next_cursor: Cursor for the next page.
        previous_cursor: Cursor for the previous page.

    Returns:
        ``AlertHistoryResponse`` with results and pagination metadata.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Alert not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        history = ws.get_alert_history(42, page_size=10)
        for entry in history.results:
            print(entry)
        ```
    """
    client = self._require_api_client()
    raw = client.get_alert_history(
        alert_id,
        page_size=page_size,
        next_cursor=next_cursor,
        previous_cursor=previous_cursor,
    )
    return AlertHistoryResponse.model_validate(raw)

test_alert

test_alert(params: CreateAlertParams) -> dict[str, Any]

Send a test alert notification.

PARAMETER DESCRIPTION
params

Alert parameters for the test (same shape as create).

TYPE: CreateAlertParams

RETURNS DESCRIPTION
dict[str, Any]

Dictionary with test result status (opaque response).

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
result = ws.test_alert(
    CreateAlertParams(
        bookmark_id=123, name="Test",
        condition={}, frequency=86400,
        paused=False, subscriptions=[],
    )
)
Source code in src/mixpanel_data/workspace.py
def test_alert(self, params: CreateAlertParams) -> dict[str, Any]:
    """Send a test alert notification.

    Args:
        params: Alert parameters for the test (same shape as create).

    Returns:
        Dictionary with test result status (opaque response).

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        result = ws.test_alert(
            CreateAlertParams(
                bookmark_id=123, name="Test",
                condition={}, frequency=86400,
                paused=False, subscriptions=[],
            )
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    return client.test_alert(body)

get_alert_screenshot_url

get_alert_screenshot_url(gcs_key: str) -> AlertScreenshotResponse

Get a signed URL for an alert screenshot.

PARAMETER DESCRIPTION
gcs_key

GCS object key for the screenshot.

TYPE: str

RETURNS DESCRIPTION
AlertScreenshotResponse

AlertScreenshotResponse with the signed URL.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Screenshot not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
resp = ws.get_alert_screenshot_url("screenshots/abc.png")
print(resp.signed_url)
Source code in src/mixpanel_data/workspace.py
def get_alert_screenshot_url(self, gcs_key: str) -> AlertScreenshotResponse:
    """Get a signed URL for an alert screenshot.

    Args:
        gcs_key: GCS object key for the screenshot.

    Returns:
        ``AlertScreenshotResponse`` with the signed URL.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Screenshot not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        resp = ws.get_alert_screenshot_url("screenshots/abc.png")
        print(resp.signed_url)
        ```
    """
    client = self._require_api_client()
    raw = client.get_alert_screenshot_url(gcs_key)
    return AlertScreenshotResponse.model_validate(raw)

validate_alerts_for_bookmark

validate_alerts_for_bookmark(
    params: ValidateAlertsForBookmarkParams,
) -> ValidateAlertsForBookmarkResponse

Validate alerts against a bookmark configuration.

PARAMETER DESCRIPTION
params

Validation parameters (alert_ids, bookmark_type, bookmark_params are required).

TYPE: ValidateAlertsForBookmarkParams

RETURNS DESCRIPTION
ValidateAlertsForBookmarkResponse

ValidateAlertsForBookmarkResponse with per-alert validations

ValidateAlertsForBookmarkResponse

and invalid count.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
resp = ws.validate_alerts_for_bookmark(
    ValidateAlertsForBookmarkParams(
        alert_ids=[1, 2],
        bookmark_type="insights",
        bookmark_params={"event": "Signup"},
    )
)
if resp.invalid_count > 0:
    for v in resp.alert_validations:
        if not v.valid:
            print(f"{v.alert_name}: {v.reason}")
Source code in src/mixpanel_data/workspace.py
def validate_alerts_for_bookmark(
    self, params: ValidateAlertsForBookmarkParams
) -> ValidateAlertsForBookmarkResponse:
    """Validate alerts against a bookmark configuration.

    Args:
        params: Validation parameters (alert_ids, bookmark_type,
            bookmark_params are required).

    Returns:
        ``ValidateAlertsForBookmarkResponse`` with per-alert validations
        and invalid count.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        resp = ws.validate_alerts_for_bookmark(
            ValidateAlertsForBookmarkParams(
                alert_ids=[1, 2],
                bookmark_type="insights",
                bookmark_params={"event": "Signup"},
            )
        )
        if resp.invalid_count > 0:
            for v in resp.alert_validations:
                if not v.valid:
                    print(f"{v.alert_name}: {v.reason}")
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.validate_alerts_for_bookmark(body)
    return ValidateAlertsForBookmarkResponse.model_validate(raw)

list_annotations

list_annotations(
    *,
    from_date: str | None = None,
    to_date: str | None = None,
    tags: list[int] | None = None,
) -> list[Annotation]

List timeline annotations for the project.

PARAMETER DESCRIPTION
from_date

Start date filter (ISO format, e.g. "2026-01-01").

TYPE: str | None DEFAULT: None

to_date

End date filter (ISO format, e.g. "2026-03-31").

TYPE: str | None DEFAULT: None

tags

Tag IDs to filter by.

TYPE: list[int] | None DEFAULT: None

RETURNS DESCRIPTION
list[Annotation]

List of Annotation objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
annotations = ws.list_annotations(from_date="2026-01-01")
for ann in annotations:
    print(f"{ann.date}: {ann.description}")
Source code in src/mixpanel_data/workspace.py
def list_annotations(
    self,
    *,
    from_date: str | None = None,
    to_date: str | None = None,
    tags: list[int] | None = None,
) -> list[Annotation]:
    """List timeline annotations for the project.

    Args:
        from_date: Start date filter (ISO format, e.g. ``"2026-01-01"``).
        to_date: End date filter (ISO format, e.g. ``"2026-03-31"``).
        tags: Tag IDs to filter by.

    Returns:
        List of ``Annotation`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        annotations = ws.list_annotations(from_date="2026-01-01")
        for ann in annotations:
            print(f"{ann.date}: {ann.description}")
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_annotations(
        from_date=from_date, to_date=to_date, tags=tags
    )
    return [Annotation.model_validate(item) for item in raw_list]

create_annotation

create_annotation(params: CreateAnnotationParams) -> Annotation

Create a new timeline annotation.

PARAMETER DESCRIPTION
params

Annotation creation parameters (date, description required).

TYPE: CreateAnnotationParams

RETURNS DESCRIPTION
Annotation

The created Annotation.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ann = ws.create_annotation(
    CreateAnnotationParams(
        date="2026-03-31", description="v2.5 release"
    )
)
Source code in src/mixpanel_data/workspace.py
def create_annotation(self, params: CreateAnnotationParams) -> Annotation:
    """Create a new timeline annotation.

    Args:
        params: Annotation creation parameters (date, description required).

    Returns:
        The created ``Annotation``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ann = ws.create_annotation(
            CreateAnnotationParams(
                date="2026-03-31", description="v2.5 release"
            )
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.create_annotation(body)
    return Annotation.model_validate(raw)

get_annotation

get_annotation(annotation_id: int) -> Annotation

Get a single annotation by ID.

PARAMETER DESCRIPTION
annotation_id

Annotation ID.

TYPE: int

RETURNS DESCRIPTION
Annotation

The Annotation object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Annotation not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ann = ws.get_annotation(42)
print(ann.description)
Source code in src/mixpanel_data/workspace.py
def get_annotation(self, annotation_id: int) -> Annotation:
    """Get a single annotation by ID.

    Args:
        annotation_id: Annotation ID.

    Returns:
        The ``Annotation`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Annotation not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ann = ws.get_annotation(42)
        print(ann.description)
        ```
    """
    client = self._require_api_client()
    raw = client.get_annotation(annotation_id)
    return Annotation.model_validate(raw)

update_annotation

update_annotation(
    annotation_id: int, params: UpdateAnnotationParams
) -> Annotation

Update an annotation (PATCH semantics).

PARAMETER DESCRIPTION
annotation_id

Annotation ID.

TYPE: int

params

Fields to update (description, tags).

TYPE: UpdateAnnotationParams

RETURNS DESCRIPTION
Annotation

The updated Annotation.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Annotation not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ann = ws.update_annotation(
    42, UpdateAnnotationParams(description="Updated text")
)
Source code in src/mixpanel_data/workspace.py
def update_annotation(
    self, annotation_id: int, params: UpdateAnnotationParams
) -> Annotation:
    """Update an annotation (PATCH semantics).

    Args:
        annotation_id: Annotation ID.
        params: Fields to update (description, tags).

    Returns:
        The updated ``Annotation``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Annotation not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ann = ws.update_annotation(
            42, UpdateAnnotationParams(description="Updated text")
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.update_annotation(annotation_id, body)
    return Annotation.model_validate(raw)

delete_annotation

delete_annotation(annotation_id: int) -> None

Delete an annotation.

PARAMETER DESCRIPTION
annotation_id

Annotation ID.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Annotation not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_annotation(42)
Source code in src/mixpanel_data/workspace.py
def delete_annotation(self, annotation_id: int) -> None:
    """Delete an annotation.

    Args:
        annotation_id: Annotation ID.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Annotation not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_annotation(42)
        ```
    """
    client = self._require_api_client()
    client.delete_annotation(annotation_id)

list_annotation_tags

list_annotation_tags() -> list[AnnotationTag]

List annotation tags for the project.

RETURNS DESCRIPTION
list[AnnotationTag]

List of AnnotationTag objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
tags = ws.list_annotation_tags()
for tag in tags:
    print(tag.name)
Source code in src/mixpanel_data/workspace.py
def list_annotation_tags(self) -> list[AnnotationTag]:
    """List annotation tags for the project.

    Returns:
        List of ``AnnotationTag`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        tags = ws.list_annotation_tags()
        for tag in tags:
            print(tag.name)
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_annotation_tags()
    return [AnnotationTag.model_validate(item) for item in raw_list]

create_annotation_tag

create_annotation_tag(params: CreateAnnotationTagParams) -> AnnotationTag

Create a new annotation tag.

PARAMETER DESCRIPTION
params

Tag creation parameters (name required).

TYPE: CreateAnnotationTagParams

RETURNS DESCRIPTION
AnnotationTag

The created AnnotationTag.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
tag = ws.create_annotation_tag(
    CreateAnnotationTagParams(name="releases")
)
Source code in src/mixpanel_data/workspace.py
def create_annotation_tag(self, params: CreateAnnotationTagParams) -> AnnotationTag:
    """Create a new annotation tag.

    Args:
        params: Tag creation parameters (name required).

    Returns:
        The created ``AnnotationTag``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        tag = ws.create_annotation_tag(
            CreateAnnotationTagParams(name="releases")
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.create_annotation_tag(body)
    return AnnotationTag.model_validate(raw)

list_webhooks

list_webhooks() -> list[ProjectWebhook]

List all webhooks for the current project.

RETURNS DESCRIPTION
list[ProjectWebhook]

List of ProjectWebhook objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
webhooks = ws.list_webhooks()
for wh in webhooks:
    print(f"{wh.name} -> {wh.url}")
Source code in src/mixpanel_data/workspace.py
def list_webhooks(self) -> list[ProjectWebhook]:
    """List all webhooks for the current project.

    Returns:
        List of ``ProjectWebhook`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        webhooks = ws.list_webhooks()
        for wh in webhooks:
            print(f"{wh.name} -> {wh.url}")
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_webhooks()
    return [ProjectWebhook.model_validate(item) for item in raw_list]

create_webhook

create_webhook(params: CreateWebhookParams) -> WebhookMutationResult

Create a new webhook.

PARAMETER DESCRIPTION
params

Webhook creation parameters.

TYPE: CreateWebhookParams

RETURNS DESCRIPTION
WebhookMutationResult

WebhookMutationResult with the new webhook's id and name.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
result = ws.create_webhook(
    CreateWebhookParams(name="Pipeline", url="https://example.com/hook")
)
print(result.id)
Source code in src/mixpanel_data/workspace.py
def create_webhook(self, params: CreateWebhookParams) -> WebhookMutationResult:
    """Create a new webhook.

    Args:
        params: Webhook creation parameters.

    Returns:
        ``WebhookMutationResult`` with the new webhook's id and name.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        result = ws.create_webhook(
            CreateWebhookParams(name="Pipeline", url="https://example.com/hook")
        )
        print(result.id)
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.create_webhook(body)
    return WebhookMutationResult.model_validate(raw)

update_webhook

update_webhook(
    webhook_id: str, params: UpdateWebhookParams
) -> WebhookMutationResult

Update an existing webhook.

PARAMETER DESCRIPTION
webhook_id

Webhook UUID string.

TYPE: str

params

Fields to update (PATCH semantics).

TYPE: UpdateWebhookParams

RETURNS DESCRIPTION
WebhookMutationResult

WebhookMutationResult with the updated webhook's id and name.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Webhook not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
result = ws.update_webhook(
    "wh-uuid-123",
    UpdateWebhookParams(name="Renamed Hook"),
)
Source code in src/mixpanel_data/workspace.py
def update_webhook(
    self, webhook_id: str, params: UpdateWebhookParams
) -> WebhookMutationResult:
    """Update an existing webhook.

    Args:
        webhook_id: Webhook UUID string.
        params: Fields to update (PATCH semantics).

    Returns:
        ``WebhookMutationResult`` with the updated webhook's id and name.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Webhook not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        result = ws.update_webhook(
            "wh-uuid-123",
            UpdateWebhookParams(name="Renamed Hook"),
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.update_webhook(webhook_id, body)
    return WebhookMutationResult.model_validate(raw)

delete_webhook

delete_webhook(webhook_id: str) -> None

Delete a webhook.

PARAMETER DESCRIPTION
webhook_id

Webhook UUID string.

TYPE: str

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Webhook not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_webhook("wh-uuid-123")
Source code in src/mixpanel_data/workspace.py
def delete_webhook(self, webhook_id: str) -> None:
    """Delete a webhook.

    Args:
        webhook_id: Webhook UUID string.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Webhook not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_webhook("wh-uuid-123")
        ```
    """
    client = self._require_api_client()
    client.delete_webhook(webhook_id)

test_webhook

test_webhook(params: WebhookTestParams) -> WebhookTestResult

Test webhook connectivity.

PARAMETER DESCRIPTION
params

Webhook test parameters (url is required).

TYPE: WebhookTestParams

RETURNS DESCRIPTION
WebhookTestResult

WebhookTestResult with success, status_code, and message.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

API error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
result = ws.test_webhook(
    WebhookTestParams(url="https://example.com/hook")
)
if result.success:
    print("Webhook is reachable")
Source code in src/mixpanel_data/workspace.py
def test_webhook(self, params: WebhookTestParams) -> WebhookTestResult:
    """Test webhook connectivity.

    Args:
        params: Webhook test parameters (url is required).

    Returns:
        ``WebhookTestResult`` with success, status_code, and message.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: API error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        result = ws.test_webhook(
            WebhookTestParams(url="https://example.com/hook")
        )
        if result.success:
            print("Webhook is reachable")
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.test_webhook(body)
    return WebhookTestResult.model_validate(raw)

get_event_definitions

get_event_definitions(*, names: list[str]) -> list[EventDefinition]

Get event definitions from Lexicon by name.

Retrieves metadata (description, tags, visibility, etc.) for the specified events from the Mixpanel Lexicon.

PARAMETER DESCRIPTION
names

List of event names to look up.

TYPE: list[str]

RETURNS DESCRIPTION
list[EventDefinition]

List of EventDefinition objects for the requested events.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
defs = ws.get_event_definitions(names=["Signup", "Login"])
for d in defs:
    print(f"{d.name}: {d.description}")
Source code in src/mixpanel_data/workspace.py
def get_event_definitions(self, *, names: list[str]) -> list[EventDefinition]:
    """Get event definitions from Lexicon by name.

    Retrieves metadata (description, tags, visibility, etc.) for the
    specified events from the Mixpanel Lexicon.

    Args:
        names: List of event names to look up.

    Returns:
        List of ``EventDefinition`` objects for the requested events.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        defs = ws.get_event_definitions(names=["Signup", "Login"])
        for d in defs:
            print(f"{d.name}: {d.description}")
        ```
    """
    client = self._require_api_client()
    raw_list = client.get_event_definitions(names)
    return [EventDefinition.model_validate(x) for x in raw_list]

update_event_definition

update_event_definition(
    event_name: str, params: UpdateEventDefinitionParams
) -> EventDefinition

Update an event definition in Lexicon.

PARAMETER DESCRIPTION
event_name

Name of the event to update.

TYPE: str

params

Fields to update (hidden, dropped, merged, verified, tags, description).

TYPE: UpdateEventDefinitionParams

RETURNS DESCRIPTION
EventDefinition

The updated EventDefinition.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Event not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
definition = ws.update_event_definition(
    "Signup",
    UpdateEventDefinitionParams(description="User signed up"),
)
Source code in src/mixpanel_data/workspace.py
def update_event_definition(
    self, event_name: str, params: UpdateEventDefinitionParams
) -> EventDefinition:
    """Update an event definition in Lexicon.

    Args:
        event_name: Name of the event to update.
        params: Fields to update (hidden, dropped, merged,
            verified, tags, description).

    Returns:
        The updated ``EventDefinition``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Event not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        definition = ws.update_event_definition(
            "Signup",
            UpdateEventDefinitionParams(description="User signed up"),
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.update_event_definition(event_name, body)
    return EventDefinition.model_validate(raw)

delete_event_definition

delete_event_definition(event_name: str) -> None

Delete an event definition from Lexicon.

PARAMETER DESCRIPTION
event_name

Name of the event to delete.

TYPE: str

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Event not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_event_definition("OldEvent")
Source code in src/mixpanel_data/workspace.py
def delete_event_definition(self, event_name: str) -> None:
    """Delete an event definition from Lexicon.

    Args:
        event_name: Name of the event to delete.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Event not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_event_definition("OldEvent")
        ```
    """
    client = self._require_api_client()
    client.delete_event_definition(event_name)

bulk_update_event_definitions

bulk_update_event_definitions(
    params: BulkUpdateEventsParams,
) -> list[EventDefinition]

Bulk-update event definitions in Lexicon.

PARAMETER DESCRIPTION
params

Bulk update parameters containing a list of event updates (name + fields to change).

TYPE: BulkUpdateEventsParams

RETURNS DESCRIPTION
list[EventDefinition]

List of updated EventDefinition objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
defs = ws.bulk_update_event_definitions(
    BulkUpdateEventsParams(events=[
        {"name": "Signup", "description": "User signed up"},
        {"name": "Login", "hidden": True},
    ])
)
Source code in src/mixpanel_data/workspace.py
def bulk_update_event_definitions(
    self, params: BulkUpdateEventsParams
) -> list[EventDefinition]:
    """Bulk-update event definitions in Lexicon.

    Args:
        params: Bulk update parameters containing a list of event
            updates (name + fields to change).

    Returns:
        List of updated ``EventDefinition`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        defs = ws.bulk_update_event_definitions(
            BulkUpdateEventsParams(events=[
                {"name": "Signup", "description": "User signed up"},
                {"name": "Login", "hidden": True},
            ])
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw_list = client.bulk_update_event_definitions(body)
    return [EventDefinition.model_validate(x) for x in raw_list]

get_property_definitions

get_property_definitions(
    *, names: list[str], resource_type: str | None = None
) -> list[PropertyDefinition]

Get property definitions from Lexicon by name.

Retrieves metadata (description, tags, visibility, etc.) for the specified properties from the Mixpanel Lexicon.

PARAMETER DESCRIPTION
names

List of property names to look up.

TYPE: list[str]

resource_type

Optional resource type filter (e.g. "event", "user", "groupprofile").

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
list[PropertyDefinition]

List of PropertyDefinition objects for the requested properties.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
defs = ws.get_property_definitions(
    names=["plan_type", "country"],
    resource_type="event",
)
for d in defs:
    print(f"{d.name}: {d.description}")
Source code in src/mixpanel_data/workspace.py
def get_property_definitions(
    self,
    *,
    names: list[str],
    resource_type: str | None = None,
) -> list[PropertyDefinition]:
    """Get property definitions from Lexicon by name.

    Retrieves metadata (description, tags, visibility, etc.) for the
    specified properties from the Mixpanel Lexicon.

    Args:
        names: List of property names to look up.
        resource_type: Optional resource type filter (e.g. ``"event"``,
            ``"user"``, ``"groupprofile"``).

    Returns:
        List of ``PropertyDefinition`` objects for the requested properties.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        defs = ws.get_property_definitions(
            names=["plan_type", "country"],
            resource_type="event",
        )
        for d in defs:
            print(f"{d.name}: {d.description}")
        ```
    """
    client = self._require_api_client()
    raw_list = client.get_property_definitions(names, resource_type=resource_type)
    return [PropertyDefinition.model_validate(x) for x in raw_list]

update_property_definition

update_property_definition(
    property_name: str, params: UpdatePropertyDefinitionParams
) -> PropertyDefinition

Update a property definition in Lexicon.

PARAMETER DESCRIPTION
property_name

Name of the property to update.

TYPE: str

params

Fields to update (hidden, dropped, merged, sensitive, description).

TYPE: UpdatePropertyDefinitionParams

RETURNS DESCRIPTION
PropertyDefinition

The updated PropertyDefinition.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Property not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
definition = ws.update_property_definition(
    "plan_type",
    UpdatePropertyDefinitionParams(description="User plan tier"),
)
Source code in src/mixpanel_data/workspace.py
def update_property_definition(
    self, property_name: str, params: UpdatePropertyDefinitionParams
) -> PropertyDefinition:
    """Update a property definition in Lexicon.

    Args:
        property_name: Name of the property to update.
        params: Fields to update (hidden, dropped, merged,
            sensitive, description).

    Returns:
        The updated ``PropertyDefinition``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Property not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        definition = ws.update_property_definition(
            "plan_type",
            UpdatePropertyDefinitionParams(description="User plan tier"),
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.update_property_definition(property_name, body)
    return PropertyDefinition.model_validate(raw)

bulk_update_property_definitions

bulk_update_property_definitions(
    params: BulkUpdatePropertiesParams,
) -> list[PropertyDefinition]

Bulk-update property definitions in Lexicon.

PARAMETER DESCRIPTION
params

Bulk update parameters containing a list of property updates (name + fields to change).

TYPE: BulkUpdatePropertiesParams

RETURNS DESCRIPTION
list[PropertyDefinition]

List of updated PropertyDefinition objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
defs = ws.bulk_update_property_definitions(
    BulkUpdatePropertiesParams(properties=[
        {"name": "plan_type", "description": "User plan tier"},
        {"name": "country", "hidden": True},
    ])
)
Source code in src/mixpanel_data/workspace.py
def bulk_update_property_definitions(
    self, params: BulkUpdatePropertiesParams
) -> list[PropertyDefinition]:
    """Bulk-update property definitions in Lexicon.

    Args:
        params: Bulk update parameters containing a list of property
            updates (name + fields to change).

    Returns:
        List of updated ``PropertyDefinition`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        defs = ws.bulk_update_property_definitions(
            BulkUpdatePropertiesParams(properties=[
                {"name": "plan_type", "description": "User plan tier"},
                {"name": "country", "hidden": True},
            ])
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True, by_alias=True)
    raw_list = client.bulk_update_property_definitions(body)
    return [PropertyDefinition.model_validate(x) for x in raw_list]

list_lexicon_tags

list_lexicon_tags() -> list[LexiconTag]

List all Lexicon tags.

RETURNS DESCRIPTION
list[LexiconTag]

List of LexiconTag objects with id and name fields.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
tags = ws.list_lexicon_tags()
for tag in tags:
    print(tag.name)
Note

The list endpoint may return plain tag name strings without IDs. In that case, id is set to 0 as a sentinel value. Do not pass this sentinel to update_lexicon_tag() — use name-based operations (e.g. delete_lexicon_tag(tag.name)) for tags obtained from this method.

Source code in src/mixpanel_data/workspace.py
def list_lexicon_tags(self) -> list[LexiconTag]:
    """List all Lexicon tags.

    Returns:
        List of ``LexiconTag`` objects with ``id`` and ``name`` fields.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        tags = ws.list_lexicon_tags()
        for tag in tags:
            print(tag.name)
        ```

    Note:
        The list endpoint may return plain tag name strings without IDs.
        In that case, ``id`` is set to ``0`` as a sentinel value. Do not
        pass this sentinel to ``update_lexicon_tag()`` — use name-based
        operations (e.g. ``delete_lexicon_tag(tag.name)``) for tags
        obtained from this method.
    """
    client = self._require_api_client()
    raw_list = client.list_lexicon_tags()
    result: list[LexiconTag] = []
    for x in raw_list:
        if isinstance(x, str):
            # List endpoint returns plain tag name strings (no id);
            # id=0 is a sentinel — see docstring Note.
            result.append(LexiconTag(id=0, name=x))
        else:
            result.append(LexiconTag.model_validate(x))
    return result

create_lexicon_tag

create_lexicon_tag(params: CreateTagParams) -> LexiconTag

Create a new Lexicon tag.

PARAMETER DESCRIPTION
params

Tag creation parameters (name is required).

TYPE: CreateTagParams

RETURNS DESCRIPTION
LexiconTag

The created LexiconTag.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400) or tag already exists.

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
tag = ws.create_lexicon_tag(CreateTagParams(name="core-events"))
Source code in src/mixpanel_data/workspace.py
def create_lexicon_tag(self, params: CreateTagParams) -> LexiconTag:
    """Create a new Lexicon tag.

    Args:
        params: Tag creation parameters (name is required).

    Returns:
        The created ``LexiconTag``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400) or tag already exists.
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        tag = ws.create_lexicon_tag(CreateTagParams(name="core-events"))
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.create_lexicon_tag(body)
    return LexiconTag.model_validate(raw)

update_lexicon_tag

update_lexicon_tag(tag_id: int, params: UpdateTagParams) -> LexiconTag

Update a Lexicon tag.

PARAMETER DESCRIPTION
tag_id

Tag ID (integer).

TYPE: int

params

Fields to update (e.g. name).

TYPE: UpdateTagParams

RETURNS DESCRIPTION
LexiconTag

The updated LexiconTag.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Tag not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
tag = ws.update_lexicon_tag(
    5, UpdateTagParams(name="renamed-tag")
)
Source code in src/mixpanel_data/workspace.py
def update_lexicon_tag(self, tag_id: int, params: UpdateTagParams) -> LexiconTag:
    """Update a Lexicon tag.

    Args:
        tag_id: Tag ID (integer).
        params: Fields to update (e.g. name).

    Returns:
        The updated ``LexiconTag``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Tag not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        tag = ws.update_lexicon_tag(
            5, UpdateTagParams(name="renamed-tag")
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.update_lexicon_tag(tag_id, body)
    return LexiconTag.model_validate(raw)

delete_lexicon_tag

delete_lexicon_tag(tag_name: str) -> None

Delete a Lexicon tag by name.

PARAMETER DESCRIPTION
tag_name

Name of the tag to delete.

TYPE: str

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Tag not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_lexicon_tag("deprecated-tag")
Source code in src/mixpanel_data/workspace.py
def delete_lexicon_tag(self, tag_name: str) -> None:
    """Delete a Lexicon tag by name.

    Args:
        tag_name: Name of the tag to delete.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Tag not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_lexicon_tag("deprecated-tag")
        ```
    """
    client = self._require_api_client()
    client.delete_lexicon_tag(tag_name)

get_tracking_metadata

get_tracking_metadata(event_name: str) -> dict[str, Any]

Get tracking metadata for an event.

Retrieves information about how an event is being tracked (sources, SDKs, volume, etc.).

PARAMETER DESCRIPTION
event_name

Name of the event.

TYPE: str

RETURNS DESCRIPTION
dict[str, Any]

Raw tracking metadata dictionary.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Event not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
metadata = ws.get_tracking_metadata("Signup")
print(metadata)
Source code in src/mixpanel_data/workspace.py
def get_tracking_metadata(self, event_name: str) -> dict[str, Any]:
    """Get tracking metadata for an event.

    Retrieves information about how an event is being tracked
    (sources, SDKs, volume, etc.).

    Args:
        event_name: Name of the event.

    Returns:
        Raw tracking metadata dictionary.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Event not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        metadata = ws.get_tracking_metadata("Signup")
        print(metadata)
        ```
    """
    client = self._require_api_client()
    return client.get_tracking_metadata(event_name)

get_event_history

get_event_history(event_name: str) -> list[dict[str, Any]]

Get change history for an event definition.

PARAMETER DESCRIPTION
event_name

Name of the event.

TYPE: str

RETURNS DESCRIPTION
list[dict[str, Any]]

List of history entries (raw dictionaries) showing changes

list[dict[str, Any]]

to the event definition over time.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Event not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
history = ws.get_event_history("Signup")
for entry in history:
    print(f"{entry['timestamp']}: {entry['action']}")
Source code in src/mixpanel_data/workspace.py
def get_event_history(self, event_name: str) -> list[dict[str, Any]]:
    """Get change history for an event definition.

    Args:
        event_name: Name of the event.

    Returns:
        List of history entries (raw dictionaries) showing changes
        to the event definition over time.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Event not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        history = ws.get_event_history("Signup")
        for entry in history:
            print(f"{entry['timestamp']}: {entry['action']}")
        ```
    """
    client = self._require_api_client()
    return client.get_event_history(event_name)

get_property_history

get_property_history(
    property_name: str, entity_type: str
) -> list[dict[str, Any]]

Get change history for a property definition.

PARAMETER DESCRIPTION
property_name

Name of the property.

TYPE: str

entity_type

Entity type (e.g. "event", "user", "group").

TYPE: str

RETURNS DESCRIPTION
list[dict[str, Any]]

List of history entries (raw dictionaries) showing changes

list[dict[str, Any]]

to the property definition over time.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Property not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
history = ws.get_property_history("plan_type", "event")
for entry in history:
    print(f"{entry['timestamp']}: {entry['action']}")
Source code in src/mixpanel_data/workspace.py
def get_property_history(
    self, property_name: str, entity_type: str
) -> list[dict[str, Any]]:
    """Get change history for a property definition.

    Args:
        property_name: Name of the property.
        entity_type: Entity type (e.g. ``"event"``, ``"user"``,
            ``"group"``).

    Returns:
        List of history entries (raw dictionaries) showing changes
        to the property definition over time.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Property not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        history = ws.get_property_history("plan_type", "event")
        for entry in history:
            print(f"{entry['timestamp']}: {entry['action']}")
        ```
    """
    client = self._require_api_client()
    return client.get_property_history(property_name, entity_type)

export_lexicon

export_lexicon(*, export_types: list[str] | None = None) -> dict[str, Any]

Export Lexicon data definitions.

Exports event and property definitions from Lexicon, optionally filtered by type.

PARAMETER DESCRIPTION
export_types

Optional list of types to export (e.g. ["All Events and Properties", "All User Profile Properties"]).

TYPE: list[str] | None DEFAULT: None

RETURNS DESCRIPTION
dict[str, Any]

Raw export dictionary containing the exported definitions.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
export = ws.export_lexicon(
    export_types=["All Events and Properties"]
)
print(len(export.get("events", [])))
Source code in src/mixpanel_data/workspace.py
def export_lexicon(
    self, *, export_types: list[str] | None = None
) -> dict[str, Any]:
    """Export Lexicon data definitions.

    Exports event and property definitions from Lexicon, optionally
    filtered by type.

    Args:
        export_types: Optional list of types to export (e.g.
            ``["All Events and Properties", "All User Profile Properties"]``).

    Returns:
        Raw export dictionary containing the exported definitions.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        export = ws.export_lexicon(
            export_types=["All Events and Properties"]
        )
        print(len(export.get("events", [])))
        ```
    """
    client = self._require_api_client()
    return client.export_lexicon(export_types=export_types)

list_drop_filters

list_drop_filters() -> list[DropFilter]

List all drop filters.

RETURNS DESCRIPTION
list[DropFilter]

List of DropFilter objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
filters = ws.list_drop_filters()
for f in filters:
    print(f"{f.event_name}: active={f.active}")
Source code in src/mixpanel_data/workspace.py
def list_drop_filters(self) -> list[DropFilter]:
    """List all drop filters.

    Returns:
        List of ``DropFilter`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        filters = ws.list_drop_filters()
        for f in filters:
            print(f"{f.event_name}: active={f.active}")
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_drop_filters()
    return [DropFilter.model_validate(x) for x in raw_list]

create_drop_filter

create_drop_filter(params: CreateDropFilterParams) -> list[DropFilter]

Create a new drop filter.

PARAMETER DESCRIPTION
params

Drop filter creation parameters.

TYPE: CreateDropFilterParams

RETURNS DESCRIPTION
list[DropFilter]

Full list of DropFilter objects after creation.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
filters = ws.create_drop_filter(
    CreateDropFilterParams(
        event_name="Debug Event",
        filters={"property": "env", "value": "test"},
    )
)
Source code in src/mixpanel_data/workspace.py
def create_drop_filter(self, params: CreateDropFilterParams) -> list[DropFilter]:
    """Create a new drop filter.

    Args:
        params: Drop filter creation parameters.

    Returns:
        Full list of ``DropFilter`` objects after creation.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        filters = ws.create_drop_filter(
            CreateDropFilterParams(
                event_name="Debug Event",
                filters={"property": "env", "value": "test"},
            )
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw_list = client.create_drop_filter(body)
    return [DropFilter.model_validate(x) for x in raw_list]

update_drop_filter

update_drop_filter(params: UpdateDropFilterParams) -> list[DropFilter]

Update a drop filter.

PARAMETER DESCRIPTION
params

Drop filter update parameters (must include the filter ID).

TYPE: UpdateDropFilterParams

RETURNS DESCRIPTION
list[DropFilter]

Full list of DropFilter objects after update.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Filter not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
filters = ws.update_drop_filter(
    UpdateDropFilterParams(
        id=42, event_name="Debug Event v2"
    )
)
Source code in src/mixpanel_data/workspace.py
def update_drop_filter(self, params: UpdateDropFilterParams) -> list[DropFilter]:
    """Update a drop filter.

    Args:
        params: Drop filter update parameters (must include the filter ID).

    Returns:
        Full list of ``DropFilter`` objects after update.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Filter not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        filters = ws.update_drop_filter(
            UpdateDropFilterParams(
                id=42, event_name="Debug Event v2"
            )
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw_list = client.update_drop_filter(body)
    return [DropFilter.model_validate(x) for x in raw_list]

delete_drop_filter

delete_drop_filter(drop_filter_id: int) -> list[DropFilter]

Delete a drop filter.

PARAMETER DESCRIPTION
drop_filter_id

Drop filter ID (integer).

TYPE: int

RETURNS DESCRIPTION
list[DropFilter]

Full list of remaining DropFilter objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Filter not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
remaining = ws.delete_drop_filter(42)
Source code in src/mixpanel_data/workspace.py
def delete_drop_filter(self, drop_filter_id: int) -> list[DropFilter]:
    """Delete a drop filter.

    Args:
        drop_filter_id: Drop filter ID (integer).

    Returns:
        Full list of remaining ``DropFilter`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Filter not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        remaining = ws.delete_drop_filter(42)
        ```
    """
    client = self._require_api_client()
    raw_list = client.delete_drop_filter(drop_filter_id)
    return [DropFilter.model_validate(x) for x in raw_list]

get_drop_filter_limits

get_drop_filter_limits() -> DropFilterLimitsResponse

Get drop filter usage limits.

RETURNS DESCRIPTION
DropFilterLimitsResponse

DropFilterLimitsResponse with the maximum allowed

DropFilterLimitsResponse

drop filters for the project.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
limits = ws.get_drop_filter_limits()
print(f"Drop filter limit: {limits.filter_limit}")
Source code in src/mixpanel_data/workspace.py
def get_drop_filter_limits(self) -> DropFilterLimitsResponse:
    """Get drop filter usage limits.

    Returns:
        ``DropFilterLimitsResponse`` with the maximum allowed
        drop filters for the project.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        limits = ws.get_drop_filter_limits()
        print(f"Drop filter limit: {limits.filter_limit}")
        ```
    """
    client = self._require_api_client()
    raw = client.get_drop_filter_limits()
    return DropFilterLimitsResponse.model_validate(raw)

list_custom_properties

list_custom_properties() -> list[CustomProperty]

List all custom properties.

RETURNS DESCRIPTION
list[CustomProperty]

List of CustomProperty objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Server-side data corruption (e.g. invalid displayFormula on a custom property).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
props = ws.list_custom_properties()
for p in props:
    print(f"{p.name}: {p.display_formula}")
Source code in src/mixpanel_data/workspace.py
def list_custom_properties(self) -> list[CustomProperty]:
    """List all custom properties.

    Returns:
        List of ``CustomProperty`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Server-side data corruption (e.g. invalid
            ``displayFormula`` on a custom property).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        props = ws.list_custom_properties()
        for p in props:
            print(f"{p.name}: {p.display_formula}")
        ```
    """
    client = self._require_api_client()
    try:
        raw_list = client.list_custom_properties()
    except QueryError as exc:
        # Detect server-side data corruption: the API fails to serialize
        # when a custom property has an invalid displayFormula.
        details = exc.details if isinstance(exc.details, dict) else {}
        body = details.get("response_body", {}) if isinstance(details, dict) else {}
        if isinstance(body, dict) and body.get("field") == "displayFormula":
            raise QueryError(
                "list_custom_properties() failed: the project contains a "
                "custom property with an invalid displayFormula "
                "(server-side data corruption). Use "
                "get_custom_property(id) to retrieve individual "
                "properties, or contact Mixpanel support.",
                status_code=exc.status_code,
                response_body=exc.response_body,
                request_method=exc.request_method,
                request_url=exc.request_url,
                request_params=exc.request_params,
            ) from exc
        raise
    return [CustomProperty.model_validate(x) for x in raw_list]

create_custom_property

create_custom_property(params: CreateCustomPropertyParams) -> CustomProperty

Create a new custom property.

PARAMETER DESCRIPTION
params

Custom property creation parameters (name, display_formula or behavior, resource_type are required).

TYPE: CreateCustomPropertyParams

RETURNS DESCRIPTION
CustomProperty

The created CustomProperty.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
prop = ws.create_custom_property(
    CreateCustomPropertyParams(
        name="Full Name",
        display_formula='concat(properties["first"], " ", properties["last"])',
        composed_properties={"first": ComposedPropertyValue(resource_type="event"), "last": ComposedPropertyValue(resource_type="event")},
        resource_type="event",
    )
)
Source code in src/mixpanel_data/workspace.py
def create_custom_property(
    self, params: CreateCustomPropertyParams
) -> CustomProperty:
    """Create a new custom property.

    Args:
        params: Custom property creation parameters (name,
            display_formula or behavior, resource_type are required).

    Returns:
        The created ``CustomProperty``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        prop = ws.create_custom_property(
            CreateCustomPropertyParams(
                name="Full Name",
                display_formula='concat(properties["first"], " ", properties["last"])',
                composed_properties={"first": ComposedPropertyValue(resource_type="event"), "last": ComposedPropertyValue(resource_type="event")},
                resource_type="event",
            )
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True, by_alias=True, mode="json")
    raw = client.create_custom_property(body)
    return CustomProperty.model_validate(raw)

get_custom_property

get_custom_property(property_id: str) -> CustomProperty

Get a custom property by ID.

PARAMETER DESCRIPTION
property_id

Custom property ID (string).

TYPE: str

RETURNS DESCRIPTION
CustomProperty

The CustomProperty object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Property not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
prop = ws.get_custom_property("abc123")
print(prop.name)
Source code in src/mixpanel_data/workspace.py
def get_custom_property(self, property_id: str) -> CustomProperty:
    """Get a custom property by ID.

    Args:
        property_id: Custom property ID (string).

    Returns:
        The ``CustomProperty`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Property not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        prop = ws.get_custom_property("abc123")
        print(prop.name)
        ```
    """
    client = self._require_api_client()
    raw = client.get_custom_property(property_id)
    return CustomProperty.model_validate(raw)

update_custom_property

update_custom_property(
    property_id: str, params: UpdateCustomPropertyParams
) -> CustomProperty

Update a custom property.

PARAMETER DESCRIPTION
property_id

Custom property ID (string).

TYPE: str

params

Fields to update.

TYPE: UpdateCustomPropertyParams

RETURNS DESCRIPTION
CustomProperty

The updated CustomProperty.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Property not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
prop = ws.update_custom_property(
    "abc123",
    UpdateCustomPropertyParams(name="Renamed Property"),
)
Source code in src/mixpanel_data/workspace.py
def update_custom_property(
    self, property_id: str, params: UpdateCustomPropertyParams
) -> CustomProperty:
    """Update a custom property.

    Args:
        property_id: Custom property ID (string).
        params: Fields to update.

    Returns:
        The updated ``CustomProperty``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Property not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        prop = ws.update_custom_property(
            "abc123",
            UpdateCustomPropertyParams(name="Renamed Property"),
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True, by_alias=True)
    raw = client.update_custom_property(property_id, body)
    return CustomProperty.model_validate(raw)

delete_custom_property

delete_custom_property(property_id: str) -> None

Delete a custom property.

PARAMETER DESCRIPTION
property_id

Custom property ID (string).

TYPE: str

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Property not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_custom_property("abc123")
Source code in src/mixpanel_data/workspace.py
def delete_custom_property(self, property_id: str) -> None:
    """Delete a custom property.

    Args:
        property_id: Custom property ID (string).

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Property not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_custom_property("abc123")
        ```
    """
    client = self._require_api_client()
    client.delete_custom_property(property_id)

validate_custom_property

validate_custom_property(params: CreateCustomPropertyParams) -> dict[str, Any]

Validate a custom property definition without creating it.

PARAMETER DESCRIPTION
params

Custom property parameters to validate.

TYPE: CreateCustomPropertyParams

RETURNS DESCRIPTION
dict[str, Any]

Validation result as a raw dictionary.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
result = ws.validate_custom_property(
    CreateCustomPropertyParams(
        name="Full Name",
        display_formula='concat(properties["first"], " ", properties["last"])',
        composed_properties={"first": ComposedPropertyValue(resource_type="event"), "last": ComposedPropertyValue(resource_type="event")},
        resource_type="event",
    )
)
print(result)
Source code in src/mixpanel_data/workspace.py
def validate_custom_property(
    self, params: CreateCustomPropertyParams
) -> dict[str, Any]:
    """Validate a custom property definition without creating it.

    Args:
        params: Custom property parameters to validate.

    Returns:
        Validation result as a raw dictionary.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        result = ws.validate_custom_property(
            CreateCustomPropertyParams(
                name="Full Name",
                display_formula='concat(properties["first"], " ", properties["last"])',
                composed_properties={"first": ComposedPropertyValue(resource_type="event"), "last": ComposedPropertyValue(resource_type="event")},
                resource_type="event",
            )
        )
        print(result)
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True, by_alias=True)
    return client.validate_custom_property(body)

list_lookup_tables

list_lookup_tables(*, data_group_id: int | None = None) -> list[LookupTable]

List lookup tables.

PARAMETER DESCRIPTION
data_group_id

Optional filter by data group ID.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
list[LookupTable]

List of LookupTable objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
tables = ws.list_lookup_tables()
for t in tables:
    print(f"{t.name} (mapped={t.has_mapped_properties})")
Source code in src/mixpanel_data/workspace.py
def list_lookup_tables(
    self, *, data_group_id: int | None = None
) -> list[LookupTable]:
    """List lookup tables.

    Args:
        data_group_id: Optional filter by data group ID.

    Returns:
        List of ``LookupTable`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        tables = ws.list_lookup_tables()
        for t in tables:
            print(f"{t.name} (mapped={t.has_mapped_properties})")
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_lookup_tables(data_group_id=data_group_id)
    return [LookupTable.model_validate(x) for x in raw_list]

upload_lookup_table

upload_lookup_table(
    params: UploadLookupTableParams,
    *,
    poll_interval: float = 2.0,
    max_poll_seconds: float = 300.0,
) -> LookupTable

Upload a CSV file as a new lookup table.

Performs a 3-step upload process: 1. Obtains a signed upload URL from the API. 2. Uploads the CSV file to the signed URL. 3. Registers the lookup table with the uploaded data.

For files >= 5 MB, the API processes the upload asynchronously. This method automatically polls until processing completes.

PARAMETER DESCRIPTION
params

Upload parameters including name, file_path (path to the CSV file), and optional data_group_id.

TYPE: UploadLookupTableParams

poll_interval

Seconds between status polls for async uploads.

TYPE: float DEFAULT: 2.0

max_poll_seconds

Maximum seconds to wait for async processing.

TYPE: float DEFAULT: 300.0

RETURNS DESCRIPTION
LookupTable

The created LookupTable object.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400) or file not found.

ServerError

Server-side errors (5xx).

FileNotFoundError

If the CSV file does not exist.

MixpanelDataError

Async processing timed out or failed.

Example
ws = Workspace()
table = ws.upload_lookup_table(
    UploadLookupTableParams(
        name="Country Codes",
        file_path="/path/to/countries.csv",
    )
)
print(f"Created: {table.name}")
Source code in src/mixpanel_data/workspace.py
def upload_lookup_table(
    self,
    params: UploadLookupTableParams,
    *,
    poll_interval: float = 2.0,
    max_poll_seconds: float = 300.0,
) -> LookupTable:
    """Upload a CSV file as a new lookup table.

    Performs a 3-step upload process:
    1. Obtains a signed upload URL from the API.
    2. Uploads the CSV file to the signed URL.
    3. Registers the lookup table with the uploaded data.

    For files >= 5 MB, the API processes the upload asynchronously.
    This method automatically polls until processing completes.

    Args:
        params: Upload parameters including ``name``, ``file_path``
            (path to the CSV file), and optional ``data_group_id``.
        poll_interval: Seconds between status polls for async uploads.
        max_poll_seconds: Maximum seconds to wait for async processing.

    Returns:
        The created ``LookupTable`` object.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400) or file not found.
        ServerError: Server-side errors (5xx).
        FileNotFoundError: If the CSV file does not exist.
        MixpanelDataError: Async processing timed out or failed.

    Example:
        ```python
        ws = Workspace()
        table = ws.upload_lookup_table(
            UploadLookupTableParams(
                name="Country Codes",
                file_path="/path/to/countries.csv",
            )
        )
        print(f"Created: {table.name}")
        ```
    """
    client = self._require_api_client()
    logger = logging.getLogger(__name__)

    # Step 1: Get signed upload URL
    url_info = client.get_lookup_upload_url()

    # Step 2: Read and upload the CSV file
    csv_bytes = Path(params.file_path).read_bytes()
    client.upload_to_signed_url(url_info["url"], csv_bytes)

    # Step 3: Register the lookup table
    form_data: dict[str, str] = {
        "name": params.name,
        "path": url_info["path"],
        "key": url_info["key"],
    }
    if params.data_group_id is not None:
        form_data["data-group-id"] = str(params.data_group_id)

    raw = client.register_lookup_table(form_data)

    # The API returns {"uploadId": "..."} for files >= 5 MB,
    # indicating async processing via Celery.
    upload_id = raw.get("uploadId") if isinstance(raw, dict) else None
    if upload_id is not None:
        logger.info(
            "Lookup table upload is processing asynchronously "
            "(uploadId=%s), polling for completion...",
            upload_id,
        )
        raw = self._poll_lookup_upload(
            client, upload_id, poll_interval, max_poll_seconds
        )

    # Upload response may only contain {'id': '...'} without 'name';
    # inject the name from params so LookupTable validation succeeds.
    if isinstance(raw, dict) and "name" not in raw:
        raw = {**raw, "name": params.name}
    return LookupTable.model_validate(raw)

mark_lookup_table_ready

mark_lookup_table_ready(params: MarkLookupTableReadyParams) -> LookupTable

Mark a lookup table as ready after upload.

PARAMETER DESCRIPTION
params

Parameters including name, key, and optional data_group_id.

TYPE: MarkLookupTableReadyParams

RETURNS DESCRIPTION
LookupTable

The updated LookupTable.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
table = ws.mark_lookup_table_ready(
    MarkLookupTableReadyParams(
        name="Country Codes",
        key="uploads/abc123.csv",
    )
)
Source code in src/mixpanel_data/workspace.py
def mark_lookup_table_ready(
    self, params: MarkLookupTableReadyParams
) -> LookupTable:
    """Mark a lookup table as ready after upload.

    Args:
        params: Parameters including ``name``, ``key``, and optional
            ``data_group_id``.

    Returns:
        The updated ``LookupTable``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        table = ws.mark_lookup_table_ready(
            MarkLookupTableReadyParams(
                name="Country Codes",
                key="uploads/abc123.csv",
            )
        )
        ```
    """
    client = self._require_api_client()
    form_data: dict[str, str] = {
        "name": params.name,
        "key": params.key,
    }
    if params.data_group_id is not None:
        form_data["data-group-id"] = str(params.data_group_id)

    raw = client.mark_lookup_table_ready(form_data)
    return LookupTable.model_validate(raw)

get_lookup_upload_url

get_lookup_upload_url(content_type: str = 'text/csv') -> LookupTableUploadUrl

Get a signed URL for uploading lookup table data.

PARAMETER DESCRIPTION
content_type

MIME type of the file to upload (default: "text/csv").

TYPE: str DEFAULT: 'text/csv'

RETURNS DESCRIPTION
LookupTableUploadUrl

LookupTableUploadUrl with the signed URL, path, and key.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
url_info = ws.get_lookup_upload_url()
print(url_info.url)
Source code in src/mixpanel_data/workspace.py
def get_lookup_upload_url(
    self, content_type: str = "text/csv"
) -> LookupTableUploadUrl:
    """Get a signed URL for uploading lookup table data.

    Args:
        content_type: MIME type of the file to upload
            (default: ``"text/csv"``).

    Returns:
        ``LookupTableUploadUrl`` with the signed URL, path, and key.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        url_info = ws.get_lookup_upload_url()
        print(url_info.url)
        ```
    """
    client = self._require_api_client()
    raw = client.get_lookup_upload_url(content_type)
    return LookupTableUploadUrl.model_validate(raw)

get_lookup_upload_status

get_lookup_upload_status(upload_id: str) -> dict[str, Any]

Get the processing status of a lookup table upload.

PARAMETER DESCRIPTION
upload_id

Upload ID returned from the upload process.

TYPE: str

RETURNS DESCRIPTION
dict[str, Any]

Raw status dictionary with processing details.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Upload not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
status = ws.get_lookup_upload_status("upload-abc123")
print(status["state"])
Source code in src/mixpanel_data/workspace.py
def get_lookup_upload_status(self, upload_id: str) -> dict[str, Any]:
    """Get the processing status of a lookup table upload.

    Args:
        upload_id: Upload ID returned from the upload process.

    Returns:
        Raw status dictionary with processing details.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Upload not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        status = ws.get_lookup_upload_status("upload-abc123")
        print(status["state"])
        ```
    """
    client = self._require_api_client()
    return client.get_lookup_upload_status(upload_id)

update_lookup_table

update_lookup_table(
    data_group_id: int, params: UpdateLookupTableParams
) -> LookupTable

Update a lookup table.

PARAMETER DESCRIPTION
data_group_id

Data group ID of the lookup table.

TYPE: int

params

Fields to update.

TYPE: UpdateLookupTableParams

RETURNS DESCRIPTION
LookupTable

The updated LookupTable.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Table not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
table = ws.update_lookup_table(
    123,
    UpdateLookupTableParams(name="Renamed Table"),
)
Source code in src/mixpanel_data/workspace.py
def update_lookup_table(
    self, data_group_id: int, params: UpdateLookupTableParams
) -> LookupTable:
    """Update a lookup table.

    Args:
        data_group_id: Data group ID of the lookup table.
        params: Fields to update.

    Returns:
        The updated ``LookupTable``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Table not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        table = ws.update_lookup_table(
            123,
            UpdateLookupTableParams(name="Renamed Table"),
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.update_lookup_table(data_group_id, body)
    return LookupTable.model_validate(raw)

delete_lookup_tables

delete_lookup_tables(data_group_ids: list[int]) -> None

Delete one or more lookup tables.

PARAMETER DESCRIPTION
data_group_ids

List of data group IDs to delete.

TYPE: list[int]

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_lookup_tables([123, 456])
Source code in src/mixpanel_data/workspace.py
def delete_lookup_tables(self, data_group_ids: list[int]) -> None:
    """Delete one or more lookup tables.

    Args:
        data_group_ids: List of data group IDs to delete.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_lookup_tables([123, 456])
        ```
    """
    client = self._require_api_client()
    client.delete_lookup_tables(data_group_ids)

download_lookup_table

download_lookup_table(
    data_group_id: int,
    *,
    file_name: str | None = None,
    limit: int | None = None,
) -> bytes

Download lookup table data as raw bytes (CSV).

PARAMETER DESCRIPTION
data_group_id

Data group ID of the lookup table.

TYPE: int

file_name

Optional file name filter.

TYPE: str | None DEFAULT: None

limit

Optional row limit.

TYPE: int | None DEFAULT: None

RETURNS DESCRIPTION
bytes

Raw CSV bytes of the lookup table data.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Table not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
csv_data = ws.download_lookup_table(123)
Path("output.csv").write_bytes(csv_data)
Source code in src/mixpanel_data/workspace.py
def download_lookup_table(
    self,
    data_group_id: int,
    *,
    file_name: str | None = None,
    limit: int | None = None,
) -> bytes:
    """Download lookup table data as raw bytes (CSV).

    Args:
        data_group_id: Data group ID of the lookup table.
        file_name: Optional file name filter.
        limit: Optional row limit.

    Returns:
        Raw CSV bytes of the lookup table data.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Table not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        csv_data = ws.download_lookup_table(123)
        Path("output.csv").write_bytes(csv_data)
        ```
    """
    client = self._require_api_client()
    return client.download_lookup_table(
        data_group_id, file_name=file_name, limit=limit
    )

get_lookup_download_url

get_lookup_download_url(data_group_id: int) -> str

Get a signed download URL for a lookup table.

PARAMETER DESCRIPTION
data_group_id

Data group ID of the lookup table.

TYPE: int

RETURNS DESCRIPTION
str

Signed URL string for downloading the lookup table data.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Table not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
url = ws.get_lookup_download_url(123)
print(url)
Source code in src/mixpanel_data/workspace.py
def get_lookup_download_url(self, data_group_id: int) -> str:
    """Get a signed download URL for a lookup table.

    Args:
        data_group_id: Data group ID of the lookup table.

    Returns:
        Signed URL string for downloading the lookup table data.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Table not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        url = ws.get_lookup_download_url(123)
        print(url)
        ```
    """
    client = self._require_api_client()
    return client.get_lookup_download_url(data_group_id)

list_custom_events

list_custom_events() -> list[EventDefinition]

List all custom events.

RETURNS DESCRIPTION
list[EventDefinition]

List of EventDefinition objects for custom events.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
events = ws.list_custom_events()
for e in events:
    print(e.name)
Source code in src/mixpanel_data/workspace.py
def list_custom_events(self) -> list[EventDefinition]:
    """List all custom events.

    Returns:
        List of ``EventDefinition`` objects for custom events.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        events = ws.list_custom_events()
        for e in events:
            print(e.name)
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_custom_events()
    return [EventDefinition.model_validate(x) for x in raw_list]

update_custom_event

update_custom_event(
    custom_event_id: int, params: UpdateEventDefinitionParams
) -> EventDefinition

Update a custom event's lexicon entry (description, tags, etc.).

The Mixpanel data-definitions/events/ endpoint matches updates by the most specific identifier; for custom events that's the customEventId. This SDK method requires the id (rather than the display name) to avoid creating orphan lexicon entries — passing a name alone causes the server to fabricate a new, unlinked entry.

Get the id from :meth:create_custom_event's return value (CustomEvent.id) or from the custom_event_id field on entries returned by :meth:list_custom_events.

PARAMETER DESCRIPTION
custom_event_id

Server-assigned custom event ID.

TYPE: int

params

Fields to update. See :class:UpdateEventDefinitionParams for the full list of supported fields.

TYPE: UpdateEventDefinitionParams

RETURNS DESCRIPTION
EventDefinition

The updated EventDefinition (lexicon view of the custom

EventDefinition

event, with custom_event_id populated).

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Event not found (404) or validation error (400).

ServerError

Server-side errors (5xx).

MixpanelDataError

Server returned an entry with a different customEventId than requested (code="UPDATE_TARGET_MISMATCH").

Example
ws = Workspace()
ce = ws.create_custom_event(CreateCustomEventParams(
    name="Metric Tree Opened", alternatives=["Enter room"],
))
event = ws.update_custom_event(
    ce.id,
    UpdateEventDefinitionParams(
        description="Fires when a user opens a metric tree canvas.",
        verified=True,
    ),
)
Source code in src/mixpanel_data/workspace.py
def update_custom_event(
    self, custom_event_id: int, params: UpdateEventDefinitionParams
) -> EventDefinition:
    """Update a custom event's lexicon entry (description, tags, etc.).

    The Mixpanel ``data-definitions/events/`` endpoint matches updates by
    the most specific identifier; for custom events that's the
    ``customEventId``. This SDK method requires the id (rather than the
    display name) to avoid creating orphan lexicon entries — passing a
    name alone causes the server to fabricate a new, unlinked entry.

    Get the id from :meth:`create_custom_event`'s return value
    (``CustomEvent.id``) or from the ``custom_event_id`` field on entries
    returned by :meth:`list_custom_events`.

    Args:
        custom_event_id: Server-assigned custom event ID.
        params: Fields to update. See
            :class:`UpdateEventDefinitionParams` for the full list of
            supported fields.

    Returns:
        The updated ``EventDefinition`` (lexicon view of the custom
        event, with ``custom_event_id`` populated).

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Event not found (404) or validation error (400).
        ServerError: Server-side errors (5xx).
        MixpanelDataError: Server returned an entry with a different
            ``customEventId`` than requested
            (``code="UPDATE_TARGET_MISMATCH"``).

    Example:
        ```python
        ws = Workspace()
        ce = ws.create_custom_event(CreateCustomEventParams(
            name="Metric Tree Opened", alternatives=["Enter room"],
        ))
        event = ws.update_custom_event(
            ce.id,
            UpdateEventDefinitionParams(
                description="Fires when a user opens a metric tree canvas.",
                verified=True,
            ),
        )
        ```
    """
    client = self._require_api_client()
    body = params.model_dump(exclude_none=True)
    raw = client.update_custom_event(custom_event_id, body)
    return EventDefinition.model_validate(raw)

delete_custom_event

delete_custom_event(custom_event_id: int) -> None

Delete a custom event.

Identifies the entry by custom_event_id (not name) for the same reason :meth:update_custom_event does: a name-only DELETE against the data-definitions endpoint is ambiguous when multiple entries share a display name and may silently delete the wrong row, an auto-derived orphan lexicon entry, or no-op while still reporting success.

Get the id from :meth:create_custom_event's return value (CustomEvent.id) or from the custom_event_id field on entries returned by :meth:list_custom_events.

PARAMETER DESCRIPTION
custom_event_id

Server-assigned custom event ID.

TYPE: int

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Event not found (404).

ServerError

Server-side errors (5xx).

Example
ws = Workspace()
ws.delete_custom_event(42)
Source code in src/mixpanel_data/workspace.py
def delete_custom_event(self, custom_event_id: int) -> None:
    """Delete a custom event.

    Identifies the entry by ``custom_event_id`` (not name) for the
    same reason :meth:`update_custom_event` does: a name-only DELETE
    against the data-definitions endpoint is ambiguous when multiple
    entries share a display name and may silently delete the wrong row,
    an auto-derived orphan lexicon entry, or no-op while still
    reporting success.

    Get the id from :meth:`create_custom_event`'s return value
    (``CustomEvent.id``) or from the ``custom_event_id`` field on
    entries returned by :meth:`list_custom_events`.

    Args:
        custom_event_id: Server-assigned custom event ID.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Event not found (404).
        ServerError: Server-side errors (5xx).

    Example:
        ```python
        ws = Workspace()
        ws.delete_custom_event(42)
        ```
    """
    client = self._require_api_client()
    client.delete_custom_event(custom_event_id)

list_schema_registry

list_schema_registry(*, entity_type: str | None = None) -> list[SchemaEntry]

List schema registry entries.

PARAMETER DESCRIPTION
entity_type

Filter by entity type ("event", "custom_event", "profile"). If None, returns all schemas.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
list[SchemaEntry]

List of SchemaEntry objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

RateLimitError

Rate limit exceeded (429).

Example
ws = Workspace()
schemas = ws.list_schema_registry(entity_type="event")
for s in schemas:
    print(f"{s.name}: {s.entity_type}")
Source code in src/mixpanel_data/workspace.py
def list_schema_registry(
    self,
    *,
    entity_type: str | None = None,
) -> list[SchemaEntry]:
    """List schema registry entries.

    Args:
        entity_type: Filter by entity type ("event", "custom_event",
            "profile"). If None, returns all schemas.

    Returns:
        List of ``SchemaEntry`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        RateLimitError: Rate limit exceeded (429).

    Example:
        ```python
        ws = Workspace()
        schemas = ws.list_schema_registry(entity_type="event")
        for s in schemas:
            print(f"{s.name}: {s.entity_type}")
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_schema_registry(entity_type=entity_type)
    return [SchemaEntry.model_validate(r) for r in raw_list]

create_schema

create_schema(
    entity_type: str, entity_name: str, schema_json: dict[str, Any]
) -> dict[str, Any]

Create a single schema definition.

PARAMETER DESCRIPTION
entity_type

Entity type ("event", "custom_event", "profile").

TYPE: str

entity_name

Entity name (event name or "$user" for profile).

TYPE: str

schema_json

JSON Schema Draft 7 definition.

TYPE: dict[str, Any]

RETURNS DESCRIPTION
dict[str, Any]

Created schema as dict.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

RateLimitError

Rate limit exceeded (429).

Example
ws = Workspace()
ws.create_schema("event", "Purchase", {
    "properties": {"amount": {"type": "number"}}
})
Source code in src/mixpanel_data/workspace.py
def create_schema(
    self,
    entity_type: str,
    entity_name: str,
    schema_json: dict[str, Any],
) -> dict[str, Any]:
    """Create a single schema definition.

    Args:
        entity_type: Entity type ("event", "custom_event", "profile").
        entity_name: Entity name (event name or "$user" for profile).
        schema_json: JSON Schema Draft 7 definition.

    Returns:
        Created schema as dict.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        RateLimitError: Rate limit exceeded (429).

    Example:
        ```python
        ws = Workspace()
        ws.create_schema("event", "Purchase", {
            "properties": {"amount": {"type": "number"}}
        })
        ```
    """
    client = self._require_api_client()
    return client.create_schema(entity_type, entity_name, schema_json)

create_schemas_bulk

create_schemas_bulk(
    params: BulkCreateSchemasParams,
) -> BulkCreateSchemasResponse

Bulk create schemas.

PARAMETER DESCRIPTION
params

Bulk creation parameters with entries list and optional truncate flag.

TYPE: BulkCreateSchemasParams

RETURNS DESCRIPTION
BulkCreateSchemasResponse

Response with added and deleted counts.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

RateLimitError

Rate limit exceeded (429).

Example
ws = Workspace()
result = ws.create_schemas_bulk(BulkCreateSchemasParams(
    entries=[SchemaEntry(...)], truncate=True
))
print(f"Added: {result.added}")
Source code in src/mixpanel_data/workspace.py
def create_schemas_bulk(
    self,
    params: BulkCreateSchemasParams,
) -> BulkCreateSchemasResponse:
    """Bulk create schemas.

    Args:
        params: Bulk creation parameters with entries list and
            optional truncate flag.

    Returns:
        Response with ``added`` and ``deleted`` counts.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).
        RateLimitError: Rate limit exceeded (429).

    Example:
        ```python
        ws = Workspace()
        result = ws.create_schemas_bulk(BulkCreateSchemasParams(
            entries=[SchemaEntry(...)], truncate=True
        ))
        print(f"Added: {result.added}")
        ```
    """
    client = self._require_api_client()
    raw = client.create_schemas_bulk(
        params.model_dump(exclude_none=True, by_alias=True)
    )
    return BulkCreateSchemasResponse.model_validate(raw)

update_schema

update_schema(
    entity_type: str, entity_name: str, schema_json: dict[str, Any]
) -> dict[str, Any]

Update a single schema definition (merge semantics).

PARAMETER DESCRIPTION
entity_type

Entity type.

TYPE: str

entity_name

Entity name.

TYPE: str

schema_json

Partial JSON Schema to merge with existing.

TYPE: dict[str, Any]

RETURNS DESCRIPTION
dict[str, Any]

Updated schema as dict.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Entity not found or validation error (400, 404).

RateLimitError

Rate limit exceeded (429).

Example
ws = Workspace()
ws.update_schema("event", "Purchase", {
    "properties": {"tax": {"type": "number"}}
})
Source code in src/mixpanel_data/workspace.py
def update_schema(
    self,
    entity_type: str,
    entity_name: str,
    schema_json: dict[str, Any],
) -> dict[str, Any]:
    """Update a single schema definition (merge semantics).

    Args:
        entity_type: Entity type.
        entity_name: Entity name.
        schema_json: Partial JSON Schema to merge with existing.

    Returns:
        Updated schema as dict.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Entity not found or validation error (400, 404).
        RateLimitError: Rate limit exceeded (429).

    Example:
        ```python
        ws = Workspace()
        ws.update_schema("event", "Purchase", {
            "properties": {"tax": {"type": "number"}}
        })
        ```
    """
    client = self._require_api_client()
    return client.update_schema(entity_type, entity_name, schema_json)

update_schemas_bulk

update_schemas_bulk(params: BulkCreateSchemasParams) -> list[BulkPatchResult]

Bulk update schemas (merge semantics per entry).

PARAMETER DESCRIPTION
params

Bulk update parameters with entries list.

TYPE: BulkCreateSchemasParams

RETURNS DESCRIPTION
list[BulkPatchResult]

List of per-entry results with status ("ok" or "error").

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

RateLimitError

Rate limit exceeded (429).

Example
ws = Workspace()
results = ws.update_schemas_bulk(BulkCreateSchemasParams(
    entries=[SchemaEntry(...)]
))
for r in results:
    print(f"{r.name}: {r.status}")
Source code in src/mixpanel_data/workspace.py
def update_schemas_bulk(
    self,
    params: BulkCreateSchemasParams,
) -> list[BulkPatchResult]:
    """Bulk update schemas (merge semantics per entry).

    Args:
        params: Bulk update parameters with entries list.

    Returns:
        List of per-entry results with status ("ok" or "error").

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        RateLimitError: Rate limit exceeded (429).

    Example:
        ```python
        ws = Workspace()
        results = ws.update_schemas_bulk(BulkCreateSchemasParams(
            entries=[SchemaEntry(...)]
        ))
        for r in results:
            print(f"{r.name}: {r.status}")
        ```
    """
    client = self._require_api_client()
    raw_list = client.update_schemas_bulk(
        params.model_dump(exclude_none=True, by_alias=True)
    )
    return [BulkPatchResult.model_validate(r) for r in raw_list]

delete_schemas

delete_schemas(
    *, entity_type: str | None = None, entity_name: str | None = None
) -> DeleteSchemasResponse

Delete schemas by entity type and/or name.

If both provided, deletes a single schema. If only entity_type, deletes all schemas of that type. If neither, deletes all schemas.

PARAMETER DESCRIPTION
entity_type

Filter by entity type.

TYPE: str | None DEFAULT: None

entity_name

Filter by entity name (requires entity_type).

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
DeleteSchemasResponse

Response with delete_count.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid parameters (400).

RateLimitError

Rate limit exceeded (429).

MixpanelDataError

If entity_name is provided without entity_type.

Example
ws = Workspace()
resp = ws.delete_schemas(entity_type="event", entity_name="Purchase")
print(f"Deleted: {resp.delete_count}")
Source code in src/mixpanel_data/workspace.py
def delete_schemas(
    self,
    *,
    entity_type: str | None = None,
    entity_name: str | None = None,
) -> DeleteSchemasResponse:
    """Delete schemas by entity type and/or name.

    If both provided, deletes a single schema. If only entity_type,
    deletes all schemas of that type. If neither, deletes all schemas.

    Args:
        entity_type: Filter by entity type.
        entity_name: Filter by entity name (requires entity_type).

    Returns:
        Response with ``delete_count``.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid parameters (400).
        RateLimitError: Rate limit exceeded (429).
        MixpanelDataError: If entity_name is provided without entity_type.

    Example:
        ```python
        ws = Workspace()
        resp = ws.delete_schemas(entity_type="event", entity_name="Purchase")
        print(f"Deleted: {resp.delete_count}")
        ```
    """
    if entity_name is not None and entity_type is None:
        raise MixpanelDataError(
            "entity_name requires entity_type: providing entity_name "
            "without entity_type would delete all schemas",
        )
    client = self._require_api_client()
    raw = client.delete_schemas(entity_type=entity_type, entity_name=entity_name)
    return DeleteSchemasResponse.model_validate(raw)

get_schema_enforcement

get_schema_enforcement(*, fields: str | None = None) -> SchemaEnforcementConfig

Get current schema enforcement configuration.

PARAMETER DESCRIPTION
fields

Comma-separated field names to return (e.g., "ruleEvent,state"). If None, returns all fields.

TYPE: str | None DEFAULT: None

RETURNS DESCRIPTION
SchemaEnforcementConfig

Schema enforcement configuration.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

No enforcement configured (404).

Example
ws = Workspace()
config = ws.get_schema_enforcement()
print(f"Rule: {config.rule_event}")
Source code in src/mixpanel_data/workspace.py
def get_schema_enforcement(
    self,
    *,
    fields: str | None = None,
) -> SchemaEnforcementConfig:
    """Get current schema enforcement configuration.

    Args:
        fields: Comma-separated field names to return (e.g.,
            "ruleEvent,state"). If None, returns all fields.

    Returns:
        Schema enforcement configuration.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: No enforcement configured (404).

    Example:
        ```python
        ws = Workspace()
        config = ws.get_schema_enforcement()
        print(f"Rule: {config.rule_event}")
        ```
    """
    client = self._require_api_client()
    raw = client.get_schema_enforcement(fields=fields)
    return SchemaEnforcementConfig.model_validate(raw)

init_schema_enforcement

init_schema_enforcement(params: InitSchemaEnforcementParams) -> dict[str, Any]

Initialize schema enforcement.

PARAMETER DESCRIPTION
params

Init parameters with rule_event.

TYPE: InitSchemaEnforcementParams

RETURNS DESCRIPTION
dict[str, Any]

Raw API response as dict.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Already initialized or invalid rule_event (400).

Example
ws = Workspace()
ws.init_schema_enforcement(
    InitSchemaEnforcementParams(rule_event="Warn and Accept")
)
Source code in src/mixpanel_data/workspace.py
def init_schema_enforcement(
    self,
    params: InitSchemaEnforcementParams,
) -> dict[str, Any]:
    """Initialize schema enforcement.

    Args:
        params: Init parameters with rule_event.

    Returns:
        Raw API response as dict.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Already initialized or invalid rule_event (400).

    Example:
        ```python
        ws = Workspace()
        ws.init_schema_enforcement(
            InitSchemaEnforcementParams(rule_event="Warn and Accept")
        )
        ```
    """
    client = self._require_api_client()
    return client.init_schema_enforcement(
        params.model_dump(exclude_none=True, by_alias=True)
    )

update_schema_enforcement

update_schema_enforcement(
    params: UpdateSchemaEnforcementParams,
) -> dict[str, Any]

Partially update enforcement configuration.

PARAMETER DESCRIPTION
params

Partial update parameters.

TYPE: UpdateSchemaEnforcementParams

RETURNS DESCRIPTION
dict[str, Any]

Raw API response as dict.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

No enforcement configured or validation error (400).

Example
ws = Workspace()
ws.update_schema_enforcement(
    UpdateSchemaEnforcementParams(rule_event="Warn and Drop")
)
Source code in src/mixpanel_data/workspace.py
def update_schema_enforcement(
    self,
    params: UpdateSchemaEnforcementParams,
) -> dict[str, Any]:
    """Partially update enforcement configuration.

    Args:
        params: Partial update parameters.

    Returns:
        Raw API response as dict.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: No enforcement configured or validation error (400).

    Example:
        ```python
        ws = Workspace()
        ws.update_schema_enforcement(
            UpdateSchemaEnforcementParams(rule_event="Warn and Drop")
        )
        ```
    """
    client = self._require_api_client()
    return client.update_schema_enforcement(
        params.model_dump(exclude_none=True, by_alias=True)
    )

replace_schema_enforcement

replace_schema_enforcement(
    params: ReplaceSchemaEnforcementParams,
) -> dict[str, Any]

Fully replace enforcement configuration.

PARAMETER DESCRIPTION
params

Complete replacement parameters.

TYPE: ReplaceSchemaEnforcementParams

RETURNS DESCRIPTION
dict[str, Any]

Raw API response as dict.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

Example
ws = Workspace()
ws.replace_schema_enforcement(ReplaceSchemaEnforcementParams(
    events=[...], common_properties=[...],
    user_properties=[...], rule_event="Warn and Hide",
    notification_emails=["admin@example.com"],
))
Source code in src/mixpanel_data/workspace.py
def replace_schema_enforcement(
    self,
    params: ReplaceSchemaEnforcementParams,
) -> dict[str, Any]:
    """Fully replace enforcement configuration.

    Args:
        params: Complete replacement parameters.

    Returns:
        Raw API response as dict.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).

    Example:
        ```python
        ws = Workspace()
        ws.replace_schema_enforcement(ReplaceSchemaEnforcementParams(
            events=[...], common_properties=[...],
            user_properties=[...], rule_event="Warn and Hide",
            notification_emails=["admin@example.com"],
        ))
        ```
    """
    client = self._require_api_client()
    return client.replace_schema_enforcement(
        params.model_dump(exclude_none=True, by_alias=True)
    )

delete_schema_enforcement

delete_schema_enforcement() -> dict[str, Any]

Delete enforcement configuration.

RETURNS DESCRIPTION
dict[str, Any]

Raw API response as dict.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

No enforcement configured (404).

Example
ws = Workspace()
ws.delete_schema_enforcement()
Source code in src/mixpanel_data/workspace.py
def delete_schema_enforcement(self) -> dict[str, Any]:
    """Delete enforcement configuration.

    Returns:
        Raw API response as dict.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: No enforcement configured (404).

    Example:
        ```python
        ws = Workspace()
        ws.delete_schema_enforcement()
        ```
    """
    client = self._require_api_client()
    return client.delete_schema_enforcement()

run_audit

run_audit() -> AuditResponse

Run a full data audit (events + properties).

RETURNS DESCRIPTION
AuditResponse

Audit response with violations and computed_at timestamp.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

No schemas defined (400).

Example
ws = Workspace()
audit = ws.run_audit()
for v in audit.violations:
    print(f"{v.violation}: {v.name} ({v.count})")
Source code in src/mixpanel_data/workspace.py
def run_audit(self) -> AuditResponse:
    """Run a full data audit (events + properties).

    Returns:
        Audit response with violations and ``computed_at`` timestamp.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: No schemas defined (400).

    Example:
        ```python
        ws = Workspace()
        audit = ws.run_audit()
        for v in audit.violations:
            print(f"{v.violation}: {v.name} ({v.count})")
        ```
    """
    client = self._require_api_client()
    raw = client.run_audit()
    # raw is [violations_list, {"computed_at": ...}]
    if not raw:
        return AuditResponse(violations=[], computed_at="")
    if not isinstance(raw[0], list):
        raise MixpanelDataError(
            f"Unexpected audit response: expected list of violations, "
            f"got {type(raw[0]).__name__}",
        )
    violations = [AuditViolation.model_validate(v) for v in raw[0]]
    metadata = raw[1] if len(raw) > 1 and isinstance(raw[1], dict) else {}
    return AuditResponse(
        violations=violations,
        computed_at=metadata.get("computed_at", ""),
    )

run_audit_events_only

run_audit_events_only() -> AuditResponse

Run an events-only data audit (faster).

RETURNS DESCRIPTION
AuditResponse

Audit response with event violations only.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

No schemas defined (400).

Example
ws = Workspace()
audit = ws.run_audit_events_only()
Source code in src/mixpanel_data/workspace.py
def run_audit_events_only(self) -> AuditResponse:
    """Run an events-only data audit (faster).

    Returns:
        Audit response with event violations only.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: No schemas defined (400).

    Example:
        ```python
        ws = Workspace()
        audit = ws.run_audit_events_only()
        ```
    """
    client = self._require_api_client()
    raw = client.run_audit_events_only()
    if not raw:
        return AuditResponse(violations=[], computed_at="")
    if not isinstance(raw[0], list):
        raise MixpanelDataError(
            f"Unexpected audit response: expected list of violations, "
            f"got {type(raw[0]).__name__}",
        )
    violations = [AuditViolation.model_validate(v) for v in raw[0]]
    metadata = raw[1] if len(raw) > 1 and isinstance(raw[1], dict) else {}
    return AuditResponse(
        violations=violations,
        computed_at=metadata.get("computed_at", ""),
    )

list_data_volume_anomalies

list_data_volume_anomalies(
    *, query_params: dict[str, str] | None = None
) -> list[DataVolumeAnomaly]

List detected data volume anomalies.

PARAMETER DESCRIPTION
query_params

Optional filters (status, limit, event_id, etc.).

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

RETURNS DESCRIPTION
list[DataVolumeAnomaly]

List of DataVolumeAnomaly objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

Example
ws = Workspace()
anomalies = ws.list_data_volume_anomalies(
    query_params={"status": "open"}
)
Source code in src/mixpanel_data/workspace.py
def list_data_volume_anomalies(
    self,
    *,
    query_params: dict[str, str] | None = None,
) -> list[DataVolumeAnomaly]:
    """List detected data volume anomalies.

    Args:
        query_params: Optional filters (status, limit, event_id, etc.).

    Returns:
        List of ``DataVolumeAnomaly`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).

    Example:
        ```python
        ws = Workspace()
        anomalies = ws.list_data_volume_anomalies(
            query_params={"status": "open"}
        )
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_data_volume_anomalies(query_params=query_params)
    return [DataVolumeAnomaly.model_validate(r) for r in raw_list]

update_anomaly

update_anomaly(params: UpdateAnomalyParams) -> dict[str, Any]

Update the status of a single anomaly.

PARAMETER DESCRIPTION
params

Update parameters with id, status, and anomaly_class.

TYPE: UpdateAnomalyParams

RETURNS DESCRIPTION
dict[str, Any]

Raw API response as dict.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Anomaly not found or invalid parameters (400).

Example
ws = Workspace()
ws.update_anomaly(UpdateAnomalyParams(
    id=123, status="dismissed", anomaly_class="Event"
))
Source code in src/mixpanel_data/workspace.py
def update_anomaly(
    self,
    params: UpdateAnomalyParams,
) -> dict[str, Any]:
    """Update the status of a single anomaly.

    Args:
        params: Update parameters with id, status, and anomaly_class.

    Returns:
        Raw API response as dict.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Anomaly not found or invalid parameters (400).

    Example:
        ```python
        ws = Workspace()
        ws.update_anomaly(UpdateAnomalyParams(
            id=123, status="dismissed", anomaly_class="Event"
        ))
        ```
    """
    client = self._require_api_client()
    return client.update_anomaly(params.model_dump(by_alias=True))

bulk_update_anomalies

bulk_update_anomalies(params: BulkUpdateAnomalyParams) -> dict[str, Any]

Bulk update anomaly statuses.

PARAMETER DESCRIPTION
params

Bulk update with anomalies list and target status.

TYPE: BulkUpdateAnomalyParams

RETURNS DESCRIPTION
dict[str, Any]

Raw API response as dict.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid parameters (400).

Example
ws = Workspace()
ws.bulk_update_anomalies(BulkUpdateAnomalyParams(
    anomalies=[BulkAnomalyEntry(id=1, anomaly_class="Event")],
    status="dismissed",
))
Source code in src/mixpanel_data/workspace.py
def bulk_update_anomalies(
    self,
    params: BulkUpdateAnomalyParams,
) -> dict[str, Any]:
    """Bulk update anomaly statuses.

    Args:
        params: Bulk update with anomalies list and target status.

    Returns:
        Raw API response as dict.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid parameters (400).

    Example:
        ```python
        ws = Workspace()
        ws.bulk_update_anomalies(BulkUpdateAnomalyParams(
            anomalies=[BulkAnomalyEntry(id=1, anomaly_class="Event")],
            status="dismissed",
        ))
        ```
    """
    client = self._require_api_client()
    return client.bulk_update_anomalies(params.model_dump(by_alias=True))

list_deletion_requests

list_deletion_requests() -> list[EventDeletionRequest]

List all event deletion requests.

RETURNS DESCRIPTION
list[EventDeletionRequest]

List of EventDeletionRequest objects.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

Example
ws = Workspace()
for r in ws.list_deletion_requests():
    print(f"{r.event_name}: {r.status}")
Source code in src/mixpanel_data/workspace.py
def list_deletion_requests(self) -> list[EventDeletionRequest]:
    """List all event deletion requests.

    Returns:
        List of ``EventDeletionRequest`` objects.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).

    Example:
        ```python
        ws = Workspace()
        for r in ws.list_deletion_requests():
            print(f"{r.event_name}: {r.status}")
        ```
    """
    client = self._require_api_client()
    raw_list = client.list_deletion_requests()
    return [EventDeletionRequest.model_validate(r) for r in raw_list]

create_deletion_request

create_deletion_request(
    params: CreateDeletionRequestParams,
) -> list[EventDeletionRequest]

Create a new event deletion request.

PARAMETER DESCRIPTION
params

Deletion parameters with event_name, from_date, to_date, and optional filters.

TYPE: CreateDeletionRequestParams

RETURNS DESCRIPTION
list[EventDeletionRequest]

Updated full list of deletion requests.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Validation error (400).

Example
ws = Workspace()
requests = ws.create_deletion_request(
    CreateDeletionRequestParams(
        event_name="Test", from_date="2026-01-01",
        to_date="2026-01-31",
    )
)
Source code in src/mixpanel_data/workspace.py
def create_deletion_request(
    self,
    params: CreateDeletionRequestParams,
) -> list[EventDeletionRequest]:
    """Create a new event deletion request.

    Args:
        params: Deletion parameters with event_name, from_date,
            to_date, and optional filters.

    Returns:
        Updated full list of deletion requests.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Validation error (400).

    Example:
        ```python
        ws = Workspace()
        requests = ws.create_deletion_request(
            CreateDeletionRequestParams(
                event_name="Test", from_date="2026-01-01",
                to_date="2026-01-31",
            )
        )
        ```
    """
    client = self._require_api_client()
    raw_list = client.create_deletion_request(
        params.model_dump(exclude_none=True, by_alias=True)
    )
    return [EventDeletionRequest.model_validate(r) for r in raw_list]

cancel_deletion_request

cancel_deletion_request(request_id: int) -> list[EventDeletionRequest]

Cancel a pending deletion request.

PARAMETER DESCRIPTION
request_id

Deletion request ID to cancel.

TYPE: int

RETURNS DESCRIPTION
list[EventDeletionRequest]

Updated full list of deletion requests.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Request not found or not cancellable (400).

Example
ws = Workspace()
requests = ws.cancel_deletion_request(42)
Source code in src/mixpanel_data/workspace.py
def cancel_deletion_request(self, request_id: int) -> list[EventDeletionRequest]:
    """Cancel a pending deletion request.

    Args:
        request_id: Deletion request ID to cancel.

    Returns:
        Updated full list of deletion requests.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Request not found or not cancellable (400).

    Example:
        ```python
        ws = Workspace()
        requests = ws.cancel_deletion_request(42)
        ```
    """
    client = self._require_api_client()
    raw_list = client.cancel_deletion_request(request_id)
    return [EventDeletionRequest.model_validate(r) for r in raw_list]

preview_deletion_filters

preview_deletion_filters(
    params: PreviewDeletionFiltersParams,
) -> list[dict[str, Any]]

Preview what events a deletion filter would match.

This is a read-only operation that does not modify any data.

PARAMETER DESCRIPTION
params

Preview parameters with event_name, date range, and optional filters.

TYPE: PreviewDeletionFiltersParams

RETURNS DESCRIPTION
list[dict[str, Any]]

List of expanded/normalized filters.

RAISES DESCRIPTION
ConfigError

If credentials are not available.

AuthenticationError

Invalid credentials (401).

QueryError

Invalid filter parameters (400).

Example
ws = Workspace()
preview = ws.preview_deletion_filters(
    PreviewDeletionFiltersParams(
        event_name="Test", from_date="2026-01-01",
        to_date="2026-01-31",
    )
)
Source code in src/mixpanel_data/workspace.py
def preview_deletion_filters(
    self,
    params: PreviewDeletionFiltersParams,
) -> list[dict[str, Any]]:
    """Preview what events a deletion filter would match.

    This is a read-only operation that does not modify any data.

    Args:
        params: Preview parameters with event_name, date range,
            and optional filters.

    Returns:
        List of expanded/normalized filters.

    Raises:
        ConfigError: If credentials are not available.
        AuthenticationError: Invalid credentials (401).
        QueryError: Invalid filter parameters (400).

    Example:
        ```python
        ws = Workspace()
        preview = ws.preview_deletion_filters(
            PreviewDeletionFiltersParams(
                event_name="Test", from_date="2026-01-01",
                to_date="2026-01-31",
            )
        )
        ```
    """
    client = self._require_api_client()
    return client.preview_deletion_filters(
        params.model_dump(exclude_none=True, by_alias=True)
    )