Skip to content

Campaign API Documentation

Manage email marketing campaigns through programmatic API access. Create, update, monitor, and control campaign lifecycles.

Cancel a Campaign

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaign.Update
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.cancel
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign to cancel
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.cancel",
    "SessionID": "your-session-id",
    "CampaignID": 12345
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing required parameter (CampaignID)
2: Campaign not found or doesn't belong to user

Copy a Campaign

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaign.Create
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.copy
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign to copy
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.copy",
    "SessionID": "your-session-id",
    "CampaignID": 12345
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "NewCampaignID": 12346
}
json
{
  "Success": false,
  "ErrorCode": [1],
  "ErrorText": ["Invalid source campaign"]
}
txt
0: Success
1: Invalid source campaign or campaign not found

Create a Campaign

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaign.Create
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.create
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignNameStringYesName of the new campaign
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.create",
    "SessionID": "your-session-id",
    "CampaignName": "Summer Sale 2025"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "CampaignID": 12345
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing required parameter (CampaignName)

Get Campaign Details

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key or Admin API Key
  • Required permissions: Campaign.Get
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign to retrieve
RetrieveStatisticsBooleanNoInclude campaign statistics (default: false)
StatisticsDaysIntegerNoTime-series window in days for the per-day statistics blocks (Open/Click/etc.), measured from the campaign send start. Default: 15. Clamped to 1–365.
RetrieveTagsBooleanNoInclude campaign tags (default: false)
SplitABTestStatisticsBooleanNoInclude A/B test statistics (default: false)
RetrieveRecipientDomainsBooleanNoInclude recipient domain statistics (default: true)
FailOnNotFoundBooleanNoReturn an error when the campaign does not exist or is not owned by the authenticated user (default: false). When omitted or false, a missing campaign is not an error: the response is Success: true with an empty Campaign array.
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.get",
    "SessionID": "your-session-id",
    "CampaignID": 12345,
    "RetrieveStatistics": true,
    "RetrieveRecipientDomains": true
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "Campaign": {
    "CampaignID": 12345,
    "CampaignName": "Summer Sale 2025",
    "CampaignStatus": "Sent",
    "TotalRecipients": 10000,
    "TotalSent": 9950,
    "TotalOpens": 3500,
    "UniqueOpens": 2100,
    "TotalClicks": 850,
    "UniqueClicks": 650
  },
  "CampaignThroughput": {
    "EmailsPerSecond": 125.5,
    "Duration": 79
  }
}
json
{
  "Success": false,
  "ErrorCode": 3
}
txt
0: Success
2: Invalid CampaignID (non-numeric)
3: Campaign not found or doesn't belong to user - only returned when FailOnNotFound=true

Campaign Not Found Is Not An Error By Default

Unless FailOnNotFound=true is passed, requesting a campaign that does not exist (or that belongs to another user) returns:

json
{
  "Success": true,
  "ErrorCode": 0,
  "Campaign": []
}

Callers that treat Success: true as "the campaign exists" must also check that Campaign is a non-empty object, or pass FailOnNotFound=true to get ErrorCode 3 instead.

ParentCampaign Is Absent When There Is No Parent

ParentCampaign is only added to the response when the campaign is an auto-resend and its parent campaign was found and is owned by the authenticated user. Otherwise the key is omitted entirely — it is not returned as null. Test with a key-existence check (isset() / array_key_exists() / 'ParentCampaign' in response.Campaign), not a null comparison.

Note that the sibling AutoResendCampaign key behaves differently: it is always present when applicable and returns an empty array ([]) when there is no auto-resend campaign, rather than being omitted.

:::

Pause a Campaign

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaign.Update
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.pause
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign to pause
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.pause",
    "SessionID": "your-session-id",
    "CampaignID": 12345
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": 3
}
txt
0: Success
1: Missing required parameter (CampaignID)
2: Campaign not found or doesn't belong to user
3: Invalid campaign status (campaign must be Sending or Ready to be paused)

Get Campaign Recipients

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key or Admin API Key
  • Required permissions: Campaign.Get
  • This endpoint only works for unsent campaigns (Draft, Ready, Pending Approval)
  • For sent campaigns, use campaign statistics or queue endpoints instead
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.recipients.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign
OnlyTotalBooleanNoReturn only total count without recipient details (default: false)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.recipients.get",
    "SessionID": "your-session-id",
    "CampaignID": 12345,
    "OnlyTotal": false
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "CampaignID": 12345,
  "TotalRecipients": 1500,
  "Recipients": [
    {
      "SubscriberID": 101,
      "EmailAddress": "user@example.com",
      "FirstName": "John",
      "LastName": "Doe"
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 4,
  "ErrorMessage": "This endpoint only works for unsent campaigns",
  "CampaignStatus": "Sent"
}
txt
0: Success
1: Missing required parameter (CampaignID)
2: Invalid CampaignID (must be numeric)
3: Campaign not found or access denied
4: Invalid campaign status (must be Draft, Ready, or Pending Approval)
5: Campaign targeting definition unusable - no RulesJsonBundle, no criteria
   defined, or a criterion carries an invalid list_id
6: A list referenced by the campaign's criteria is unavailable
10: Error retrieving campaign recipients (unexpected internal failure)

Changed in v5.9.3

Codes 5 and 6 are returned with HTTP 200 and Success: false, like every other rejection from this endpoint.

Two conditions that used to be reported as the catch-all code 10 are now reported distinctly: an invalid list_id in a criterion is code 5, and a referenced list that is unavailable is code 6. Previously both arrived as code 10 with the message prefixed by Error retrieving campaign recipients:, which made them indistinguishable from a genuine server-side fault.

Code 6 deliberately does not distinguish "the list does not exist" from "the list belongs to another account" — the response is byte-identical in both cases, so it cannot be used to probe for the existence of other accounts' lists.

A database failure during the list lookup stays in the generic code 10 bucket. A server fault is never reported to you as "you referenced a list that does not exist".

Resume a Campaign

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaign.Update
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.resume
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign to resume
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.resume",
    "SessionID": "your-session-id",
    "CampaignID": 12345
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": 3
}
txt
0: Success
1: Missing required parameter (CampaignID)
2: Campaign not found or doesn't belong to user
3: Campaign status is not Paused (only Paused campaigns can be resumed)

