Public API & API Keys

Programmatic access to your workspace — read projects, sessions, custom events and segments, and attach customer IDs to sessions — secured by workspace-scoped API keys.

1. Overview

The MousePlayer API lets you pull data out of your workspace into your own dashboards, data warehouse, or internal tooling. Apart from attaching customer IDs to sessions (identify, needs a key with write scope, see Key scopes) it is read-only: no data can be modified or deleted through the API.

  • Base URL: https://www.mouseplayer.com/api/v1/
  • All responses are JSON.
  • Every request is authenticated with an API key and automatically scoped to the workspace that key belongs to — a key from one workspace can never see another workspace's data, even by accident.
  • The full session-replay recording (mouse movements, DOM snapshots) is not exposed through the API. You get session metadata, pageviews, custom events and segments — enough to build reports and dashboards without shipping the raw replay payload around.

2. Authentication

Create and manage your API keys under API Keys in your account menu (/api-keys). Only workspace owners and admins can create or revoke keys.

Keep your key secret. It grants read access to everything in your workspace. The full key is shown exactly once, right after you create it — store it in a password manager or secrets vault. If you lose it, revoke it and create a new one.

Send the key as a Bearer token in the Authorization header of every request:

# List projects in your workspace curl -H "Authorization: Bearer mp_live_..." \ https://www.mouseplayer.com/api/v1/projects

Missing, malformed or revoked keys receive a 401 response:

{ "error": "Invalid or missing API key. Use: Authorization: Bearer <key>" }

Plan requirement

The REST API is part of the Business and Enterprise plans. The plan is taken from the owner of the workspace, so invited team members inherit it. The check runs on every request, not only when a key is created — if a workspace is downgraded, existing keys stop working and return 403.

Key scopes

Every key carries its scope in its own prefix, so you can tell what a key does just by looking at it — in a .env, in a ticket, in a terminal.

PrefixMeaning
mp_ro_Read only. Reads projects, sessions, events and segments. Cannot change anything.
mp_rw_Read and write. Everything above, plus attaching customer IDs via the identify endpoint.
Choose the scope when you create the key; the default is read only. Using a read-only key on a writing endpoint returns 403 with a message naming what the key is and what the endpoint needs. The scope cannot be changed afterwards — create a new key and revoke the old one.

3. Rate Limits

Each API key is limited to 60 requests per minute, tracked on a rolling window. Once you exceed that, requests receive a 429 response until the window clears:

{ "error": "Rate limit exceeded. Max 60 requests per minute." }

Building something that needs a higher limit? Reach out via Support and tell us about your use case.

4. Endpoints

GET/api/v1/projects

Lists every project (tracked property) in your workspace.

{ "data": [ { "channelid": "955a45df5ee842cb", "domain": "yourdomain.com", "status": "active", "total_traffic_bytes": 27304765, "created_at": "2026-07-19 22:06:39", "last_used": "2026-08-05 01:12:55" } ] }

GET/api/v1/projects/{channelid}/sessions

Lists visitor sessions recorded for a project, newest first. Paginated.

Query paramDescription
pagePage number, default 1.
per_pageResults per page, default 25, max 100.
sinceOnly sessions starting on/after this date (YYYY-MM-DD).
untilOnly sessions starting on/before this date.
{ "data": [ { "session_id": "s_kfmist058_1785795295504", "visitor_id": "v_sv90jdpzo_1785795296042", "custom_user_id": null, "country": "DE", "device_type": "desktop", "is_bot": 0, "start_time": "2026-08-04 00:14:56", "end_time": "2026-08-04 00:16:41", "duration_seconds": 105 } ], "pagination": { "page": 1, "per_page": 25, "total": 142, "total_pages": 6 } }

GET/api/v1/sessions/{session_id}

Metadata for a single session, plus its pageviews. Deliberately excludes the full replay payload — use the dashboard's Player for that.

{ "data": { "session_id": "s_kfmist058_1785795295504", "channelid": "955a45df5ee842cb", "visitor_id": "v_sv90jdpzo_1785795296042", "country": "DE", "asn_org": "Deutsche Telekom AG", "referrer": "https://duckduckgo.com/", "device_type": "desktop", "start_time": "2026-08-04 00:14:56", "end_time": "2026-08-04 00:16:41", "duration_seconds": 105, "pageviews": [ { "pageview_id": 1199, "url": "https://yourdomain.com/pricing", "page_title": "Pricing", "start_time": "2026-08-04 00:14:56", "end_time": "2026-08-04 00:16:41" } ] } }

