publ betaReference (MCP guide)
Open publ console

promotion_campaign_create

# 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:

```json
{
  "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:

```json
{
  "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](publ_promotions_get.md) for later current state, and [promotion_campaign_apply](promotion_campaign_apply.md) 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](promotion_benefit_create.md) 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](../guides/promotion-campaigns.md) · [Existing-benefit editor](promotion_benefit_apply.md) · [Human handoff](publ_promotion_handoff.md)

For an existing connected relationship, use [promotion_benefit_apply product-disconnect](promotion_benefit_apply.md#disconnect-an-existing-product-relationship) 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](publ_promotions_get.md#product-connection-options) and use [product-connect](promotion_benefit_apply.md#connect-existing-products-to-a-discount-benefit) 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](promotion_benefit_create.md#add-a-discount-benefit-with-optional-initial-products) 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.