Create a Scheduled Campaign Draft
The Campaign API lets you programmatically create and manage campaigns in Optimove. This article covers how to create a scheduled campaign draft and retrieve it via API—giving you a flexible, code-driven approach to campaign building before you're ready to launch.
Why drafts?Drafts let you pre-configure campaigns without activating them. This is especially useful for teams running multiple similar campaigns—create a draft, adjust a few parameters per campaign (target group, start date, promotion), and queue everything up programmatically rather than clicking through the UI each time.
Base URL
https://api5.optimove.net/campaign-api
Overview
A scheduled campaign is sent at a planned date, optionally repeating on a recurrence (daily, weekly, or monthly). A campaign in the draft state is a work-in-progress and never executes until you activate it.
This guide covers:
- The required request shape for a minimum-viable scheduled draft
- How to add a recurrence so the campaign repeats
- How the API responds, including warnings and validation errors
Endpoint
POST /campaigns
POST /campaignsCreates a new scheduled campaign in draft state.
Required Headers
| Header | Value | Required |
|---|---|---|
Content-Type | application/json | Yes |
x-api-key | API key issued for your integration. It identifies your tenant, so no separate tenant header is required. | Yes |
Prerequisites
Before calling the endpoint, make sure you have the following set up in your Optimove tenant:
| Item | Description |
|---|---|
| Target group | An existing target group ID in your tenant. You will pass it as targetGroupId. |
| Start date | The first date the campaign will run, passed as startDate (YYYY-MM-DD). Required on every request. |
Quick Start
The smallest valid request body for a scheduled draft campaign is:
{
"state": "draft",
"campaignType": "scheduled",
"targetGroupId": 3,
"exclusionType": 0,
"startDate": "2026-06-01",
"controlPercentage": 10,
"isOptimized": false,
"reevaluateGroup": false,
"isRunAnyway": false,
"actions": [
{
"actionId": 22394,
"percentage": 90,
"isConditionalExecution": false,
"channels": [
{
"channelId": 15,
"brandInnerId": "AC09",
"templates": [
{
"templateId": "455297",
"isDefault": true,
"executionTime": {
"time": "10:00",
"timeZone": "UTC"
}
}
]
}
]
}
],
"tags": []
}And the equivalent curl call:
curl -X POST https://<your-host>/campaigns \
-H "Content-Type: application/json" \
-H "x-api-key: <your-api-key>" \
-d @scheduled-draft.jsonRequest Fields
The request body is a single JSON object that describes the campaign. Fields below apply to the scheduled campaign type.
Top-Level
| Field | Type | Required | Description |
|---|---|---|---|
state | string enum | Yes | Must be "draft" for this guide. Values: "draft", "active". |
campaignType | string enum | Yes | Must be "scheduled". |
targetGroupId | integer | Yes | ID of the target group the campaign will send to. |
exclusionType | integer enum | Yes | Controls how customers are excluded/included. Values: 0 = ExcludeAll, 1 = IncludeAll, 2 = ExcludeByChannel, 3 = IncludeByChannel. |
exclusionInclusionChannelIds | integer[] | Conditional | Required when exclusionType is 2 (ExcludeByChannel) or 3 (IncludeByChannel). List of channel IDs that drive the include/exclude rule. |
startDate | date (YYYY-MM-DD) | Yes | The first date the campaign would run. Must be in the future for a campaign that will be activated. |
recurrence | object | No | If absent or null, the campaign runs once on startDate. See Recurrence Patterns. |
actions | array<object> | No | Up to 4 actions. May be empty ([]) for a minimal draft; when populated, each action must follow the rules in Actions & Channels. |
controlPercentage | integer (0–100) | Yes | Percentage of the target group held out as a control group. The maximum allowed value is configurable per tenant. |
isOptimized | boolean | No | When true, Optimove distributes action percentages automatically. When true, a recurrence is required. |
reevaluateGroup | boolean | No | Re-evaluates the target group on every occurrence. |
isRunAnyway | boolean | No | When turned on, the campaign will be executed regardless of your site's batch-data process. |
leadTime | integer (days) | No | Number of days before startDate at which the audience snapshot is taken. |
duration | integer (days) | No | How many days a single occurrence remains active. Lock customers for a duration of day(s) to calculate uplift. |
kpiId | integer | No | The KPI used to measure campaign performance. |
note | string | No | Free-text note. Maximum 500 characters. |
tags | integer[] | No | Tag IDs to attach to the campaign. |
Action and control percentages must total 100
controlPercentageplus thepercentageof every action inactionsmust sum to exactly 100. If they don't, the request is rejected.
Recurrence Patterns
Omit recurrence (or send null) for a one-off campaign. To make the campaign repeat, attach a recurrence object.
Common Recurrence Fields
| Field | Type | Description |
|---|---|---|
type | string enum | "Daily", "Weekly", or "Monthly". |
interval | integer ≥ 1 | Every N days/weeks/months. 1 = every period; 2 = every other. |
endType | string enum | "Never", "AfterOccurrences", or "EndByDate". |
numberOfOccurrences | integer ≥ 1 | Required when endType is "AfterOccurrences". |
endDate | date (YYYY-MM-DD) | Required when endType is "EndByDate". Cannot be earlier than startDate. |
Daily — Example
{
"recurrence": {
"type": "Daily",
"interval": 1,
"endType": "AfterOccurrences",
"numberOfOccurrences": 10
}
}Weekly — Example
For weekly recurrence, at least one day must be selected in selectedDays. If startDate does not fall on a selected day, the API adjusts it forward to the first selected day on or after the requested date and returns a warning.
{
"recurrence": {
"type": "Weekly",
"interval": 2,
"endType": "AfterOccurrences",
"numberOfOccurrences": 20,
"selectedDays": [
{ "day": "Tuesday", "isSelected": true },
{ "day": "Thursday", "isSelected": true }
]
}
}Monthly — by Day of the Month
{
"recurrence": {
"type": "Monthly",
"interval": 1,
"endType": "AfterOccurrences",
"numberOfOccurrences": 6,
"monthlyPattern": "DayOfMonth",
"dayOfMonth": 10
}
}dayOfMonth must match the day component of startDate.
Monthly — by Day of the Week
{
"recurrence": {
"type": "Monthly",
"interval": 1,
"endType": "EndByDate",
"endDate": "2026-12-31",
"monthlyPattern": "DayOfWeek",
"dayOfWeekInMonth": {
"weekOfMonth": "Last",
"dayOfWeek": "Friday"
}
}
}weekOfMonth accepts "First", "Second", "Third", "Fourth", or "Last". The dayOfWeek must match the weekday of startDate. If startDate is on the 29th or later, weekOfMonth must be "Last".
Actions & Channels
actions is an array of 0–4 action objects. Each action represents a campaign variant: it targets a portion of the audience (via percentage) and is delivered through one or more channels.
Action Object
| Field | Type | Required | Description |
|---|---|---|---|
actionId | integer | Yes | ID of an existing action in your tenant. |
percentage | integer (0–100) | Yes | Share of the target group routed to this action. |
isConditionalExecution | boolean | No | Must match campaign execution settings value if enforced by admin. |
channels | array | No | Must be an array (may be []). Each channel must follow the rules in the Channel object below. |
Channel Object
| Field | Type | Required | Description |
|---|---|---|---|
channelId | integer | Yes | ID of the channel (email, push, SMS, etc.). |
Discover your channel structureChannel field requirements vary per channel type and per tenant. To discover the exact structure available to you, call
https://api5.optimove.net/channel-configuration/v2/channels/structure?campaignType=Scheduled—it returns OpenAPI 3.0 schemas for every channel in your tenant.
One
(channel, brand)pair per actionThe same combination of
channelIdandbrandInnerIdmay only appear once within a single action. For email-family channels, all uses of that channel across the campaign must share a common contact label.
Template Object
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string or integer | Yes | ID of the template to use. |
isDefault | boolean | Yes | Marks the default template for the channel. Exactly one default per channel. |
executionTime.time | string (HH:mm) | Yes | Time of day in 24-hour format. |
executionTime.timeZone | string (IANA) | Yes | For example "UTC" or "Europe/London". |
Successful Response
A successful call returns 201 Created with the full created campaign and an optional warnings list.
{
"isSuccess": true,
"data": {
"campaign": {
"id": "68f63f3d15d41b0012c6f486",
"displayId": "D12345",
"state": "draft",
"campaignType": "scheduled",
"startDate": "2026-06-01"
// ...all fields you sent, plus server-applied defaults
},
"warnings": null
}
}Identifiers in the Response
id—The draft's identifier (24-character hex string). Use this in subsequent Update, Get, Delete, and Activate callsdisplayId—A display-friendly identifier intended for UI use. Do not use it for API lookups
Warnings
Warnings are non-fatal: the campaign was created, but something was adjusted or skipped. Common warnings include client-supplied service-managed fields being dropped, or a default value being applied for an absent setting.
Service-managed fields
Do not set
id,externalId, ordisplayIdon creation—the service assigns them. If you send them, they will be dropped and the response will include a warning.
Errors
The endpoint maps failures to standard HTTP status codes.
| Response Code | Description |
|---|---|
201 | Created. The draft was stored successfully. The full campaign object is in data.campaign. |
400 | Malformed request. The JSON could not be parsed, or a required header (such as x-api-key) is missing. |
404 | Referenced entity not found. For example, a targetGroupId or actionId that does not exist in your tenant. |
422 | Validation failed. The request body is well-formed JSON but semantically invalid (e.g. action percentages don't total 100, weekly recurrence with no days selected, monthly pattern that doesn't match startDate). The response includes a per-field errors dictionary. |
503 | A dependent service is temporarily unavailable, or a general error occurred within the service. Retry the request after a short backoff. |
Validation Error Shape
{
"isSuccess": false,
"data": {
"errors": {
"controlPercentage": ["Action percentages and control percentage must sum to 100."],
"recurrence.endDate": ["EndDate cannot be before StartDate."]
}
},
"errorCode": "BUSINESS_RULE_VIOLATION"
}Draft Lifecycle
A draft is not executed until it is activated. After creating it, you can:
- Update it with
PUT /campaigns/{id}—drafts are always fully editable - Get it with
GET /campaigns/scheduled/{id} - Delete it with
DELETE /campaigns/scheduled/{id}—soft-deletes the draft
Use the draft'sidIn the calls above, the
{id}is the draft's identifier—use thedata.campaign.idvalue returned by the create response.
Need help? Contact your Optimove integration manager or open a support ticket and reference this guide.