Dry-run a Campaign
POST /v1/campaigns/{campaign_id}/dry-run
| Property | Value |
|---|---|
| Operation | dry_run_campaign |
| Group | Campaigns |
| Scope | campaigns:write |
| Route class | control |
| Idempotency | Idempotency-Key accepted: a retry with the same key replays the first answer |
| Audit | Not audited: A dry run: it writes nothing and answers counts and codes, so no Audit event (ADR 0012). |
| Released in | 1.65.0 |
| Phase 0 host | Served (the hosted api pins 1.65.0) |
Answers what the Campaign would do now (1.65.0; section 9): the enrollments eligible and ineligible by code, the arm split, the next window opening, an estimated duration, the disposition of each sample attempt under the draft's or the revision's table, and the issues a launch would refuse with. It writes nothing and holds counts and codes only.
Request
Set VOCAL_API to the base URL of your API and VOCAL_API_KEY to an API key with the campaigns:write scope (Authentication and keys). Replace each placeholder, such as agt_... or <name>, with your own value.
curl -X POST "$VOCAL_API/v1/campaigns/cmp_.../dry-run" \
-H "Authorization: Bearer $VOCAL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{}'const response = await fetch(`${process.env.VOCAL_API}/v1/campaigns/cmp_.../dry-run`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VOCAL_API_KEY}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({}),
});
console.log(response.status, await response.text());import os
import uuid
import requests
response = requests.post(
f"{os.environ['VOCAL_API']}/v1/campaigns/cmp_.../dry-run",
headers={
"Authorization": f"Bearer {os.environ['VOCAL_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={},
)
print(response.status_code, response.text)Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
campaign_id | path | CampaignId | yes | The id of a Campaign: cmp_ and the row's UUIDv7 in 26 Crockford base32 characters. Opaque and safe to log. |
idempotency-key | header | string | no | A client-chosen key, kept 24 hours per Organization, environment and route. A replay returns the stored response. |
Request body
Content type application/json, required: CampaignDryRunRequest.
| Field | Type | Required | Description |
|---|---|---|---|
samples | array of CampaignDryRunSampleInput | no |
Responses
| Status | Content type | Shape | Error codes |
|---|---|---|---|
200 | application/json | CampaignDryRun | |
400 | application/json | ErrorEnvelope | invalid_request |
401 | application/json | ErrorEnvelope | unauthenticated |
403 | application/json | ErrorEnvelope | forbidden, org_suspended |
404 | application/json | ErrorEnvelope | not_found |
422 | application/json | ErrorEnvelope | validation_failed |
429 | application/json | ErrorEnvelope | rate_limited |
500 | application/json | ErrorEnvelope | internal_error |
Fields of the 200 answer
The shape is CampaignDryRun.
| Field | Type | Required | Description |
|---|---|---|---|
arm_split | map of integer | yes | |
campaign_id | CampaignId | yes | |
dispositions | array of CampaignDisposition | yes | The disposition of each sample, in order. |
eligible | integer | yes | |
estimated_duration_s | integer or null | yes | An estimate from pacing and windows, never a measurement. |
ineligible | map of integer | yes | The enrollments that would be ineligible, by code. |
launch_issues | array of CampaignIssueCode | yes | What a launch now would refuse with; empty when it would pass. |
next_window_opens_at | Timestamp or null | yes | |
object | "campaign_dry_run" | yes | |
revision | integer or null | yes | The revision judged; null for a draft. |