Skip to content

Report Links

Turn a headless query into a shareable Mixpanel URL, or turn a Mixpanel report URL back into the query behind it and run it — from Python or the mp CLI.

Two directions, one round trip each

create_report_link makes one App API POST and returns a URL; when no workspace is pinned or passed, workspace auto-resolution may add a lookup call first. resolve_report_link makes at most two (one for a shortlink, one for the record) and returns the raw params. saved_report_link makes none.

What a slug is

When you open a report in the Mixpanel web app the URL ends in a 12-character hash such as https://mixpanel.com/project/3/view/75/app/insights#EBrV5bW2u9Mw. That hash is a slug: a lookup key for an unsaved report that Mixpanel stores on the server, per project and per region. It is not an encoded query, so nothing can decode it offline — headless reads the record back through the App API instead.

A saved report (a bookmark) has a numeric id and a different hash form, for example #report/123 or, for funnels, #view/123. A shortlink looks like https://mixpanel.com/s/AbC123 and redirects to one of the two forms above.

From a typed result — the report type is inferred from the result class:

import mixpanel_headless as mp

ws = mp.Workspace()

result = ws.query(mp.Metric.total("Login"), last=7)
link = ws.create_report_link(result, name="Logins, last 7 days")
print(link.url)
# https://mixpanel.com/project/3/view/75/app/insights#EBrV5bW2u9Mw

From raw params, without running the query first:

params = ws.build_params("Login", last=7)
link = ws.create_report_link(params)                    # insights by default

funnel = ws.build_funnel_params([mp.FunnelStep("Login"), mp.FunnelStep("Purchase")], last=30)
link = ws.create_report_link(funnel, report_type="funnels")

ReportLink carries url, slug, report_type, project_id, workspace_id, name, description, bookmark_id, and created_at. str(link) is the URL.

Notes:

  • Params are validated against the bookmark schema before the upload. Pass validate=False to skip the check.
  • The /view/{wid} segment comes from an explicit workspace_id, then the pinned session workspace, then auto-resolution. If nothing resolves, the URL is project-only and still opens.
  • An explicit report_type that contradicts the result class raises ParamValidationError (RL4_REPORT_TYPE_CONFLICT) before any network call.

From the CLI:

mp reports link --params-file params.json --name "Logins"
cat params.json | mp reports link -f plain            # prints only the URL
mp reports link --params '{"sections": {...}}' --type funnels --jq .url

resolve_report_link accepts a full URL, a bare slug, or a shortlink:

r = ws.resolve_report_link("https://mixpanel.com/project/3/view/75/app/insights#EBrV5bW2u9Mw")
r.report_type   # "insights"  (from the server record, not the URL)
r.params        # the raw params dict
r.url           # the canonical URL, rebuilt from the record

ws.resolve_report_link("EBrV5bW2u9Mw")                  # bare slug, active project
ws.resolve_report_link("https://mixpanel.com/s/AbC123")  # shortlink, followed once
ws.resolve_report_link("https://mixpanel.com/project/3/app/insights#report/123")  # saved report

ResolvedReport carries source (slug or bookmark), report_type, params, project_id, workspace_id, region, url, input, expanded_url (the shortlink target), slug, bookmark_id, bookmark, name, description, and overrides. Stored overrides are surfaced as data; they are never merged into params.

The Mixpanel server canonicalizes params when it stores them. The record you read back is the web app's internal form: it adds defaults such as displayOptions.primaryYAxisOptions, behavior.behaviors, and executedMigrations; for Insights it rewrites behavior.type from event to simple, may replace an auto-captured event name such as $mp_web_page_view with its display name [Auto] Page View, and drops filtersDeterminer. The canonical params run through query_report_link without change, but do not expect byte-equality with what you sent.

For a saved-report link the type comes from the bookmark itself, so a /app/insights#report/123 link whose bookmark is a funnel resolves as funnels. A trailing ~(...) override segment on a saved-report link is ignored with a warning; the base params are returned.

From the CLI — quote the URL so the shell does not interpret #:

mp reports resolve 'https://mixpanel.com/project/3/view/75/app/insights#EBrV5bW2u9Mw'
mp reports resolve EBrV5bW2u9Mw --jq .params

Run a resolved report

query_report_link dispatches on the report type to query, query_funnel, query_retention, or query_flow and returns the matching typed result:

df = ws.query_report_link(r).df                 # already resolved: no second fetch
df = ws.query_report_link("EBrV5bW2u9Mw").df    # resolve and run in one call

resolved = ws.resolve_report_link(url)
if resolved.report_type == "flows":
    result = ws.query_report_link(resolved, mode="paths")   # else mode comes from params["chartType"]

A launch-analysis report resolves but cannot be run; query_report_link raises UnsupportedReportLinkError (UNSUPPORTED_REPORT_TYPE).

A ResolvedReport remembers the region, project, and workspace it was resolved in. If you keep one across ws.use(project=...) or ws.use(workspace=...), or hand it to a Workspace on another project, query_report_link raises ReportLinkScopeMismatchError before it runs anything, the same check resolve_report_link applies to a URL. The workspace part applies only when the session has a pinned workspace and the report records one.