Update a Campaign

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaign.Update
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.update
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign to update
CampaignNameStringNoNew campaign name
CampaignStatusStringNoCampaign status (Draft, Ready, Sending, Paused, Pending Approval, Sent, Failed)
CampaignStatusReasonStringNoReason for status change
RelEmailIDIntegerNoID of the email content to use
ScheduleTypeStringNoSchedule type (Not Scheduled, Immediate, Future, Recursive)
SendDateStringNoSend date (YYYY-MM-DD format) - required if ScheduleType=Future. Must be a valid future date (not 0000-00-00)
SendTimeStringNoSend time (HH:MM:SS format) - required if ScheduleType=Future. Combined with SendDate and SendTimeZone, the scheduled datetime must be in the future
SendTimeZoneStringNoTimezone for scheduled send
ScheduleRecDaysOfWeekStringNoDays of week for recurring campaigns
ScheduleRecDaysOfMonthStringNoDays of month for recurring campaigns
ScheduleRecMonthsStringNoMonths for recurring campaigns - required if ScheduleType=Recursive
ScheduleRecHoursStringNoHours for recurring campaigns - required if ScheduleType=Recursive
ScheduleRecMinutesStringNoMinutes for recurring campaigns - required if ScheduleType=Recursive
ScheduleRecSendMaxInstanceIntegerNoMax instances for recurring campaigns - required if ScheduleType=Recursive
ApprovalUserExplanationStringNoUser explanation for approval
GoogleAnalyticsDomainsStringNoGoogle Analytics tracking domains
PublishOnRSSStringNoPublish on RSS (Enabled/Disabled)
AutoResendEnabledBooleanNoEnable auto-resend to non-openers
AutoResendWaitDaysIntegerNoDays to wait before auto-resend - required if AutoResendEnabled=true
AutoResendSubjectStringNoSubject line for auto-resend - required if AutoResendEnabled=true
AutoResendPreHeaderTextStringNoPre-header text for auto-resend. If omitted or empty, the auto-resend uses the original campaign's pre-header.
OriginalCampaignIDIntegerNoID of original campaign if this is a resend
RecipientListsAndSegmentsStringNoComma-separated list (format: ListID:SegmentID)
Exclude_RecipientListsAndSegmentsStringNoComma-separated exclusion list (format: ListID:SegmentID)
RulesJsonBundleStringNoJSON string with advanced recipient selection rules
S2SEnabledBooleanNoEnable server-to-server tracking
ABTestingObjectNoA/B testing configuration (see A/B Testing Parameters below)

A/B Testing Parameters:

When the ABTesting object is provided, the campaign is configured as an A/B split test. Each variation references a separate email (created via Email.Create + Email.Update) and is assigned a weight that determines the distribution percentage across recipients.

ParameterTypeRequiredDescription
ABTesting[Variations]ArrayYesArray of variation objects (minimum 2, maximum 5)
ABTesting[Variations][N][emailid]IntegerYesEmail ID for this variation (must belong to the authenticated user)
ABTesting[Variations][N][weight]IntegerYesRelative weight for distribution (must be > 0). Distribution percentages are calculated automatically from the weights. For example, two variations with weight 1 each results in 50%/50% distribution. Weights of 1, 1, 2 result in 25%/25%/50%

Important Notes

  • When A/B testing is enabled, the campaign's RelEmailID is automatically set to 0 — do not pass a RelEmailID alongside ABTesting.
  • Each variation must have a distribution of at least 1%.
  • To disable A/B testing on a campaign, pass an empty ABTesting parameter.
  • The system randomly distributes recipients across variations during queue generation, so each subscriber receives exactly one variation.

Untrusted Accounts Are Held At Pending Approval

When the campaign's owner has a ReputationLevel of Untrusted, a DraftReady transition is rewritten to Pending Approval and an approval notification is sent to the administrators. The campaign is not queued for sending until an administrator approves it.

The API still returns Success: true in this case — the response does not indicate that the status was changed from the one requested. Callers must not assume the requested CampaignStatus took effect; re-read the campaign with campaign.get to confirm the resulting status.

bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.update",
    "SessionID": "your-session-id",
    "CampaignID": 12345,
    "CampaignName": "Updated Summer Sale",
    "CampaignStatus": "Ready",
    "ScheduleType": "Future",
    "SendDate": "2025-06-15",
    "SendTime": "10:00:00",
    "SendTimeZone": "America/New_York"
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": 6
}
txt
0: Success
1: Missing required parameter (CampaignID)
2: Campaign not found or doesn't belong to user
3: Invalid campaign status
4: Email not found or doesn't belong to user
5: Invalid schedule type
6: Missing or invalid SendDate for Future schedule (empty, null, or 0000-00-00)
7: Missing SendTime for Future schedule
8: Missing ScheduleRecDaysOfWeek or ScheduleRecDaysOfMonth for Recursive schedule
9: Missing ScheduleRecMonths for Recursive schedule
10: Missing ScheduleRecHours for Recursive schedule
11: Missing ScheduleRecMinutes for Recursive schedule
12: Missing ScheduleRecSendMaxInstance for Recursive schedule
14: Missing or invalid AutoResendWaitDays when AutoResendEnabled=true
15: Missing AutoResendSubject when AutoResendEnabled=true
17: Invalid ABTesting parameter format (must be array)
18: Invalid ABTesting variations format (must be array)
19: Email variation not found or doesn't belong to user
21: Minimum 2 A/B test variations required
22: Maximum 5 A/B test variations allowed
23: Missing required fields in A/B variation (EmailID, Weight)
24: Invalid EmailID or Weight in A/B variation
25: A/B variation distribution percentage below 1%
26: Auto-resend cannot be used with repeating (recursive) campaigns
27: Scheduled send date and time is in the past

