Choose an A/B test's winnerMenu

Choose an A/B test's winner

POST/v1/campaigns/{id}/ab-test/winner

Requires an API key with the campaigns:send scope. About keys and scopes

Works with integration and partner keys, not agent keys.

Safe to retry: a repeat returns the current state, or 409 once it has moved on from it. About idempotency

Picks the winning version of a campaign in testing and sends it to the rest of the audience at once, instead of waiting for the automatic pick (or when the test has none, decideAfter: null). The campaign turns sending, then sent. Asking again for the version already picked returns 202 and queues its send again, so a retry is safe; another version once one is picked, or a campaign not in testing, is refused with 409 CONFLICT. Agent keys are refused with 403: this sends.

Path parameters

idstringrequired

The campaign's id.

uuid

Header parameters

Kirora-Workspacestringoptional

Partner keys only: the child workspace to act in, by the external id the partner gave it. Without it, a partner key acts in its own workspace. Any other key that sends it is refused with 403, and an unknown workspace is 404.

pattern ^[A-Za-z0-9._:-]{1,128}$

Idempotency-Keystringoptional

Makes a retry safe: a repeat with the same key and body within 24 hours gets the first response again, with Idempotency-Replay: true. The same key with another request is 409 IDEMPOTENCY_KEY_REUSED; while the first is still running, 409 IDEMPOTENCY_KEY_IN_USE; after an unsafe first request failed or never returned, 409 IDEMPOTENCY_KEY_OUTCOME_UNKNOWN. Use a new key, such as a UUID, for each new request.

8–255 characters

Request body

versionintegerrequired

The version the rest of the audience gets.

min 1 · max 4

Returns

Returns 202 Accepted with a Campaign in data. It can also return 409 Conflict, 413 Payload Too Large or 415 Unsupported Media Type; see the statuses below. Otherwise returns an error; Errors lists every code and which to retry.

  • 202 AcceptedThe campaign with its winner; the rest of the audience is being queued.
  • 409 ConflictThe request conflicts with current state — a duplicate unique value, or a resource whose status does not allow this operation.
  • 413 Payload Too LargeThe body is larger than this operation accepts (PAYLOAD_TOO_LARGE).
  • 415 Unsupported Media TypeThe body is not JSON, or not the upload the operation takes (UNSUPPORTED_MEDIA_TYPE).
idstringrequired

uuid

namestringrequired
channelsstring[]required

The channels it sends on.

allContactsbooleanrequired

Whether it goes to all contacts: everyone its channels reach, with consent and suppressions applied as on any send.

listIdsstring[] | nullrequired

The lists it goes to, if any: a member of any of them receives it once.

segmentIdstring | nullrequired

The segment it goes to, if any.

uuid

audienceNamestring | nullrequired

The names of the lists or the segment it went to, once one of them has been deleted (the list leaves listIds, or segmentId becomes null). Only a sent or cancelled campaign can lose its audience this way.

audienceRulesobject | nullrequired

Inline rules choosing the audience, if any. At most one audience is set.

Any of 2 shapes
Match all
1 child attribute
allobject[]required

Every rule or nested group must match.

at least 1 item · at most 20 items

Any of 10 shapes
Attribute
5 child attributes
type"attribute"required
fieldstringrequired

attributes.<key>, e.g. attributes.plan.

pattern ^attributes\.[\p{L}\p{N}\p{M}_.-]{1,64}$

operatorstringrequired

How to compare. Text comparisons (equals, notEquals, contains, notContains, startsWith, in) ignore case and accents ("jose" matches "José"). notEquals and notContains also match contacts without the value. Date operators (before, after, withinLastDays, moreThanDaysAgo) compare as dates and times in UTC: a stored ISO-8601 date is the start of that day, before means earlier than the given day and after later than it, and a value that is not an ISO-8601 date or date and time never matches. inNextDays is a date from today to that many days ahead. anniversaryInNextDays (0 = today) and anniversaryThisMonth ignore the year, so they suit birthdays, and also read a yearless --MM-DD; a 29 February counts on 28 February in other years. Days are UTC days; these three are for attributes and dates, not contact fields.

one of equals, notEquals, contains, notContains, startsWith, exists, notExists, before, after, gt, gte, lt, lte, in, withinLastDays, moreThanDaysAgo, inNextDays, anniversaryInNextDays, anniversaryThisMonth

valuestringoptional

What to compare with, always as text: "pro", "3", "true". Equality compares the stored value as text, so a stored 5 matches "5". An ISO date for before and after; a number for gt, gte, lt and lte; a whole number of days (1 to 3650) for withinLastDays, moreThanDaysAgo and inNextDays, and 0 to 365 for anniversaryInNextDays. Omit for in, exists, notExists and anniversaryThisMonth.