mp reports resolve 'https://mixpanel.com/s/AbC123' --run -f csv
mp reports resolve 'https://mixpanel.com/project/3/app/flows#report/8' --run --mode paths

Four mp query commands take an opt-in --link flag that adds a report_url key to the output. Without the flag the output is unchanged.

Command What --link does
mp query segmentation -e EVENT --from D --to D [-u UNIT] [--on PROP] --link Builds Insights params for the same event, dates, unit, and breakdown, creates an unsaved report, and adds its URL. One network call, plus workspace auto-resolution when none is pinned.
mp query funnel FUNNEL_ID ... --link Adds the saved funnel's URL. No network call.
mp query saved-report ID --link Adds the saved report's URL, using the detected report type. No network call.
mp query flows ID --link Adds the saved flows report's URL. No network call.
mp query saved-report 123 --link --jq .report_url
mp query segmentation -e Login --from 2026-08-01 --to 2026-08-31 --link --jq .report_url

The segmentation link is an approximation

The legacy segmentation endpoint and the Insights engine can differ at the edges. The link reproduces the event, the dates, the unit, and a bare breakdown property such as --on country, --on 'Plan Type', or --on 'Terms and Conditions'. A value that contains a bracket, a quote, a comparison operator, or a cast call such as number(...) is an expression, and so is any --where; the command then prints a warning on stderr and omits the link. A link failure never fails the query: the result still prints, the exit code stays 0, report_url is null, and report_url_error holds the reason. This holds for all four commands, including an auth or server error during the link step.

ws.saved_report_link(123)                              # insights: .../app/insights#report/123
ws.saved_report_link(456, report_type="funnels")       # .../app/funnels#view/456
ws.saved_report_link(8, report_type="flows", workspace_id=75)

The singular "funnel" that SavedReportResult.report_type reports is accepted and normalized to "funnels".

When resolution fails

Situation Exception / code What to do
The link names another project ReportLinkScopeMismatchError / REPORT_LINK_PROJECT_MISMATCH Switch: ws.use(project="3") or mp --project 3 .... The record was not fetched. For a full URL or a bare slug no network call was made; for a shortlink the one redirect GET had to run first.
The link host is another region ReportLinkScopeMismatchError / REPORT_LINK_REGION_MISMATCH Use an account for that region: ws.use(account="NAME") or mp --account NAME .... No network call was made, for shortlinks too.
The link names a workspace and your session is pinned to a different one ReportLinkScopeMismatchError / REPORT_LINK_WORKSPACE_MISMATCH Switch: ws.use(workspace=75) or mp --workspace 75 .... Query requests carry the pinned workspace, so a different data view would change the results. An unpinned session accepts any link. query_report_link runs under exactly the scope the report records, the URL wid or the pin at resolve time or project-wide, and never injects the current pin, so a pin cleared or set after resolve time cannot change the data view. Checked at the same point as the project.
A saved-report link 404s while a workspace is pinned ReportLinkNotFoundError / REPORT_LINK_BOOKMARK_NOT_FOUND The lookup was workspace-scoped, so the report may live in a sibling workspace of the same project. The message names the pinned workspace; switch or unpin and retry.
The hash starts with ~( UnsupportedReportLinkError / UNSUPPORTED_LEGACY_HASH Open the link in a browser. The app re-issues a slug on load; copy the new URL.
The link is a board UnsupportedReportLinkError / UNSUPPORTED_DASHBOARD_LINK ws.get_dashboard(ID) lists its reports; resolve one report link. A board URL that carries edited-bookmark=<slug> resolves that slug.
The slug is unknown here ReportLinkNotFoundError / REPORT_LINK_SLUG_NOT_FOUND A slug is readable only in the project and region that created it.
The shortlink redirects to another shortlink ShortLinkResolutionError / SHORT_LINK_CHAIN Resolve the target shortlink directly. Headless follows one redirect only.
The shortlink redirects to the login page AuthenticationError The credentials cannot see the shortlink.

Every error in the family subclasses ReportLinkError and carries the parsed link parts plus a hint in details. The CLI prints the hint on its own hint: line. CLI exit codes: not found 4; parse, unsupported, and scope errors 3; auth 2; shortlink extraction 1. The pure URL builders and the create_report_link input guards raise ParamValidationError (RL1..RL6; RL6_INVALID_ID rejects a zero or negative project, workspace, or bookmark id), not a ReportLinkError. They fire before any network call.

The parser tolerates surrounding whitespace, a trailing slash before or after #, a query string before # or a ? tail after the slug or report/{id} hash, an upper-case host, a missing scheme, http as well as https (no other scheme), a percent-encoded # (no other escape is decoded), the legacy /report/{pid}/ path form, and mixpanel.org.

Out of scope

  • Typed decompile of params into Metric / Filter / GroupBy objects (follow-up feature).
  • Decode of legacy ~(...) JSURL hashes.
  • Creation of shortlinks. The Mixpanel server allows this only from a browser session.
  • --link on mp query retention and on the other legacy query commands.
  • Merge of stored overrides into params.
  • Any change to how saved reports are created or edited.

Next Steps