Get Campaign Archive URL

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaigns.Get
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaigns.archive.geturl
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
TagIDIntegerYesID of the tag for archive URL
TemplateURLStringNoCustom template URL for archive
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaigns.archive.geturl",
    "SessionID": "your-session-id",
    "TagID": 5,
    "TemplateURL": "https://example.com/archive-template"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "URL": "https://example.com/archive/tag/5"
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (TagID)
2: Tag not found or doesn't belong to user

Delete Campaigns

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaign.Delete
  • This endpoint also deletes associated auto-resend campaigns automatically
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaigns.delete
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignsStringYesComma-separated list of campaign IDs to delete
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaigns.delete",
    "SessionID": "your-session-id",
    "Campaigns": "12345,12346,12347"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": ""
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing required parameter (Campaigns)

Get Campaigns List

POST /api.php (legacy)

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaigns.Get
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaigns.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignStatusStringNoFilter by status (Draft, Ready, Scheduled, Sending, Sent, Paused, Failed, All)
ScheduleTypeString/ArrayNoFilter by schedule type (Not Scheduled, Immediate, Future, Recursive)
SearchKeywordStringNoSearch campaigns by name or email subject (LIKE query)
FilterByUserIDIntegerNoIgnored (v5.9.3, #2309). campaigns.get is a user-authenticated endpoint and the listing is always scoped to the authenticated user's own campaigns; any value sent here is overridden with the caller's own user ID. Retained only for backward compatibility.
CampaignIDsString/ArrayNoFilter by specific campaign IDs (comma-separated or array)
Date_FromStringNoStart date for filtering (YYYY-MM-DD format)
Date_ToStringNoEnd date for filtering (YYYY-MM-DD format)
OrderFieldStringNoField to sort by (CampaignName, SendDate, etc.)
OrderTypeStringNoSort direction (ASC or DESC)
RecordsPerRequestIntegerNoNumber of records per page (0 for all, default: 0)
RecordsFromIntegerNoOffset for pagination (default: 0)
CountOnlyBooleanNoReturn only TotalCampaigns with an empty Campaigns array (default: false). Skips the full result-set fetch and all per-row enrichment; takes precedence over RecordsPerRequest. Use for cheap count/badge calls.
RetrieveStatisticsBooleanNoInclude campaign statistics (default: true)
RetrieveTagsBooleanNoInclude campaign tags (default: false)
TagsStringNoComma-separated tag IDs to filter by
SplitABTestStatisticsBooleanNoInclude A/B split test statistics (default: false)
ExcludeColumnsArrayNoColumn names to exclude from SELECT for performance
Include_AutoResendBooleanNoInclude auto-resend campaigns (default: false)
IncludeTotalRecipientsBooleanNoInclude aggregate sums over the filtered window: TotalRecipients, TotalSent, TotalDelivered, TotalFailed, TotalOpens, UniqueOpens, TotalClicks, UniqueClicks, TotalHardBounces, TotalSoftBounces, TotalUnsubscriptions (default: false)
RetrieveSetupMetaBooleanNoOpt-in draft setup metadata for the Campaigns "Draft" tab readiness checklist (default: false). When absent the response is unchanged. When true, each row in Campaigns[] also carries the extra fields listed below. Adds one bulk email query for the page; the main listing query is unchanged.

When RetrieveSetupMeta is true, each campaign object additionally includes:

FieldTypeDescription
EmailSubjectStringSubject of the linked email (via RelEmailID); "" when no email is linked. For an A/B draft (RelEmailID == 0) this is the first variation's subject.
HasHTMLContentBooleanWhether the linked email (or, for A/B, any variation) has a non-empty HTML body. Presence only — the body itself is never returned.
HasPlainContentBooleanWhether the linked email (or, for A/B, any variation) has a non-empty plain-text body. Presence only.
AudienceListCountIntegerNumber of distinct recipient lists in the audience. Derived from RulesJsonBundle, falling back to the legacy campaign_recipients data for legacy drafts.
AudienceSegmentCountIntegerNumber of segments in the audience (criteria with an inline rules filter for modern drafts; rows with a segment ID for legacy drafts).
HasAudienceBooleanConvenience flag: AudienceListCount + AudienceSegmentCount > 0.
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaigns.get",
    "SessionID": "your-session-id",
    "CampaignStatus": "Sent",
    "Date_From": "2025-01-01",
    "Date_To": "2025-12-31",
    "RecordsPerRequest": 50,
    "RecordsFrom": 0,
    "OrderField": "SendDate",
    "OrderType": "DESC",
    "RetrieveStatistics": true
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "Campaigns": [
    {
      "CampaignID": 12345,
      "CampaignName": "Summer Sale 2025",
      "CampaignStatus": "Sent",
      "SendDate": "2025-06-15",
      "TotalRecipients": 10000,
      "TotalSent": 9950,
      "TotalOpens": 3500,
      "UniqueOpens": 2100
    }
  ],
  "TotalCampaigns": 125
}
json
{
  "Success": false,
  "ErrorCode": 0
}
txt
0: Success

Get Campaign Status Counts

POST /api.php (legacy)

