publ betaReference (MCP guide)
Open publ console

Read, create and edit promotion campaigns

# Read, create and edit promotion campaigns

Use the connected server's discovery for availability and its current schema. Use the selected authorized channel and MANAGE_PROMOTION. These campaigns are separate from [conversion links](conversion-links.md), [legacy promotion links](legacy-promotion-links.md) and [notification settings](promotion-notifications.md).

1. Call [promotions_get](../tools/publ_promotions_get.md) for one campaign page. Follow returned nextPage when needed. An empty first page is valid when the returned total is zero. Copy observed pcid values rather than using numeric row IDs.
2. Select profile or benefits. For benefit detail/products, copy a returned pbid from that selected campaign. Keep source price amounts/currencies unchanged; do not infer entitlement or recalculate a discount.
3. To edit an existing campaign, read `view:{kind:"profile",pcid}` and pass that profile's `campaignRevision` as `expectedRevision` to [promotion_campaign_apply](../tools/promotion_campaign_apply.md). Use `action.kind:profile` for a title and/or complete schedule, or `action.kind:activation` with `active:true` or `false`. Keep the exact channel, pcid and stable requestId; inspect `dryRun:true` before saving. Benefits and product prices are preserved.
4. For an existing benefit, read `view:{kind:"benefit",pcid,pbid}` and use its `benefitRevision` with [promotion_benefit_apply](../tools/promotion_benefit_apply.md). Choose supported profile fields or activation. For an existing connected-product discount, read the exact products page instead and use its productRevision with action product-discount, observed row.id, page/limit and the existing mode.
5. For a new active campaign with one initial ORDER_DISCOUNT benefit, use [promotion_campaign_create](../tools/promotion_campaign_create.md). Inspect a complete dryRun, copy creationRevision into expectedRevision, then submit the same values/requestId. It does not reserve plan capacity or perform a customer purchase.
6. For an additional ORDER_DISCOUNT or DISCOUNT benefit, use [promotion_benefit_create](../tools/promotion_benefit_create.md) with an observed pcid and a complete dryRun. Additional benefits require Business-or-higher; Essential's initial-campaign allowance does not apply.
7. For other initial or additional benefit types, or unsupported product connections, use [promotion_handoff](../tools/publ_promotion_handoff.md) with the matching action. It checks the selected parents and returns one exact human destination. Creation checks the unfiltered count and source plan; denied/unknown policy leads to channel-plan without a purchase. A handoff returns `action_required` and has no writer acknowledgement or task. A subsequent read observes stored state, not customer benefit delivery.

Start with a list:

```json
{"channelCode":"selected-channel-code","view":{"kind":"campaigns","page":1,"limit":15}}
```

For an observed campaign, this optional handoff opens its human editor; it does not save changes:

```json
{"channelCode":"selected-channel-code","action":{"kind":"campaign-edit","pcid":"observed-campaign"}}
```

The reader checks page identity, explicit channel fields and stable selected parents. These bounded observations do not create an atomic inventory or financial approval. Notification delivery, browser click-through, visitor effects, actual LLM selection and hosted operation require separate evidence.

The current reader returns its selected `view` and keeps page, size and supported filters in the single console link for ordinary paginated views. Product-options opens the add wizard without modal page/search query parameters. Open the returned address to reach the corresponding selection. If `PROMOTION_PAGE_OUT_OF_RANGE` is returned, request page1 again; no replacement page was read automatically. Financial handoffs remain human actions and have no completed-write claim.

## Current existing-campaign editing

The title accepts 1–50 UTF-16 code units after trimming. A schedule supplies both `startAt` and nullable `endAt` as ISO8601 instants with `Z` or explicit UTC offsets; `endAt:null` removes the end. A non-null end must follow the start. Do not infer the user's timezone. Omitted fields retain their values. The source does not offer an atomic conditional write, so revision checks cannot eliminate a concurrent console edit.

Recovery uses `taskType:promotion-campaign`. Preserve the returned taskId and original inputs. Status/resume only observe; a prepared task continues through the identical apply input, and a sent request is never replayed. Completed replay is historical. Read the profile again for current state rather than assuming the saved result is fresh.

[promotion_campaign_create](../tools/promotion_campaign_create.md) adds the bounded new-campaign flow described below; [promotion_benefit_create](../tools/promotion_benefit_create.md) adds an ORDER_DISCOUNT or DISCOUNT to an existing campaign. The earlier GET-only contract applies to the reader/handoff, not the separate writer.