up to 255 characters

valuesstring[]optional

For in only: up to 100 values, each as text, any of which matches.

at least 1 item · at most 100 items

Contact field
5 child attributes
type"contact"required
fieldstringrequired

The contact column to test. Each takes these operators, and is refused with others: email: equals, notEquals, contains, startsWith, exists, notExists, in; firstName: equals, notEquals, contains, startsWith, exists, notExists, in; lastName: equals, notEquals, contains, startsWith, exists, notExists, in; phone: equals, notEquals, contains, startsWith, exists, notExists, in; timezone: equals, notEquals, contains, startsWith, exists, notExists, in; externalId: equals, notEquals, contains, startsWith, exists, notExists, in; consent.email: equals; consent.push: equals; country: equals, notEquals, in, exists, notExists; locale: equals, notEquals, startsWith, in, exists, notExists; rfmGroup: equals, notEquals, in, exists, notExists; rfmRecency: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; rfmFrequency: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; rfmMonetary: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; purchaseCount: equals, gt, gte, lt, lte; totalSpent: equals, gt, gte, lt, lte; lastPurchasedAt: before, after, withinLastDays, moreThanDaysAgo, exists, notExists; createdAt: before, after, withinLastDays, moreThanDaysAgo. phone holds +country numbers: write equals, notEquals and in values with their country code. Consent takes "true" or "false". Purchases: purchaseCount, totalSpent (in the main currency) and lastPurchasedAt are a customer's totals of the workspace's purchase event (purchaseEvent in workspace settings: an order, a paid invoice, a booking), recomputed daily; the contact's stored totalSpent is null when the workspace's purchases carry no amount. In comparisons on purchaseCount and totalSpent a missing count or spend is 0, so someone who never purchased has 0 purchases and 0 spent. RFM: rfmGroup and its scores rfmRecency, rfmFrequency and rfmMonetary (monetary value: how much they spend) come from the same daily pass. A score is a whole number from 1 to 5, written as text ("5"; values of them for in), 5 being the most recent, most frequent or highest spending; any other value is refused with 400. Someone who never purchased has no rfmGroup or scores: on those four fields they match only notExists and notEquals. So rfmRecency notEquals 5 includes everyone who never purchased; add an exists rule on the same field to leave them out (bought, but not recently).

one of email, firstName, lastName, phone, timezone, externalId, consent.email, consent.push, country, locale, rfmGroup, rfmRecency, rfmFrequency, rfmMonetary, purchaseCount, totalSpent, lastPurchasedAt, createdAt

operatorstringrequired

How to compare. Text comparisons (equals, notEquals, contains, notContains, startsWith, in) ignore case and accents ("jose" matches "José"). notEquals and notContains also match contacts without the value. Date operators (before, after, withinLastDays, moreThanDaysAgo) compare as dates and times in UTC: a stored ISO-8601 date is the start of that day, before means earlier than the given day and after later than it, and a value that is not an ISO-8601 date or date and time never matches. inNextDays is a date from today to that many days ahead. anniversaryInNextDays (0 = today) and anniversaryThisMonth ignore the year, so they suit birthdays, and also read a yearless --MM-DD; a 29 February counts on 28 February in other years. Days are UTC days; these three are for attributes and dates, not contact fields.

one of equals, notEquals, contains, notContains, startsWith, exists, notExists, before, after, gt, gte, lt, lte, in, withinLastDays, moreThanDaysAgo, inNextDays, anniversaryInNextDays, anniversaryThisMonth

valuestringoptional

What to compare with, always as text: "pro", "3", "true". Equality compares the stored value as text, so a stored 5 matches "5". An ISO date for before and after; a number for gt, gte, lt and lte; a whole number of days (1 to 3650) for withinLastDays, moreThanDaysAgo and inNextDays, and 0 to 365 for anniversaryInNextDays. Omit for in, exists, notExists and anniversaryThisMonth.

up to 255 characters

valuesstring[]optional

For in only: up to 100 values, each as text, any of which matches.

at least 1 item · at most 100 items

Event
6 child attributes
type"event"required
namestringrequired

The event name.

1–128 characters

withinDaysintegeroptional

Only events from the last withinDays days.

min > 0 · max 3650

notFollowedBystringoptional

Only when this event name did not follow it afterwards.

1–128 characters

countobjectoptional

How often; defaults to at least once. { operator: equals, value: 0 } is "has not done".

whereobject[]optional

Property conditions the event must meet, up to 5; count counts only events meeting all of them.

at least 1 item · at most 5 items

Message activity
9 child attributes
type"activity"required
activitystringrequired

