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

Creates a new scheduled campaign in draft state.

Required Headers

HeaderValueRequired
Content-Typeapplication/jsonYes
x-api-keyAPI 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:

ItemDescription
Target groupAn existing target group ID in your tenant. You will pass it as targetGroupId.
Start dateThe 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.json

Request Fields

The request body is a single JSON object that describes the campaign. Fields below apply to the scheduled campaign type.

Top-Level

FieldTypeRequiredDescription
statestring enumYesMust be "draft" for this guide. Values: "draft", "active".
campaignTypestring enumYesMust be "scheduled".
targetGroupIdintegerYesID of the target group the campaign will send to.
exclusionTypeinteger enumYesControls how customers are excluded/included. Values: 0 = ExcludeAll, 1 = IncludeAll, 2 = ExcludeByChannel, 3 = IncludeByChannel.
exclusionInclusionChannelIdsinteger[]ConditionalRequired when exclusionType is 2 (ExcludeByChannel) or 3 (IncludeByChannel). List of channel IDs that drive the include/exclude rule.
startDatedate (YYYY-MM-DD)YesThe first date the campaign would run. Must be in the future for a campaign that will be activated.
recurrenceobjectNoIf absent or null, the campaign runs once on startDate. See Recurrence Patterns.
actionsarray<object>NoUp to 4 actions. May be empty ([]) for a minimal draft; when populated, each action must follow the rules in Actions & Channels.
controlPercentageinteger (0–100)YesPercentage of the target group held out as a control group. The maximum allowed value is configurable per tenant.
isOptimizedbooleanNoWhen true, Optimove distributes action percentages automatically. When true, a recurrence is required.
reevaluateGroupbooleanNoRe-evaluates the target group on every occurrence.
isRunAnywaybooleanNoWhen turned on, the campaign will be executed regardless of your site's batch-data process.
leadTimeinteger (days)NoNumber of days before startDate at which the audience snapshot is taken.
durationinteger (days)NoHow many days a single occurrence remains active. Lock customers for a duration of day(s) to calculate uplift.
kpiIdintegerNoThe KPI used to measure campaign performance.
notestringNoFree-text note. Maximum 500 characters.
tagsinteger[]NoTag IDs to attach to the campaign.
📘

Action and control percentages must total 100

controlPercentage plus the percentage of every action in actions must 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

FieldTypeDescription
typestring enum"Daily", "Weekly", or "Monthly".
intervalinteger ≥ 1Every N days/weeks/months. 1 = every period; 2 = every other.
endTypestring enum"Never", "AfterOccurrences", or "EndByDate".
numberOfOccurrencesinteger ≥ 1Required when endType is "AfterOccurrences".
endDatedate (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

FieldTypeRequiredDescription
actionIdintegerYesID of an existing action in your tenant.
percentageinteger (0–100)YesShare of the target group routed to this action.
isConditionalExecutionbooleanNoMust match campaign execution settings value if enforced by admin.
channelsarrayNoMust be an array (may be []). Each channel must follow the rules in the Channel object below.

Channel Object

FieldTypeRequiredDescription
channelIdintegerYesID of the channel (email, push, SMS, etc.).
📘

Discover your channel structure

Channel 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 action

The same combination of channelId and brandInnerId may 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

FieldTypeRequiredDescription
templateIdstring or integerYesID of the template to use.
isDefaultbooleanYesMarks the default template for the channel. Exactly one default per channel.
executionTime.timestring (HH:mm)YesTime of day in 24-hour format.
executionTime.timeZonestring (IANA)YesFor 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 calls
  • displayId—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, or displayId on 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 CodeDescription
201Created. The draft was stored successfully. The full campaign object is in data.campaign.
400Malformed request. The JSON could not be parsed, or a required header (such as x-api-key) is missing.
404Referenced entity not found. For example, a targetGroupId or actionId that does not exist in your tenant.
422Validation 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.
503A 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's id

In the calls above, the {id} is the draft's identifier—use the data.campaign.id value returned by the create response.


Need help? Contact your Optimove integration manager or open a support ticket and reference this guide.