---
title: "Create a judging Test"
method: POST
path: "/agent/workspaces/{workspace}/tests"
tags: ["AdTestApi"]
---

# Create a judging Test

`POST /agent/workspaces/{workspace}/tests`

## Request

- Base URL: `https://plainrouter.com/api/v1`
- URL: `https://plainrouter.com/api/v1/agent/workspaces/{workspace}/tests`
- Auth: HTTP bearer

Creates a judging Test with frozen conversion definition, settings, axis, and 2 to 4 ads from one mirrored ad set; it creates no proposal or Meta write.

## Path parameters

- `workspace` integer, required

## Request body

- object
  - `platform_ad_account_id` integer, required
  - `ad_set_id` string, required
  - `member_ad_ids` string[], required: Two to four distinct Meta ad IDs shown in the selected ad set inventory.
  - `axis` 'hook' | 'angle' | 'visual' | 'offer' | 'format' | 'landing', required
  - `conversion_mode` 'workspace_default' | 'explicit': Omit to freeze the workspace default. Explicit requires event_name; an explicit choice is always preserved.
  - `event_name` string, nullable: Optional observed workspace conversion event other than PageView. Omission uses the workspace default; legacy singular requests remain supported.
  - `arrivals_floor` integer: Optional override; default 50 counted arrivals per member.
  - `minimum_conversions` integer: Optional override; default 10 recorded conversions for a winner.
  - `maximum_days` integer: Optional override; default 14 complete account-local days.
  - `maximum_spend_minor` union: Optional stop limit in positive whole Meta minor units, using the same unit scale as Launch plan budget_amount_minor.
    - string: A positive whole number of Meta minor units in the selected account currency, using the same unit scale as Launch plan budget_amount_minor; the string form supports up to 18 digits.
    - integer: A positive whole number of Meta minor units in the selected account currency, using the same unit scale as Launch plan budget_amount_minor.

## Response `201`

The frozen judging Test.

- AdTestCreateRead
  - `test` object, required
    - `id` string, required
    - `workspace_id` integer, required
    - `platform_ad_account_id` integer, required
    - `ad_set_id` string, required
    - `axis` 'hook' | 'angle' | 'visual' | 'offer' | 'format' | 'landing', required
    - `event_name` string, nullable, required: Legacy explicit event name; null for workspace-default mode.
    - `conversion_definition_version` 1 | 2, required
    - `conversion_events` string[], required: Immutable canonical choices. Version 1 exposes its original event choice.
    - `exclude_subscription_payments` boolean, required: Subscription-payment exclusion captured at creation; false for version 1.
    - `arrivals_floor` integer, required
    - `count_health_click_threshold` integer, nullable: Link-click threshold frozen at conclusion; null until concluded or for legacy Tests.
    - `minimum_conversions` integer, required
    - `maximum_days` integer, required
    - `maximum_spend_minor` string, nullable, required: Optional maximum in positive whole Meta minor units, using the same unit scale as Launch plan budget_amount_minor.
    - `anomaly_threshold_percent` string, required
    - `first_day` string, date, required
    - `timezone` string, required
    - `currency` string, required
    - `created_at` string, date-time, nullable, required
    - `members` object[], required
      - `ad_id` string, required
      - `position` integer, required
      - `baseline_host` string, nullable: Immutable normalized landing host once the member has a baseline.
      - `baseline_path` string, nullable: Immutable normalized landing path once the member has a baseline.
    - `pause_proposals` object[], required
      - `ad_id` string, required
      - `status` 'pending' | 'proposed' | 'skipped' | 'expired', required
      - `reason` 'already_paused' | 'deleted' | 'archived' | 'campaign_paused' | 'adset_paused' | 'decision_window_elapsed' | 'null', nullable, required: Stable reason code when the proposal was skipped or expired.
      - `decision_deadline_at` string, date-time, required: Immutable deadline 24 hours after the Test's candidates are created on the database clock.
    - `verdict` object, nullable, required
      - `outcome` 'winner' | 'no_clear_winner' | 'not_judged' | 'halted' | 'null', nullable, required
      - `winner_ad_id` string, nullable, required
      - `reason` string, nullable, required
      - `halt_details` object, nullable
        - `ad_id` string
        - `effective_status` 'DISAPPROVED' | 'WITH_ISSUES'
        - `date` string, date
        - `link_clicks` integer
        - `counted_arrivals` integer
        - `ad_test_member_id` string
        - `baseline` object
          - `host` string, required
          - `path` string, required
        - `observed_destinations` object[]
          - `host` string, required
          - `path` string, required
          - `counted_arrivals` integer, required
      - `window_start` string, date, nullable, required
      - `window_end` string, date, nullable, required
      - `concluded_at` string, date-time, nullable, required
      - `members` object[], required
        - `ad_id` string, required
        - `state` 'not_delivered' | 'insufficient' | 'ranked' | 'facts_unavailable' | 'halted', required
        - `rank` integer, nullable, required
        - `spend` string, nullable, required
        - `impressions` integer, nullable, required
        - `counted_arrivals` integer, nullable, required
        - `recorded_conversions` integer, nullable, required: PlainRouter recorded conversions under this Test’s frozen definition. Version 1 retains its original recorded-count semantics.
        - `admitted_conversions` integer, nullable, required: Admitted conversions are shown beside recorded conversions and are never substituted for them.
        - `spend_per_recorded_conversion` string, nullable, required: Major-unit spend divided by PlainRouter recorded conversions; used only to compare members within this Test.
        - `covered_days` string[], required
      - `daily_facts` object[], required
        - `date` string, date, required
        - `ad_test_member_id` string, required
        - `ad_id` string, required
        - `spend_minor` string, required: Exact spend in Meta minor units for the account currency, including any stored fractional component.
        - `spend` string, nullable, required: Account-currency spend in major units.
        - `impressions` integer, nullable, required
        - `inline_link_clicks` integer, nullable
        - `counted_arrivals` integer, nullable, required
        - `recorded_conversions` integer, nullable, required
        - `admitted_conversions` integer, nullable, required

## Other responses

- `401`: An active workspace principal is required; credential refusals include an error code and description.
- `403`: The workspace principal lacks a matching plan grant or account binding.
- `404`: The scoped workspace, account, or Test is unavailable, or the API route is not found.
- `422`: Request validation failed, the event is not in the observed list, or selected ads are not members of the ad set.
- `429`: The shared API rate limit was exceeded; Retry-After gives the seconds until another request is allowed.

---

[API](https://skmtc.dev/plainrouter/apis/plainrouter-conversion-api.md) · [All operations](https://skmtc.dev/plainrouter/apis/plainrouter-conversion-api/llms.txt) · [OpenAPI document](https://skmtc.dev/plainrouter/apis/plainrouter-conversion-api/revisions/d071d4f4f05c?raw)