Returns per-status campaign counts in a single GROUP BY query — intended for the Campaign Browse sidebar so it can render all status buckets in one round-trip instead of one campaigns.get call per status. Owner-scoped to the authenticated user.

The response contains two views of the same data: Counts (raw CampaignStatus totals) and Buckets (the sidebar partition, where the single Ready status splits by ScheduleTypeOutbox = Sending + Ready/Immediate, Scheduled = Ready/Future|Recursive, Draft = Draft + Ready/Not Scheduled). Counts.Total always equals Buckets.Total.

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaigns.Get
  • Legacy endpoint access via /api.php

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaigns.counts
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
TagsStringNoCSV of tag IDs to filter by
date_fromStringNoStart date (Y-m-d); narrows by SendDate (uniform window across statuses)
date_toStringNoEnd date (Y-m-d)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaigns.counts",
    "APIKey": "your-api-key",
    "date_from": "2026-04-01",
    "date_to": "2026-04-30"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "Counts": {
    "Draft": 5,
    "Ready": 3,
    "Sending": 0,
    "Paused": 2,
    "Pending Approval": 2,
    "Sent": 14,
    "Failed": 1,
    "Total": 27
  },
  "Buckets": {
    "Sent": 14,
    "Outbox": 1,
    "Draft": 6,
    "Scheduled": 1,
    "Paused": 2,
    "Pending Approval": 2,
    "Failed": 1,
    "Total": 27
  }
}
json
{
  "success": "false",
  "errors": { "code": 99999, "message": "Not enough privileges" }
}
txt
0: Success

Get Tags List

POST /api.php (legacy)

API Usage Notes

  • Authentication required: User API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: tags.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "tags.get",
    "SessionID": "your-session-id"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "TotalTagCount": 5,
  "Tags": [
    {
      "TagID": 1,
      "Tag": "newsletter",
      "RelOwnerUserID": 123
    },
    {
      "TagID": 2,
      "Tag": "promotion",
      "RelOwnerUserID": 123
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 0
}
txt
0: Success

Create a Tag

POST /api.php (legacy)

API Usage Notes

  • Authentication required: User API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: tag.create
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
TagStringYesTag name. Allowed characters: letters, numbers, spaces, hyphens, underscores, percent signs (%). Leading and trailing whitespace is trimmed automatically.
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "tag.create",
    "SessionID": "your-session-id",
    "Tag": "summer-campaign"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "TagID": 15
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing required parameter (Tag)
2: Tag already exists in the system
3: Invalid tag format (only letters, numbers, spaces, hyphens and underscores allowed, and percent signs)
4: Tag cannot be empty after trimming whitespace

Update a Tag

POST /api.php (legacy)

API Usage Notes

  • Authentication required: User API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: tag.update
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
TagIDIntegerYesID of the tag to update
TagStringYesNew tag name. Allowed characters: letters, numbers, spaces, hyphens, underscores, percent signs (%). Leading and trailing whitespace is trimmed automatically.
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "tag.update",
    "SessionID": "your-session-id",
    "TagID": 15,
    "Tag": "summer-promotion"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": ""
}
json
{
  "Success": false,
  "ErrorCode": [1, 2]
}
txt
0: Success
1: Missing required parameter (TagID)
2: Missing required parameter (Tag)
3: Invalid tag format (only letters, numbers, spaces, hyphens and underscores allowed, and percent signs)
4: Tag cannot be empty after trimming whitespace

Delete Tags

POST /api.php (legacy)

API Usage Notes

  • Authentication required: User API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: tags.delete
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
TagsStringYesComma-separated list of tag IDs to delete
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "tags.delete",
    "SessionID": "your-session-id",
    "Tags": "15,16,17"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": ""
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing required parameter (Tags)

Assign Tag to Campaigns

POST /api.php (legacy)

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: Campaigns.Get
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: tag.assigntocampaigns
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
TagIDIntegerYesID of the tag to assign
CampaignIDsStringYesComma-separated list of campaign IDs
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "tag.assigntocampaigns",
    "SessionID": "your-session-id",
    "TagID": 15,
    "CampaignIDs": "100,101,102"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": ""
}
json
{
  "Success": false,
  "ErrorCode": [1, 2]
}
txt
0: Success
1: Missing required parameter (TagID)
2: Missing required parameter (CampaignIDs)

Unassign Tag from Campaigns

POST /api.php (legacy)

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: Campaigns.Get
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: tag.unassignfromcampaigns
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
TagIDIntegerYesID of the tag to unassign
CampaignIDsStringYesComma-separated list of campaign IDs
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "tag.unassignfromcampaigns",
    "SessionID": "your-session-id",
    "TagID": 15,
    "CampaignIDs": "100,101,102"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": ""
}
json
{
  "Success": false,
  "ErrorCode": [1, 2]
}
txt
0: Success
1: Missing required parameter (TagID)
2: Missing required parameter (CampaignIDs)

Create an A/B Split Test Campaign

A/B split test campaigns allow you to send different email variations to segments of your audience and compare performance. The system randomly distributes recipients across variations based on configurable weights.

How A/B Split Testing Works

  1. Create a campaign using Campaign.Create
  2. Create email variations — one Email.Create + Email.Update call per variation (minimum 2, maximum 5)
  3. Configure the campaign using Campaign.Update with ABTesting[Variations], audience rules, and schedule
  4. The system handles the rest — during delivery, recipients are randomly assigned to variations based on distribution weights

When the campaign is sent, each recipient receives exactly one email variation. Statistics (opens, clicks, conversions, unsubscriptions, revenue) are tracked per variation, allowing you to compare performance.

Step-by-Step Example

bash
# Create the campaign shell
curl -X POST https://example.com/api.php \
  -d 'ResponseFormat=JSON' \
  -d 'Command=Campaign.Create' \
  -d 'APIKey=your-api-key' \
  -d 'CampaignName=A/B Test: Subject Line Comparison'

