Correlate two to five metrics
Returns one row per bucket per metric (the series), a summary per metric, and one entry per metric pair carrying Pearson, Spearman, r², a p-value, and the rolling correlation series.
Every empty bucket is counted as a zero — genuinely correct for spend and revenue, and the single most common source of a spurious result when a metric only starts partway through the window. Check each metric's first_nonzero / last_nonzero, and the partial_coverage warning.
curl -X GET "https://mythic-analytics.gulp.workers.dev/client/v1/correlation?location_id=example_string&metrics=spend%2Cnew_revenue%3Aorder_completed&grain=day&lookback_days=90&from=2026-05-01&to=2026-07-31&rolling_window=42&max_lag=42&new_lookback_days=90&revenue_path=%24.value&table=meta_ads_ads_insights_action_type" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN"
import requests
import json
url = "https://mythic-analytics.gulp.workers.dev/client/v1/correlation?location_id=example_string&metrics=spend%2Cnew_revenue%3Aorder_completed&grain=day&lookback_days=90&from=2026-05-01&to=2026-07-31&rolling_window=42&max_lag=42&new_lookback_days=90&revenue_path=%24.value&table=meta_ads_ads_insights_action_type"
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/correlation?location_id=example_string&metrics=spend%2Cnew_revenue%3Aorder_completed&grain=day&lookback_days=90&from=2026-05-01&to=2026-07-31&rolling_window=42&max_lag=42&new_lookback_days=90&revenue_path=%24.value&table=meta_ads_ads_insights_action_type", {
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/correlation?location_id=example_string&metrics=spend%2Cnew_revenue%3Aorder_completed&grain=day&lookback_days=90&from=2026-05-01&to=2026-07-31&rolling_window=42&max_lag=42&new_lookback_days=90&revenue_path=%24.value&table=meta_ads_ads_insights_action_type", 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/correlation?location_id=example_string&metrics=spend%2Cnew_revenue%3Aorder_completed&grain=day&lookback_days=90&from=2026-05-01&to=2026-07-31&rolling_window=42&max_lag=42&new_lookback_days=90&revenue_path=%24.value&table=meta_ads_ads_insights_action_type')
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,
"window": {
"from": "2026-05-03",
"to": "2026-07-31",
"grain": "day",
"buckets": 90,
"rolling_window": 14
},
"metrics": [
{
"id": "spend",
"label": "example_string",
"unit": "example_string",
"source": "example_string",
"total": 48210.55,
"mean": 3.14,
"min": 3.14,
"max": 3.14,
"nonzero_buckets": 88,
"first_nonzero": "2026-05-03",
"last_nonzero": "2026-07-31"
}
],
"correlations": [
{
"x": "spend",
"y": "new_revenue:order_completed",
"n": 90,
"pearson": 0.7412,
"spearman": 0.6883,
"r_squared": 0.5494,
"p_value": 0.00001,
"direction": "positive",
"strength": "strong",
"lag": {
"max": 14,
"unit": "day",
"interpretation": "positive lag = spend leads new_revenue:order_completed by that many days; negative = new_revenue:order_completed leads spend",
"best": {
"lag": 3,
"r": 0.8114,
"n": 87,
"leader": "spend"
},
"values": [
{
"lag": -2,
"r": 0.41,
"n": 88
}
]
},
"rolling": {
"window": 14,
"series_key": "corr:spend~new_revenue:order_completed",
"values": [
{
"bucket": "2026-05-16",
"r": 0.81
}
]
},
"warnings": [
{
"code": "partial_coverage",
"message": "example_string"
}
]
}
],
"series": [
{
"bucket": "2026-05-16",
"spend": 512.4,
"new_revenue:order_completed": 1840,
"corr:spend~new_revenue:order_completed": 0.81
}
],
"notes": [
"example_string"
]
}
{
"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": "Error",
"message": "`scan_limit_exceeded` — the window is too large to scan under the bytes-billed cap. Shorten it or use a coarser grain.",
"code": 413
}
{
"error": "Error",
"message": "`dataset_missing` — this client has no BigQuery dataset yet, so nothing can be correlated.",
"code": 424
}
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please try again later",
"code": 429,
"retryAfter": 3600
}
/client/v1/correlation
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 correlation:read. See Using an mcp_ key over HTTP.
ak_) or location secret key (sk_). Scoped keys (mcp_) are accepted too and need correlation:read. See Using an mcp_ key over HTTP.Location to scope to. Required for ak_ keys (or send the X-Location-Id header); ignored for sk_.
2–5 comma-separated metric ids. Fixed ids (spend, impressions, clicks, meta_purchases, meta_purchase_value, sessions, visitors, pageviews, events) or a family applied to an event name (event:X, people:X, revenue:X, new_people:X, new_revenue:X). List what this client actually has with GET /client/v1/correlation/metrics.
Bucket size. Weeks start Monday.
Window length ending yesterday (today is partial). Ignored when from/to are given.
Explicit window start, YYYY-MM-DD. Send with to.
Explicit window end (inclusive), YYYY-MM-DD.
Buckets in the trailing correlation series. Defaults to 14 (day), 6 (week), 3 (month). 0 turns the rolling series off. The first rolling_window - 1 buckets are null — a coefficient from three points looks like signal and is not.
Buckets to shift when scanning for a lead/lag relationship. Defaults to 14 (day), 6 (week), 3 (month), and an unrequested default is auto-capped at a quarter of the window — a wide scan over a short series finds a peak in noise every time. 0 turns the scan off.
Sign convention: lag +k correlates the FIRST metric at bucket i with the second at bucket i + k, so a positive peak means the first metric leads the second.
How far before the window "first ever" is judged for new_people: / new_revenue:. Without it, every returning customer in the opening buckets would count as new.
JSON path into properties_json used by the value metrics.
Meta ads-insights table backing the Meta metrics.
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 correlation:read. See Using an mcp_ key over HTTP.
Query Parameters
Location to scope to. Required for ak_ keys (or send the X-Location-Id header); ignored for sk_.
2–5 comma-separated metric ids. Fixed ids (spend, impressions, clicks, meta_purchases, meta_purchase_value, sessions, visitors, pageviews, events) or a family applied to an event name (event:X, people:X, revenue:X, new_people:X, new_revenue:X). List what this client actually has with GET /client/v1/correlation/metrics.
spend,new_revenue:order_completedWindow length ending yesterday (today is partial). Ignored when from/to are given.
Buckets in the trailing correlation series. Defaults to 14 (day), 6 (week), 3 (month). 0 turns the rolling series off. The first rolling_window - 1 buckets are null — a coefficient from three points looks like signal and is not.
Buckets to shift when scanning for a lead/lag relationship. Defaults to 14 (day), 6 (week), 3 (month), and an unrequested default is auto-capped at a quarter of the window — a wide scan over a short series finds a peak in noise every time. 0 turns the scan off.
Sign convention: lag +k correlates the FIRST metric at bucket i with the second at bucket i + k, so a positive peak means the first metric leads the second.
How far before the window "first ever" is judged for new_people: / new_revenue:. Without it, every returning customer in the opening buckets would count as new.
JSON path into properties_json used by the value metrics.
Meta ads-insights table backing the Meta metrics.
Responses
One row per bucket, keyed by metric id, plus a corr:\<x\>~\<y\> column per pair — one flat table that plots the metrics and their correlation on the same x-axis.