## Current existing-benefit editing

[promotion_benefit_apply](../tools/promotion_benefit_apply.md) accepts `action.kind:profile` or `activation` for one observed pcid/pbid, with `product-discount` or `product-disconnect` for the separately observed products selection below. For profile/activation, read benefit detail first; the campaignRevision used by the campaign editor is not a substitute for benefitRevision. The profile permits a 4–20 UTF-16-unit alias, bounded schedule and source-supported target/control changes. Available fields depend on benefit type and the stored source screen. LIMITED_TIME target type remains locked; connected-product prices and benefit type are not arbitrary writable fields.

It configures an existing offer and does not execute a payment. Use the detailed Reference for member display text, limits, grant triggers and notice/automatic sales-stop conditions; do not infer support from a generic discount API.

Recovery uses `taskType:promotion-benefit`, original inputs and the returned taskId. Resume only observes; a prepared save requires identical apply and a sent request is never repeated. Stored verification does not prove customer eligibility, presentation, delivery or charging. A human handoff remains available for supported console actions.

## Create a campaign with one order discount

Supply the campaign and benefit schedules, alias/display text, ALL or one observed SEGMENT distinctId, channel-currency decimal amount and ONE_TIME/UNLIMITED limit. Explicit ISO offsets and the benefit-inside-campaign rule apply. Currency, plan and unfiltered campaign count come from repeated source observations; Essential requires zero existing campaigns. See the Reference for exact amount and plan rules.

Begin with dryRun:true. Review sourceWrite/sourceDependencies and copy creationRevision into expectedRevision for the same requestId/configuration with dryRun:false. This revision is distinct from an existing campaignRevision or benefitRevision. The source creates active immediately; there is no draft/activation selector in this method. Plan capacity is observed, not reserved.

Recovery uses taskType promotion-campaign-create. Both status and resume only observe. Prepared work requires the identical create call; sent creation never repeats. The authoritative receipt binds campaign id/pcid and first-benefit id/pbid, then two reads verify those exact identities and values. An identical replacement benefit, title match or count increase cannot establish success. Missing receipt identity stays unresolved. Completed results are historical; use promotions_get for current state.

Confirm connected discovery. Additional ORDER_DISCOUNT or DISCOUNT creation uses the separate [promotion_benefit_create](../tools/promotion_benefit_create.md) contract and Business-or-higher gate. Existing connected-product discounts use the bounded editor below. There is no campaign deletion or automatic rollback; deactivation does not restore the pre-creation absence.

## Edit an existing connected-product discount