# Response: {"Success": true, "ErrorCode": 0, "CampaignID": 200}
bash
# --- Variation A ---
# Create the first email
curl -X POST https://example.com/api.php \
  -d 'ResponseFormat=JSON' \
  -d 'Command=Email.Create' \
  -d 'APIKey=your-api-key'

# Response: {"Success": true, "ErrorCode": 0, "EmailID": 301}

# Set the content for Variation A
curl -X POST https://example.com/api.php \
  -d 'ResponseFormat=JSON' \
  -d 'Command=Email.Update' \
  -d 'APIKey=your-api-key' \
  -d 'EmailID=301' \
  -d 'ValidateScope=Campaign' \
  -d 'EmailName=Variation A - Discount Subject' \
  -d 'Subject=Save 50% Today Only!' \
  -d 'FromName=My Store' \
  -d 'FromEmail=deals@mystore.com' \
  -d 'ReplyToName=My Store' \
  -d 'ReplyToEmail=deals@mystore.com' \
  -d 'Mode=Editor' \
  -d 'HTMLContent=<html><body><h1>Half Price Sale!</h1><p>...</p><p><a href="%Link:Unsubscribe%">Unsubscribe</a></p></body></html>'

# --- Variation B ---
# Create the second email
curl -X POST https://example.com/api.php \
  -d 'ResponseFormat=JSON' \
  -d 'Command=Email.Create' \
  -d 'APIKey=your-api-key'

# Response: {"Success": true, "ErrorCode": 0, "EmailID": 302}

# Set the content for Variation B
curl -X POST https://example.com/api.php \
  -d 'ResponseFormat=JSON' \
  -d 'Command=Email.Update' \
  -d 'APIKey=your-api-key' \
  -d 'EmailID=302' \
  -d 'ValidateScope=Campaign' \
  -d 'EmailName=Variation B - Urgency Subject' \
  -d 'Subject=Last Chance: Sale Ends at Midnight' \
  -d 'FromName=My Store' \
  -d 'FromEmail=deals@mystore.com' \
  -d 'ReplyToName=My Store' \
  -d 'ReplyToEmail=deals@mystore.com' \
  -d 'Mode=Editor' \
  -d 'HTMLContent=<html><body><h1>Sale Ending Soon!</h1><p>...</p><p><a href="%Link:Unsubscribe%">Unsubscribe</a></p></body></html>'
bash
# Update the campaign with A/B testing, audience, and schedule
curl -X POST https://example.com/api.php \
  -d 'ResponseFormat=JSON' \
  -d 'Command=Campaign.Update' \
  -d 'APIKey=your-api-key' \
  -d 'CampaignID=200' \
  -d 'CampaignStatus=Ready' \
  -d 'ScheduleType=Immediate' \
  -d 'RulesJsonBundle={"operator":"and","criteria":[{"list_id":1,"operator":"and","rules":[[{"type":"fields","field_id":"EmailAddress","operator":"is_not_empty","value":""}]]}]}' \
  -d 'ABTesting[Variations][0][emailid]=301' \
  -d 'ABTesting[Variations][0][weight]=1' \
  -d 'ABTesting[Variations][1][emailid]=302' \
  -d 'ABTesting[Variations][1][weight]=1'

# Response: {"Success": true, "ErrorCode": 0}
# With equal weights of 1, each variation receives ~50% of recipients.
bash
# Retrieve campaign with A/B test statistics
curl -X POST https://example.com/api.php \
  -d 'ResponseFormat=JSON' \
  -d 'Command=Campaign.Get' \
  -d 'APIKey=your-api-key' \
  -d 'CampaignID=200' \
  -d 'RetrieveStatistics=true' \
  -d 'SplitABTestStatistics=true'

# The response includes per-variation stats inside Campaign.Options.ABTesting.Variations:
# Each variation object includes: EmailID, Weight, Distribution,
# TotalRecipient, TotalSent, TotalFailed, UniqueOpens, UniqueClicks,
# TotalRevenue, TotalUnsubscriptions

Unequal Weight Distribution

You can assign different weights to send more traffic to a preferred variation:

bash
# 75% to Variation A, 25% to Variation B
-d 'ABTesting[Variations][0][emailid]=301'
-d 'ABTesting[Variations][0][weight]=3'
-d 'ABTesting[Variations][1][emailid]=302'
-d 'ABTesting[Variations][1][weight]=1'

# Three variations: 50% / 25% / 25%
-d 'ABTesting[Variations][0][emailid]=301'
-d 'ABTesting[Variations][0][weight]=2'
-d 'ABTesting[Variations][1][emailid]=302'
-d 'ABTesting[Variations][1][weight]=1'
-d 'ABTesting[Variations][2][emailid]=303'
-d 'ABTesting[Variations][2][weight]=1'

Retrieving A/B Test Statistics

When retrieving a campaign with SplitABTestStatistics=true, the response includes per-variation performance metrics:

json
{
  "Success": true,
  "Campaign": {
    "CampaignID": 200,
    "CampaignName": "A/B Test: Subject Line Comparison",
    "CampaignStatus": "Sent",
    "RelEmailID": 0,
    "Options": {
      "ABTesting": {
        "Variations": [
          {
            "EmailID": 301,
            "Weight": 1,
            "Distribution": 50,
            "TotalRecipient": 5000,
            "TotalSent": 4980,
            "TotalFailed": 20,
            "UniqueOpens": 1200,
            "UniqueClicks": 350,
            "TotalRevenue": 15000,
            "TotalUnsubscriptions": 5
          },
          {
            "EmailID": 302,
            "Weight": 1,
            "Distribution": 50,
            "TotalRecipient": 5000,
            "TotalSent": 4975,
            "TotalFailed": 25,
            "UniqueOpens": 1450,
            "UniqueClicks": 420,
            "TotalRevenue": 18500,
            "TotalUnsubscriptions": 3
          }
        ]
      }
    },
    "OpenStatistics": { ... },
    "ClickStatistics": { ... }
  }
}

