promotion_campaign_create
Create one active promotion campaign with exactly one initial ORDER_DISCOUNT benefit in the source DEFAULT wizard flow. The campaign and benefit are submitted together in one POST. This configures an offer that may become available to visitors; it does not perform a purchase, charge, coupon issuance or settlement.
Registration, channel scope, task routing and receipt-identity correction are included in this source.
Select the channel and inspect a plan
Use the exact channelCode selected through channels_list/channel_context_get. Requires OWNER or a delegate with MANAGE_PROMOTION in that selected channel. Account scope and a different channel resource cannot use the method or its recovery task. Do not provide credentials, a service URL, numeric channel ID or invented segment identity.
Call this method with dryRun:true and the complete desired campaign/benefit values. It repeatedly reads the unfiltered first campaign page and total, source plan, channel base currency and, for SEGMENT, the selected segment detail. The observations and selected context must agree. A partial or malformed first page cannot establish capacity.
The plan returns creationRevision, sourceWrite and sourceDependencies. Review them, copy creationRevision to expectedRevision and submit the same values/requestId with dryRun:false. A campaignRevision from an existing profile is not a creation revision. Dry run creates no campaign, journal or reserved slot. Creation requires a fresh matching plan; no plan purchase or automatic upgrade is performed.
NORMAL_ESSENTIAL permits creation only when the unfiltered campaign total is zero. The other allowed source plan codes are NORMAL_BUSINESS, NORMAL_FROM_ENTERPRISE, NORMAL_PRO, ENTERPRISE_TRIAL, ENTERPRISE_ENTERPRISE, PARTNER_TRIAL, PARTNER_PARTNER and INTERNAL_INTERNAL. Null, unknown or lower plan codes fail. Plan/capacity observation is not an atomic backend reservation.
Inputs
| Field | Required here | Contract |
|---|---|---|
title |
Yes | string; min length 1; max length 50 |
schedule |
Yes | object |
schedule.startAt |
Yes | string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time |
schedule.endAt |
Yes | string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time or null |
benefit |
Yes | one of: ORDER_DISCOUNT, DISCOUNT, GRANT, LIMITED_TIME (by kind) |
benefit.alias |
Yes | When benefit.kind is ORDER_DISCOUNT: string; min length 4; max length 20 |
benefit.memberDisplayText |
Yes | When benefit.kind is ORDER_DISCOUNT: string; min length 1; max length 14 |
benefit.schedule |
Yes | When benefit.kind is ORDER_DISCOUNT: object |
benefit.schedule.startAt |
Yes | When benefit.kind is ORDER_DISCOUNT: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time |
benefit.schedule.endAt |
Yes | When benefit.kind is ORDER_DISCOUNT: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time or null |
benefit.target |
Yes | When benefit.kind is ORDER_DISCOUNT: one of: ALL, SEGMENT (by kind) |
benefit.target.distinctId |
Yes | When benefit.kind is ORDER_DISCOUNT and benefit.target.kind is SEGMENT: string; pattern ^[A-Za-z0-9_-]{1,128}$ |
benefit.discountAmount |
Yes | When benefit.kind is ORDER_DISCOUNT: string; pattern ^(0|[1-9]\d{0,7})(\.\d{1,2})?$; Plain decimal amount in the freshly observed channel base currency. No currency conversion or rounding. |
benefit.benefitLimitOption |
Yes | When benefit.kind is ORDER_DISCOUNT: ONE_TIME, UNLIMITED |
benefit.alias |
Yes | When benefit.kind is DISCOUNT: string; min length 4; max length 20 |
benefit.memberDisplayText |
Yes | When benefit.kind is DISCOUNT: string; min length 1; max length 14 |
benefit.schedule |
Yes | When benefit.kind is DISCOUNT: object |
benefit.schedule.startAt |
Yes | When benefit.kind is DISCOUNT: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time |
benefit.schedule.endAt |
Yes | When benefit.kind is DISCOUNT: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time or null |
benefit.target |
Yes | When benefit.kind is DISCOUNT: one of: ALL, SEGMENT (by kind) |
benefit.target.distinctId |
Yes | When benefit.kind is DISCOUNT and benefit.target.kind is SEGMENT: string; pattern ^[A-Za-z0-9_-]{1,128}$ |
benefit.benefitLimitOption |
Yes | When benefit.kind is DISCOUNT: ONE_TIME, UNLIMITED |
benefit.productType |
Yes | When benefit.kind is DISCOUNT: SubscriptionProduct, ChannelActionProduct, PAppUnlockProduct, TicketEvent, CommerceProduct |
benefit.mode |
Yes | When benefit.kind is DISCOUNT: FIXED_PRICE, RATE_DISCOUNT, AMOUNT_DISCOUNT |
benefit.products |
Yes | When benefit.kind is DISCOUNT: array; max items 100 |
benefit.products[].targetId |
Yes | When benefit.kind is DISCOUNT: string; pattern ^[A-Za-z0-9_-]{1,128}$; targetId of a selectable choice from the options view. |
benefit.products[].value |
Yes | When benefit.kind is DISCOUNT: string; max length 40; pattern ^(0|[1-9]\d*)(\.\d+)?$; FIXED_PRICE (membership plans, content category products, paid-access products): the new sale price in the channel currency. RATE_DISCOUNT: the percentage. AMOUNT_DISCOUNT: the amount taken off. |
benefit.alias |
Yes | When benefit.kind is GRANT: string; min length 4; max length 20 |
benefit.schedule |
Yes | When benefit.kind is GRANT: object |
benefit.schedule.startAt |
Yes | When benefit.kind is GRANT: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time |
benefit.schedule.endAt |
Yes | When benefit.kind is GRANT: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time or null |
benefit.target |
Yes | When benefit.kind is GRANT: one of: ALL, SEGMENT (by kind) |
benefit.target.distinctId |
Yes | When benefit.kind is GRANT and benefit.target.kind is SEGMENT: string; pattern ^[A-Za-z0-9_-]{1,128}$ |
benefit.executionTrigger |
No | When benefit.kind is GRANT: EXECUTION_SIGNUP, EXECUTION_SIGNIN, EXECUTION_PURCHASE; default EXECUTION_SIGNUP |
benefit.coupons |
No | When benefit.kind is GRANT: array; max items 100; default [] |
benefit.coupons[].targetId |
Yes | When benefit.kind is GRANT: string; pattern ^[A-Za-z0-9_-]{1,128}$ |
benefit.alias |
Yes | When benefit.kind is LIMITED_TIME: string; min length 4; max length 20 |
benefit.schedule |
Yes | When benefit.kind is LIMITED_TIME: object; Sales period. The end is always required. |
benefit.schedule.startAt |
Yes | When benefit.kind is LIMITED_TIME: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time |
benefit.schedule.endAt |
Yes | When benefit.kind is LIMITED_TIME: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time |
benefit.target |
No | When benefit.kind is LIMITED_TIME: object; default {"kind":"ALL"} |
benefit.target.kind |
Yes | When benefit.kind is LIMITED_TIME: ALL |
benefit.noticeStartAt |
No | When benefit.kind is LIMITED_TIME: string; pattern ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$; format date-time |
benefit.productType |
Yes | When benefit.kind is LIMITED_TIME: SubscriptionProduct, ChannelActionProduct, PAppUnlockProduct |
benefit.products |
No | When benefit.kind is LIMITED_TIME: array; max items 100; default [] |
benefit.products[].targetId |
Yes | When benefit.kind is LIMITED_TIME: string; pattern ^[A-Za-z0-9_-]{1,128}$ |
benefit.autoToggleOnBenefitPeriod |
No | When benefit.kind is LIMITED_TIME: boolean; default true |
confirmToken |
No | string; pattern ^ct[a-f\d]{32}$; The confirmToken from this tool's preview result. Omit it to preview; repeat the same input with it to execute. |
Objects reject unknown fields. There are no caller campaign/benefit IDs, campaign type, activation switch, product list, percentage discount, grant configuration or arbitrary trigger fields. The wire body fixes isActivated:true, AMOUNT_DISCOUNT, EXPRESSION_VIEW and EXECUTION_PURCHASE, with an empty targetProducts array and no targetProductType field. Do not infer support for other benefit paths from the broader source wizard.
Both schedules require ISO8601 instants with Z or an explicit UTC offset. Values normalize to UTC. Each non-null end must be strictly after its start. The benefit cannot start before the campaign; a bounded campaign also requires a non-null benefit end no later than the campaign end. A null end is omitted from the wire body. No user/browser/host timezone or source date-picker day-granularity equivalence is inferred.
The string allows no sign, whitespace, commas, exponent or leading zeroes except the single zero before a fraction. There is no conversion, rounding or caller override of the currency.
Examples
Illustrative ALL-target dry run:
{
"channelCode": "selected-channel-code",
"requestId": "campaign-create-all-001",
"title": "Community welcome offer",
"schedule": {"startAt": "2030-01-01T00:00:00Z", "endAt": null},
"benefit": {
"kind": "ORDER_DISCOUNT",
"alias": "Welcome discount",
"memberDisplayText": "Welcome offer",
"schedule": {"startAt": "2030-01-01T00:00:00Z", "endAt": null},
"target": {"kind": "ALL"},
"discountAmount": "100",
"benefitLimitOption": "ONE_TIME"
},
"dryRun": true
}
Illustrative single-segment dry run with an explicit offset and bounded period:
{
"channelCode": "selected-channel-code",
"requestId": "campaign-create-segment-001",
"title": "Returning member offer",
"schedule": {"startAt": "2030-02-01T09:00:00+09:00", "endAt": "2030-03-01T09:00:00+09:00"},
"benefit": {
"kind": "ORDER_DISCOUNT",
"alias": "Returning discount",
"memberDisplayText": "Member offer",
"schedule": {"startAt": "2030-02-02T09:00:00+09:00", "endAt": "2030-02-28T09:00:00+09:00"},
"target": {"kind": "SEGMENT", "distinctId": "observed-segment"},
"discountAmount": "200",
"benefitLimitOption": "UNLIMITED"
},
"dryRun": true
}
Replace the channel, segment and dates with the intended observed values. These example amounts use the channel's currency, not an assumed USD price. Inspect the returned sourceWrite and sourceDependencies. To perform the intended creation, add the returned creationRevision as expectedRevision and change only dryRun to false. Do not manufacture a revision or use a second requestId after an uncertain submission.
Creation result and identity
PROMOTION_CAMPAIGN_CREATE_PLAN is a successful read-only plan with writesPerformed:false and verification.confirmed:false. It supplies the exact configured creation-screen destination. A dry run against an already saved matching task returns historical task information rather than a fresh plan; its original input, including expectedRevision, must still match.
Actual creation records prepared, then sent before the single POST. A usable receipt must identify one campaign and exactly one first benefit. Its campaign id/pcid and benefit id/pbid are saved together and checked against later reads. An identity already present in the observed first campaign page is rejected; this is not a global deduplication guarantee. A missing ID is never recovered from a title, count increase or list difference.
The receipt and two independent reads of that exact pcid must agree with the requested title, activation, schedules, benefit fields, segment and amount/currency, and the absence of connected products. A different benefit with identical values cannot replace the receipt's benefit identity. Completion is durably saved before PROMOTION_CAMPAIGN_CREATED is returned. A fresh completion includes promotionCampaign, pcid, campaignRevision and the created campaign profile destination. Use promotions_get for later current state, and promotion_campaign_apply for a separate existing-campaign edit.
The shared result wrapper provides one validated navigation link for a fresh plan or verified create. Pending, error and historical results do not supply a current usable console destination. Demo has no live console link. Use the returned address; do not reconstruct a host or substitute a console home page.
Recovery and errors
Saved incomplete progress uses PROMOTION_CAMPAIGN_CREATE_PENDING; phase distinguishes prepared from sent. Keep the original taskId/requestId and taskType:promotion-campaign-create. Both task_status and task_resume are observation-only for this workflow. A prepared task has not dispatched the POST; only the identical create call can continue it after fresh context/dependency checks. A sent task never repeats creation, even after a timeout, unexpected response, lost receipt or crash before the recorded dispatch actually reached the server.
If the receipt identity is unavailable, the task stays unresolved; inspect the console without adopting a campaign by name or starting another create. When an authoritative receipt was saved but its configuration or later read disagrees, recovery may reread that exact campaign and first benefit. It cannot accept a replacement benefit or prove that this workflow was the only writer. Completed replay is historical with no fresh verification or current link; writesPerformed describes stored receipt/progress, not a new request trace.
| Result/error | Next step |
|---|---|
INVALID_INPUT / PROMOTION_BENEFIT_INPUT_UNVERIFIED |
Correct the shape, dates or amount against observed currency before a new unsent plan. |
PROMOTION_CREATION_UNAVAILABLE |
The observed plan/capacity does not permit creation. No purchase or upgrade is attempted. |
PROMOTION_PAGE_UNVERIFIED / PROMOTION_SEGMENT_UNVERIFIED |
Resolve missing, malformed or foreign dependencies; do not invent a count or segment. |
PROMOTION_CHANGED / PROMOTION_CONFLICT |
Reinspect a fresh plan for an intended unsent change. Do not replace an uncertain sent task. |
PROMOTION_CONTEXT_CHANGED / REQUEST_ID_CONFLICT / TASK_STATE_UNVERIFIED |
Resolve the bound channel/operator, input or journal mismatch; do not weaken authorization or overwrite task identity. |
PROMOTION_RECEIPT_MISMATCH / PROMOTION_CREATION_ID_UNKNOWN / PROMOTION_READBACK_MISMATCH |
Preserve the task and observe only. No repeated POST, title-based adoption or automatic rollback. |
Prepared tasks require their original credential version. Sent recovery may use refreshed credentials only for the same connection, channel, operator and permission context. Inspect phase, outcome, journalPersistence and nextStep after failure; persistence failure is not success. A recorded sent marker may precede the actual network effect, so uncertainty must not be treated as proof that nothing happened.
Boundaries
Use promotion_benefit_create separately to add one ORDER_DISCOUNT or DISCOUNT benefit to an existing campaign; its Business-or-higher gate differs from initial campaign creation. Use their supported human console handoffs when appropriate. There is no campaign deletion or automatic rollback in this flow. Deactivation is not deletion and does not restore the pre-creation absence. Any actual test needs separately authorized cleanup; this Reference grants no live-operation permission.
Campaign guide · Existing-benefit editor · Human handoff
For an existing connected relationship, use promotion_benefit_apply product-disconnect with the exact products-page revision and connected row ID. This removes only the relationship, verifies complete bounded absence and preserves the underlying product. Eligible new connections now use the bounded product-connect flow below; restorationSupported remains false, with no automatic relinking or rollback. A handoff is still GET-only/action_required and never claims the change happened. Financial execution remains a human-console action.
To connect eligible existing products to an existing DISCOUNT benefit, read product-options and use product-connect with connectionRevision and explicit values. The active-campaign prerequisite and five supported families apply; category model ambiguity, fixed-price CP conversion and deleted-product reconnection remain gaps. This creates relationships only. Additional DISCOUNT creation uses promotion_benefit_create with its own options/creation revisions and Business-or-higher gate, permitting inactive parents. Neither flow creates underlying products, restores previous identities or executes finance.