---
title: "Stop an A/B test"
method: POST
path: "/v1/sites/{site_id}/widgets/ab-tests/{test_id}/stop"
tags: ["Widgets"]
---

# Stop an A/B test

`POST /v1/sites/{site_id}/widgets/ab-tests/{test_id}/stop`

## Request

- Base URL: `https://api.getsitecontrol.com`
- URL: `https://api.getsitecontrol.com/v1/sites/{site_id}/widgets/ab-tests/{test_id}/stop`
- Auth: HTTP bearer

Ends the test keeping one widget: the one named in `winner_id`, or without it the test's `leader_id`. The test is removed, the winner stays as it was (on or off), every other widget is turned off and kept. The admin's Stop; like the admin, it needs two or more widgets in the test.

## Path parameters

- `site_id` integer, required
- `test_id` integer, required: A/B test id from List A/B tests

## Headers

- `Idempotency-Key` string: Makes a retry of a POST safe: a repeat with the same key, body and GSC-Version returns the stored response with `Idempotency-Replayed: true` instead of running again. The key protects against repeated execution while the key store works. Saving the change and storing the response under the key are not atomic: if the process or the key store fails between them, the change is kept, the key is released after 15 minutes and a retry runs the request again. After a timeout or a 5xx, read the resource to check the result before retrying. 5xx and 429 responses and responses over 256 KB are not stored, so a repeat runs the request again. A key is kept for 24 hours. The same key with another body or version is 422; a repeat while the first request runs is 409.

## Request body

- WidgetAbTestStopRequest
  - `winner_id` integer, nullable: The widget to keep, one of the test's `widgets`; omitted: the test's `leader_id`

## Response `200`

Successful Response