notOpened and notClicked: sent, and not opened or clicked. Opens include machine opens (see openedByMachine).

one of sent, notSent, opened, clicked, notOpened, notClicked, bounced

countobjectoptional

How many messages had this activity; defaults to at least one. Not with notSent.

linkstringoptional

With clicked or notClicked: only clicks on a link whose address contains this text, ignoring case.

1–500 characters

channelstringoptional

Only messages on this channel (opened a push, not an email); any when left out.

one of email, push

campaignIdstringoptional

Only messages of this campaign.

uuid

tagIdstringoptional

Only messages of campaigns or journeys carrying this tag, as they are tagged when the rule is evaluated.

uuid

journeyStepIdstringoptional

Inside a journey only: the message sent by the step with this id in the contact's current enrollment. Matches nobody outside a journey.

pattern ^[a-z0-9][a-z0-9_-]{0,63}$

withinDaysintegeroptional

Only activity from the last withinDays days. For notOpened and notClicked it is a waiting time instead: only messages sent at least this many days ago count, so each has had that long to be opened or clicked.

min > 0 · max 3650

Push installation
4 child attributes
type"installation"required
hasstringrequired

active: reachable by push (granted, active, not invalidated); none: not.

one of active, none

pushProjectIdstringoptional

Only devices of this push project; any when omitted.

uuid

platformstringoptional

Only devices on this platform; any when omitted.

one of ios, android, web

List membership
3 child attributes
type"list"required
listIdsstring[]required

One list or several: with member: true in any of them, with false in none of them. Each must be one of the workspace's lists when the rules are saved.

at least 1 item · at most 50 items

memberbooleanrequired

True for in the lists, false for not in them.

Segment membership
3 child attributes
type"segment"required
segmentIdstringrequired

uuid

memberbooleanrequired

True for in the segment, false for not in it.

Topic
4 child attributes
type"topic"required
topicIdstringrequired

One of the workspace's topics when the rules are saved.

uuid

channelstringoptional

Reads the contact's choice for this channel, which overrides their topic-wide choice. Without it, the rule reads the topic-wide choice, which a contact's subscribed shows.

one of email, push

subscribedbooleanrequired

True for contacts the topic reaches, false for those it does not.

Match all
1 child attribute
allobject[]required

Every rule must match.

at least 1 item · at most 20 items

Match any
1 child attribute
anyobject[]required

At least one rule must match.

at least 1 item · at most 20 items

Match any
1 child attribute
anyobject[]required

At least one rule or nested group must match.

at least 1 item · at most 20 items

Any of 10 shapes
Attribute
5 child attributes
type"attribute"required
fieldstringrequired

attributes.<key>, e.g. attributes.plan.

pattern ^attributes\.[\p{L}\p{N}\p{M}_.-]{1,64}$

operatorstringrequired

How to compare. Text comparisons (equals, notEquals, contains, notContains, startsWith, in) ignore case and accents ("jose" matches "José"). notEquals and notContains also match contacts without the value. Date operators (before, after, withinLastDays, moreThanDaysAgo) compare as dates and times in UTC: a stored ISO-8601 date is the start of that day, before means earlier than the given day and after later than it, and a value that is not an ISO-8601 date or date and time never matches. inNextDays is a date from today to that many days ahead. anniversaryInNextDays (0 = today) and anniversaryThisMonth ignore the year, so they suit birthdays, and also read a yearless --MM-DD; a 29 February counts on 28 February in other years. Days are UTC days; these three are for attributes and dates, not contact fields.

one of equals, notEquals, contains, notContains, startsWith, exists, notExists, before, after, gt, gte, lt, lte, in, withinLastDays, moreThanDaysAgo, inNextDays, anniversaryInNextDays, anniversaryThisMonth

valuestringoptional

What to compare with, always as text: "pro", "3", "true". Equality compares the stored value as text, so a stored 5 matches "5". An ISO date for before and after; a number for gt, gte, lt and lte; a whole number of days (1 to 3650) for withinLastDays, moreThanDaysAgo and inNextDays, and 0 to 365 for anniversaryInNextDays. Omit for in, exists, notExists and anniversaryThisMonth.

up to 255 characters

valuesstring[]optional

For in only: up to 100 values, each as text, any of which matches.

at least 1 item · at most 100 items

Contact field
5 child attributes
type"contact"required
fieldstringrequired

