Docs·Reports
/api/v1/spreelytics/chartRun a chart
Runs a chart definition and returns the numbers, without saving anything. This is the engine behind the report builder, and the call to make when the web analytics endpoints do not give you a number directly. It is a read sent as a POST because a definition does not fit in a URL, so every role can call it, Viewer included.
A definition has a chart type, a window (range, or startDate and endDate) cut into buckets of one interval, and one or more series. Each series measures one event, or a group of events, with a metric, and can carry its own filters. globalFilters apply to every series, and breakdowns split every series by one or more properties.
{
"chartType": "line",
"interval": "day",
"range": "30d",
"series": [
{ "id": "a", "event": "page_view", "metric": "unique" },
{ "id": "b", "event": "signup", "filters": [{ "name": "plan", "operator": "is", "value": ["pro"] }] }
],
"breakdowns": ["country"],
"globalFilters": [{ "name": "channel", "operator": "is", "value": ["Organic Search"] }]
}
That asks, day by day over the last 30 days and split by country, how many people viewed a page and how many signup events had plan set to pro, counting only visits from organic search.
A series has an id (any string, returned on the result) and an event: page_view, a custom event name or another tracked type such as scroll, as listed by GET /meta/events. The rest is optional:
metric:count(every event, the default),unique(distinct people),session(distinct sessions),user_average(events per person),one_event_per_user(each person's latest matching event only),property_sum,property_average,property_maxorproperty_min(arithmetic over the numeric values ofproperty; values that are not numbers are skipped),sum(revenue in your reporting currency, net of refunds) oraverage(the average payment).events: two or more event names counted as one metric, such as["signup", "login"], with the first one also inevent. Underunique, a person who did both counts once.type:event(the default),profile(the people seen in each bucket, whatever they did) orcohort(the members of the segment incohortIdseen in each bucket). Send an emptyeventwith these two.displayName: the name the series comes back under.filters: conditions for this series only, shaped likeglobalFilters.
A filter is { "name", "operator", "value" } and nothing else. name is a property: a standard one such as path, country, browser or utm_source (the catalog from GET /meta/events lists them all), profile.email and the like for a person attribute, a custom event property key, or cohort for segment membership. Custom names may only use letters, digits, _, . and -, up to 128 characters. value is always an array of strings, and a row with an empty array is skipped unless its operator takes no value. The operators:
is,isNot: an exact match with any of the values.isNoton a custom property also matches events without it.contains,doesNotContain,startsWith,endsWith: text matches that ignore case.regex: a regular expression in RE2 syntax.isNull,isNotNull: missing or empty, and present. Send[]as the value.gt,gte,lt,lte: numeric comparisons. Only the first value is used.inCohort,notInCohort: only with"name": "cohort", and the values are segment ids.
Chart types. line, area, bar, pie and metric all return the same time series; the type only tells your own code how to draw it. Four types compute something else, and profile and segment series are ignored by them:
funnel: the series are the steps, 2 to 8 of them, in order. The answer is infunnel.conversion: the first series starts the clock and the second is the conversion. The answer is one series of percentages per bucket, plus the totals inconversion.retention: the first series puts a person in a period, and the second counts as coming back (the first again when there is no second). The answer is inretention.sankey: each series, up to 4, is a starting event, and the answer is the paths around it, insankeyandsankeyFlows.
Limits. At most 20 series, not counting event series with an empty event, which are dropped. A series holds at most 1,000 buckets, and a longer window is cut off at the 1,000th, so pick a coarser interval for long ranges. An unknown field anywhere in the body answers 400, which is why a saved report's config cannot be sent here: run it with GET /reports/{reportId}/chart. When the workspace is past its plan's allowance, the window ends where the allowance ran out (see dataCutoff on GET /billing/usage).
Keys: an account key or an audience key. Getting started explains both kinds.
Parameters
| Prop | In | Type | Notes |
|---|---|---|---|
contactListId | query | string | The audience id, a UUID. With an audience key it must be that key's own audience. |
Request body
| Prop | Type | Notes |
|---|---|---|
chartType | SpreelyticsChartType | line, area, bar, pie and metric return the same time series. funnel, conversion, retention and sankey change what is computed, as described above. |
interval | SpreelyticsInterval | The bucket size: minute, hour, day, week (weeks start on Monday) or month, in the audience's reporting timezone. Retention takes week or month as given and uses day for anything else. |
range | SpreelyticsRangePreset | A preset: 30min, lastHour, 24h, today, yesterday, 7d, 30d, 90d, 6m (182 days), 12m (365 days), monthToDate, lastMonth, yearToDate, lastYear, lastNDays (with customDays) or custom (with startDate and endDate; without them you get 30 days). Presets follow the audience's timezone. 7d, 30d, 90d, 6m, 12m and lastNDays start at midnight and include today, so 7d is today and the six days before it. |
startDate? | string | Start of the window. YYYY-MM-DD means the start of that day in the audience's timezone; a full timestamp is used as given. Only used when endDate is sent too, and then it wins over range. |
endDate? | string | End of the window. A bare YYYY-MM-DD includes that whole day; a timestamp is an exclusive end. An end before the start answers 400. |
series | SpreelyticsReportSeries[] | Required. What to measure, at most 20 series. See "A series" above. |
breakdowns? | string[] | Optional. Property names to split every series by, outermost first. Each combination found becomes its own series, named by its values joined with ·, such as US · Chrome. cohort splits by membership of your newest 12 segments; a person in several counts under the first, and everyone else under (none). |
globalFilters? | SpreelyticsReportFilter[] | Optional. Filters for every series, as { name, operator, value }. A saved report's filter rows also carry an id (and sometimes an event), which this endpoint refuses, so strip them. An inCohort filter whose segment ids are all unknown returns an empty result. |
previous? | boolean | Optional. true adds the previous period of the same length: a previous value on every point and in each series' metrics. Ignored by funnel, conversion, retention and sankey. |
customDays? | number | The N of lastNDays, from 1 to 730. Defaults to 30. |
funnelWindowSeconds? | number | Funnel and conversion: how long after the first step the rest may happen, in seconds. Defaults to the length of the window, and is capped at 366 days. |
funnelStrictOrder? | boolean | Funnel: true means no later step may happen before the step ahead of it. Events that are not steps do not matter. |
retentionCriteria? | ``on` \ | `on_or_after`` |
sankeySteps? | number | Sankey: how many steps to follow after the starting event when a series sets no stepsAfter. Defaults to 4, at most 8. |
sankeyExclude? | string[] | Sankey: event names to leave out of every path, up to 50. |
limit? | number | Optional, with a breakdown: keep only the top values by event count in the window, 1 to 500. Without it every value comes back. |
Response
| Prop | Type | Notes |
|---|---|---|
series | SpreelyticsChartSeries[] | The time series, biggest first by window total: one per series, or one per series and breakdown value. Each has id, name, data and metrics. data holds one { date, value } per bucket, date being the bucket start as an ISO timestamp, plus previous when you asked for a comparison. name is the series' displayName, or a name made from its event; with a breakdown it is the value ((none) when empty) and id is the series id, a colon and that value. In a series' metrics, sum is the value over the whole window, so a person seen on three days counts once for unique, while average, min and max are over the buckets and count is the number of buckets. Empty for funnel, retention and sankey. |
metrics | SpreelyticsSeriesMetrics | Totals across the series: sum adds up their sum values, average spreads that over the buckets, max is the largest bucket of any series, min is always 0 and count is how many series came back. For funnel, conversion, retention and sankey, read that chart's own field instead. |
funnel | SpreelyticsFunnelData | Funnel only. steps holds each step's name, visitors (people who reached at least that step, in order, within the window) and dropOffRate (the share lost since the step before, 0 to 1, always 0 on the first). completionRate is the last step over the first, 0 to 1. |
retention | SpreelyticsRetentionRow[] | Retention only. One row per period in which people first did the entry event: cohort (that period), size (how many people) and values, the share of them who came back in each period from their own one (index 0) onwards, 0 to 1. A period that has not happened yet is null, which is not the same as nobody coming back. |
retentionIdentifiedOnly | boolean | Retention only. Present and true when the audience tracks cookielessly, or switched that setting before. Anonymous ids change every day under cookieless tracking, so for that stretch only identified people are counted. |
conversion | SpreelyticsConversionTotals | Conversion only. Totals over the whole window: entered (people who did the first event), converted (how many of them did the second within funnelWindowSeconds) and rate, which is converted over entered as a percentage, 0 to 100. |
sankey | SpreelyticsSankeyData | Sankey only. The first starting event's flow: nodes (each { name }; the same event at two depths is two nodes) and links (source and target are positions in nodes, value is how many people took that step). |
sankeyFlows | SpreelyticsSankeyFlow[] | Sankey only. One flow per starting event, up to 4, each with its series id, the anchor it starts from and its own nodes and links. |
Request
curl -X POST 'https://app.spreelytics.com/api/v1/spreelytics/chart?contactListId=…' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{ … }'
const res = await fetch('https://app.spreelytics.com/api/v1/spreelytics/chart', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({ /* see Request body above */ }),
});
const data = await res.json();
import requests
res = requests.post(
"https://app.spreelytics.com/api/v1/spreelytics/chart",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={}, # see Request body above
)
data = res.json()
Response
{
"series": [
{
"id": "string",
"name": "string",
"metrics": {
"sum": 0,
"average": 0,
"min": 0,
"max": 0,
"count": 0,
"previous": 0
},
"data": [
{
"date": null,
"value": null,
"previous": null
}
]
}
],
"metrics": {
"sum": 0,
"average": 0,
"min": 0,
"max": 0,
"count": 0,
"previous": 0
},
"funnel": {
"steps": [
{
"name": "string",
"visitors": 0,
"dropOffRate": 0
}
],
"completionRate": 0
},
"retention": [
{
"cohort": "string",
"size": 0,
"values": [
0
]
}
],
"retentionIdentifiedOnly": true,
"conversion": {
"entered": 0,
"converted": 0,
"rate": 0
},
"sankey": {
"nodes": [
{
"name": "string"
}
],
"links": [
{
"source": 0,
"target": 0,
"value": 0
}
]
},
"sankeyFlows": [
{
"nodes": [
{
"name": null
}
],
"links": [
{
"source": null,
"target": null,
"value": null
}
],
"id": "string",
"anchor": "string"
}
]
}