Skip to content

Analytics

The analytics views answer the same numbers the dashboard’s Insights show. Store-wide: overview (views, starts, completions, opt-ins, orders and revenue, one row per day plus totals), breakdown (the same counts per channel, device, new or returning, or country), quizzes (one row per quiz) and summary (the headline numbers for the last 7 or 30 days against the period before). Per quiz: overview (with a marker per publish, so a change in the numbers lines up with a version), funnel (page by page), paths (page-to-page journeys and the jump rules that fired), breakdown, products (revenue attributed per product), answers (what shoppers who picked each option did next) and traffic (referrers and UTM values). Every field is catalogued in the reference.

Every view needs analytics:read, costs 3 units when it answers a body and 1 otherwise (see Rate limits), and answers 503 limiter_unavailable when limits cannot be checked.

Dates, timezone and ranges

from and to are inclusive days in tz, an IANA timezone that defaults to UTC. to defaults to today in tz and from to 29 days before to, so a call without dates covers the last 30 days including today. Every answer echoes what it resolved:

"range": { "days": 30, "end": "2026-09-13", "start": "2026-08-15", "timezone": "UTC" }

summary takes period (7d or 30d) and tz instead of dates. A range over 366 days answers 422 validation_error; funnel, paths, answers and traffic read raw rows and are capped at 92 days, so ask a longer span in slices. For per-quiz totals over a whole range in one file, request a quiz_analytics export instead of a call per quiz.

Units

  • Counts are sessions: a shopper’s run through the quiz counts once wherever it appears. The funnel’s starts is a cohort, the sessions that answered their first question inside the range, each followed to its end even past to, so starts == completions + sum(sessions_dropped) + in_progress always holds. A session that opened a page three times reaches it once; time on a page is per visit, so every landing is timed.
  • Rates and shares are percentages (62.5, not 0.625) and null when there is nothing to divide by. Timings are milliseconds.
  • Money is {"amount": "12.50", "currency": "USD"} with amount a decimal string. A day, quiz, product or answer without orders reports {"amount": "0", "currency": ...}, never null; money is null only when the store’s currency is not known yet (its details have not synced), so guard for it.
  • revenue_by_bucket splits attributed revenue by how the product reached the order: added_from_quiz (added to the cart from the quiz), shown_bought_later (shown in the quiz, bought later) and bought_not_shown (bought by a quiz taker without being shown). It is null as a whole under a dimension filter, because the split cannot be computed on a subset.

Dimension filters

channel, device, new_returning and country each narrow an answer to the sessions with that value; the answer repeats all four under filters, null when unset. The legal values, the same ones a person’s sessions, the session webhooks and the responses export carry:

FilterValues
channeldirect, organic_search, paid_search, organic_social, paid_social, email, sms, affiliate, display, paid_other, referral, ai_assistant, other
devicemobile, tablet, desktop
new_returningnew, returning
countryAn ISO 3166-1 alpha-2 code in upper case (US, DE), or unknown for sessions without one

products, quizzes and summary take no dimension filter; a filter they do not support, or an unknown value, answers 422 validation_error.

What counts as a finished quiz

A session is finished when the shopper reaches the quiz’s last page (the results page, or the last question page when the quiz has no results page) and has answered whatever that page requires. completions counts those sessions, one per session, everywhere it appears.

An optional input never holds a finish back: a results page whose email is optional is finished the moment the shopper lands on it, even if they close the tab without typing a thing. A required input at the end does hold it back: until it is answered the session is not finished, and a shopper who leaves instead ends it as abandoned. A one-page quiz is finished on its first Next, and a session that skipped every question is finished on its last page. Going back afterwards does not undo it, and a session finishes at most once.

The same moment drives quiz.finished on the webhooks and in the JS API. The webhook is sent about a minute later and carries the answers and contact details the session held then. A contact typed after the finish is not in quiz.finished; it arrives on identity.captured and, when the person is new or changed, on profile.created or profile.updated; tie them by session_id.

Examples

A week of store-wide numbers

Terminal window
curl "https://api.octaneai.com/v1/analytics/overview?from=2026-09-01&to=2026-09-07&tz=America/New_York" \
-H "Authorization: Bearer $OCTANE_API_KEY"

The answer, cut to one day of the series:

