Explore query (trend, funnel, paths or attribution)
Self-serve analytics over the event stream in one call. A typed spec — no raw SQL — is validated, compiled server-side to ClickHouse SQL over a wide, typed events table, and scoped to your location by the compiler. Sub-second at current scale: a 30-day trend broken down by a property reads tens of megabytes where the same question over raw JSON properties did not finish.
Trend (kind: trend): 1–5 series, each an event (or null for any event) × math (total, dau, unique_sessions, sum, avg, min, max, median, p90, p95, p99; numeric maths need math_property), bucketed by interval (hour|day|week|month) or returned as one number per series when interval is omitted. Optional breakdown by any property (top-N by the first series; the rest fold into "$$other") and compare: previous_period (the immediately preceding window of equal length, aligned by bucket index, and truncated at the same time of day so today-so-far is not compared against a whole day).
Funnel (kind: funnel): 2–10 ordered steps, a conversion window (default 7 days, capped at the range), optional breakdown taken from the first step. Sequential: other events may occur between steps. Per-step users, conversion_from_first, conversion_from_previous.
Paths (kind: paths): the journeys persons took before a goal event, plus first-touch and last-touch counts. By default each session is one step labeled by channel group (Paid Search, Paid Social, Email, Organic Search, Direct, …) over a 90-day lookback, with all of a person's devices and CRM ids joined. touchpoint: event makes each event a step instead (pageviews by pathname).
Properties: typed names resolve to columns — $current_url, $pathname, $referrer, $referring_domain, utm_source, utm_medium, utm_campaign, utm_content, utm_term, $device_type, $browser, $os, $geo_country, $geo_city, $experiment_arm, plus event, distinct_id, session_id, user_id. Any other name is a custom event property (scalar, ≤200 chars; nested objects and meta_* blobs are not queryable). utm_*, $referrer and $referring_domain are session-entry values with an event-level fallback. Discover keys with GET /properties and values with GET /query/values.
Semantics: buckets are UTC and zero-filled; presets resolve to [today-N, today] inclusive; unique counts are uniq() (approximate) on distinct_id, not resolved persons. Excluded events: $replay_summary, $replay_started, $heatmap, $web_vital_*, $set, $set_once, $identify (each has its own surface).
Limits (400 invalid_spec, every problem listed in error.details): unknown keys rejected, ≤5 series, 2–10 steps, ≤20 filters in total, range ≤366 days, hour only for ranges ≤31 days, in lists ≤100 values, breakdown limit ≤50. Rate limit 30 requests / 10 s per location. Cached 120 s when the range touches today, 6 h otherwise (meta.cached).
curl -X POST "https://mythic-analytics.gulp.workers.dev/client/v1/data/query?location_id=example_string&debug=example_string" \
-H "Content-Type: application/json" \
-H "X-Location-Id: example_string" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"kind": "trend",
"date_range": {
"preset": "30d"
},
"interval": "day",
"series": [
{
"event": "$pageview",
"math": "dau"
}
],
"breakdown": {
"property": "utm_source",
"limit": 5
},
"compare": "previous_period",
"filters": [
{
"property": "$device_type",
"op": "eq",
"value": "mobile"
}
]
}'
import requests
import json
url = "https://mythic-analytics.gulp.workers.dev/client/v1/data/query?location_id=example_string&debug=example_string"
headers = {
"Content-Type": "application/json",
"X-Location-Id": "example_string",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"kind": "trend",
"date_range": {
"preset": "30d"
},
"interval": "day",
"series": [
{
"event": "$pageview",
"math": "dau"
}
],
"breakdown": {
"property": "utm_source",
"limit": 5
},
"compare": "previous_period",
"filters": [
{
"property": "$device_type",
"op": "eq",
"value": "mobile"
}
]
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://mythic-analytics.gulp.workers.dev/client/v1/data/query?location_id=example_string&debug=example_string", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Location-Id": "example_string",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"kind": "trend",
"date_range": {
"preset": "30d"
},
"interval": "day",
"series": [
{
"event": "$pageview",
"math": "dau"
}
],
"breakdown": {
"property": "utm_source",
"limit": 5
},
"compare": "previous_period",
"filters": [
{
"property": "$device_type",
"op": "eq",
"value": "mobile"
}
]
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"kind": "trend",
"date_range": {
"preset": "30d"
},
"interval": "day",
"series": [
{
"event": "$pageview",
"math": "dau"
}
],
"breakdown": {
"property": "utm_source",
"limit": 5
},
"compare": "previous_period",
"filters": [
{
"property": "$device_type",
"op": "eq",
"value": "mobile"
}
]
}`)
req, err := http.NewRequest("POST", "https://mythic-analytics.gulp.workers.dev/client/v1/data/query?location_id=example_string&debug=example_string", bytes.NewBuffer(data))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Location-Id", "example_string")
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/query?location_id=example_string&debug=example_string')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['X-Location-Id'] = 'example_string'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request.body = '{
"kind": "trend",
"date_range": {
"preset": "30d"
},
"interval": "day",
"series": [
{
"event": "$pageview",
"math": "dau"
}
],
"breakdown": {
"property": "utm_source",
"limit": 5
},
"compare": "previous_period",
"filters": [
{
"property": "$device_type",
"op": "eq",
"value": "mobile"
}
]
}'
response = http.request(request)
puts response.body
{
"success": true,
"data": {
"kind": "trend",
"buckets": [
"2026-08-30",
"2026-08-31",
"2026-09-01"
],
"breakdown_values": [
"google",
"",
"$$other"
],
"series": [
{
"label": "$pageview · dau",
"math": "dau",
"event": "$pageview",
"breakdown_value": "google",
"data": [
412,
388,
97
],
"total": 897,
"previous": [
390,
401,
120
]
}
],
"meta": {
"from": "2026-08-30",
"to": "2026-09-01",
"interval": "day",
"previous": {
"from": "2026-08-27",
"to": "2026-08-29"
},
"sql_ms": 212,
"rows_read": 778534,
"bytes_read": 36060000,
"cached": false
}
}
}
{
"success": true,
"data": {
"kind": "funnel",
"steps": [
{
"step": 1,
"label": "$pageview",
"event": "$pageview",
"users": 1180,
"conversion_from_first": 1,
"conversion_from_previous": 1
},
{
"step": 2,
"label": "signup",
"event": "signup",
"users": 143,
"conversion_from_first": 0.1212,
"conversion_from_previous": 0.1212
},
{
"step": 3,
"label": "order_completed",
"event": "order_completed",
"users": 31,
"conversion_from_first": 0.0263,
"conversion_from_previous": 0.2168
}
],
"meta": {
"from": "2026-08-18",
"to": "2026-09-01",
"window_seconds": 259200,
"sql_ms": 340,
"rows_read": 51200,
"bytes_read": 2100000,
"cached": false
}
}
}
{
"success": true,
"data": {
"kind": "paths",
"goal_persons": 667,
"no_touchpoint": 38,
"sampled": false,
"paths": [
{
"touchpoints": [
"Paid Social"
],
"truncated": false,
"persons": 551,
"avg_touchpoints": 1.42,
"share": 0.826
},
{
"touchpoints": [
"Paid Social",
"Direct"
],
"truncated": false,
"persons": 8,
"avg_touchpoints": 2.25,
"share": 0.012
}
],
"first_touch": [
{
"touchpoint": "Paid Social",
"persons": 575,
"share": 0.862
}
],
"last_touch": [
{
"touchpoint": "Paid Social",
"persons": 557,
"share": 0.835
}
],
"meta": {
"from": "2026-08-25",
"to": "2026-09-24",
"grain": "person",
"goal": {
"label": "order_completed",
"event": "order_completed"
},
"touchpoint": "session",
"dimension": "channel_group",
"lookback_days": 90,
"depth": 5,
"limit": 20,
"goal_id_cap": 20000,
"sql_ms": 1070,
"cached": false
}
}
}
{
"success": true,
"data": {
"kind": "attribution",
"totals": {
"conversions": 299,
"revenue": 0,
"spend": 29386.09,
"roas": 0,
"cpa": 98.28,
"ad_conversions": 45,
"ad_revenue": 0,
"unmatched_ad_conversions": 3,
"no_touchpoint": 232,
"over_cap": 0,
"mixed_currency": false
},
"filtered": {
"rows": 23,
"conversions": 299,
"revenue": 0,
"spend": 29386.09,
"roas": 0,
"cpa": 98.28,
"mixed_currency": false
},
"sampled": false,
"rows": [
{
"type": "ad",
"platform": "meta_ads",
"account_id": "332171725295572",
"campaign_id": "120240199320680108",
"campaign_name": "AlphaVYVE TRT | LP",
"currency": "USD",
"spend": 2885.05,
"impressions": 43287,
"clicks": 1098,
"conversions": 22,
"revenue": 0,
"persons": 18,
"cpa": 131.14,
"roas": 0,
"median_days": 0
},
{
"type": "channel",
"channel": "(no touchpoint)",
"spend": 0,
"impressions": 0,
"clicks": 0,
"conversions": 232,
"revenue": 0,
"persons": 201
}
],
"meta": {
"from": "2026-09-19",
"to": "2026-10-19",
"grain": "person",
"goal": {
"label": "appointment_booked",
"event": "appointment_booked"
},
"model": "last",
"level": "campaign",
"lookback_days": 30,
"limit": 100,
"goal_id_cap": 20000,
"sql_ms": 1326,
"cached": false
}
}
}
{
"error": "Bad Request",
"message": "The request contains invalid parameters or malformed data",
"code": 400,
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please try again later",
"code": 429,
"retryAfter": 3600
}
{
"error": "Error",
"message": "Upstream query failed (message from the query engine).",
"code": 502
}
/query
Target server for requests. Edit to use your own host.
Agency key (ak_) or location secret key (sk_) as a bearer token. Format: Bearer ak_... or Bearer sk_.... Scoped keys (mcp_) are accepted too and need the read scope of the family the route belongs to (people, events, sessions, replays, exceptions, heatmaps or bigquery_export). See Using an mcp_ key over HTTP.
ak_) or location secret key (sk_) as a bearer token. Format: Bearer ak_... or Bearer sk_.... Scoped keys (mcp_) are accepted too and need the read scope of the family the route belongs to (people, events, sessions, replays, exceptions, heatmaps or bigquery_export). See Using an mcp_ key over HTTP.
Alternative to the X-Location-Id header for agency (ak_) keys. Ignored for secret (sk_) keys.
1 adds the compiled SQL to meta.sql (agency keys only).
The media type of the request body
Location to scope the request to. Required for agency (ak_) keys on the event routes. Ignored for secret (sk_) keys, which resolve their own location, and not used by /export.
Trend only. Omit for one number per series.
Trend only.
Trend only.
Funnel only. Ordered; other events may occur between steps.
Funnel only. Default 7 days, capped at the range length.
Funnel (default device) and paths (default person). device counts the raw distinct_id; person resolves each id to its canonical person first, so a journey across devices — or a step that is a server/CRM event keyed by a contact id — links instead of breaking. Slower (it joins the identity map for the window); trends have no grain.
Applied to every series / step. Not accepted on paths (use goal.filters).
Paths: required. Persons who did this in the range; each journey is what they did in the lookback before their first goal in the range. Attribution: the conversion event (or use deals); every occurrence in the range is one conversion, credited to the touches before it.
Paths only. session: one step per session, labeled by dimension. event: one step per event, pageviews labeled by pathname.
Paths with session touchpoints only. Self-referral sessions (referrer on the landing page's own registrable domain) are dropped; a journey made only of them reads as Direct. campaign is case- and URL-encoding-insensitive, and numeric Meta campaign ids are replaced by their names when the client syncs Meta Ads.
Paths: default 90 for session touchpoints, 7 for event touchpoints. Attribution: default 30, how far before a conversion a touch can be.
Paths only. Last N touchpoints kept per journey.
Paths: default 20, max 50, journeys returned and rows per first/last-touch list. Attribution: default 100, max 500 rows; totals still cover every row.
Paths with event touchpoints only. Added to the default excludes ($pageleave, $engagement, $exception, $autocapture, $experiment_viewed, tag_added, tag_removed).
Paths with event touchpoints only. false keeps the default-excluded events.
Paths and attribution (with goal). Numeric property of the goal event (e.g. value, activity_value). Paths: adds goal_value, goal_first_value, and value / first_value on journey, first-touch and last-touch rows (value on linear_touch), summed per person over their goal events in the range; first_value is the first goal only. Attribution: the revenue of each conversion. Non-numeric values count as 0.
Attribution only, instead of goal: won CRM deals (GHL opportunities whose latest status is won). One conversion per opportunity, at the time it was first marked won, valued at its latest monetary value.
Attribution only. last credits the last non-Direct touch (Direct only when every touch is Direct). first credits the first touch in the lookback. linear splits each conversion evenly across its touches.
Attribution only. Row grain. Ad set and ad ids come from the landing URL (Meta adset_id/ad_id, Google adgroupid/creative, or Meta's utm_term/utm_content id template); spend at those levels is Meta only.
Attribution only. Keep only this platform's rows. Totals still cover every conversion.
Attribution only. Keep only rows of this ad account (digits; an act_ prefix is removed).
Request Preview
Response
Response will appear here after sending the request
Authentication
Bearer token. Agency key (ak_) or location secret key (sk_) as a bearer token. Format: Bearer ak_... or Bearer sk_.... Scoped keys (mcp_) are accepted too and need the read scope of the family the route belongs to (people, events, sessions, replays, exceptions, heatmaps or bigquery_export). See Using an mcp_ key over HTTP.
Query Parameters
Alternative to the X-Location-Id header for agency (ak_) keys. Ignored for secret (sk_) keys.
1 adds the compiled SQL to meta.sql (agency keys only).
Headers
Location to scope the request to. Required for agency (ak_) keys on the event routes. Ignored for secret (sk_) keys, which resolve their own location, and not used by /export.
Body
trendfunnelpathsattributionTrend only.
Funnel only. Ordered; other events may occur between steps.
Funnel only. Default 7 days, capped at the range length.
Funnel (default device) and paths (default person). device counts the raw distinct_id; person resolves each id to its canonical person first, so a journey across devices — or a step that is a server/CRM event keyed by a contact id — links instead of breaking. Slower (it joins the identity map for the window); trends have no grain.
devicepersonApplied to every series / step. Not accepted on paths (use goal.filters).
Paths: required. Persons who did this in the range; each journey is what they did in the lookback before their first goal in the range. Attribution: the conversion event (or use deals); every occurrence in the range is one conversion, credited to the touches before it.
Paths only. session: one step per session, labeled by dimension. event: one step per event, pageviews labeled by pathname.
sessioneventPaths with session touchpoints only. Self-referral sessions (referrer on the landing page's own registrable domain) are dropped; a journey made only of them reads as Direct. campaign is case- and URL-encoding-insensitive, and numeric Meta campaign ids are replaced by their names when the client syncs Meta Ads.
channel_groupsource_mediumsourcecampaignPaths: default 90 for session touchpoints, 7 for event touchpoints. Attribution: default 30, how far before a conversion a touch can be.
Paths only. Last N touchpoints kept per journey.
Paths: default 20, max 50, journeys returned and rows per first/last-touch list. Attribution: default 100, max 500 rows; totals still cover every row.
Paths with event touchpoints only. Added to the default excludes ($pageleave, $engagement, $exception, $autocapture, $experiment_viewed, tag_added, tag_removed).
Paths with event touchpoints only. false keeps the default-excluded events.
Paths and attribution (with goal). Numeric property of the goal event (e.g. value, activity_value). Paths: adds goal_value, goal_first_value, and value / first_value on journey, first-touch and last-touch rows (value on linear_touch), summed per person over their goal events in the range; first_value is the first goal only. Attribution: the revenue of each conversion. Non-numeric values count as 0.
Attribution only, instead of goal: won CRM deals (GHL opportunities whose latest status is won). One conversion per opportunity, at the time it was first marked won, valued at its latest monetary value.
Attribution only. last credits the last non-Direct touch (Direct only when every touch is Direct). first credits the first touch in the lookback. linear splits each conversion evenly across its touches.
firstlastlinearAttribution only. Row grain. Ad set and ad ids come from the landing URL (Meta adset_id/ad_id, Google adgroupid/creative, or Meta's utm_term/utm_content id template); spend at those levels is Meta only.
campaignadsetadAttribution only. Keep only this platform's rows. Totals still cover every conversion.
meta_adsgoogle_adsAttribution only. Keep only rows of this ad account (digits; an act_ prefix is removed).
Responses
Trend: buckets[] (ISO, UTC), previous_buckets[] with compare, breakdown_values[] with breakdown, and series[] — one entry per spec series × breakdown value with label, math, event, breakdown_value?, data[] + total (or value without interval) and previous[] / previous_value with compare. Funnel: steps[] and breakdown[] { value, steps[] }. Paths: goal_persons, no_touchpoint, sampled, paths[] { touchpoints[], truncated, persons, share, avg_touchpoints, median_days, avg_days }, median_days, avg_days, at_lookback, linear_touch[] { touchpoint, persons, credit, share }, value fields with value_property, first_touch[] and last_touch[] { touchpoint, persons, share }; meta also echoes goal, touchpoint, dimension/exclude, lookback_days, depth, limit, goal_id_cap, and campaign_names { ids, named, status } when campaign touchpoints were Meta ids. Attribution: totals { conversions, revenue, spend, roas, cpa, ad_conversions, ad_revenue, unmatched_ad_conversions, no_touchpoint, over_cap, mixed_currency } over every row, filtered { rows, conversions, revenue, spend, roas, cpa, mixed_currency } after platform/account_id, sampled (rows cover a sample of converters, so row cpa/roas are null; over_cap counts the rest), and rows[] { type (ad | channel), platform, channel, account_id, campaign_id, campaign_name, adset_id?, adset_name?, ad_id?, ad_name?, currency, spend, impressions, clicks, conversions, revenue, persons, cpa, roas, median_days } (null fields omitted in the example: an ad row has channel: null, a channel row has null platform, ids, names, currency, cpa and roas); meta echoes goal/deals, model, level, lookback_days, limit, platform?, account_id?, value_property?, goal_id_cap. Always meta { from, to, interval?, previous?, window_seconds?, sql_ms, rows_read, bytes_read, cached, sql? }.