---
title: "Create a draft experiment"
method: POST
path: "/api/v1/campaigns/{campaign_id}/experiments"
tags: ["experiments"]
---

# Create a draft experiment

`POST /api/v1/campaigns/{campaign_id}/experiments`

## Request

- Base URL: `https://api.outrival.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.outrival.com/api/v1/campaigns/{campaign_id}/experiments`
- Auth: HTTP bearer

Store a draft. Nothing serves and nobody is enrolled until it launches.

Group shares are proportions in 0..1 on the way in and come back the same
way, alongside the basis points the database keeps.

## Path parameters

- `campaign_id` string, required

## Request body

- CreateExperimentRequest: A new experiment, created as a draft. Nothing serves until it launches. Only the primary metric is checked here for meaning. The structural rules — at least one treatment group (the default is implicit, so treatment-only is allowed), at most one control, a non-control that changes something, shares that fit inside the traffic — belong to launch, because a draft is EDITED, and a draft that cannot be saved half-finished cannot be built.
  - `name` string, required: What this experiment is testing
  - `primary_metric` 'transfer' | 'success' | 'contact_rate' | 'opted_out' | 'not_interested' | 'post_transfer_follow_up', required
  - `groups` ExperimentGroupRequest[]: The groups to create with the draft. May be empty and filled in later; launch is where the group structure has to be complete
    - `name` string, required: What this group is called
    - `is_control` boolean: Whether this group is the baseline every other group is compared against. Exactly one group is the control, checked at launch
    - `allocation` number, required: Share of the campaign's traffic this group receives, as a PROPORTION IN 0..1 — 0.5 is half of the traffic, not half a percent. Never a percentage. The shares of all groups sum to at most 1; whatever is left is unallocated and stays outside the experiment
    - `pin_overrides` object: {component: version id} — what this group serves instead
  - `allocation` number, nullable: Optional. The experiment's total share of the campaign's traffic, as a PROPORTION IN 0..1. It is NOT stored — allocation lives on the groups — so supplying it only asks this endpoint to confirm that the group shares add up to what you meant, and the request is refused when they do not
  - `targeting_rules` TargetingCondition[]: Conditions a contact must meet to be eligible; empty targets everyone
    - `field` string, required: Contact field to compare
    - `operator` string, required: One of the nine operators the engine matches on
    - `value` string, required: What the field is compared against
  - `note` string, nullable: Free text stored with the experiment

## Response `201`

Successful Response

- object

## Other responses

- `400`: Invalid campaign ID format
- `403`: User may not edit this campaign
- `404`: Campaign not found
- `422`: A metric the registry does not carry, a missing or duplicated group name, a pin on a section that is not pinnable (the detail names it), or a stated allocation the group shares do not add up to

## Changes

- **2026-10-10** `15bec682217c`: 1 info
  - added the new `post_transfer_follow_up` enum value to the request property `primary_metric`
- **2026-10-10** `15bec682217c`: 1 info
  - added the new `post_transfer_follow_up` enum value to the request property `primary_metric`
- **2026-09-03** `219a10b80707`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/outrival/apis/fastapi/changes/api/v1/campaigns/:campaign_id/experiments/post.md)

---

[API](https://skmtc.dev/outrival/apis/fastapi.md) - [All operations](https://skmtc.dev/outrival/apis/fastapi/llms.txt) - [OpenAPI document](https://skmtc.dev/outrival/apis/fastapi/revisions/f27d90300b5e?raw)