The contact column to test. Each takes these operators, and is refused with others: email: equals, notEquals, contains, startsWith, exists, notExists, in; firstName: equals, notEquals, contains, startsWith, exists, notExists, in; lastName: equals, notEquals, contains, startsWith, exists, notExists, in; phone: equals, notEquals, contains, startsWith, exists, notExists, in; timezone: equals, notEquals, contains, startsWith, exists, notExists, in; externalId: equals, notEquals, contains, startsWith, exists, notExists, in; consent.email: equals; consent.push: equals; country: equals, notEquals, in, exists, notExists; locale: equals, notEquals, startsWith, in, exists, notExists; rfmGroup: equals, notEquals, in, exists, notExists; rfmRecency: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; rfmFrequency: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; rfmMonetary: equals, notEquals, in, exists, notExists, gt, gte, lt, lte; purchaseCount: equals, gt, gte, lt, lte; totalSpent: equals, gt, gte, lt, lte; lastPurchasedAt: before, after, withinLastDays, moreThanDaysAgo, exists, notExists; createdAt: before, after, withinLastDays, moreThanDaysAgo. phone holds +country numbers: write equals, notEquals and in values with their country code. Consent takes "true" or "false". Purchases: purchaseCount, totalSpent (in the main currency) and lastPurchasedAt are a customer's totals of the workspace's purchase event (purchaseEvent in workspace settings: an order, a paid invoice, a booking), recomputed daily; the contact's stored totalSpent is null when the workspace's purchases carry no amount. In comparisons on purchaseCount and totalSpent a missing count or spend is 0, so someone who never purchased has 0 purchases and 0 spent. RFM: rfmGroup and its scores rfmRecency, rfmFrequency and rfmMonetary (monetary value: how much they spend) come from the same daily pass. A score is a whole number from 1 to 5, written as text ("5"; values of them for in), 5 being the most recent, most frequent or highest spending; any other value is refused with 400. Someone who never purchased has no rfmGroup or scores: on those four fields they match only notExists and notEquals. So rfmRecency notEquals 5 includes everyone who never purchased; add an exists rule on the same field to leave them out (bought, but not recently).

one of email, firstName, lastName, phone, timezone, externalId, consent.email, consent.push, country, locale, rfmGroup, rfmRecency, rfmFrequency, rfmMonetary, purchaseCount, totalSpent, lastPurchasedAt, createdAt

operatorstringrequired

How to compare. Text comparisons (equals, notEquals, contains, notContains, startsWith, in) ignore case and accents ("jose" matches "José"). notEquals and notContains also match contacts without the value. Date operators (before, after, withinLastDays, moreThanDaysAgo) compare as dates and times in UTC: a stored ISO-8601 date is the start of that day, before means earlier than the given day and after later than it, and a value that is not an ISO-8601 date or date and time never matches. inNextDays is a date from today to that many days ahead. anniversaryInNextDays (0 = today) and anniversaryThisMonth ignore the year, so they suit birthdays, and also read a yearless --MM-DD; a 29 February counts on 28 February in other years. Days are UTC days; these three are for attributes and dates, not contact fields.

one of equals, notEquals, contains, notContains, startsWith, exists, notExists, before, after, gt, gte, lt, lte, in, withinLastDays, moreThanDaysAgo, inNextDays, anniversaryInNextDays, anniversaryThisMonth

valuestringoptional

What to compare with, always as text: "pro", "3", "true". Equality compares the stored value as text, so a stored 5 matches "5". An ISO date for before and after; a number for gt, gte, lt and lte; a whole number of days (1 to 3650) for withinLastDays, moreThanDaysAgo and inNextDays, and 0 to 365 for anniversaryInNextDays. Omit for in, exists, notExists and anniversaryThisMonth.

up to 255 characters

valuesstring[]optional

For in only: up to 100 values, each as text, any of which matches.

at least 1 item · at most 100 items

Event
6 child attributes
type"event"required
namestringrequired

The event name.

1–128 characters

withinDaysintegeroptional

Only events from the last withinDays days.

min > 0 · max 3650

notFollowedBystringoptional

Only when this event name did not follow it afterwards.

1–128 characters

countobjectoptional

How often; defaults to at least once. { operator: equals, value: 0 } is "has not done".

whereobject[]optional

Property conditions the event must meet, up to 5; count counts only events meeting all of them.

at least 1 item · at most 5 items

Message activity
9 child attributes
type"activity"required
activitystringrequired

notOpened and notClicked: sent, and not opened or clicked. Opens include machine opens (see openedByMachine).

one of sent, notSent, opened, clicked, notOpened, notClicked, bounced

countobjectoptional

How many messages had this activity; defaults to at least one. Not with notSent.

linkstringoptional

With clicked or notClicked: only clicks on a link whose address contains this text, ignoring case.

1–500 characters

channelstringoptional

Only messages on this channel (opened a push, not an email); any when left out.

one of email, push