- WidgetAbTestStop
  - `winner` Widget, required
    - `id` integer, required
    - `name` string, nullable
    - `headline` string, nullable: The widget's first headline, plain text; null when it has none
    - `layout` 'modal' | 'bar' | 'panel' | 'slide_in' | 'sidebar' | 'fullscreen' | 'inline' | 'other', required: Where the widget sits on the page, named as in the admin. modal: a window centred over the page. bar: a strip across the top or bottom of the page. panel: a box attached to the bottom of the screen, opened from its header. slide_in: a box that slides in from a corner. sidebar: a panel attached to the left or right edge of the screen. fullscreen: covers the whole screen. inline: placed inside the page content. other: a layout the admin no longer offers. "Pop-up" is not a layout: users call any widget a pop-up.
    - `is_active` boolean, required
    - `ab_test_group` WidgetAbTestGroup
      - `id` integer, required
      - `name` string, nullable
    - `folder` Folder, required
      - `id` integer, required: 0 is General, where items outside any folder are
      - `name` string, required
    - `actions` string[], required: Event types the widget can produce besides show and close
    - `features` WidgetFeatures, required
      - `collects_email` boolean, required: Has an e-mail field
      - `collects_phone` boolean, required: Has a phone field
      - `has_coupon` boolean, required: A button copies or applies a discount code
      - `has_game` boolean, required: Has a game: a wheel, a scratch card or a reveal
      - `has_countdown` boolean, required: Has a countdown timer
      - `has_teaser` boolean, required: Has a teaser, the small tab shown before or after it
      - `captcha` boolean, required: reCAPTCHA checks its form submits
      - `double_opt_in` boolean, required: A new subscriber confirms the address by e-mail first
      - `email_quality_filter` boolean, required: Submits with disposable or untrusted e-mail addresses are rejected
      - `notifications` boolean, required: Responses are e-mailed: switched on, with a recipient
      - `in_ab_test` boolean, required: In an A/B test, named in `ab_test_group`
      - `has_integration` boolean, required: An integration, webhook or Zapier switched on sends its responses
      - `has_draft` boolean, required: You have a draft of it with unsaved changes, see `draft`
    - `targeting` WidgetTargeting, required
      - `is_complete` boolean, required: False when some rules could not be read into the blocks below
      - `warnings` string[], required: Informational notes; wording is not stable, do not parse
      - `pages` TargetingPages
        - `include` string[], required
        - `exclude` string[], required
      - `audience` TargetingAudience
        - `match` 'all' | 'any', required: all: every item must hold; any: one is enough.
        - `segments` TargetingAudienceSegment[], required
          - `match` 'all' | 'any', required: all: every item must hold; any: one is enough.
          - `conditions` TargetingAudienceCondition[], required
            - `key` string, required
            - `operator` 'is' | 'is_not' | 'less_than' | 'greater_than' | 'starts_with' | 'does_not_start_with' | 'ends_with' | 'does_not_end_with' | 'contains' | 'does_not_contain' | 'matches' | 'does_not_match' | 'has_any_value' | 'is_undefined' | 'matches_url' | 'does_not_match_url' | 'matches_ip' | 'does_not_match_ip' | 'any_of' | 'none_of' | 'during' | 'not_during', required: How a condition tests its value; any_of and none_of take a list, during and not_during a range, has_any_value and is_undefined no value.
            - `value` unknown
            - `subject` string, nullable
      - `start` TargetingStart
        - `mode` 'immediate' | 'programmatically' | 'triggers', required: immediate: on page load; programmatically: only when the site's code shows it; triggers: when one of `triggers` fires.
        - `triggers` TargetingTrigger[], required
          - `type` 'exit_intent' | 'scroll_percent' | 'time_on_page_seconds' | 'time_on_site_seconds' | 'inactivity_seconds' | 'add_to_cart' | 'remove_from_cart' | 'automatic' | 'display_time_seconds', required: What fires a show or a hide; a *_seconds or *_percent trigger carries its threshold in `value`.
          - `value` union
            - integer
            - number
      - `hide` TargetingHide
        - `triggers` TargetingTrigger[], required
          - `type` 'exit_intent' | 'scroll_percent' | 'time_on_page_seconds' | 'time_on_site_seconds' | 'inactivity_seconds' | 'add_to_cart' | 'remove_from_cart' | 'automatic' | 'display_time_seconds', required: What fires a show or a hide; a *_seconds or *_percent trigger carries its threshold in `value`.
          - `value` union
            - integer
            - number
      - `stop` TargetingStop
        - `rules` TargetingStopRule[], required
          - `event` 'action' | 'close' | 'hide' | 'show' | 'show_count', required: The visitor's event after which the widget stops showing; close and show are named for their event. action: after the visitor submits it or clicks its button. hide: after a hide trigger hides it. show_count: after `count` shows.
          - `mode` 'forever' | 'duration': forever: never again after the event; duration: not for `duration_seconds` after it.
          - `duration_seconds` number, nullable
          - `count` integer, nullable
      - `schedule` TargetingSchedule
        - `match` 'all' | 'any', required: all: every item must hold; any: one is enough.
        - `segments` TargetingScheduleSegment[], required
          - `match` 'all' | 'any', required: all: every item must hold; any: one is enough.
          - `conditions` TargetingScheduleCondition[], required
            - `key` 'date' | 'time' | 'weekday', required: date: a range of days; time: a range of hours in a time zone; weekday: days of the week, 0 is Sunday.
            - `operator` 'is' | 'is_not' | 'less_than' | 'greater_than' | 'starts_with' | 'does_not_start_with' | 'ends_with' | 'does_not_end_with' | 'contains' | 'does_not_contain' | 'matches' | 'does_not_match' | 'has_any_value' | 'is_undefined' | 'matches_url' | 'does_not_match_url' | 'matches_ip' | 'does_not_match_ip' | 'any_of' | 'none_of' | 'during' | 'not_during', required: How a condition tests its value; any_of and none_of take a list, during and not_during a range, has_any_value and is_undefined no value.
            - `value` union, required
              - …
    - `draft` WidgetDraftState
      - `state` 'new' | 'draft' | 'synced', required: new: started with create_widget_draft and not saved yet; the widget exists only in this draft. draft: your unsaved changes over the saved widget. synced: the draft matches the saved widget.
      - `revision` integer, nullable: The draft's revision: 1 when it started, one more per change; null when you have no draft of this widget
      - `saved_changed` boolean, required: true: the widget was saved after your draft started, and saving would replace that save
      - `saved_by` string, nullable: Who made that save, when saved_changed; e-mail or name
      - `saved_at` string, date-time, nullable: When they saved, when saved_changed
      - `started_at` string, date-time, nullable: When this draft started; a draft that started before this conversation is unfinished work, maybe yours
      - `updated_at` string, date-time, nullable: Last change to the draft
      - `preview_url` string, nullable: One permanent page per widget: your draft while it has unsaved changes, the saved widget otherwise, refreshed as you edit; null where live previews are not set up
    - `warnings` string[], required: Informational notes about the widget itself; wording is not stable, do not parse
    - `created_at` string, date-time, nullable
    - `updated_at` string, date-time, nullable: Last change of the widget itself; a move to another folder does not change it
    - `edited_at` string, date-time, nullable: Last change made in the editor
    - `truncated` string[]: Parts shortened in the list, as paths; the card has them whole. Empty when nothing was cut
  - `loser_ids` integer[], required: The other widgets, now turned off and kept

## Other responses

- `400`: Unsupported GSC-Version
- `401`: Missing or invalid API key or access token
- `403`: Access denied
- `404`: `ab_test_not_found`: the site has no such test, or it was stopped; `ab_test_winner_not_found`: `winner_id` is not a widget of the test
- `409`: `ab_test_single_widget`: the test has one widget
- `413`: Request body too large
- `422`: Validation failed
- `429`: Rate limited
- `503`: Temporarily unavailable; retry later

---

[API](https://skmtc.dev/getsitecontrol/apis/getsitecontrol-public-api.md) - [All operations](https://skmtc.dev/getsitecontrol/apis/getsitecontrol-public-api/llms.txt) - [OpenAPI document](https://skmtc.dev/getsitecontrol/apis/getsitecontrol-public-api/revisions/084fcad80e75?raw)