{
"range": { "days": 7, "end": "2026-09-07", "start": "2026-09-01", "timezone": "America/New_York" },
"filters": { "channel": null, "device": null, "new_returning": null, "country": null },
"freshness": { "as_of": "2026-09-07T12:00:00Z", "closed_days_through": "2026-09-06", "today_live": true, "stale": false, "pending": false, "next_refresh_at": "2026-09-07T13:00:00Z" },
"series": [
{
"day": "2026-09-01",
"views": 10, "starts": 8, "completions": 5,
"email_opt_ins": 2, "phone_opt_ins": 1,
"orders": 1,
"revenue": { "amount": "12.50", "currency": "USD" },
"revenue_by_bucket": { "added_from_quiz": { "amount": "12.50", "currency": "USD" }, "shown_bought_later": { "amount": "0", "currency": "USD" }, "bought_not_shown": { "amount": "0", "currency": "USD" } }
}
],
"totals": { "views": 10, "starts": 8, "completions": 5, "start_rate": 80.0, "completion_rate": 62.5, "revenue": { "amount": "12.50", "currency": "USD" }, "aov": { "amount": "12.50", "currency": "USD" } }
}

series[] has one row per day in the range, zero-filled on quiet days. totals sums the counts and money and computes distinct viewers, rates and timings over the whole range.

Answers to one question

answers lists the published version’s choice and numeric questions under questions[] and answers for one of them, chosen with component_key (the first question when absent). It is also where a store with no people yet reads the page_key, component_key and option_id values a People search term needs.

Terminal window
curl "https://api.octaneai.com/v1/quizzes/quiz_7c9e6679742540de944be07fc1f90ae7/analytics/answers?component_key=choice-abc12&from=2026-09-01&to=2026-09-07" \
-H "Authorization: Bearer $OCTANE_API_KEY"
{
"quiz_id": "quiz_7c9e6679742540de944be07fc1f90ae7",
"questions": [{ "page_key": "skin", "page_title": "Skin", "component_key": "choice-abc12", "question": "Skin type?", "kind": "choice" }],
"question": { "page_key": "skin", "page_title": "Skin", "component_key": "choice-abc12", "question": "Skin type?", "kind": "choice" },
"answered": 5,
"options": [
{ "option_id": "opt-1", "label": "Dry", "kind": "image", "removed": false, "chose": 3, "chose_share": 60.0, "completed": 2, "completion_rate": 66.67, "opted_in": 1, "buyers": 1, "answer_orders": 1, "answer_revenue": { "amount": "12.50", "currency": "USD" } }
],
"buckets": [],
"summary": null
}

A choice question answers with options[]; removed marks an option the published version no longer has. A number question (a slider, a number input) answers with buckets[] and summary instead: each bucket is one band of values with the same outcome columns an option row carries, every answer lands in exactly one band, and a slider is banded on the scale it was authored with while a number input gets ten equal bands over the observed 1st to 99th percentile. Nobody having answered means no buckets and no summary. A component_key the published version has no question for answers 404.

Per-quiz gotchas

  • paths keeps the 50 busiest sequential and back edges and sums the rest into other_paths; jump edges are always listed. cohort=finished|left restricts it to sessions that finished or left. A read whose estimated visits (starts times the page count) exceed 2,000,000 is refused with 422 validation_error; shorten the range.
  • funnel and paths flag a page the published version no longer has with removed: true, and the per-quiz overview carries markers[], one per publish.
  • products sorts by revenue, units or orders and can be narrowed to one revenue bucket; traffic and breakdown need by.
  • A deleted quiz answers 404 on every per-quiz view.

Freshness and caching

Every answer carries freshness: closed_days_through is the last day that will not change any more (yesterday in the request’s timezone), as_of is when those days were last rolled up, today_live says today is read live, stale that the rollup is overdue, pending that it has never completed for this store yet, and next_refresh_at when it runs again.

Answers are cached for up to 300 seconds and refreshed in the background once older than the plan’s analytics_soft_ttl_seconds (read it from GET /v1/store); a publish, a quiz deletion or an analytics reset makes every cached answer of the store stale at once. Responses carry Cache-Control: private, no-cache.

Analytics responses are the only ones with an ETag. Send it back in If-None-Match and an unchanged answer is a 304 with no body, charged 1 unit instead of 3:

Terminal window
curl -i "https://api.octaneai.com/v1/analytics/summary?period=30d" \
-H "Authorization: Bearer $OCTANE_API_KEY" \
-H 'If-None-Match: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"'
HTTP/1.1 304 Not Modified
Request-Id: 7c9e6679-7425-40de-944b-e07fc1f90ae7
ETag: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
RateLimit: limit=72, remaining=70, reset=1

If-None-Match accepts a strong or weak tag, a list of tags, or *. A 304 still needs authentication, the scope and rate capacity; never cache a problem body.

Errors you will meet

Every one is a Problem body: 422 validation_error for to before from, a range past the cap (max_days says which), an unknown tz, a missing or unknown by, a refused filter combination or a paths read that is too wide; 404 not_found for an unknown, foreign or deleted quiz or a component_key the published version has no question for; 503 limiter_unavailable when limits could not be checked, retry after Retry-After.