campaignIdstringoptional

Only messages of this campaign.

uuid

tagIdstringoptional

Only messages of campaigns or journeys carrying this tag, as they are tagged when the rule is evaluated.

uuid

journeyStepIdstringoptional

Inside a journey only: the message sent by the step with this id in the contact's current enrollment. Matches nobody outside a journey.

pattern ^[a-z0-9][a-z0-9_-]{0,63}$

withinDaysintegeroptional

Only activity from the last withinDays days. For notOpened and notClicked it is a waiting time instead: only messages sent at least this many days ago count, so each has had that long to be opened or clicked.

min > 0 · max 3650

Push installation
4 child attributes
type"installation"required
hasstringrequired

active: reachable by push (granted, active, not invalidated); none: not.

one of active, none

pushProjectIdstringoptional

Only devices of this push project; any when omitted.

uuid

platformstringoptional

Only devices on this platform; any when omitted.

one of ios, android, web

List membership
3 child attributes
type"list"required
listIdsstring[]required

One list or several: with member: true in any of them, with false in none of them. Each must be one of the workspace's lists when the rules are saved.

at least 1 item · at most 50 items

memberbooleanrequired

True for in the lists, false for not in them.

Segment membership
3 child attributes
type"segment"required
segmentIdstringrequired

uuid

memberbooleanrequired

True for in the segment, false for not in it.

Topic
4 child attributes
type"topic"required
topicIdstringrequired

One of the workspace's topics when the rules are saved.

uuid

channelstringoptional

Reads the contact's choice for this channel, which overrides their topic-wide choice. Without it, the rule reads the topic-wide choice, which a contact's subscribed shows.

one of email, push

subscribedbooleanrequired

True for contacts the topic reaches, false for those it does not.

Match all
1 child attribute
allobject[]required

Every rule must match.

at least 1 item · at most 20 items

Match any
1 child attribute
anyobject[]required

At least one rule must match.

at least 1 item · at most 20 items

exclusionsobject | nullrequired

Lists and segments left out of its audience ("don't send to"); null for none.

2 child attributes
listIdsstring[]required

Lists whose members are left out.

at most 20 items · default []

segmentIdsstring[]required

Segments whose members are left out, by the segment's rules at send.

at most 10 items · default []

topicIdstring | nullrequired

Sends only to contacts the topic reaches; null sends to everyone with consent.

uuid

messageLimitsMessageLimitUserequired
2 child attributes
applybooleanrequired

Whether its messages count toward the workspace's message limits and are held by them.

retryForDurationoptional

How long a message over a limit keeps being retried before it is skipped with reason message_limit: whole hours, at most 48. Unset, it is skipped at once. Absent when apply is false.

2 child attributes
valueintegerrequired

min > 0

unitstringrequired

one of seconds, minutes, hours, days

utmobject | nullrequired

UTM parameters added to the links in the email.

6 child attributes
enabledbooleanrequired
sourcestringoptional

pattern ^[\p{L}\p{N}\p{M} ._\-+/]+$ · 1–100 characters

mediumstringoptional

pattern ^[\p{L}\p{N}\p{M} ._\-+/]+$ · 1–100 characters

campaignstringoptional

pattern ^[\p{L}\p{N}\p{M} ._\-+/]+$ · 1–100 characters

contentstringoptional

pattern ^[\p{L}\p{N}\p{M} ._\-+/]+$ · 1–100 characters

termstringoptional

pattern ^[\p{L}\p{N}\p{M} ._\-+/]+$ · 1–100 characters

contentCampaignContentrequired
2 child attributes
emailobjectoptional

The email fields and design; any may be missing on a draft.

7 child attributes
subjectstringoptional

The subject line. Merge tags are allowed.

1–998 characters

previewTextstringoptional

Shown by inboxes after the subject. Merge tags are allowed; keep it near 100 characters.

1–255 characters

htmlstringoptional

The HTML body. Rendered from the design when one is set.

1–512,000 characters

textstringoptional

The plain-text alternative.

1–512,000 characters

fromstringoptional

The sender address, on a verified sending domain.

1–320 characters

replyTostringoptional

Where replies go; the part before the @ must be ASCII.

email

templateSnapshotobjectoptional

The email design or HTML, copied into the campaign so later template edits do not change it.

One of 2 shapes
With schemaVersion, blocks and theme
6 child attributes
kind"design"optional
schemaVersionintegerrequired

min > 0 · max 1000

blocksobject[]required

The blocks, rendered top to bottom: at most 200 across the whole tree and five levels including the root. The footer stays at the root.

at least 1 item · at most 200 items

themeEmailThemerequired
languagestringoptional