When SplitABTestStatistics=true, the OpenStatistics, ClickStatistics, ConversionStatistics, and UnsubscriptionStatistics objects also include per-variation breakdowns grouped by RelEmailID.

Disabling A/B Testing

To remove A/B testing from a campaign and revert to a single email, pass an empty ABTesting parameter and set RelEmailID:

bash
curl -X POST https://example.com/api.php \
  -d 'ResponseFormat=JSON' \
  -d 'Command=Campaign.Update' \
  -d 'APIKey=your-api-key' \
  -d 'CampaignID=200' \
  -d 'ABTesting=' \
  -d 'RelEmailID=301'

Copying an A/B Test Campaign

Use Campaign.Copy to duplicate an A/B test campaign. All email variations are automatically duplicated with new Email IDs, and the A/B testing configuration is preserved in the copy.


Create a Split Test (Legacy)

Legacy API

This endpoint uses the older split test system where a test portion of recipients receive different email variations, the campaign pauses to wait for results, then the winning variation is sent to the remaining audience. For the modern A/B testing approach where all recipients are distributed across variations simultaneously, see Create an A/B Split Test Campaign above.

POST /api.php (legacy)

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: Campaign.Create
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: splittest.create
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign to create split test for
TestSizeIntegerYesPercentage of recipients to include in test (e.g., 20 for 20%)
TestDurationIntegerYesDuration in seconds to wait before selecting the winner
WinnerStringYesWinner criteria: Highest Open Rate or Most Unique Clicks
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "splittest.create",
    "SessionID": "your-session-id",
    "CampaignID": 12345,
    "TestSize": 20,
    "TestDuration": 86400,
    "Winner": "Highest Open Rate"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "SplitTestID": 567
}
json
{
  "Success": false,
  "ErrorCode": [1, 2]
}
txt
0: Success
1: Missing required parameter (CampaignID)
2: Missing required parameter (TestSize)
4: Missing required parameter (TestDuration)
5: Missing required parameter (Winner)
6: Campaign not found or doesn't belong to user

Retry Failed Recipients

POST /api/v1/admin.campaign.retryfailed

API Usage Notes

  • Authentication required: Admin API Key
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Retries failed recipients for campaigns with status "Sent" or "Failed". This endpoint resets failed queue entries to Pending, creates new delivery batches, sets the campaign to "Sending" status, and pushes it to RabbitMQ for delivery workers to process.

Important: This endpoint bypasses the campaign picker and handles batch creation + RabbitMQ publishing directly. This ensures only the original failed recipients are retried without re-inserting subscribers who joined target lists after the original send.

If delivery cannot be dispatched

The database work — resetting failed recipients to Pending, creating batches, moving the campaign to Sending — is committed before the campaign is published to the message queue. The publish is retried up to three times.

If it still fails, for example while the message queue service is unreachable, the committed work is deliberately not rolled back: the recipients stay re-queued and the campaign stays in Sending. The response reports ErrorCode 6 with an ErrorText saying exactly that, and naming the recovery action.

Because the campaign then has pending batches with no worker processing them, it is reported as stuck. Resume delivery with Unstuck a Stuck Campaign (admin.campaign.unstuck). Calling admin.campaign.retryfailed again will not help — it rejects campaigns already in Sending status with ErrorCode 3.

Changed in v5.9.3 — a failed COMMIT is now reported

The transaction's COMMIT result was not inspected. A failed commit fell through to the message-queue publish and the endpoint could return Success: true for work that never became durable — and a delivery worker consuming that message would find the campaign without its new pending batches.

A failed commit is now caught and returned as ErrorCode 6 with the "Database error" wording. Conversely, a message-queue dispatch failure after a successful commit is no longer misreported as a database error accompanied by a no-op ROLLBACK; it gets its own explicit ErrorText, as described above.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: admin.campaign.retryfailed
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesID of the campaign to retry failed recipients for
bash
curl -X POST https://example.com/api/v1/admin.campaign.retryfailed \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "admin.campaign.retryfailed",
    "APIKey": "your-admin-api-key",
    "CampaignID": 4069
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "RetriedCount": 150,
  "BatchesCreated": 1,
  "Message": "150 failed recipients queued for retry in 1 batch(es)."
}
json
{
  "Success": false,
  "ErrorCode": 3,
  "ErrorText": "Campaign is not in Sent or Failed status. Only completed or failed campaigns can have their failed recipients retried."
}
json
{
  "Success": false,
  "ErrorCode": 6,
  "ErrorText": "150 failed recipients were re-queued and the campaign is now in Sending status, but the delivery queue could not be notified after 3 attempts, so no delivery worker has started. Nothing was rolled back. Once the message queue service is reachable again, resume delivery with the campaign unstuck action (admin.campaign.unstuck). Message queue error: ..."
}
txt
0: Success
1: campaign_id parameter is required
2: Campaign not found
3: Campaign is not in Sent or Failed status
4: Queue table does not exist for this campaign
5: No failed recipients found for this campaign
6: Database error during retry operation, or the retry was committed but the campaign
   could not be dispatched to the delivery queue. The ErrorText distinguishes the two.

Export Campaigns to CSV

POST /api/v1/campaigns.export

