publ betaReference (MCP guide)
Open publ console

promotion_benefit_create

# promotion_benefit_create

Add one ORDER_DISCOUNT or DISCOUNT benefit to an observed existing promotion campaign.
Requires OWNER or MANAGE_PROMOTION and an observed Business-or-higher plan.
Essential permits an initial campaign but does not permit this additional-benefit
operation. The existing campaign may be inactive; adding a benefit does not
activate it. Use the connected server's discovery to check availability; source
implementation is not evidence that the running service has been updated.

1. Read `promotions_get` and select an observed opaque `pcid`.
2. Submit the complete benefit below with `dryRun: true`. Dates must fit within
   the selected campaign; supply your intended timezone as an offset or `Z`.
3. Copy the returned `creationRevision` into `expectedRevision`, set
   `dryRun: false`, and submit the exact same values and stable `requestId`.

```json
{
  "channelCode": "selected-channel-code",
  "pcid": "observed-campaign",
  "requestId": "add-order-benefit-001",
  "dryRun": true,
  "benefit": {
    "kind": "ORDER_DISCOUNT",
    "alias": "Seasonal offer",
    "memberDisplayText": "Order offer",
    "schedule": {"startAt": "2026-11-01T00:00:00+09:00", "endAt": "2026-11-30T23:59:59+09:00"},
    "target": {"kind": "ALL"},
    "discountAmount": "100",
    "benefitLimitOption": "ONE_TIME"
  }
}
```

The example dates and amount are illustrative. Use the selected campaign period
and observed channel currency. KRW/JPY allow whole multiples of100 from100 to
10,000,000; USD allows0.01 to10,000 at cent precision. No conversion or rounding
is performed. Alias uses4–20 UTF-16 units and display text1–14. Use UNLIMITED for
unlimited application count. An unlimited period uses `endAt: null` only when the
parent is unlimited. A segment target uses
`{"kind":"SEGMENT","distinctId":"observed-segment"}` and must be freshly
verified for the channel. There are no product-selection, trigger-override or
activation fields for this bounded order-discount operation.

The plan reads the parent, existing benefits, plan, currency and audience twice.
Saving rechecks them before one POST. The authoritative receipt supplies the new
`pbid`; two parent/detail reads must agree and preserve existing settings and
benefits. Source activation is retained without an additional activation call.
Successful verification covers stored configuration, not visitor checkout or
benefit delivery. This operation does not buy a service plan or transfer money.

If uncertain, retain the task ID and use `task_status`/`task_resume` with
`taskType: "promotion-benefit-create"`. Neither dispatches creation. Missing
receipt identity requires inspecting the console; do not create again under a
new ID or infer identity from a title/list difference. A prepared task needs the
identical original call. Completed history is unconfirmed on replay; use a fresh
`promotions_get` benefit detail for current values.

Open the one returned console link: the selected add-benefit screen for a current
plan, or the exact new benefit detail after verified completion. Historical or
uncertain results do not fabricate a destination. GRANT and LIMITED_TIME creation and
unsupported product configuration use the source handoff where still unsupported.
No benefit deletion or automatic restoration is promised.

## Add a discount benefit with optional initial products

Confirm current connected discovery. Initial [campaign creation](promotion_campaign_create.md) still accepts only ORDER_DISCOUNT; this extension adds one benefit to an existing campaign.

The strict DISCOUNT object uses the common alias, memberDisplayText, schedule, target and required benefitLimitOption above, plus these fields. It has no benefit-level discountAmount, caller-supplied pbid, activation, trigger, repeatability, notice or automatic sales-stop controls. Source expression/execution triggers are EXPRESSION_VIEW and EXECUTION_PURCHASE. Explicit ONE_TIME or UNLIMITED controls the application limit; the source wizard's UNLIMITED default is not an omitted-input default. GRANT repeatability and LIMITED_TIME controls are separate contracts.

| Field | Contract |
| --- | --- |
| kind | DISCOUNT. |
| productType | SubscriptionProduct, ChannelActionProduct, PAppUnlockProduct, TicketEvent or CommerceProduct. |
| mode | FIXED_PRICE for the three digital families; RATE_DISCOUNT or AMOUNT_DISCOUNT for TicketEvent/CommerceProduct. |
| products | Required array of zero to100 unique `{targetId,value}` objects. Empty initial selection is permitted. |
| products[].targetId | The observed choice.distinctId, not a numeric selector ID or existing relationship row ID. |
| products[].value | Explicit plain decimal string, up to40 characters, in the selected mode. No sign, exponent, rounding, currency conversion or automatic price inference. |
| selection | Required for a nonempty products array: exact page/limit/search from benefit-options plus its64-character lowercase hexadecimal optionsRevision. Omit for an empty selection unless intentionally binding an observed selector. If present, it must remain current even with no products. |

For a nonempty selection, first call [promotions_get benefit-options](publ_promotions_get.md#new-discount-benefit-options) using only pcid, productType and selector query on the selected channel. The new benefit has no pbid yet. Choose only selectable rows from that exact page and retain their distinctId values. Preserve the query and optionsRevision in the create dry run. Review sourceWrite/sourceDependencies and copy the separately returned creationRevision to expectedRevision for actual submission with the same requestId and values. optionsRevision, connectionRevision, benefitRevision and creationRevision are not interchangeable. A dry run creates no journal or reserved campaign slot.

Values follow the [connected-product discount rules](promotion_benefit_apply.md#existing-connected-product-discount): FIXED_PRICE is the final price bounded by the channel minimum and verified original selling price; RATE_DISCOUNT is a percentage, not a fractional ratio; AMOUNT_DISCOUNT is the reduction in observed channel currency. The channel-specific percentage exception and exact payment units still apply. A selectable choice does not prove its requested value is valid. Category/CommerceProductCategory mapping and fixed-price CP conversion remain unsupported. Products must already exist; this method does not create a product.

This empty initial selection needs no options read. Replace the channel, parent and dates with intended observed values:

```json
{
  "channelCode": "selected-channel-code",
  "pcid": "observed-campaign",
  "requestId": "add-discount-empty-001",
  "dryRun": true,
  "benefit": {
    "kind": "DISCOUNT",
    "alias": "Member offer",
    "memberDisplayText": "Member price",
    "schedule": {"startAt": "2026-11-01T00:00:00+09:00", "endAt": "2026-11-30T23:59:59+09:00"},
    "target": {"kind": "ALL"},
    "benefitLimitOption": "UNLIMITED",
    "productType": "SubscriptionProduct",
    "mode": "FIXED_PRICE",
    "products": []
  }
}
```

This selected ticket example requires a fresh matching benefit-options read.

```json
{
  "channelCode": "selected-channel-code",
  "pcid": "observed-campaign",
  "requestId": "add-ticket-discount-001",
  "dryRun": true,
  "benefit": {
    "kind": "DISCOUNT",
    "alias": "Ticket offer",
    "memberDisplayText": "Ticket offer",
    "schedule": {"startAt": "2026-11-01T00:00:00+09:00", "endAt": "2026-11-30T23:59:59+09:00"},
    "target": {"kind": "ALL"},
    "benefitLimitOption": "ONE_TIME",
    "productType": "TicketEvent",
    "mode": "RATE_DISCOUNT",
    "products": [{"targetId": "observed-ticket-event", "value": "10"}],
    "selection": {"page": 1, "limit": 15, "optionsRevision": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
  }
}
```

## Inputs

| Field | Required here | Contract |
| --- | --- | --- |
| `pcid` | Yes | string; pattern `^[A-Za-z0-9_-]{1,128}$` |
| `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. |