Auth¶
The auth surface in mixpanel_data 0.4.0 is organized around three independent axes — Account, Project, Workspace — with three first-class account types, a single resolver, fluent in-session switching via Workspace.use(), and a Cowork bridge for remote authentication.
Explore on DeepWiki
🤖 Configuration Reference → (updated for 0.4.0)
Ask questions about account types, session axes, OAuth, the Cowork bridge, or in-session switching.
Overview¶
import mixpanel_data as mp
# Construct a Workspace from active config
ws = mp.Workspace()
# Override per Workspace (env > param > target > bridge > [active] > default_project)
ws = mp.Workspace(account="team", project="3713224")
ws = mp.Workspace(target="ecom")
ws = mp.Workspace(session=mp.Session(account=..., project=..., workspace=...))
# In-session switching — fluent, O(1), no re-auth on project swap
ws.use(project="3018488").events()
ws.use(account="personal").events() # rebuilds auth header; preserves underlying HTTP client
ws.use(target="ecom").events() # applies all three axes atomically
ws.use(workspace=3448414).events()
# Functional namespaces (also re-exported as mp.accounts / mp.session / mp.targets)
summaries = mp.accounts.list()
mp.accounts.use("team")
active = mp.session.show() # ActiveSession
mp.targets.add("ecom", account="team", project="3018488", workspace=3448414)
See Configuration for the full setup walkthrough.
Account Types¶
Account is a Pydantic discriminated union over three first-class variants. The type field selects the variant; each variant carries the credentials it needs.
from mixpanel_data import (
Account, # discriminated union type
ServiceAccount, # type == "service_account"
OAuthBrowserAccount, # type == "oauth_browser"
OAuthTokenAccount, # type == "oauth_token"
AccountType, # Literal["service_account" | "oauth_browser" | "oauth_token"]
Region, # Literal["us" | "eu" | "in"]
)
account: Account = ServiceAccount(
name="team",
region="us",
default_project="3018488",
username="team-mp...",
secret="...",
)
if isinstance(account, ServiceAccount):
print(f"SA {account.name} → project {account.default_project}")
ServiceAccount¶
Long-lived HTTP Basic Auth credentials. Best for CI / scripts / unattended automation.
mixpanel_data.ServiceAccount
¶
Bases: _AccountBase
Basic-auth service account credentials.
Long-lived credentials provisioned via the Mixpanel UI ("Service Accounts"
section). Encodes username:secret as base64 for the Authorization
header per the Mixpanel REST API spec.
Example
type
class-attribute
instance-attribute
¶
Discriminator value for this variant.
username
instance-attribute
¶
Service account username (e.g. sa.demo).
secret
instance-attribute
¶
Service account secret. Redacted in repr/str via Pydantic.
auth_header
¶
Return the Authorization header value for HTTP requests.
| PARAMETER | DESCRIPTION |
|---|---|
token_resolver
|
Ignored for service accounts (kept for signature parity with the other variants).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The |
Source code in src/mixpanel_data/_internal/auth/account.py
is_long_lived
¶
Return whether this account survives across restarts without refresh.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
OAuthBrowserAccount¶
PKCE browser flow; access/refresh tokens persisted at ~/.mp/accounts/{name}/tokens.json and auto-refreshed on expiry.
mixpanel_data.OAuthBrowserAccount
¶
Bases: _AccountBase
OAuth account authenticated via PKCE browser flow.
The Account itself carries no secret — tokens are persisted at
~/.mp/accounts/{name}/tokens.json and produced on demand by a
:class:TokenResolver.
Example
type
class-attribute
instance-attribute
¶
Discriminator value for this variant.
auth_header
¶
Return the Authorization header value for HTTP requests.
| PARAMETER | DESCRIPTION |
|---|---|
token_resolver
|
Resolver responsible for loading + refreshing the on-disk token. Required.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The |
Source code in src/mixpanel_data/_internal/auth/account.py
is_long_lived
¶
Return whether this account survives across restarts without refresh.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
OAuthTokenAccount¶
Static bearer token (CI / agents) — supplied inline or via an env var (token_env).
mixpanel_data.OAuthTokenAccount
¶
Bases: _AccountBase
OAuth account using a static bearer token (CI, agents, ephemeral runs).
Exactly one of token (inline SecretStr) or token_env (env-var
name) must be provided — never both, never neither. This is enforced at
construction time by :meth:_validate_exactly_one_token_source.
Example
type
class-attribute
instance-attribute
¶
Discriminator value for this variant.
token
class-attribute
instance-attribute
¶
Inline static bearer token (mutually exclusive with token_env).
token_env
class-attribute
instance-attribute
¶
Env-var name to read the bearer from at resolution time.
auth_header
¶
Return the Authorization header value for HTTP requests.
| PARAMETER | DESCRIPTION |
|---|---|
token_resolver
|
Resolver responsible for materializing the token
(from inline
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The |
Source code in src/mixpanel_data/_internal/auth/account.py
is_long_lived
¶
Return whether this account survives across restarts without refresh.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
Session Axes¶
A Session is the immutable resolved state for a single Workspace at construction time — account, project, optional workspace, and the auth headers they generate.
mixpanel_data.Session
¶
Bases: BaseModel
Immutable in-memory tuple of (Account, Project, optional WorkspaceRef).
Holds the resolved auth/scope state for a single chain of API calls.
Switching to a different account, project, or workspace produces a new
Session via :meth:replace; the original is never mutated.
Workspace is optional: a session with workspace=None lazy-resolves
on the first workspace-scoped API call (per FR-025).
account
instance-attribute
¶
Resolved account (one of the three discriminated variants).
workspace
class-attribute
instance-attribute
¶
Resolved workspace; None triggers lazy resolution on first use.
headers
class-attribute
instance-attribute
¶
Custom HTTP headers attached at resolution time.
Populated from [settings].custom_header and/or bridge.headers.
Never read from os.environ after Session construction (per FR-014).
Wrapped in :class:types.MappingProxyType after validation, so any
in-place mutation (session.headers["X"] = "Y") raises
:class:TypeError instead of silently sharing state across
sessions. Consumers that need a mutable copy should use
dict(session.headers).
project_id
property
¶
Return the project's numeric string ID.
| RETURNS | DESCRIPTION |
|---|---|
str
|
|
workspace_id
property
¶
Return the workspace ID if set, else None.
| RETURNS | DESCRIPTION |
|---|---|
int | None
|
|
region
property
¶
Return the account's region.
| RETURNS | DESCRIPTION |
|---|---|
Region
|
|
auth_header
¶
Return the Authorization header for HTTP requests.
| PARAMETER | DESCRIPTION |
|---|---|
token_resolver
|
Required for OAuth accounts; ignored for
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The header value ( |
Source code in src/mixpanel_data/_internal/auth/session.py
replace
¶
replace(
*,
account: Account | None = None,
project: Project | None = None,
workspace: WorkspaceRef | None | _SentinelType = _SENTINEL,
headers: Mapping[str, str] | _SentinelType = _SENTINEL,
) -> Session
Return a new Session with the supplied axes swapped in.
Workspace and headers use a sentinel because None (resp. {})
is a valid replacement value, semantically distinct from "do not
touch this axis".
| PARAMETER | DESCRIPTION |
|---|---|
account
|
Replacement account; omitted preserves the current value.
TYPE:
|
project
|
Replacement project; omitted preserves the current value.
TYPE:
|
workspace
|
Replacement workspace;
TYPE:
|
headers
|
Replacement headers map;
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Session
|
A new :class: |
Source code in src/mixpanel_data/_internal/auth/session.py
mixpanel_data.Project
¶
Bases: BaseModel
Mixpanel project reference.
Project IDs come from the Mixpanel API as numeric strings. timezone
and organization_id are populated when the resolver has access to
a /me response; both are optional.
id
instance-attribute
¶
Numeric project ID (Mixpanel's wire format is a digit string).
name
class-attribute
instance-attribute
¶
Display name from /me, when known.
organization_id
class-attribute
instance-attribute
¶
Owning organization ID from /me, when known.
timezone
class-attribute
instance-attribute
¶
Project timezone (e.g. "US/Pacific") from /me, when known.
mixpanel_data.WorkspaceRef
¶
Bases: BaseModel
Mixpanel workspace reference (cohort/dashboard scoping unit).
The data model is named WorkspaceRef to avoid colliding with the
public Workspace facade class. Public re-export keeps the
WorkspaceRef name.
The optional project_id lets a :class:Session cross-check that the
workspace actually belongs to the bound project — every workspace lives
inside exactly one project, and routing a workspace ID to the wrong
project returns 400/404 deep inside the API call rather than at session
construction. When populated (typically from a /me enumeration), the
session-level model_validator raises :class:ValueError on mismatch
instead of letting the bug surface as an HTTP error mid-query.
id
instance-attribute
¶
Positive integer workspace ID assigned by Mixpanel.
name
class-attribute
instance-attribute
¶
Display name from /me or /projects/{pid}/workspaces/public.
is_default
class-attribute
instance-attribute
¶
Whether this is the project's default workspace, when known.
project_id
class-attribute
instance-attribute
¶
Owning project ID, when known.
Populated by /me enumeration paths so :class:Session can verify
project ↔ workspace coupling. Left None when the workspace was
constructed from a bare ID (e.g. MP_WORKSPACE_ID=N) — in that
case the cross-check degrades to "trust the caller" rather than raising
a spurious mismatch error.
mixpanel_data.auth_types.ActiveSession
¶
Bases: BaseModel
Persisted shape of the [active] block in ~/.mp/config.toml.
Only account and workspace live in [active]. Project lives
on the account itself as Account.default_project — switching
accounts implicitly switches projects (per FR-033). Any legacy
[active].project field is rejected by extra="forbid" to surface
the migration loudly.
Both fields are optional — environment variables or per-command flags can supply each one independently.
Workspace.use() — In-Session Switching¶
Workspace.use() is the only in-session switching method. It returns self for fluent chaining and preserves the underlying httpx.Client and per-account /me cache across switches, so cross-project / cross-account iteration is O(1) per turn.
import mixpanel_data as mp
ws = mp.Workspace() # active session
# In-session switching (returns self for chaining)
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 account.default_project; [active] only stores account + workspace
# Fluent chain
result = ws.use(project="3018488").segmentation(
"Login", from_date="2026-04-01", to_date="2026-04-21"
)
Switching the active account clears the workspace (workspaces are project-scoped). The project re-resolves on account swap via env > explicit > new account's default_project. There is no silent cross-axis fallback: if an axis can't be resolved on the new account, use() raises ConfigError.
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:
|
project
|
Replacement project ID.
TYPE:
|
workspace
|
Replacement workspace ID.
TYPE:
|
target
|
Apply this target's three axes atomically.
TYPE:
|
persist
|
When
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Workspace
|
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
Mutually exclusive args, or referenced name missing. |
OAuthError
|
New auth header construction fails (atomic on success). |
ConfigError
|
|
Source code in src/mixpanel_data/workspace.py
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 | |
Snapshot mode (parallel iteration)¶
For parallel cross-project iteration, snapshot the resolved Session and construct a fresh Workspace per task:
from concurrent.futures import ThreadPoolExecutor
import mixpanel_data as mp
ws = mp.Workspace()
sessions = [
ws.session.replace(project=mp.Project(id=p.id))
for p in ws.projects()
]
def event_count(s: mp.Session) -> int:
return len(mp.Workspace(session=s).events())
with ThreadPoolExecutor(max_workers=4) as pool:
counts = list(pool.map(event_count, sessions))
Functional Namespaces¶
The auth surface exposes three module-level namespaces re-exported from mixpanel_data. These are the canonical Python API for managing accounts, the active session, and saved targets.
mp.accounts¶
Account lifecycle: register, switch, probe, OAuth flows, bridge export.
mixpanel_data.accounts
¶
Public mp.accounts namespace.
Thin wrapper around :class:~mixpanel_data._internal.config.ConfigManager
exposing account CRUD, switching, and probing operations as the canonical
Python API for mp account ... CLI commands and the plugin's
auth_manager.py.
Reference: specs/042-auth-architecture-redesign/contracts/python-api.md §5.
list
¶
Return all configured accounts as AccountSummary records.
| RETURNS | DESCRIPTION |
|---|---|
list[AccountSummary]
|
Sorted-by-name list of summaries. |
add
¶
add(
name: str,
*,
type: AccountType,
region: Region,
default_project: str | None = None,
username: str | None = None,
secret: SecretStr | str | None = None,
token: SecretStr | str | None = None,
token_env: str | None = None,
) -> AccountSummary
Add a new account.
Per FR-004, default_project is required at add-time for
service_account and oauth_token. For oauth_browser it is
OPTIONAL — the value gets backfilled by login(name) after the PKCE
flow completes via a /me lookup.
Per FR-045, the first account added auto-promotes to
[active].account. Subsequent accounts do not.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account name (must match
TYPE:
|
type
|
One of
TYPE:
|
region
|
One of
TYPE:
|
default_project
|
Numeric project ID. Required for SA / oauth_token;
optional for oauth_browser (backfilled by
TYPE:
|
username
|
Required for
TYPE:
|
secret
|
Required for
TYPE:
|
token
|
For
TYPE:
|
token_env
|
For
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AccountSummary
|
class: |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Validation failure or duplicate name. |
Source code in src/mixpanel_data/accounts.py
update
¶
update(
name: str,
*,
region: Region | None = None,
default_project: str | None = None,
username: str | None = None,
secret: SecretStr | str | None = None,
token: SecretStr | str | None = None,
token_env: str | None = None,
) -> AccountSummary
Update fields on an existing account in place.
Type cannot be changed via this function (remove + re-add for that).
Type-incompatible fields raise ConfigError.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account to update.
TYPE:
|
region
|
New region.
TYPE:
|
default_project
|
New
TYPE:
|
username
|
New username (service_account only).
TYPE:
|
secret
|
New secret (service_account only).
TYPE:
|
token
|
New inline token (oauth_token only).
TYPE:
|
token_env
|
New env-var name (oauth_token only).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Updated
|
class:
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Account not found, type-incompatible field, or validation failure. |
Source code in src/mixpanel_data/accounts.py
remove
¶
Remove an account.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account name.
TYPE:
|
force
|
When
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[str]
|
List of orphaned target names (empty unless |
list[str]
|
the account had references). |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Missing account. |
AccountInUseError
|
Referenced and |
Source code in src/mixpanel_data/accounts.py
use
¶
Switch the active account, clearing any prior workspace pin.
The new account becomes [active].account and any prior
[active].workspace is dropped — workspaces are project-scoped, so
a leftover workspace ID from a different account would resolve to a
foreign workspace (or a 404) on the next Workspace() construction.
Project lives on the account itself as
:attr:Account.default_project, so it travels with the new account
automatically — no separate project axis to reset.
Both writes happen in a single _mutate() transaction so the
next process never sees a half-swapped state.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account to make active.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Account does not exist. |
Source code in src/mixpanel_data/accounts.py
show
¶
Return the named account summary, or the active one if no name given.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account name; if
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AccountSummary
|
class: |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Account not found OR no active account configured. |
Source code in src/mixpanel_data/accounts.py
test
¶
Probe /me for the named account and return the structured result.
Resolves the named account (or active account when name is None),
constructs a short-lived :class:MixpanelAPIClient against /me,
and reports whether the credentials are accepted plus the
authenticated user identity / accessible-project count from the
response body.
Never raises — every failure mode (account not found, missing
credentials, OAuth refresh failure, HTTP error) is captured in
result.error so the CLI can render a structured failure message
and downstream tooling can color accounts as
needs_login / needs_token based on the error string.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account to test;
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
AccountTestResult
|
class: |
AccountTestResult
|
on success, or |
Source code in src/mixpanel_data/accounts.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 | |
login
¶
Run the OAuth browser flow for an oauth_browser account.
Drives the full PKCE login dance:
- Validate
nameresolves to anoauth_browseraccount. - Run :meth:
OAuthFlow.login(PKCE + browser callback + token exchange). - Persist the resulting tokens atomically to
~/.mp/accounts/{name}/tokens.json. - Probe
/meto capture the authenticated user identity and (when missing) backfillaccount.default_projectwith the first accessible project.
The browser is opened by default; pass open_browser=False to
skip the call (useful for headless environments where the user copies
the authorization URL manually).
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account name (must be
TYPE:
|
open_browser
|
Whether to launch the system browser. When False,
the authorize URL is printed to stderr for manual copy
(CLI flag:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
An
|
class:
TYPE:
|
OAuthLoginResult
|
token expiry, and (best-effort) authenticated user identity. |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
|
OAuthError
|
Any leg of the PKCE flow fails (registration, browser, callback, token exchange). |
Source code in src/mixpanel_data/accounts.py
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | |
logout
¶
Remove the on-disk OAuth tokens for an oauth_browser account.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account name.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Account not found. |
Source code in src/mixpanel_data/accounts.py
token
¶
Return the current bearer token for an OAuth account.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account name;
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str | None
|
For |
str | None
|
For |
str | None
|
via the resolver if unavailable). |
str | None
|
For |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Account not found. |
OAuthError
|
OAuth token cannot be resolved (missing tokens, missing env var, etc.). |
Source code in src/mixpanel_data/accounts.py
export_bridge
¶
export_bridge(
*,
to: Path,
account: str | None = None,
project: str | None = None,
workspace: int | None = None,
) -> Path
Export the named (or active) account as a v2 bridge file.
Resolves the account, attaches any [settings].custom_header as
bridge.headers (B5 — header attaches in memory at resolution time
for the consumer), and writes a 0o600 file at to via
:func:bridge.export_bridge.
| PARAMETER | DESCRIPTION |
|---|---|
to
|
Destination path for the bridge file.
TYPE:
|
account
|
Account to export;
TYPE:
|
project
|
Optional pinned project ID.
TYPE:
|
workspace
|
Optional pinned workspace ID.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Path
|
The path that was written (same as |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Account not found, no active account, or
|
OAuthError
|
|
Source code in src/mixpanel_data/accounts.py
remove_bridge
¶
Remove the v2 bridge file at at (or the default path).
| PARAMETER | DESCRIPTION |
|---|---|
at
|
Bridge file path;
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
Source code in src/mixpanel_data/accounts.py
mp.session¶
Read and write the persisted [active] block.
mixpanel_data.session
¶
Public mp.session namespace.
Thin wrapper around :class:~mixpanel_data._internal.config.ConfigManager
exposing the persisted [active] session and per-axis updates.
Note: this module shadows the :class:Session value type. Public
callers access via import mixpanel_data; mp.session.show() (module)
or import mixpanel_data; mp.Session(...) (the type).
Reference: specs/042-auth-architecture-redesign/contracts/python-api.md §7.
show
¶
Return the persisted [active] block.
| RETURNS | DESCRIPTION |
|---|---|
ActiveSession
|
|
ActiveSession
|
None). Project lives on the active account as |
ActiveSession
|
|
Source code in src/mixpanel_data/session.py
use
¶
use(
*,
account: str | None = None,
project: str | None = None,
workspace: int | None = None,
target: str | None = None,
) -> None
Update one or more axes in the persisted config.
account= and workspace= are written to [active].
project= is written to the active account's default_project
(project lives on the account, not in [active]). target= is
mutually exclusive with the per-axis kwargs and applies all three
axes atomically (writing project to the target account's
default_project).
All updates land in a single apply_session transaction so the
on-disk state never reflects a partial swap (e.g., new account but
stale project).
| PARAMETER | DESCRIPTION |
|---|---|
account
|
New active account name.
TYPE:
|
project
|
New project ID (digit string) for the active account.
TYPE:
|
workspace
|
New active workspace ID.
TYPE:
|
target
|
Apply this target's three axes atomically.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
|
ConfigError
|
Referenced account or target not found, or
|
Source code in src/mixpanel_data/session.py
mp.targets¶
Manage saved (account, project, optional workspace) cursor positions.
mixpanel_data.targets
¶
Public mp.targets namespace.
Thin wrapper around :class:~mixpanel_data._internal.config.ConfigManager
exposing target CRUD and activation. Targets are saved
(account, project, workspace?) triples used as named cursor positions:
mp.targets.use("ecom") writes all three axes to [active] in a
single config save.
Reference: specs/042-auth-architecture-redesign/contracts/python-api.md §6.
list
¶
Return all configured targets sorted by name.
| RETURNS | DESCRIPTION |
|---|---|
list[Target]
|
Sorted list of :class: |
add
¶
Add a new target block.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Target name (block key).
TYPE:
|
account
|
Referenced account name (must exist).
TYPE:
|
project
|
Project ID (digit string).
TYPE:
|
workspace
|
Optional workspace ID.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Target
|
The constructed :class: |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Duplicate name, missing account, or validation failure. |
Source code in src/mixpanel_data/targets.py
remove
¶
Remove a target block.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Target to remove.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Target does not exist. |
use
¶
Apply the target — write all three axes to [active] atomically.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Target to apply.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Target does not exist OR its referenced account is gone. |
Source code in src/mixpanel_data/targets.py
show
¶
Return the named :class:Target.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Target name.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Target
|
The Target record. |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
Target does not exist. |
Result Types¶
Read-only structured results returned from the namespaces above.
AccountSummary¶
mixpanel_data.AccountSummary
¶
Bases: BaseModel
Read-only summary of a configured account for mp account list.
Fields are derived from the persisted [accounts.NAME] block plus
runtime context (is_active, referenced_by_targets). Status
reflects the most recent mp account test outcome — "untested"
is the default for accounts that have never been tested in this session.
Example
AccountTestResult¶
mixpanel_data.AccountTestResult
¶
Bases: BaseModel
Outcome of mp account test NAME — captures the /me probe.
Never raises — error context is captured in error so the CLI can
print structured failure messages and mp account list can color
accounts as needs_login / needs_token based on the error code.
The ok/error fields are paired by an invariant: ok=True
iff error is None. Constructing the model with both ok=True
and a non-empty error (or ok=False and error=None) raises
:class:pydantic.ValidationError to prevent ambiguous result states
that would force callers to guess the right field to read.
OAuthLoginResult¶
mixpanel_data.OAuthLoginResult
¶
Bases: BaseModel
Outcome of mp.accounts.login(name) — captures the PKCE flow result.
Returned after a successful OAuth browser flow. user is populated
from the immediate /me probe issued after the token exchange so
callers can confirm "you are now logged in as alice@example.com"
without needing a follow-up call.
user
class-attribute
instance-attribute
¶
Authenticated principal identity from the post-login /me probe.
expires_at
class-attribute
instance-attribute
¶
Access-token expiry (UTC) from the token endpoint response.
tokens_path
instance-attribute
¶
Where the tokens were persisted (~/.mp/accounts/{name}/tokens.json).
client_path
instance-attribute
¶
Where the DCR client info was persisted (~/.mp/accounts/{name}/client.json).
Target¶
mixpanel_data.Target
¶
Bases: BaseModel
A saved (account, project, workspace?) triple persisted in [targets.NAME].
Targets are named cursor positions: mp target use prod writes all
three axes to [active] in a single config save. Workspace is
optional — when omitted, the target resolves to the project's default
workspace at use time (per FR-025 lazy resolution).
account
instance-attribute
¶
Local config name of the referenced account (must exist).
project
instance-attribute
¶
Numeric project ID (Mixpanel's wire format).
workspace
class-attribute
instance-attribute
¶
Optional workspace ID (must be a positive integer when set);
None defers to lazy resolution. Mirrors WorkspaceRef.id's
PositiveInt constraint so bad values fail at construction rather
than corrupting downstream config.
Credential Resolution Chain¶
When constructing a Workspace, each axis is resolved independently in this priority order:
- Environment variables — the resolver reads
MP_USERNAME+MP_SECRET+MP_PROJECT_ID+MP_REGION(service-account quad),MP_OAUTH_TOKEN+MP_PROJECT_ID+MP_REGION(OAuth-token triple),MP_PROJECT_ID(project axis), andMP_WORKSPACE_ID(workspace axis).MP_ACCOUNTis not consumed by the Python resolver — it only feeds the CLI's--account/-aflag via Typer'senvvar=default. - Constructor / CLI param —
Workspace(account="..."),mp -a NAME .... - Saved target —
Workspace(target="ecom"),mp -t ecom .... - Bridge file —
MP_AUTH_FILEor~/.claude/mixpanel/auth.json. - Persisted active session — the
[active]block in~/.mp/config.toml. - Account default —
account.default_projectfor the project axis.
See Configuration → Credential Resolution Chain for examples.
Cowork Bridge (v2)¶
The Cowork bridge is a v2 JSON file that lets a remote VM authenticate against Mixpanel using your host machine's account and tokens. It embeds the full Account, optional OAuth tokens, and optional pinned project/workspace/headers.
from pathlib import Path
import mixpanel_data as mp
# On the host
mp.accounts.export_bridge(to=Path("~/.claude/mixpanel/auth.json").expanduser())
mp.accounts.remove_bridge()
# CLI equivalents
mp account export-bridge --to ~/.claude/mixpanel/auth.json
mp account remove-bridge
mp session --bridge # show bridge-resolved state
Default search order: MP_AUTH_FILE → ~/.claude/mixpanel/auth.json → ./mixpanel_auth.json.
mixpanel_data.auth_types.BridgeFile
¶
Bases: BaseModel
Cowork credential bridge file — v2 schema.
Embeds a full :class:~mixpanel_data._internal.auth.account.Account
record (with secrets inline) plus optional project / workspace pinning
and a custom-headers map.
Example
{
"version": 2,
"account": {"type": "oauth_browser", "name": "personal", "region": "us"},
"tokens": {"access_token": "...", "refresh_token": "...",
"expires_at": "2026-04-22T12:00:00Z",
"token_type": "Bearer", "scope": "read"},
"project": "3713224",
"workspace": 3448413,
"headers": {"X-Mixpanel-Cluster": "internal-1"}
}
version
class-attribute
instance-attribute
¶
Bridge schema version — always 2.
account
instance-attribute
¶
Full Account discriminated-union record (with secrets inline by design).
tokens
class-attribute
instance-attribute
¶
OAuth tokens — required iff account.type == "oauth_browser".
project
class-attribute
instance-attribute
¶
Optional pinned project ID (numeric string).
workspace
class-attribute
instance-attribute
¶
Optional pinned workspace ID.
headers
class-attribute
instance-attribute
¶
Custom HTTP headers attached to outbound requests at resolution time.
mixpanel_data.auth_types.load_bridge
¶
Load and validate a v2 bridge file from disk.
Resolves the path in this order:
- Argument
path(if not None). $MP_AUTH_FILEenv var (if set).- Default search paths (
~/.claude/mixpanel/auth.json, then<cwd>/mixpanel_auth.json) — first existing file wins.
| PARAMETER | DESCRIPTION |
|---|---|
path
|
Optional explicit bridge path.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
BridgeFile | None
|
The parsed :class: |
BridgeFile | None
|
path exists. |
| RAISES | DESCRIPTION |
|---|---|
ConfigError
|
If a candidate file exists but is malformed or fails schema validation. |
Source code in src/mixpanel_data/_internal/auth/bridge.py
OAuth Token Plumbing¶
Low-level types for OAuth token handling. Most users never touch these directly — mp.accounts.login(name) drives the full flow and OnDiskTokenResolver materializes refreshed tokens automatically.
OAuthTokens¶
mixpanel_data.auth_types.OAuthTokens
¶
Bases: BaseModel
Immutable OAuth 2.0 token set with expiry tracking.
Stores access and optional refresh tokens along with metadata
from the token response. The is_expired method includes a
30-second safety buffer to avoid using tokens that are about to expire.
| ATTRIBUTE | DESCRIPTION |
|---|---|
access_token |
The OAuth access token (redacted in output).
TYPE:
|
refresh_token |
The OAuth refresh token, if provided (redacted in output).
TYPE:
|
expires_at |
UTC datetime when the access token expires.
TYPE:
|
scope |
Space-separated list of granted scopes.
TYPE:
|
token_type |
Token type, typically
TYPE:
|
access_token
instance-attribute
¶
The OAuth access token (redacted in output).
refresh_token
class-attribute
instance-attribute
¶
The OAuth refresh token, if provided (redacted in output).
expires_at
instance-attribute
¶
UTC datetime when the access token expires.
Must be timezone-aware. Naive datetimes are rejected at validation
time so a downstream consumer can never accidentally compare against
an aware datetime.now(timezone.utc) and silently fall through
the expiry check (Fix 25).
is_expired
¶
Check whether the access token is expired or about to expire.
Uses a 30-second safety buffer to avoid sending tokens that are about to expire during in-flight requests.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
True if the token is expired or will expire within 30 seconds. |
Example
Source code in src/mixpanel_data/_internal/auth/token.py
from_token_response
classmethod
¶
Create an OAuthTokens instance from a raw token endpoint response.
Computes expires_at by adding the expires_in value (in seconds)
to the current UTC time.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Raw JSON response from the token endpoint. Must contain
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
OAuthTokens
|
A new frozen OAuthTokens instance. |
| RAISES | DESCRIPTION |
|---|---|
KeyError
|
If required keys are missing from |
ValueError
|
If |
Example
Source code in src/mixpanel_data/_internal/auth/token.py
OAuthClientInfo¶
mixpanel_data.auth_types.OAuthClientInfo
¶
Bases: BaseModel
Immutable OAuth client registration metadata.
Stores client information from Dynamic Client Registration (RFC 7591) for reuse across sessions without re-registering.
| ATTRIBUTE | DESCRIPTION |
|---|---|
client_id |
The OAuth client identifier.
TYPE:
|
region |
Mixpanel data residency region (
TYPE:
|
redirect_uri |
The redirect URI registered with the authorization server.
TYPE:
|
scope |
Space-separated list of requested scopes.
TYPE:
|
created_at |
UTC datetime when the client was registered.
TYPE:
|
redirect_uri
instance-attribute
¶
The redirect URI registered with the authorization server.
TokenResolver Protocol¶
mixpanel_data.auth_types.TokenResolver
¶
Bases: Protocol
Protocol for producing bearer tokens for OAuth accounts.
Implementations decide how to fetch (and refresh) tokens for the two
OAuth account variants. Concrete implementations live in
:mod:mixpanel_data._internal.auth.token_resolver.
get_browser_token
¶
Return a fresh access token for an :class:OAuthBrowserAccount.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account name (used to locate persisted tokens on disk).
TYPE:
|
region
|
Mixpanel region (used by some implementations).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The current access token (no |
Source code in src/mixpanel_data/_internal/auth/account.py
get_static_token
¶
Return the static bearer for an :class:OAuthTokenAccount.
| PARAMETER | DESCRIPTION |
|---|---|
account
|
The account whose
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The bearer token (no |
Source code in src/mixpanel_data/_internal/auth/account.py
OnDiskTokenResolver¶
mixpanel_data.auth_types.OnDiskTokenResolver
¶
Bases: TokenResolver
Default resolver: tokens live on disk per account.
Reads OAuth browser tokens from ~/.mp/accounts/{name}/tokens.json
written by :class:OAuthFlow. Reads static tokens from either the
inline token field on the account or the environment variable
named in token_env.
The resolver is intentionally I/O-light: the only side effects are
reading files that already exist and (for expired browser tokens)
refreshing via :meth:_refresh_and_persist, which delegates to
:class:OAuthFlow.refresh_tokens and rewrites
~/.mp/accounts/{name}/tokens.json atomically via
atomic_write_bytes. All failures surface as :class:OAuthError
so callers can give actionable error messages.
get_browser_token
¶
Return a fresh access token for an :class:OAuthBrowserAccount.
Reads ~/.mp/accounts/{name}/tokens.json, checks the recorded
expires_at (with a 30s safety buffer), and returns the token
if not expired. If expired, refreshes via
:meth:_refresh_and_persist; raises
:class:OAuthError(code="OAUTH_REFRESH_ERROR") if no refresh
token is recorded.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Account name (used to locate the tokens file).
TYPE:
|
region
|
Mixpanel region (kept for parity with the protocol; used by some refresh paths).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The current access token (no |
| RAISES | DESCRIPTION |
|---|---|
OAuthError
|
If the tokens file is missing, malformed, expired without a refresh token, or refresh fails. |
Source code in src/mixpanel_data/_internal/auth/token_resolver.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | |
get_static_token
¶
Return the static bearer for an :class:OAuthTokenAccount.
Resolves the bearer from the inline token field if present;
otherwise reads the environment variable named in token_env.
| PARAMETER | DESCRIPTION |
|---|---|
account
|
The account whose
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The bearer token (no |
| RAISES | DESCRIPTION |
|---|---|
OAuthError
|
If |