Enqueues a background job that exports the campaigns list (past sends plus engagement aggregates) to a CSV file. Accepts the same filter shape as Get Campaigns List, so the export matches the equivalent campaigns.get query row-for-row (subject to the selected column allow-list). Returns a JobID to poll with Get Campaign Export Status.

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaigns.Get
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaigns.export.post
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
ExportFormatStringNoOutput format. Possible values: csv. (json is reserved for a future release and currently rejected.) Defaults to csv.
FieldsToExportArrayNoColumns to include, from the allow-list (see below). Defaults to a standard column set when omitted.
CampaignStatusStringNoFilter by status (Draft, Ready, Scheduled, Sending, Sent, Paused, Failed, All)
ScheduleTypeString/ArrayNoFilter by schedule type (Not Scheduled, Immediate, Future, Recursive)
SearchKeywordStringNoSearch campaigns by name or email subject (LIKE query)
CampaignIDsString/ArrayNoFilter by specific campaign IDs (comma-separated or array)
TagsStringNoComma-separated tag IDs to filter by
Date_FromStringNoStart date for filtering (YYYY-MM-DD format)
Date_ToStringNoEnd date for filtering (YYYY-MM-DD format)
FilterByUserIDIntegerNoFilter by account/user ID (admin only)
Include_AutoResendBooleanNoInclude auto-resend campaigns (default: false)
SplitABTestStatisticsBooleanNoEmit one row per A/B variation under each parent campaign (default: false)

Allowed FieldsToExport values: CampaignID, CampaignName, EmailSubject, CampaignStatus, ScheduleType, SendDate, SendTime, CreateDateTime, SendProcessStartedOn, SendProcessFinishedOn, TotalRecipients, TotalSent, TotalFailed, TotalDelivered, TotalOpens, UniqueOpens, TotalClicks, UniqueClicks, TotalConversions, UniqueConversions, TotalForwards, UniqueForwards, TotalViewsOnBrowser, UniqueViewsOnBrowser, TotalUnsubscriptions, TotalHardBounces, TotalSoftBounces, TotalRevenue, RecipientLists, RecipientSegments.

bash
curl -X POST https://example.com/api/v1/campaigns.export \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaigns.export.post",
    "SessionID": "your-session-id",
    "CampaignStatus": "Sent",
    "Tags": "5,12",
    "Date_From": "2026-04-01",
    "Date_To": "2026-04-30",
    "ExportFormat": "csv",
    "FieldsToExport": ["CampaignID", "CampaignName", "EmailSubject", "SendProcessFinishedOn", "TotalRecipients", "TotalSent", "TotalDelivered", "UniqueOpens", "UniqueClicks", "TotalHardBounces", "TotalSoftBounces", "TotalUnsubscriptions"],
    "SplitABTestStatistics": false,
    "Include_AutoResend": true
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "JobID": 8421,
  "ExportID": 8421,
  "EstimatedRows": 173
}
json
{
  "Errors": [
    { "Code": 1, "Message": "Invalid ExportFormat (only \"csv\" is currently supported)" }
  ]
}
txt
0: Success
1: Invalid ExportFormat (only "csv" is currently supported)
2: FieldsToExport must be a non-empty array
3: FieldsToExport contains unknown field(s)

Get Campaign Export Status

GET /api/v1/campaigns.export

Polls the status of a campaign export job created by Export Campaigns to CSV, or streams the produced file. When Download=1 and the job is finished, the endpoint streams the CSV directly with explicit Content-Type: text/csv and Content-Disposition: attachment headers (it does not return JSON in that case).

API Usage Notes

  • Authentication is done by User API Key
  • Required permissions: Campaigns.Get
  • Rate limit: 100 requests per 60 seconds
  • A job can only be accessed by its owner, and only as a CampaignExport job (a SubscriberExport/JourneyExport ID owned by the same user returns "not found")
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaigns.export.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
JobIDIntegerYesThe export job ID returned by campaigns.export.post (alias: ExportID)
DownloadBooleanNoWhen 1 and Status=Finished, streams the CSV file instead of returning the JSON envelope
bash
# Poll status
curl -X GET "https://example.com/api/v1/campaigns.export?Command=campaigns.export.get&SessionID=your-session-id&JobID=8421"

# Download the finished file
curl -X GET "https://example.com/api/v1/campaigns.export?Command=campaigns.export.get&SessionID=your-session-id&JobID=8421&Download=1" -o campaigns.csv
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "JobID": 8421,
  "Status": "Finished",
  "Progress": 100,
  "RowCount": 173,
  "DownloadURL": "api.php?Command=campaigns.export.get&jobid=8421&download=1",
  "DownloadSize": 28456,
  "ExpiresAt": "2026-05-07 15:37:58"
}
json
{
  "Errors": [
    { "Code": 5, "Message": "Export job is not completed yet" }
  ]
}
txt
0: Success
1: Missing JobID parameter
2: Invalid JobID parameter (not a positive integer)
3: Export job not found (wrong owner or never existed)
4: Export job not found (job exists but is not a CampaignExport)
5: Download requested but the job is not finished yet
POST /api.php

