---
title: "Read the complete experiment results"
method: GET
path: "/v1/experiments/{experiment_id}/results"
tags: ["experiments"]
---

# Read the complete experiment results

`GET /v1/experiments/{experiment_id}/results`

## Request

- Base URL: `https://api.octaneai.com`
- URL: `https://api.octaneai.com/v1/experiments/{experiment_id}/results`
- Auth: HTTP bearer

## Path parameters

- `experiment_id` string, required

## Response `200`

The complete results, including decision evidence, gates, periods and attention context.

- ExperimentResultsResponse
  - `experiment` ExperimentResponse, required
    - `id` string, required
    - `app_id` string, required
    - `name` string, required
    - `status` 'draft' | 'running' | 'waiting' | 'ended', required: draft: set up, not started. running: shoppers are being split. waiting: still splitting, automatic decisions paused. ended: no longer assigning.
    - `decision_metric` string, required: completion_rate | email_opt_in | order_conversion | rpv | aov | phone_opt_in | reached_page | product_page_clicked | custom_url_clicked | button_clicked | chose_answer
    - `decision_target` object, nullable: What a targeted metric points at: reached_page {mode: specific | after, page_key}, button_clicked {page_key, component_key}, chose_answer {page_key, component_key, option_id}; null for any other metric
    - `decision_option` 'manual' | 'when_clear' | 'on_date' | 'ai', required: manual: the merchant decides. when_clear: publish at the first clear result. on_date: decide on a date. ai: the AI decides.
    - `ai_trigger` 'when_clear' | 'on_date'
    - `decision_date` string, date, nullable: The day the test decides, read in timezone, at least seven days out; null when it decides on no date
    - `decides_at` string, date-time, nullable: decision_date as an instant, midnight at its start in timezone, UTC ISO 8601; null without a date
    - `timezone` string, required: IANA name of the timezone decision_date is read in
    - `fallback` 'keep' | 'revert' | 'notify', required: What happens on the date without a clear result: keep running, put the live version back, or tell the merchant and wait.
    - `ai_max_runs` integer, nullable: How many times the AI may run before it stops; null lets it run as often as the test asks
    - `ai_fallback` 'when_clear' | 'notify', required: What the test does once the AI has run ai_max_runs times without ending it: decide at the first clear result without the AI, or tell the merchant and wait.
    - `revenue_tiers` string[], required: The kinds of revenue counted: added_from_quiz | shown_bought_later | bought_not_shown
    - `protect_metrics` string[], required: The metrics the test watches for harm: completion_rate | email_opt_in | order_conversion | rpv | aov | phone_opt_in | reached_page | product_page_clicked | custom_url_clicked | button_clicked | chose_answer
    - `weekly_digest` boolean, required: Whether a weekly email reports where the test stands
    - `long_run_reminders` boolean, required: Whether reminder emails go out six weeks after the start and every two weeks after until it ends
    - `merchant_note` string, nullable: A note our AI weighs when it decides, up to 2000 characters: context, never an order
    - `period` integer, required: The current counting period: 0 on a draft, 1 once started, one higher each time a new one opens
    - `period_started_at` string, date-time, nullable: When the current counting period began, UTC ISO 8601; null on a draft
    - `period_reason` 'started' | 'split_changed' | 'control_published' | 'variant_republished' | 'metric_changed' | 'revenue_tiers_changed', required: Why a counting period began.
    - `clear_since_at` string, date-time, nullable: When the decision metric first read clear in this counting period, UTC ISO 8601; null until then
    - `srm_flagged_at` string, date-time, nullable: When the split check found shoppers not landing in the versions as set, UTC ISO 8601; null while healthy
    - `ai_verdict` 'publish' | 'keep_running' | 'no_change': What the AI decided last, as the trusted decide path accepted it; a refused verdict lives on the timeline only.
    - `ai_rationale` string, nullable: Our AI's reasoning for its last decision; null before it has decided
    - `winner_arm_id` string, nullable
    - `ended_by` 'manual' | 'auto' | 'ai' | 'fallback_revert'
    - `ended_at` string, date-time, nullable
    - `outcome` 'winner_manual' | 'winner_auto' | 'winner_ai' | 'ai_no_change' | 'ai_recommended' | 'ai_keep_running' | 'ai_blocked_credits' | 'srm_flagged' | 'no_winner_notify' | 'reverted' | 'plan_downgraded' | 'ended_manual' | 'cancelled': How a test ended, in one word.
    - `revision` integer, required: A number that goes up with every change to the test
    - `started_at` string, date-time, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `arms` ExperimentArmResponse[], required
      - `id` string, required
      - `position` integer, required: 0 for the live version, 1 for the variant
      - `label` string, required
      - `weight` integer, required: The arm's share of the split, a whole number weighed against the other arm's
      - `version_id` string, required
      - `version_number` integer, required
      - `is_control` boolean, required
  - `as_of` string, date-time, required: When these numbers were read, UTC ISO 8601; a read is served again for up to two hours
  - `window_start` string, date-time, nullable, required: The start of the visits counted, when the counting period began, UTC ISO 8601; null on a draft
  - `window_end` string, date-time, nullable, required: The end of the visits counted, as_of or when the test ended, UTC ISO 8601; null on a draft
  - `frozen` boolean, required: True once the test has ended: the visits counted stop at its end while their orders keep arriving
  - `admission_cutoff` string, date-time, nullable, required: For a money decision metric on a live test, the decision counts only visits before this, UTC ISO 8601; null otherwise
  - `admission_line` string, nullable, required
  - `attribution_window_days` integer, required: The store's order-matching window in days: how long after a visit an order still counts
  - `attribution_line` string, required
  - `cookieless_share` number, nullable, required: Share of visits without cookies, assigned a version per visit, 0 to 1; null without visits
  - `cookieless_line` string, nullable, required
  - `arms` ExperimentArmCounts[], required: Each version's numbers in the current counting period, live version first
    - `arm_id` string, required
    - `label` string, required
    - `version_id` string, required
    - `version_number` integer, required
    - `weight` integer, required: The arm's share of the split, a whole number weighed against the other arm's
    - `is_control` boolean, required
    - `views` integer, required: Visits that opened the quiz on the store, whether or not the shopper answered anything; previews are not counted
    - `starts` integer, required: Visits where the shopper answered or stepped forward at least once
    - `completions` integer, required: Visits that finished the quiz: reached its last page and answered whatever that page requires
    - `completion_rate` number, nullable, required: Completions as a percentage of starts, 0 to 100; null without starts
    - `email_opt_ins` integer, required: Visits where the shopper submitted an email address
    - `phone_opt_ins` integer, required: Visits where the shopper submitted a phone number
    - `orders` integer, required: Orders linked to these visits with a line in a revenue kind the test counts
    - `visits_with_order` integer, required: Visits followed by at least one such order
    - `revenue` Money, required
      - `amount` string, required: A decimal string, as "79.97"
      - `currency` string, required: ISO 4217 code
    - `revenue_by_bucket` ProductBucketMoney, required
      - `added_from_quiz` Money, required
        - `amount` string, required: A decimal string, as "79.97"
        - `currency` string, required: ISO 4217 code
      - `shown_bought_later` Money, required
        - `amount` string, required: A decimal string, as "79.97"
        - `currency` string, required: ISO 4217 code
      - `bought_not_shown` Money, required
        - `amount` string, required: A decimal string, as "79.97"
        - `currency` string, required: ISO 4217 code
    - `counted_revenue` Money, required
      - `amount` string, required: A decimal string, as "79.97"
      - `currency` string, required: ISO 4217 code
    - `assignment_units` integer, required: Distinct shoppers assigned to this version; a shopper without cookies counts once per visit
  - `header` ExperimentHeader, required
    - `testing` string, required: What is tested: each version with its number and share of shoppers
    - `difference` string, nullable, required: Which pages the variant adds, removes or changes; null when both hold the same pages
    - `current_read` string, required: What the decision metric reads now, in one sentence
    - `current_read_numbers` string, nullable: The numbers behind current_read; null while collecting or when it cannot be read
    - `next` string, required: What happens next, per how the test decides
    - `day_line` string, nullable, required: The day of the counting period in words; null past day 42
    - `evaluated_on_line` string, nullable, required: Why the decision waits past decision_date; null when it does not
  - `changes` ExperimentPageChange[], required: Pages the variant adds, removes or changes against the live version
    - `kind` 'added' | 'removed' | 'changed', required: How the variant differs on this page: added | removed | changed
    - `page_key` string, required
    - `page_title` string, nullable, required
  - `progress` ExperimentProgress, required
    - `day` integer, required: The day of the current counting period, 1 on the day it began
    - `share` number, nullable, required: The lower of the two versions' progress toward its planned traffic, 0 to 1; null without a plan
    - `pace` number, nullable, required: Starts or visits a day, as the decision metric counts them, over the latest seven days; null without a plan
    - `days_at_pace` integer, nullable, required: About how many more days the planned traffic takes at this pace; null past six weeks, at no pace or once ended
    - `planned` integer, nullable, required: The visits both versions together need to spot a 10 percent difference; null when the period has no plan
    - `quotas` object, nullable, required: planned split by version, keyed control and variant; null without a plan
    - `observed` object, required: What each version has so far toward its quota, keyed control and variant
    - `tier` string, required: min_days: inside the minimum days | estimating: no pace yet | reached: enough shoppers and clear | reached_unclear: enough shoppers, not clear | on_track | slow
    - `line` string, required: What the progress says; empty when the period has no planned sample or the decision metric is clear
  - `gates` ExperimentGate[], required
    - `key` 'decision_metric' | 'min_days' | 'enough_data' | 'split_health' | 'protected', required: decision_metric | min_days (seven days in the period) | enough_data | split_health | protected (no protected metric harmed)
    - `label` string, required
    - `met` boolean, nullable, required
    - `detail` string, required
  - `metrics` ExperimentMetricReadout[], required
    - `kind` string, required: completion_rate | email_opt_in | order_conversion | rpv | aov | phone_opt_in | reached_page | product_page_clicked | custom_url_clicked | button_clicked | chose_answer
    - `label` string, required
    - `target` object, nullable, required: What the decision metric points at, as decision_target; null on every other metric
    - `role` 'decider' | 'exploratory', required: decider: the decision metric | exploratory: reported beside it, never decides
    - `population` string, required: What the metric counts over: starts | visits | orders
    - `unit` string, required: The unit of delta and its range: percentage points, sent as a fraction (0.05 is five points) | currency per order | currency per visit
    - `state` 'collecting' | 'leaning' | 'clear', required: collecting: not enough data | leaning: one version ahead, range still includes zero | clear: range excludes zero
    - `why` string, required
    - `control` ExperimentArmReadout, required
      - `arm_id` string, required
      - `label` string, required
      - `n` integer, required: The visits the metric is read on: starts for completion and opt-in rates, visits for the rest
      - `events` integer, required: How many of those did what the metric counts; for the money metrics, the distinct counted orders
      - `mean` number, required: The metric on this version: a rate from 0 to 1, or revenue per visit or per order in the store's currency
      - `se` number, required: The standard error of mean, with each returning shopper's visits grouped
      - `design_effect` number, required: How much returning shoppers widened the range; 1 means repeats made no difference
      - `raw_money` number, required: Counted revenue before the cap on unusually large visits, in the store's currency; 0 for a rate
      - `capped_money` number, required: Counted revenue with unusually large visits counted at the cap, in the store's currency; 0 for a rate
      - `floors` ExperimentFloor[], required
        - `name` string, required: What the minimum counts: starts | visits | orders | events
        - `needed` integer, required: The minimum a version needs before its number is read
        - `have` integer, required
        - `met` boolean, required
    - `variant` ExperimentArmReadout, required
      - `arm_id` string, required
      - `label` string, required
      - `n` integer, required: The visits the metric is read on: starts for completion and opt-in rates, visits for the rest
      - `events` integer, required: How many of those did what the metric counts; for the money metrics, the distinct counted orders
      - `mean` number, required: The metric on this version: a rate from 0 to 1, or revenue per visit or per order in the store's currency
      - `se` number, required: The standard error of mean, with each returning shopper's visits grouped
      - `design_effect` number, required: How much returning shoppers widened the range; 1 means repeats made no difference
      - `raw_money` number, required: Counted revenue before the cap on unusually large visits, in the store's currency; 0 for a rate
      - `capped_money` number, required: Counted revenue with unusually large visits counted at the cap, in the store's currency; 0 for a rate
      - `floors` ExperimentFloor[], required
        - `name` string, required: What the minimum counts: starts | visits | orders | events
        - `needed` integer, required: The minimum a version needs before its number is read
        - `have` integer, required
        - `met` boolean, required
    - `delta` number, nullable, required: The variant's number minus the live version's, a rate as a fraction (0.05 is five points) or money in the store's currency; null until both versions have visits
    - `abs_lo` number, nullable, required: The low end of the range for the variant's number minus the live version's, in the same unit as delta; null until it can be read
    - `abs_hi` number, nullable, required: The high end of the range for the variant's number minus the live version's, in the same unit as delta; null until it can be read
    - `lift` number, nullable, required: delta relative to the live version's number, 0.1 is 10 percent better; null until that number is clear of zero
    - `lo` number, nullable, required: The low end of the range for lift; null with lift
    - `hi` number, nullable, required: The high end of the range for lift; null with lift
    - `k` number, nullable, required: The width multiplier of the range, used instead of 1.96 so the range holds however often it is read
    - `se` number, nullable, required: The standard error of delta; null with delta
    - `alpha` number, required: The error rate the range allows, 0.05
    - `n_star` number, required: The visits expected by the decision date, where the range is tuned to be narrowest
    - `design_effect` number, required: The larger of the two versions' design effects
    - `line` string, required: The gap between the versions in one sentence, relative to the live version
    - `level` boolean, required: Whether the line reads the two versions as level so far
  - `protection` ExperimentProtection[], required
    - `kind` string, required: The protected metric: completion_rate | email_opt_in | order_conversion | rpv | aov | phone_opt_in | reached_page | product_page_clicked | custom_url_clicked | button_clicked | chose_answer
    - `label` 'not checked yet' | 'no harm' | 'worse', required: not checked yet: too little data | no harm | worse: blocks an automatic publish
    - `lo` number, nullable, required: The low end of the range for the variant's number minus the live version's, in the same unit as delta; null until it can be read
    - `hi` number, nullable, required: The high end of the range for the variant's number minus the live version's, in the same unit as delta; null until it can be read
    - `assessed` boolean, required: Whether the metric has enough data to be checked
    - `harmed` boolean, required: Whether even the best case for the variant is worse than the live version
    - `detail` string, required
  - `split` ExperimentSplitCheck, required
    - `units_per_arm` integer[], required: Shoppers counted per version, live version first
    - `expected` number[], required: The share of shoppers each version should get, 0 to 1, live version first
    - `log_odds` number, required: The evidence that shoppers are not landing as the split was set; the check fires above 6.91
    - `latched` boolean, required: Whether the check has fired; once fired it stays on for the rest of the test
    - `surfaced` boolean, required: Whether the 200 shoppers the check needs have been counted
    - `expected_share` number, required: The variant's share of shoppers the split was set to, 0 to 1
    - `observed_share` number, nullable, required: The variant's share of the shoppers counted, 0 to 1; null before any shopper is counted
    - `n` integer, required: The shoppers counted on both versions
    - `line` string, nullable, required: What share of shoppers the variant should get and got; null before any shopper is counted
  - `periods` ExperimentPeriod[], required
    - `period` integer, required
    - `reason` string, required: Why the period began: started | split_changed | control_published | variant_republished | metric_changed | revenue_tiers_changed
    - `started_at` string, date-time, required
    - `ended_at` string, date-time, nullable, required
    - `current` boolean, required
    - `label` string, required
  - `events` ExperimentTimelineEvent[], required
    - `at` string, date-time, required
    - `kind` string, required: created | started | period_opened | weights_changed | control_published | arm_republished | decision_metric_changed | decision_option_changed | protect_changed | revenue_tiers_changed | settings_changed | evaluation_delayed | decision_traced | ai_decided | srm_flagged | clear | nudged | digest_sent | warning_sent | result_sent | ended | target_missing; a new kind may be added
    - `arm_id` string, nullable, required
    - `data` object, required: What the event recorded, by kind; version ids in it are public ids, null once that version is deleted
    - `label` string, required
  - `decision_trace` ExperimentDecisionTrace, required
    - `plain_line` string, required
    - `as_of` string, date-time, required: When the decision was made, UTC ISO 8601
    - `judgement` boolean, required: True for our AI's decision, its own judgement rather than the automatic rule's
    - `attribution_window_days` integer, required: The store's order-matching window in days: how long after a visit an order still counts
    - `revenue_tiers` string[], required: The kinds of revenue counted: added_from_quiz | shown_bought_later | bought_not_shown
    - `compared_version_ids` string[], required: The live version and the variant, in that order
    - `revision` integer, required: The test's revision when decided
    - `period` integer, required
    - `cap_value` number, nullable, required: The revenue above which a visit was counted at the cap, in the store's currency; null without a cap
    - `alpha` number, required: The error rate the range allows, 0.05
    - `n_star` number, required: The visits expected by the decision date, where the range is tuned to be narrowest
    - `decision_metric` string, required: completion_rate | email_opt_in | order_conversion | rpv | aov | phone_opt_in | reached_page | product_page_clicked | custom_url_clicked | button_clicked | chose_answer
    - `delta` number, nullable, required: The variant's number minus the live version's, a rate as a fraction (0.05 is five points) or money in the store's currency; null until both versions have visits
    - `lo` number, nullable, required: The low end of the range for the variant's number minus the live version's, in the same unit as delta; null until it can be read
    - `hi` number, nullable, required: The high end of the range for the variant's number minus the live version's, in the same unit as delta; null until it can be read
    - `winner` string, nullable, required: The label of the version the evidence favoured; null when it favoured neither
    - `publish_arm` string, nullable, required: The label of the version the rule would publish; null when it would publish none
    - `reason` string, required: under_minimum_hours | split_unhealthy | collecting | leaning | clear_for_control | clear | protected_harmed:<metric kind>
    - `arms` object[], required: The decision metric per version, live version first: label, n, events, rate, se, raw_money, capped_money, design_effect
    - `bound` object, required: What the range had to clear: k, alpha_used, se, n and needed_lo (the lead that excludes zero)
    - `floors` ExperimentFloor[], required
      - `name` string, required: What the minimum counts: starts | visits | orders | events
      - `needed` integer, required: The minimum a version needs before its number is read
      - `have` integer, required
      - `met` boolean, required
    - `split` ExperimentSplitCheck, required
      - `units_per_arm` integer[], required: Shoppers counted per version, live version first
      - `expected` number[], required: The share of shoppers each version should get, 0 to 1, live version first
      - `log_odds` number, required: The evidence that shoppers are not landing as the split was set; the check fires above 6.91
      - `latched` boolean, required: Whether the check has fired; once fired it stays on for the rest of the test
      - `surfaced` boolean, required: Whether the 200 shoppers the check needs have been counted
      - `expected_share` number, required: The variant's share of shoppers the split was set to, 0 to 1
      - `observed_share` number, nullable, required: The variant's share of the shoppers counted, 0 to 1; null before any shopper is counted
      - `n` integer, required: The shoppers counted on both versions
      - `line` string, nullable, required: What share of shoppers the variant should get and got; null before any shopper is counted
    - `protection` object[], required: Each protected metric when decided: kind, label, lo, hi, assessed, harmed
    - `formulas` ExperimentTraceFormula[], required
      - `key` string, required: formula_cs | formula_k | formula_se | formula_srm | formula_floor | formula_guardrail | formula_rpv_cap
      - `label` string, required
      - `latex` string, required: The formula as written, in LaTeX
      - `latex_plugged` string, required: The formula with this test's numbers, in LaTeX
      - `terms` object, required: Each symbol with its meaning in words
    - `changes` string[], required: Notes recorded with the decision; currently always empty
  - `ai_decision_trace` ExperimentDecisionTrace, required
    - `plain_line` string, required
    - `as_of` string, date-time, required: When the decision was made, UTC ISO 8601
    - `judgement` boolean, required: True for our AI's decision, its own judgement rather than the automatic rule's
    - `attribution_window_days` integer, required: The store's order-matching window in days: how long after a visit an order still counts
    - `revenue_tiers` string[], required: The kinds of revenue counted: added_from_quiz | shown_bought_later | bought_not_shown
    - `compared_version_ids` string[], required: The live version and the variant, in that order
    - `revision` integer, required: The test's revision when decided
    - `period` integer, required
    - `cap_value` number, nullable, required: The revenue above which a visit was counted at the cap, in the store's currency; null without a cap
    - `alpha` number, required: The error rate the range allows, 0.05
    - `n_star` number, required: The visits expected by the decision date, where the range is tuned to be narrowest
    - `decision_metric` string, required: completion_rate | email_opt_in | order_conversion | rpv | aov | phone_opt_in | reached_page | product_page_clicked | custom_url_clicked | button_clicked | chose_answer
    - `delta` number, nullable, required: The variant's number minus the live version's, a rate as a fraction (0.05 is five points) or money in the store's currency; null until both versions have visits
    - `lo` number, nullable, required: The low end of the range for the variant's number minus the live version's, in the same unit as delta; null until it can be read
    - `hi` number, nullable, required: The high end of the range for the variant's number minus the live version's, in the same unit as delta; null until it can be read
    - `winner` string, nullable, required: The label of the version the evidence favoured; null when it favoured neither
    - `publish_arm` string, nullable, required: The label of the version the rule would publish; null when it would publish none
    - `reason` string, required: under_minimum_hours | split_unhealthy | collecting | leaning | clear_for_control | clear | protected_harmed:<metric kind>
    - `arms` object[], required: The decision metric per version, live version first: label, n, events, rate, se, raw_money, capped_money, design_effect
    - `bound` object, required: What the range had to clear: k, alpha_used, se, n and needed_lo (the lead that excludes zero)
    - `floors` ExperimentFloor[], required
      - `name` string, required: What the minimum counts: starts | visits | orders | events
      - `needed` integer, required: The minimum a version needs before its number is read
      - `have` integer, required
      - `met` boolean, required
    - `split` ExperimentSplitCheck, required
      - `units_per_arm` integer[], required: Shoppers counted per version, live version first
      - `expected` number[], required: The share of shoppers each version should get, 0 to 1, live version first
      - `log_odds` number, required: The evidence that shoppers are not landing as the split was set; the check fires above 6.91
      - `latched` boolean, required: Whether the check has fired; once fired it stays on for the rest of the test
      - `surfaced` boolean, required: Whether the 200 shoppers the check needs have been counted
      - `expected_share` number, required: The variant's share of shoppers the split was set to, 0 to 1
      - `observed_share` number, nullable, required: The variant's share of the shoppers counted, 0 to 1; null before any shopper is counted
      - `n` integer, required: The shoppers counted on both versions
      - `line` string, nullable, required: What share of shoppers the variant should get and got; null before any shopper is counted
    - `protection` object[], required: Each protected metric when decided: kind, label, lo, hi, assessed, harmed
    - `formulas` ExperimentTraceFormula[], required
      - `key` string, required: formula_cs | formula_k | formula_se | formula_srm | formula_floor | formula_guardrail | formula_rpv_cap
      - `label` string, required
      - `latex` string, required: The formula as written, in LaTeX
      - `latex_plugged` string, required: The formula with this test's numbers, in LaTeX
      - `terms` object, required: Each symbol with its meaning in words
    - `changes` string[], required: Notes recorded with the decision; currently always empty
  - `changed_since` boolean, required: Whether revenue arriving since the decision moved the lead past half the recorded range
  - `changed_since_line` string, nullable, required
  - `explainer_keys` string[], required: The camelCase names of the help topics that explain the numbers in this response

## Other responses

- `401`: The key is missing, malformed, revoked or expired.
- `403`: The store's plan has no API access, or the key lacks the scope.
- `404`: Not found
- `422`: A request parameter is invalid.
- `429`: The key's or the store's bucket is full; Retry-After says when to try again.
- `503`: Request limits cannot be checked right now; retry shortly.
- `504`: The request timed out.

---

[API](https://skmtc.dev/octaneai/apis/octane-ai-merchant-api.md) - [All operations](https://skmtc.dev/octaneai/apis/octane-ai-merchant-api/llms.txt) - [OpenAPI document](https://skmtc.dev/octaneai/apis/octane-ai-merchant-api/revisions/dc2d1aa2b7c6?raw)