GET/api/v1/sessions/{session_id}/events

Custom events fired during a session (the same events you send with mp('event', ...)), including their parameters.

{ "data": [ { "event_id": 61, "pageview_id": 1199, "event_name": "add_to_cart", "created_at": "2026-08-04 00:14:59", "params": { "item_id": "SKU_98765", "price": 299.99 } } ] }

GET/api/v1/projects/{channelid}/segments

Lists the visitor segments defined for a project (both project-specific and workspace-wide segments).

{ "data": [ { "segment_id": 4, "name": "High Intent Visitors", "description": "Pageviews > 5", "badge_color": "cyan", "is_global": 0, "created_at": "2026-08-03 00:00:43" } ] }

5. Attaching a customer ID from your backend

The browser often does not know who is sitting there. In a B2B product the identity is settled after login — in your backend. In a shop it is settled with the order confirmation. This endpoint is for exactly that moment.

POST /api/v1/sessions/{session_id}/identify
Authorization: Bearer mp_live_xxxxxxxxxxxx
Content-Type: application/json

{"user_id": "customer-4711"}

// 200 OK
{"data": {"session_id": "s_a1b2c3", "user_id": "customer-4711",
          "changed": true, "previous": null, "webhook_queued": true}}

The ID then appears in the session list, is searchable there, and the session_identified webhook fires — which is how it reaches your CRM.

Calling twice with the same ID is safe. The response comes back with "changed": false and no webhook is sent. A repeated order does not create a second lead.
StatusWhen
400Missing field, user_id over 128 characters, or control characters in it. We reject rather than truncate — a silently shortened ID matches nothing in your CRM and still looks like success.
404The session does not exist or does not belong to your workspace. Deliberately the same answer for both.
403The workspace plan does not include the API.

6. Webhooks: verifying the signature

Webhooks are configured per project under Integrations, where you also find the signing secret. Every delivery carries these headers:

HeaderMeaning
X-MousePlayer-EventEvent name, e.g. session_identified
X-MousePlayer-TimestampUnix seconds — part of the signed text
X-MousePlayer-Attempt1 on the first try, higher on retries
X-MousePlayer-Signaturesha256=<hex>

The signature is an HMAC-SHA256 over timestamp + "." + raw body, keyed with your project's signing secret.

// PHP
$erwartet = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
if (!hash_equals($erwartet, $signature)) { http_response_code(401); exit; }

// Node
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
                                     .update(timestamp + '.' + rawBody).digest('hex');
Sign over the raw body, before any JSON parsing. Re-serialising changes key order and whitespace, and the signature will never match. Also reject anything whose timestamp is more than a few minutes old — that is what the timestamp is for.

Delivery and retries

A failed delivery is retried after 1 min, 5 min, 15 min, 1 h and 6 h, then given up. 4xx responses other than 408 and 429 are not retried — they say "not like this", not "not right now". Respond 2xx quickly and do the work afterwards; delivery times out after 10 seconds.

Available events