API Usage Notes

  • Authentication required: User API Key (or Admin API Key)
  • Required permissions: Campaign.Get
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Returns the per-link click ranking ("most clicked links") of a sent campaign, or the per-subscriber click breakdown ("who clicked"), depending on GroupBy. Results are ordered by click count (descending) and paginated. Automated/bot clicks are excluded (IsAutomated=0), so the numbers match the bundled campaign report. Access is owner-scoped; admins (via Admin API Key) may read any campaign.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.linkclicks.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesThe campaign to report on
GroupByStringNoGrouping mode. Possible values: Links (default), Subscribers
RecordsFromIntegerNoPagination offset (default: 0)
RecordsPerRequestIntegerNoPage size (default: 25, hard cap: 1000)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.linkclicks.get",
    "APIKey": "your-api-key",
    "CampaignID": 7524,
    "GroupBy": "Links",
    "RecordsPerRequest": 25
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "CampaignID": 7524,
  "GroupBy": "Links",
  "TotalRecords": 12,
  "Links": [
    {
      "LinkTitle": "Read more",
      "LinkURL": "https://example.com/landing",
      "TotalClicks": 2771
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 3,
  "ErrorMessage": "Campaign not found or access denied"
}
txt
0: Success
2: CampaignID is not numeric
3: Campaign not found or access denied

When GroupBy is Subscribers, the response returns a Subscribers array instead of Links, where each row is { "SubscriberID": 62362, "ListID": 47, "EmailAddress": "user@example.com", "TotalClicks": 3 }. Seed-list recipients (SubscriberID 0) are returned with an empty EmailAddress.

Get Campaign Recipients Activity

POST /api.php

API Usage Notes

  • Authentication required: User API Key (or Admin API Key)
  • Required permissions: Campaign.Get
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

For a sent campaign, returns the per-subscriber engagement breakdown for a single activity type ("who opened", "who clicked", "who bounced", etc.), read from the MySQL oempro_stats_* tables. Results are grouped per subscriber (one row per subscriber/list), ordered by most recent activity, and paginated. This is the sent-campaign counterpart to campaign.recipients.get, which only previews the audience of unsent campaigns. Automated/bot opens and clicks are excluded (IsAutomated=0). Access is owner-scoped.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.recipients.activity.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesThe campaign to report on
ActivityStringYesActivity type. Possible values: open, click, bounce, unsubscription, forward, conversion
SearchKeywordStringNoCase-insensitive substring filter on the recipient's email address (LIKE %keyword%), applied before pagination so TotalRecords reflects the filtered count. Empty/absent = no filter. EmailAddress is accepted as an alias.
RecordsFromIntegerNoPagination offset (default: 0)
RecordsPerRequestIntegerNoPage size (default: 25, hard cap: 1000)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.recipients.activity.get",
    "APIKey": "your-api-key",
    "CampaignID": 7524,
    "Activity": "click",
    "RecordsPerRequest": 25
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "CampaignID": 7524,
  "Activity": "click",
  "TotalRecords": 1488,
  "Recipients": [
    {
      "SubscriberID": 62362,
      "ListID": 47,
      "ListName": "Buyers List",
      "EmailAddress": "user@example.com",
      "ActivityCount": 2,
      "LastActivityDate": "2026-05-28 00:33:36"
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 4,
  "ErrorMessage": "Invalid activity. Allowed: open, click, bounce, unsubscription, forward, conversion"
}
txt
0: Success
2: CampaignID is not numeric
3: Campaign not found or access denied
4: Invalid activity type

For bounce, each recipient row additionally includes a BounceType field (Hard or Soft). For conversion, each row additionally includes TotalRevenue (decimal string; stored internally as integer cents).

Each row carries ListName (the subscriber's list name) alongside ListID. When SearchKeyword (or its EmailAddress alias) is supplied, the email match is performed server-side across every list the campaign targeted, before pagination — so TotalRecords is the filtered total and RecordsFrom/RecordsPerRequest page through the matches. Seed-list recipients (SubscriberID 0) have no email address and are therefore excluded from search results.

Get Campaign A/B Test & Auto-Resend Uplift

POST /api.php

API Usage Notes

  • Authentication required: User API Key (or Admin API Key)
  • Required permissions: Campaign.Get
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Exposes the per-variation A/B (MVT) statistics and the auto-resend uplift metrics that the bundled campaign report assembles. Per-variation metrics exclude seed-list recipients so the numbers reflect the weighted audience split. The response always contains ABTest, AutoResend, and ParentCampaign keys; AutoResend and ParentCampaign are null when not applicable, and ABTest.IsABTest is false for non-A/B campaigns. Access is owner-scoped.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: campaign.abtest.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
CampaignIDIntegerYesThe campaign to report on
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "campaign.abtest.get",
    "APIKey": "your-api-key",
    "CampaignID": 7524
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "CampaignID": 7524,
  "ABTest": {
    "IsABTest": true,
    "Variations": [
      {
        "EmailID": 9001,
        "EmailName": "Variation A",
        "Subject": "Subject A",
        "FromName": "Acme",
        "FromEmail": "news@acme.com",
        "ContentType": "HTML",
        "TotalRecipients": 5000,
        "TotalSent": 4980,
        "TotalFailed": 20,
        "UniqueOpens": 1200,
        "UniqueClicks": 300,
        "Unsubscriptions": 5,
        "Revenue": "120.00",
        "OpenRate": 24.1,
        "ClickRate": 6,
        "CTOR": 25
      }
    ],
    "Winner": {
      "EmailID": 9001,
      "EmailName": "Variation A"
    }
  },
  "AutoResend": {
    "Status": "completed",
    "CampaignID": 7600,
    "CampaignName": "Resend to non-openers",
    "TotalSent": 3000,
    "UniqueOpens": 400,
    "UniqueClicks": 90,
    "OpenRate": 13.3,
    "ClickRate": 3,
    "OpenUplift": 33.3,
    "ClickUplift": 30
  },
  "ParentCampaign": null
}
json
{
  "Success": false,
  "ErrorCode": 3,
  "ErrorMessage": "Campaign not found or access denied"
}
txt
0: Success
2: CampaignID is not numeric
3: Campaign not found or access denied

AutoResend.Status is completed when the auto-resend child campaign has been created (uplift fields are populated relative to this parent), or scheduled when auto-resend is enabled but not yet sent (in which case it carries WaitDays, Subject, and PreHeaderText). When the requested campaign is itself an auto-resend child, ParentCampaign is populated with the parent's headline metrics and CTOR.

Any questions? Contact us.