Page background for the overlay
Two rrweb events — a Meta and the FullSnapshot that follows it — pulled from a real recording of the page. Feed data.events into an rrweb player (rrweb-player) to rebuild the DOM, then draw the cells on top: multiply x_rel by data.width, and use y_px as-is.
Requires session replay to be enabled for the client. Candidates are recorded page loads of this URL from the last 30 days, newest first, so mid-funnel pages work without a session_id. Pass the same viewport_min/viewport_max as the click request: the background is then captured inside that range, so the DOM under the dots is the layout the clicks happened on. A different breakpoint is never substituted. With no match the endpoint returns 404.
height is the captured VIEWPORT height, not the document height — sizing a stage from it hides every click below the fold. Take the document height from /heatmap/scroll's page_height, or from the deepest y_px you received, and stretch the rebuilt page to it.
Cells with target_fixed: true were captured on a position fixed/sticky element; their y_px is viewport-relative, not page-absolute, so they belong wherever that element sits — not at the page offset.
curl -X GET "https://mythic-analytics.gulp.workers.dev/client/v1/data/heatmap/snapshot?location_id=example_string&url=acme.com%2Fpricing&viewport_min=1280&viewport_max=20000&state=loaded&min_page_height=2500&session_id=example_string" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN"
import requests
import json
url = "https://mythic-analytics.gulp.workers.dev/client/v1/data/heatmap/snapshot?location_id=example_string&url=acme.com%2Fpricing&viewport_min=1280&viewport_max=20000&state=loaded&min_page_height=2500&session_id=example_string"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
response = requests.get(url, headers=headers)
print(response.json())
const response = await fetch("https://mythic-analytics.gulp.workers.dev/client/v1/data/heatmap/snapshot?location_id=example_string&url=acme.com%2Fpricing&viewport_min=1280&viewport_max=20000&state=loaded&min_page_height=2500&session_id=example_string", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
)
func main() {
req, err := http.NewRequest("GET", "https://mythic-analytics.gulp.workers.dev/client/v1/data/heatmap/snapshot?location_id=example_string&url=acme.com%2Fpricing&viewport_min=1280&viewport_max=20000&state=loaded&min_page_height=2500&session_id=example_string", nil)
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
require 'net/http'
require 'json'
uri = URI('https://mythic-analytics.gulp.workers.dev/client/v1/data/heatmap/snapshot?location_id=example_string&url=acme.com%2Fpricing&viewport_min=1280&viewport_max=20000&state=loaded&min_page_height=2500&session_id=example_string')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
response = http.request(request)
puts response.body
{
"success": true,
"data": {
"page": "acme.com/pricing",
"session_id": "example_string",
"window_id": "example_string",
"state": "loaded",
"ended_at": 42,
"captured_at": 42,
"width": 1440,
"height": 900,
"events": [
{}
]
}
}
{
"error": "Bad Request",
"message": "The request contains invalid parameters or malformed data",
"code": 400,
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
{
"error": "Not Found",
"message": "The requested resource was not found",
"code": 404
}
/client/v1/data/heatmap/snapshot
Target server for requests. Edit to use your own host.
Agency key (ak_) or location secret key (sk_). Scoped keys (mcp_) are accepted too and need heatmaps:read. See Using an mcp_ key over HTTP.
ak_) or location secret key (sk_). Scoped keys (mcp_) are accepted too and need heatmaps:read. See Using an mcp_ key over HTTP.Required with an agency key (ak_); ignored with a location key (sk_). Can also be sent as the X-Location-Id header.
Only use a snapshot captured at a viewport width >= this (px).
Only use a snapshot captured at a viewport width <= this (px).
loaded (default): the Meta + FullSnapshot pair, the page as it loaded. final: every event of one recorded visit of the page, up to the next page. Replay it to its LAST event and the background shows the page as the visitor last saw it (a form step opened, a section expanded).
Only use visits whose page ended at least this tall (px, from the visit's scroll-depth measurement). Pair with state=final to get a background from a visit where the page grew. Answered only from indexed recordings, so a page with none returns 404.
Take the snapshot from this recording instead of searching. The viewport range still applies.
Request Preview
Response
Response will appear here after sending the request
Authentication
Bearer token. Agency key (ak_) or location secret key (sk_). Scoped keys (mcp_) are accepted too and need heatmaps:read. See Using an mcp_ key over HTTP.
Query Parameters
Required with an agency key (ak_); ignored with a location key (sk_). Can also be sent as the X-Location-Id header.
acme.com/pricingloaded (default): the Meta + FullSnapshot pair, the page as it loaded. final: every event of one recorded visit of the page, up to the next page. Replay it to its LAST event and the background shows the page as the visitor last saw it (a form step opened, a section expanded).
loadedfinalOnly use visits whose page ended at least this tall (px, from the visit's scroll-depth measurement). Pair with state=final to get a background from a visit where the page grew. Answered only from indexed recordings, so a page with none returns 404.
2500Take the snapshot from this recording instead of searching. The viewport range still applies.