The language the email is written in, as a BCP 47 tag ("en", "fr-BE", "ar"). It sets the HTML lang and the reading direction: Arabic, Hebrew, Persian, Urdu and other right-to-left languages render right to left. Defaults to "en".

plainTextOverridestring | nulloptional

Plain text sent instead of the version generated from the design or HTML.

up to 512,000 characters

With kind and html
3 child attributes
kind"html"required
htmlstringrequired

up to 512,000 characters

plainTextOverridestring | nulloptional

Plain text sent instead of the version generated from the design or HTML.

up to 512,000 characters

pushobjectoptional

The notification content.

14 child attributes
titlestringoptional

The notification title (up to 100 characters).

1–100 characters

bodystringoptional

The notification text (up to 500 characters).

1–500 characters

actionUrlstringoptional

The HTTPS page opened when the notification is tapped.

uri · up to 2,048 characters

imageUrlstringoptional

A large picture: web and Android show it; iOS needs the app's service extension.

uri · up to 2,048 characters

iconUrlstringoptional

Web only; iOS and Android show the app's own notification icon.

uri · up to 2,048 characters

datamap<string, string>optional

Up to 10 string key-value pairs passed to the app untouched, for deep links and in-app routing.

at most 10 keys · keys: pattern ^[A-Za-z0-9_.-]{1,64}$ · values: up to 1,024 characters

buttonsobject[]optional

Up to 2 action buttons.

at least 1 item · at most 2 items

2 child attributes
titlestringrequired

The button label.

1–30 characters

urlstringrequired

The HTTPS page the button opens.

uri · up to 2,048 characters

collapseKeystringoptional

A newer notification with the same key replaces the older one on the device.

pattern ^[A-Za-z0-9_-]{1,64}$

soundstringoptional

The name of a sound the app ships; the device's default if unset.

1–64 characters

badgeintegeroptional

The number shown on the iOS app icon.

min 0 · max 99999

ttlDurationOrZerooptional

How long a provider keeps trying an offline device, up to 28 days; zero delivers now or never. Defaults to one day.

2 child attributes
valueintegerrequired

min 0

unitstringrequired

one of seconds, minutes, hours, days

urgencystringoptional

high asks the push service to deliver promptly, even to a sleeping device; normal lets it wait for a good moment to save battery. Device settings and the app still decide whether and how it is shown. Unset, a transactional push is high and any other normal.

one of high, normal

overridesobjectoptional

Fields that differ on one platform, each replacing the shared one there (a whole data map, not merged key by key). On iOS and Android actionUrl may be an app link such as myapp://orders; the shared one and the web one must be HTTPS.

3 child attributes
iosobjectoptional

Replaces shared fields on iOS.

14 child attributes
titlestringoptional

The notification title (up to 100 characters).

1–100 characters

bodystringoptional

The notification text (up to 500 characters).

1–500 characters

actionUrlstringoptional

pattern ^[a-z][a-z0-9+.-]*:\S+$ · up to 2,048 characters

imageUrlstringoptional

A large picture: web and Android show it; iOS needs the app's service extension.

uri · up to 2,048 characters

iconUrlstringoptional

Web only; iOS and Android show the app's own notification icon.

uri · up to 2,048 characters

datamap<string, string>optional

Up to 10 string key-value pairs passed to the app untouched, for deep links and in-app routing.

at most 10 keys · keys: pattern ^[A-Za-z0-9_.-]{1,64}$ · values: up to 1,024 characters

buttonsobject[]optional

Up to 2 action buttons.

at least 1 item · at most 2 items

collapseKeystringoptional

A newer notification with the same key replaces the older one on the device.

pattern ^[A-Za-z0-9_-]{1,64}$

soundstringoptional

The name of a sound the app ships; the device's default if unset.

1–64 characters

badgeintegeroptional

The number shown on the iOS app icon.

min 0 · max 99999

ttlDurationOrZerooptional

How long a provider keeps trying an offline device, up to 28 days; zero delivers now or never. Defaults to one day.

urgencystringoptional

high asks the push service to deliver promptly, even to a sleeping device; normal lets it wait for a good moment to save battery. Device settings and the app still decide whether and how it is shown. Unset, a transactional push is high and any other normal.

one of high, normal

interruptionLevelstringoptional

How iOS presents it: passive arrives silently, time-sensitive can break through Focus when the user allows it, and needs the app's Time Sensitive Notifications capability.

one of passive, active, time-sensitive

threadIdstringoptional

Notifications with the same thread are grouped together in Notification Center.

pattern ^[A-Za-z0-9_.-]{1,64}$

androidobjectoptional

Replaces shared fields on Android.

13 child attributes
titlestringoptional