EventFires
session_identifiedWhen a session first gets a customer ID — from the browser or from the API
session_endedOnce, when the session closes
goal_reachedOnce per session, when a custom event fulfils one of your goals. Only goals of type "event" — URL, duration, pageview and funnel goals are not evaluated. Carries goal_id, goal_name, goal_value and currency.
high_value_sessionWhen a visitor first crosses this project’s value threshold on a given day. The threshold is derived from your own score distribution (90th percentile of the last 90 days), not a fixed number, and it does not exist until at least 20 visitors have been scored. Carries visitor_id, value_score, threshold and the most watchable session of that day.
funnel_dropoffAt the end of a session that entered one of your funnels but did not finish it. Names the step it stopped at (dropped_at). A drop-off is the absence of an event, so it can only be known once the session is over. Sessions that never entered the funnel are not drop-offs and do not fire.
high_friction_sessionOnce per session, when its friction score first reaches the threshold (default 60 of 100). Carries friction_score, the signals behind it (reasons) and the signal that crossed the line (trigger).
element_brokenOnce, when a tracked element has found nothing in any checked page view for several days — usually after a relaunch. It fires again only after the marking has matched and broken once more. This event belongs to no session: session_id and session_url are null, link_url opens the marking instead, and subject_id names the marking — the same for every version, so a new selector that breaks again joins the open incident. Carries rule_id, rule_name, selector, page, checked_page_views, window_days and last_matched. No visitor data.
element_recoveredThe all-clear for element_broken: once the marking actually finds an element again — too few page views are not enough. Only sent if element_broken went out by webhook, and it has no switch of its own: subscribing to element_broken includes it. Same subject_id, so PagerDuty resolves the incident and Opsgenie closes the alert. Carries matched_page_views and checked_page_views in addition.
anomaly_detectedOnce a day, when yesterday clearly differed from the 28 days before: far fewer or far more sessions, a dropped goal rate, more sessions with friction or with JS errors. The same metric and direction stay quiet for a week (adjustable). This event belongs to no session: session_id and session_url are null, link_url opens the project overview, and subject_id names metric, direction and day. Carries metric (sessions, goal_rate, friction_rate, js_error_rate, lcp_p75, inp_p75, cls_p75), direction, day, value, expected, sessions, robust_z, z_score and a readable summary and hint. No visitor data. For Web Vitals, measurements replaces sessions, and page (empty for the whole site) and device (desktop or mobil) say where it happened; link_url then opens the Web Vitals tab.
rage_clickOn a detected rage click
error_detectedOn a front-end exception
ceCollective subscription for your own custom events. It does not include the events above — those have their own switches.

7. Errors & Status Codes

StatusMeaning
200Success.
401Missing, malformed or revoked API key.
404The requested project or session doesn't exist, or doesn't belong to your workspace.
429Rate limit exceeded — you're sending more than 60 requests/minute with this key.
403The workspace plan does not include the REST API. The message names both the required plan and the current one.

8. Security Notes

  • Read-only keys (mp_ro_) cannot change anything. Write keys (mp_rw_) can only attach customer IDs; no key can delete data or export raw replay data.
  • Every request is automatically scoped to the key's own workspace — there is no way to pass a different workspace ID and see someone else's data.
  • Revoke a key any time from /api-keys; revocation takes effect immediately.
  • Treat your API key like a password. If you accidentally commit one to a public repository, revoke it immediately and create a new one.

Bounded event pages (API v1, 14 October 2026)

GET /api/v1/sessions/{session_id}/events returns up to 100 events by default. Set per_page to an integer from 1 to 100. Follow pagination.next_cursor by sending cursor unchanged on the next request; keep the same session and workspace. Events are ordered by created_at and event_id. Cursors are signed and bound to that session and workspace. No total count or numeric page is returned.

The existing data array and params map remain available. pagination.returned is the actual number returned; pagination.has_more indicates another page. next_cursor is null when complete. A response is capped at 524288 bytes, so a page may contain fewer events than per_page. Per event, at most 128 parameter rows are returned; params_truncated is true if more exist. Individual string parameters retain the database limit of 255 characters. Truncated parameters are not available through this endpoint. Reducing per_page does not raise the parameter cap.

Invalid page sizes, altered cursors and cursors for another session/workspace return 400. Oversized individual event data returns 413. Existing access checks still return 401, 403, 404 or 409 as appropriate.

The limit remains 60 authenticated requests per API key per minute. Exhaustion returns 429 with Retry-After: 60. If Redis or the limiter is unavailable, requests return 503 with Retry-After: 5; they are never admitted without a limit. Respect Retry-After and add random backoff; do not retry in a tight loop. Event-database failures also return 503. Cursors do not freeze a snapshot: concurrent inserts with older timestamps may require a fresh scan.

GET /api/v1/sessions/s_example/events?per_page=25
GET /api/v1/sessions/s_example/events?per_page=25&cursor=SIGNED_CURSOR

/api/v1/native is a Dev-only backend control route for the native SDK preview. It remains disabled in production. Backend Bearer keys need read scope for list/replay and write scope for register/grant/delete/disable. Additional native-preview availability checks apply. Never embed permanent workspace API keys in a mobile app.