Analyze experience
Analyze analytics data for a specific experience using a POST request body for filters and request settings.
The legacy GET /analytics/resource/{experienceId} endpoint remains available only as a beta compatibility path for existing integrations. It is deprecated, limited to its original query-parameter contract, and will be removed when the beta version ends.
view is optional and defaults to overview; a body of {} returns the same payload as {"view": "overview"}. Valid values are overview, audience, and post_test. The value summary is not supported. When view is audience, you must also provide the audience field. When view is post_test, the audience field is optional (supply it to break the post-test metrics down by that dimension). The audience field is not allowed when view is overview — including when view is omitted and therefore defaulted, which rejects audience with a 400.
A request body is required even when every field is optional: sending no body at all returns 400 with { "errors": ["Invalid input: expected object, received undefined"] }. Send {}.
Use startTs and endTs in the request body to match the backend contract. These values must be 10-digit Unix epoch timestamps in seconds, not milliseconds. Use the filters object for all advanced filtering instead of query parameters.
Audience breakdowns (view=audience)
When view is audience, metrics are segmented by the selected audience dimension — one row per (variation × segment) — ordered by visitor count.
Accepted audience dimensions are device_type, visitor_type, source_channel, source_site, country_code, landing_page_full_path, and purchase_likelihood.
Only the high-cardinality dimensions country_code and landing_page_full_path are limited by default: when you omit topN, they return the top 10 segments by visitor count. The other dimensions — device_type, visitor_type, source_channel, source_site, and purchase_likelihood — are low-cardinality and are not limited by default; they return all their segments.
Segment values in the response are display-cased, not the lowercase wire values used by the request filters: device_type returns "Mobile" / "Desktop", visitor_type returns "New" / "Returning". Match on those exact strings, not on the filters.deviceType enum values.
Use the topN field to control this. It accepts an integer between 1 and 1000:
- Omit topN — country_code and landing_page_full_path return their top 10; all other dimensions return every segment.
- Set topN to a value between 1 and 1000 — caps the selected dimension at that many top segments (applies to any dimension). Values above 1000 are rejected.
The 1000 maximum bounds the response size and query cost, so a breakdown can never degrade into the unbounded scan that caused earlier timeouts. This is why landing_page_full_path, which can have thousands of distinct URLs, is never returned in full — request the top segments you need (up to 1000).
Upsell metrics (includeUpsellMetrics)
Set includeUpsellMetrics: true (boolean, default false) with view=overview to add per-variation upsell performance to the metrics array. This answers whether a variation drove a higher upsell take rate, not just higher overall revenue.
view=overview is the supported combination. The flag is currently also honoured under view=audience, where it adds the same fifteen fields to every (variation × segment) row, but that is not a guaranteed behaviour — the request schema documents the flag as overview-only, so treat the audience case as incidental and do not build on it. Under view=post_test the flag is ignored and no upsell fields are returned.
Metrics are returned for each of the three Intelligems upsell surfaces, which are distinct and reported separately:
| Surface | What it is |
|---|---|
| checkout_upsell | An upsell block in the checkout extension. |
| onsite_upsell | The onsite upsell widget, which renders on the product page or the cart. |
| post_purchase_offer | An offer presented after checkout completed. |
For each surface <s>, five metrics are added:
| Metric | Definition |
|---|---|
| net_revenue_from_<s> | Net revenue from lines added by that surface. |
| pct_orders_with_<s> | Share of orders containing at least one line from that surface (the upsell take rate). |
| pct_revenue_from_<s> | That surface's revenue as a share of total net revenue. |
| n_units_from_<s> | Units sold through that surface. |
| net_revenue_from_<s>_per_order_with_upsell | AOV contribution — revenue divided by the orders that took the offer, not by all orders. |
Note the denominator in net_revenue_from_<s>_per_order_with_upsell: it divides by the orders that took the offer, not by every order. It answers "how much does an upsell add when it lands", not "how much does this surface add per order overall".
The all-orders variant, net_revenue_from_<s>_per_order, exists in the internal metric catalogue but is not reachable through the external API — includeUpsellMetrics: true returns only the fifteen fields listed above, and _per_order is not a valid graphs.ids value either. To get a per-all-orders figure, divide net_revenue_from_<s> by n_orders from the same response.
This is off by default because computing it adds per-line-item aggregation that stores without upsells should not pay for on every request.
To see which individual line items an upsell added, use the order_line_items export and its upsell_type column.
Post-test metrics (view=post_test)
Post-test metrics look at customers who converted during the test and track their purchase behavior after that conversion, split by variation — answering "which variation produced more valuable customers over time?". Tracking is at the customer level (not cookie). Order filters narrow the in-test customer set only; they do not constrain the post-test orders.
The includeInTestOrders field (boolean, default true) controls whether in-test orders are folded into the metrics:
- true (default) — returns all three families. total_* is the LTV view (post-test + in-test combined), and the in-test eso_* family is included.
- false — the eso_* family is omitted entirely, and total_* collapses to equal post_* (since no in-test orders are folded in). The post_* family and headcounts are returned as usual.
Headcounts and rates are always present: n_visitors, n_customers, customers_per_visitor, pct_repeat_customers.
Every metric is { value }. There is no pct_change field.
uplift is only present on rate metrics. On a non-control variation, per-visitor and per-customer rate metrics additionally carry uplift: { value } — the ratio vs control as a fraction. Two rules govern it:
- The control variation never carries uplift.
- Raw counts and absolute totals never carry uplift, on any variation. That covers n_visitors, n_customers, n_post_orders, n_eso_orders, n_total_orders, post_net_revenue, post_gross_profit, eso_net_revenue, eso_gross_profit, total_net_revenue, and total_gross_profit — all of which are always bare { value }. Compute a comparison on those yourself if you need one.
The metrics that do carry uplift are the eighteen *_per_visitor / *_per_customer rates across the three families (six per family — orders, revenue, and profit, each per visitor and per customer), plus customers_per_visitor and pct_repeat_customers. That is twenty uplift-carrying fields on a non-control variation when includeInTestOrders is true, and fourteen when it is false (the eso_* family drops out, taking six of the rates with it).
Metric families:
- post_* — post-test orders only (after the customer's in-test conversion): n_post_orders, post_net_revenue, post_gross_profit, post_orders_per_visitor, post_revenue_per_visitor, post_profit_per_visitor, post_orders_per_customer, post_revenue_per_customer, post_profit_per_customer.
- eso_* — in-test ("experiment span") orders, only when includeInTestOrders=true: n_eso_orders, eso_net_revenue, eso_gross_profit, eso_orders_per_visitor, eso_revenue_per_visitor, eso_profit_per_visitor, eso_orders_per_customer, eso_revenue_per_customer, eso_profit_per_customer.
- total_* — post + in-test combined (LTV view): n_total_orders, total_net_revenue, total_gross_profit, total_orders_per_visitor, total_revenue_per_visitor, total_profit_per_visitor, total_orders_per_customer, total_revenue_per_customer, total_profit_per_customer. Always present, but when includeInTestOrders=false these values equal the corresponding post_* values.
When view=post_test is combined with audience, each metric row additionally carries an audience key (the bucket value, e.g. "mobile"), with one row per (variation × audience value), and audienceOrder is populated.
Graphs
When requesting graphs via the graphs object, graphs.ids accepts an array of metric identifiers.
The authoritative list of valid values is the graphs.ids enum in this endpoint's request schema, which is generated from the metric catalogue — see the schema for the current set. It is deliberately not duplicated in prose here: the previous hand-maintained list had fallen four values behind the enum (net_revenue_from_upsell, pct_orders_with_upsell, net_revenue_from_post_purchase_offer, pct_orders_with_post_purchase_offer were all accepted but undocumented).
Note: revenue_per_visitor is not a valid graph ID — use net_revenue_per_visitor instead.
Invalid IDs are rejected with a 400 validation error on this endpoint. The deprecated GET /analytics/resource/{experienceId} instead silently drops them and returns graphs: [].
graphs.outputFormat is optional and defaults to "png", so {"graphs": {"ids": [...]}} returns CDN URLs.
Each graph object in the response includes id (string), key (the original metric key), type (chart type, e.g. "bar"), title (display title), and either url (CDN URL when outputFormat is "png") or data (base64-encoded PNG when outputFormat is "base64").
The graphs key is omitted from the response entirely when the request does not ask for graphs; it is only present (possibly as []) when graphs was sent. A valid graphs.ids value for a surface this store has no data for yields graphs: [] rather than an error.
Additional Response Fields
Response payloads include impact (object with est_monthly_revenue_increase: the projected 30-day revenue lift from the best non-control variation vs. control, or null when there is insufficient data) plus the ordering helper audienceOrder (segment display ordering, populated only for view=audience and for view=post_test with an audience; an empty array otherwise).
impact is calculated from whole-variation overview metrics with the same filters and time window, including when view=audience. Audience segmentation and topN do not change this experience-level calculation. Under view=post_test, the estimate is null.
histogram, kde, and cdf are also always present alongside the primary metrics and variations arrays, but this endpoint does not currently populate them — they are returned as empty arrays regardless of view, analyticsViewType, or date range. Do not build on them here. For an order-value distribution, use POST /analytics/sitewide/order-distribution, which returns a populated orderValueDistribution.kernel_density_estimate and orderValueDistribution.cumulative_distribution_function.
audienceOrder
audienceOrder is a display ordering hint, not the order of the metrics array. The metrics rows come back in the order the analytics store returned them, so do not assume the two line up — sort or index by the audience key if you need them to.
The ordering is by total visitors summed across all variations, descending. Two consequences:
- It will not match a descending sort of any single variation's n_visitors, because the sort key is the cross-variation total.
- "Other" and "Unknown" are pinned to the end regardless of size, in that order, so they can appear after segments with fewer visitors.
Rate Limit: 50 requests per 30s window, refilling 25 tokens every 30s.
Request
- Base URLhttps://api.intelligems.io
- AuthAPI key in header intelligems-access-token
- TypeScriptA function with typed inputs. Its response is checked against the schema.
Path parameters
Request body
Response
OK
Changes
No recorded changes to this operation across all 1 revision of this API.