The notification title (up to 100 characters).

1–100 characters

bodystringoptional

The notification text (up to 500 characters).

1–500 characters

actionUrlstringoptional

pattern ^[a-z][a-z0-9+.-]*:\S+$ · up to 2,048 characters

imageUrlstringoptional

A large picture: web and Android show it; iOS needs the app's service extension.

uri · up to 2,048 characters

iconUrlstringoptional

Web only; iOS and Android show the app's own notification icon.

uri · up to 2,048 characters

datamap<string, string>optional

Up to 10 string key-value pairs passed to the app untouched, for deep links and in-app routing.

at most 10 keys · keys: pattern ^[A-Za-z0-9_.-]{1,64}$ · values: up to 1,024 characters

buttonsobject[]optional

Up to 2 action buttons.

at least 1 item · at most 2 items

collapseKeystringoptional

A newer notification with the same key replaces the older one on the device.

pattern ^[A-Za-z0-9_-]{1,64}$

soundstringoptional

The name of a sound the app ships; the device's default if unset.

1–64 characters

badgeintegeroptional

The number shown on the iOS app icon.

min 0 · max 99999

ttlDurationOrZerooptional

How long a provider keeps trying an offline device, up to 28 days; zero delivers now or never. Defaults to one day.

urgencystringoptional

high asks the push service to deliver promptly, even to a sleeping device; normal lets it wait for a good moment to save battery. Device settings and the app still decide whether and how it is shown. Unset, a transactional push is high and any other normal.

one of high, normal

channelIdstringoptional

The Android notification channel to post in, which the app creates; it sets the sound and importance a person can tune, e.g. one for offers and one for orders.

pattern ^[A-Za-z0-9_.-]{1,64}$

webobjectoptional

Replaces shared fields on web.

12 child attributes
titlestringoptional

The notification title (up to 100 characters).

1–100 characters

bodystringoptional

The notification text (up to 500 characters).

1–500 characters

actionUrlstringoptional

The HTTPS page opened when the notification is tapped.

uri · up to 2,048 characters

imageUrlstringoptional

A large picture: web and Android show it; iOS needs the app's service extension.

uri · up to 2,048 characters

iconUrlstringoptional

Web only; iOS and Android show the app's own notification icon.

uri · up to 2,048 characters

datamap<string, string>optional

Up to 10 string key-value pairs passed to the app untouched, for deep links and in-app routing.

at most 10 keys · keys: pattern ^[A-Za-z0-9_.-]{1,64}$ · values: up to 1,024 characters

buttonsobject[]optional

Up to 2 action buttons.

at least 1 item · at most 2 items

collapseKeystringoptional

A newer notification with the same key replaces the older one on the device.

pattern ^[A-Za-z0-9_-]{1,64}$

soundstringoptional

The name of a sound the app ships; the device's default if unset.

1–64 characters

badgeintegeroptional

The number shown on the iOS app icon.

min 0 · max 99999

ttlDurationOrZerooptional

How long a provider keeps trying an offline device, up to 28 days; zero delivers now or never. Defaults to one day.

urgencystringoptional

high asks the push service to deliver promptly, even to a sleeping device; normal lets it wait for a good moment to save battery. Device settings and the app still decide whether and how it is shown. Unset, a transactional push is high and any other normal.

one of high, normal

pushProjectIdstringoptional

The push project it goes out through. Needed to send.

uuid

abTestobject | nullrequired

Its A/B test, if it has one; null sends one version to everyone.

8 child attributes
versionsobject[]required

Versions 2 onward; version 1 is the campaign's own content.

2 child attributes
emailobjectoptional

This version's email fields and design; any left out are version 1's.

7 child attributes
subjectstringoptional

The subject line. Merge tags are allowed.

1–998 characters

previewTextstringoptional

Shown by inboxes after the subject. Merge tags are allowed; keep it near 100 characters.

1–255 characters

htmlstringoptional

The HTML body. Rendered from the design when one is set.

1–512,000 characters

textstringoptional

The plain-text alternative.

1–512,000 characters

fromstringoptional

The sender address, on a verified sending domain.

1–320 characters

replyTostringoptional

Where replies go; the part before the @ must be ASCII.

email

templateSnapshotobjectoptional

The email design or HTML, copied into the campaign so later template edits do not change it.

One of 2 shapes
With schemaVersion, blocks and theme
With kind and html
pushobjectoptional

This version's push title, body and image; any left out are version 1's.

3 child attributes
titlestringoptional

The notification title (up to 100 characters).

1–100 characters

bodystringoptional

The notification text (up to 500 characters).

1–500 characters

imageUrlstringoptional

A large picture: web and Android show it; iOS needs the app's service extension.