Read [promotions_get](../tools/publ_promotions_get.md#current-connected-product-selection) with products, observed pcid/pbid and an explicit page/limit. Select a non-deleted row's `id`, retain that selection and copy `productRevision`. Then inspect `promotion_benefit_apply` with `action.kind:"product-discount"`, `targetProductId`, the same page/limit, existing `mode` and a plain decimal `value`, using `dryRun:true`. Submit the same original input/requestId with dryRun false only for the intended business change.

Digital product types use FIXED_PRICE; commerce products/categories and tickets use RATE_DISCOUNT or AMOUNT_DISCOUNT. Percentages are percentage values, not ratio fractions. Changing the mode, creating underlying products and inventing missing embedded rows are outside this discount action. New eligible connections use the separate connection flow below. Relationship removal uses the separate action below. Existing discount editing has no activation prerequisite; all activation flags are preserved.

Only the selected discount field changes. The full observed page, other price fields, parent and benefit content must survive readback, apart from the campaign's generated updatedAt. A matching value is a no-op only after pending admission. The returned link preserves the products tab/page/limit and requires manual row/dialog selection. One-page readback is not an atomic inventory or checkout/visitor verification.

All edits to the same benefit share [pending protection](../tools/promotion_benefit_apply.md#benefit-wide-pending-protection), including profile, activation, product disconnect and fresh no-ops. Do not bypass uncertain work with a new ID, row or inverse request. Keep taskType promotion-benefit, original taskId and inputs; [resume](../tools/publ_task_resume.md#promotion-benefit-pending-recovery) observes uncertain attempts and never repeats sent effects. Runtime rollout remains on HOLD pending inspection of pre-upgrade unregistered work.

## Disconnect one existing product relationship

Read the selected products page, preserve its pcid/pbid/channel/page/limit and copy productRevision. Choose the connected row.id, then inspect [promotion_benefit_apply product-disconnect](../tools/promotion_benefit_apply.md#disconnect-an-existing-product-relationship) with that targetProductId and `dryRun:true`. The action takes no discount mode/value or replacement product. Submit the same configuration/requestId with dryRun false only for the intended relationship removal.

The source supports DISCOUNT/ORDER_DISCOUNT digital and physical rows, GRANT coupon rows and LIMITED_TIME digital rows. It does not require active records and allows an existing relationship to a deleted underlying product. The writer removes only the relationship; it never deletes the product. Eligible new connections use the separate bounded flow below. Disconnect still returns restorationSupported:false and does not automatically relink a removed relationship.

Before and after sending, complete stable inventory checks cover at most 1000 rows using 100-row pages and preserve all remaining relationships/prices/flags/parents. One missing page or receipt is not proof of absence. An already-absent ID can be a guarded no-op only with a fresh valid revision and complete absence evidence; it is not a performed removal.

After the requested page collapses, the writer explicitly observes surviving page 1 and explains the returned products-tab link. Follow its current page/revision; it never chooses a replacement product. Original-task recovery uses the same benefit guard and never repeats sent POST. A later eligible connection is a separate intentional operation; it is not automatic restoration of the old relationship or earlier customer effects.

## Connect existing products to a discount benefit

Read [product-options](../tools/publ_promotions_get.md#product-connection-options) with the exact existing campaign/benefit and selector page/limit/search. Keep its connectionRevision and stored mode. Select only eligible choices and copy their distinctId values, not numeric IDs, into targetProducts[].targetId. Supply an explicit decimal value for each target in [promotion_benefit_apply product-connect](../tools/promotion_benefit_apply.md#connect-existing-products-to-a-discount-benefit), first with dryRun:true. Preserve the exact selector query and requestId when submitting the intended change.

This flow requires an active campaign and existing DISCOUNT benefit. It supports five families: SubscriptionProduct, ChannelActionProduct, PAppUnlockProduct, TicketEvent and CommerceProduct. Benefit/product activation remains unchanged. Digital products use the source fixed-price rules; ticket/commerce use rate or amount rules. At most100 choices come from one selector page, with at most1000 resulting relationships. These are verification bounds, not source business limits. Already-linked or deleted-existing relationships are not silently updated or duplicated.

One POST adds relationships to existing products. Complete stable inventory and unchanged selector observations must identify exactly the requested new target/type/value rows while preserving old relationships/prices/order/parents. Partial or ambiguous outcomes stay on the original guarded task; sent effects never replay. The options link opens the add wizard without fabricated modal parameters; confirmed writes open the observed first products page and explain that added rows can be later in the list.

[Connection gaps](../tools/promotion_benefit_apply.md#product-connection-gaps) retain Category/CommerceProductCategory ambiguity, CP fixed-price conversion and deleted-product reconnection. This connection action does not create products; additional DISCOUNT benefits use the separate creator below.

## Add a discount benefit to an existing campaign

Use [promotion_benefit_create](../tools/promotion_benefit_create.md#add-a-discount-benefit-with-optional-initial-products) for one additional DISCOUNT or ORDER_DISCOUNT under Business-or-higher. Inactive parents are allowed for creation; the active-campaign rule for connecting products to an existing benefit above does not apply. The original campaign and old benefits remain preserved. Initial campaign creation still supports only ORDER_DISCOUNT.

For DISCOUNT, choose one supported family/mode and either products:[] or observed initial products. With products, first read [benefit-options](../tools/publ_promotions_get.md#new-discount-benefit-options) using pcid/productType and exact selector page/limit/search, without pbid. Copy selectable choice.distinctId values and optionsRevision into the complete benefit draft, specify explicit values, then inspect dryRun. Copy creationRevision to expectedRevision and submit the same requestId and configuration. Source fixed triggers and ONE_TIME/UNLIMITED limits do not add GRANT repeatability or LIMITED_TIME controls.

One POST must yield authoritative benefit and initial relationship identities, followed by independent complete bounded product/detail/parent readback. Missing or contradictory receipts cannot be adopted from a title/count/list difference. Keep the original promotion-benefit-create task; prepared work needs identical create, sent recovery only observes and historical completion supplies no current verified link. The plan opens the add-benefit wizard and verified completion opens the exact new benefit detail.