uri · up to 2,048 characters

testPercentintegerrequired

The share of the audience, in percent, the versions go to, split evenly between them. The rest get the winner.

min 10 · max 50

winnerBystringrequired

How the winner is picked: the version with the highest click or open rate over its delivered messages (as the campaign's own rates; over sent until any version has a delivery), or the highest share of its sent messages that reached the campaign's conversion goal (conversionGoalId). Opens overcount: machine opens (Apple Mail Privacy Protection, Gmail's prefetch, mail scanners) count unread email as opened. A push test is decided by clicks (a tap the app reports with a link to follow, so the push needs an actionUrl) or conversions, not opens. A tie goes to the lower version.

one of clicks, opens, conversions

conversionGoalIdstring | nullrequired

The conversion goal that decides, when winnerBy is conversions.

uuid

decideAfterobject | nullrequired

How long after the test went out to pick the winner and send it to the rest, from 1 to 72 hours; null to pick it by hand.

2 child attributes
valueintegerrequired

min > 0

unitstringrequired

one of seconds, minutes, hours, days

winnerinteger | nullrequired

The winning version, once picked.

min 1

testSentAtstring | nullrequired

When every test message was queued.

date-time

decidedAtstring | nullrequired

When the winner was picked.

date-time

statusstringrequired

Where the campaign is in its lifecycle. testing: an A/B test went out and the rest of the audience waits for the winner. sent: every message was created and queued; it says nothing about delivery, which GET /v1/campaigns/{id}/metrics reports (some may still be queued, skipped or failed).

one of draft, scheduled, sending, testing, sent, cancelled

scheduleobject | nullrequired

When it sends: fixed, everyone at one instant, or local_time, each contact at a time on their own clock. Set while it is scheduled; a local-time schedule stays set once sending starts, since each contact's time still decides when theirs goes. null otherwise.

One of 2 shapes
Fixed time
2 child attributes
type"fixed"required
sendAtstringrequired

The instant everyone receives it at.

date-time

Local time
5 child attributes
type"local_time"required
localDateTimestringrequired

The wall-clock time (YYYY-MM-DDTHH:MM, no timezone) each contact receives it at on their own clock. It may already have passed in some timezones, not in all.

pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$

timezoneIfUnknownstring | nullrequired

The IANA timezone for contacts without one; null: the workspace's.

ifTimeHasPassedstringrequired

Where the time has already gone when it is scheduled: send_now sends there at once, next_day at the same time the next day, and skip leaves those contacts out (skipped with local_time_passed).

one of send_now, skip, next_day

startsAtstringrequired

When sending starts: the earliest timezone reaching localDateTime.

date-time

sentAtstring | nullrequired

When sending started; set from sending on (testing, sent, cancelled), null before. A local-time campaign starts in the earliest timezone.

date-time

deliveryCampaignDeliveryrequired

How fast it goes out, and until when; frozen with its content at send.

2 child attributes
rateLimitobject | nullrequired

How fast it goes out; null as fast as the plan allows. The plan's own send rate still applies, so a campaign can only slow a send down.

2 child attributes
recipientsintegerrequired

At most this many recipients per per, spread evenly across it. Counts people, so one person's email and push go together.

min 1 · max 1000000

perstringrequired

The period the limit counts over.

one of minute, hour, day

expiresAfterobject | nullrequired

From when sending starts, from 1 to 720 hours: recipients not yet sent when it passes are skipped with reason expired. A rate-limited send always expires, after 720 hours unless set. With an A/B test it must outlast abTest.decideAfter. null: no expiry.

2 child attributes
valueintegerrequired

min > 0

unitstringrequired

one of seconds, minutes, hours, days

pendingAgentReviewbooleanrequired

An agent key authored or edited it and it is still a draft, so it waits in the agent review queue: sending needs a person's approval.

sendApprovalRequestobject | nullrequired

The latest request, from a member in the dashboard, that an admin send it while the workspace keeps sending to admins; kept after it sends, cleared by any edit before then.

3 child attributes
userIdstringrequired

The member who asked.

uuid

requestedAtstringrequired

date-time

notestring | nullrequired
archivedAtstring | nullrequired

When it was archived; archived campaigns are left out of the list by default.

date-time

tagsstring[]required

Tag names, sorted without case.

revisionintegerrequired

Raised by every edit. Send it back as expectedRevision on the update so a change made since you read it is refused instead of overwritten.

min > 0 · max 2147483647

createdAtstringrequired

date-time

updatedAtstringrequired

date-time

Headers on 202 Accepted

Idempotency-Replaystringoptional

true when this is the stored response to an earlier request with the same key.

one of true