publ betaReference (MCP guide)
Open publ console

marketing_popup_apply

[Migration map](../reference/tool-name-migration.md).

# marketing_popup_apply

See the [current correction and older-server guidance](../reference/README.md#current-input-casing-correction) before preparing new inputs; keep existing task inputs unchanged during recovery.

Create a default popup, change visibility, or compose content/image/visibility changes through one tool. The selected object is globalPopup; the other widget and all unrelated custom settings must be preserved.

`settings.description` permits empty text or 2–100 UTF-16 code units, including
editor newlines before storage encoding. Omit it to preserve the current value.
One unit and values above 100 are rejected. Popup title remains empty or 2–20;
visible button labels remain 2–10. For example, after get returns a revision,
configure can supply this description without changing images or visibility:

```json
{
  "settings": {
    "description": "Explore our newest stories, upcoming events, and member benefits in one place."
  }
}
```

Include this settings fragment with `action:"configure"`, the selected
`channelCode`, the returned `expectedRevision` and a stable `requestId`.

Use `dryRun:true` for a plan without a business write or task JSON. Explicit
execution can initialize custom settings after verified404 and upload images
before saving. All other custom business fields must match readback. Keep
`taskType:marketing-widget` for status/resume; unknown sent requests are never
repeated. An unstarted image after restart requires the original apply bytes.
Historical completion is not current verification. Human plan changes use the
returned screen; no billing mutation, rollback or orphan deletion occurs.

## Access and preparation

Requires OWNER or DELEGATEE with MANAGE_MARKETING_TOOLS for the selected channel. An unrelated permission or CREATOR role is insufficient. The source-defined hosted selected-channel resource admits this tool only for its exact channelCode; the account resource excludes it.

Read [marketing_popup_get](publ_marketing_popup_get.md) first and retain its exact revision. New plans independently reread the full custom record and plan, then verify channel/context continuity. The revision includes unrelated settings, timestamps and plan; a change returns MARKETING_CONFLICT before dispatch. Later plan changes return MARKETING_PLAN_CHANGED and pause remaining effects.

The host configures CHANNEL_ENV. Replacement images additionally require a resource gateway, upload bucket and region, including for dry runs. No DNS/recache service is used. A no-op skips mutation-service validation but still performs preparation reads. Service URLs and credentials are not inputs.

## Inputs and actions

The outer object, settings, images and each image object are strict. Action-specific combinations are runtime refinements beyond the exported flat object schema.

| Field | Required here | Contract |
| --- | --- | --- |
| `action` | Yes | `create`, `visibility`, `configure`; create: source defaults only if absent; visibility: change visible only; configure: merge settings/images with source plan eligibility. |
| `visible` | No | boolean |
| `settings` | No | object |
| `settings.title` | No | string; max length 20; Empty or 2–20 UTF-16 code units, matching the source editor. Emoji may occupy two units. Omit to preserve the saved value. |
| `settings.description` | No | string; max length 100; Popup editor text: empty or 2–100 UTF-16 code units, including newlines before storage encoding. Omit to preserve the current description. |
| `settings.title_color` | No | string; min length 1; max length 128 |
| `settings.description_color` | No | string; min length 1; max length 128 |
| `settings.btn_count_option` | No | `NOT_USE`, `USE_ONE`, `USE_TWO` |
| `settings.btn_1_text` | No | string; max length 10; Empty or 2–10 UTF-16 code units, matching the source editor. Emoji may occupy two units. Omit to preserve the saved value. |
| `settings.btn_1_conversion_link_url` | No | string; max length 4096 |
| `settings.btn_1_conversion_link_type` | No | `WEB_LINK`, `INTERNAL_LINK` |
| `settings.btn_2_text` | No | string; max length 10; Empty or 2–10 UTF-16 code units, matching the source editor. Emoji may occupy two units. Omit to preserve the saved value. |
| `settings.btn_2_conversion_link_url` | No | string; max length 4096 |
| `settings.btn_2_conversion_link_type` | No | `WEB_LINK`, `INTERNAL_LINK` |
| `images` | No | object |
| `images.bg_image_src` | No | one of: `local-file`, `more-parts`, `complete`, `cancel` (by `kind`) or null |
| `images.bg_image_src.filename` | Yes | When `images.bg_image_src.kind` is `local-file`: string; min length 1; max length 240; pattern `^[^/\\:\x00-\x1f\x7f]+$`; The name of the file on this computer, with its extension. |
| `images.bg_image_src.byteLength` | Yes | When `images.bg_image_src.kind` is `local-file`: integer; greater than 0; The exact file size in bytes (for example from `wc -c < FILE` or `stat`). |
| `images.bg_image_src.sha256` | No | When `images.bg_image_src.kind` is `local-file`: string; pattern `^[0-9a-f]{64}$` |
| `images.bg_image_src.uploadId` | Yes | When `images.bg_image_src.kind` is `more-parts`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `images.bg_image_src.fromPart` | Yes | When `images.bg_image_src.kind` is `more-parts`: integer; minimum 1; maximum 10000 |
| `images.bg_image_src.uploadId` | Yes | When `images.bg_image_src.kind` is `complete`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `images.bg_image_src.parts` | Yes | When `images.bg_image_src.kind` is `complete`: array; min items 1; max items 10000 |
| `images.bg_image_src.parts[].partNumber` | Yes | When `images.bg_image_src.kind` is `complete`: integer; minimum 1; maximum 10000 |
| `images.bg_image_src.parts[].etag` | Yes | When `images.bg_image_src.kind` is `complete`: string; min length 1; max length 200 |
| `images.bg_image_src.measured` | No | When `images.bg_image_src.kind` is `complete`: object; What the video measures on this computer, read with measureCommand. |
| `images.bg_image_src.measured.durationSeconds` | Yes | When `images.bg_image_src.kind` is `complete`: number; maximum 1000000; greater than 0; Video duration in seconds (ffprobe format.duration). |
| `images.bg_image_src.measured.frameRate` | Yes | When `images.bg_image_src.kind` is `complete`: number; maximum 1000; greater than 0; Frames per second (ffprobe avg_frame_rate, for example 30000/1001 = 29.97). |
| `images.bg_image_src.measured.width` | Yes | When `images.bg_image_src.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `images.bg_image_src.measured.height` | Yes | When `images.bg_image_src.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `images.bg_image_src.measured.frameRateMode` | No | When `images.bg_image_src.kind` is `complete`: `CFR`, `VFR` |
| `images.bg_image_src.measured.minFrameRate` | No | When `images.bg_image_src.kind` is `complete`: number; maximum 1000; greater than 0 |
| `images.bg_image_src.measured.maxFrameRate` | No | When `images.bg_image_src.kind` is `complete`: number; maximum 1000; greater than 0 |
| `images.bg_image_src.uploadId` | Yes | When `images.bg_image_src.kind` is `cancel`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `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. |
| `irreversibleConfirmed` | No | `true`; true only after the person confirmed again, when the preview has requiresReconfirmation: the action cannot be undone. |

create accepts no visible/settings/images. It writes source defaults only when this widget is absent; an existing widget is a no-op. Defaults start NOT_ACTIVATED. visibility requires an existing widget and visible; settings/images are forbidden. It changes only is_activated while retaining the complete existing content.

configure merges supplied settings/images and optional visible into one final nested save. It requires at least one supplied setting, image slot or visible, checked by the workflow after schema parsing. Missing widgets start from source defaults. Even a configure containing only visible or unchanged values requires a recognized eligible editor plan. Use visibility for the separate visibility action. Denied or unknown plans return action_required / MARKETING_EDIT_PLAN_REQUIRED with the exact screen's human-console guidance and no writes. No plan upgrade or payment occurs. See the [complete eligibility list](../guides/marketing-widgets.md#plan-and-input-rules).

### Settings

| Field | Contract |
| --- | --- |
| settings.title | Empty or 2–20 characters. |
| settings.description | Empty or 2–100 UTF-16 code units before newline encoding. Omit to preserve; no trimming or truncation. |
| settings.title_color | Nonempty string, 1–128 characters, no ASCII control or DEL; not a complete CSS color validator. |
| settings.description_color | Same color constraint. |
| settings.btn_count_option | NOT_USE, USE_ONE or USE_TWO. |
| settings.btn_1_text | Empty or 2–10 characters. |
| settings.btn_1_conversion_link_url | Empty or a normalized HTTP(S) destination satisfying the source pattern. |
| settings.btn_1_conversion_link_type | WEB_LINK or INTERNAL_LINK. |
| settings.btn_2_text | Empty or 2–10 characters. |
| settings.btn_2_conversion_link_url | Same destination constraint. |
| settings.btn_2_conversion_link_type | WEB_LINK or INTERNAL_LINK. |
| settings.design_template_code | GLOBAL_POPUP_TYPE_A, GLOBAL_POPUP_TYPE_B or GLOBAL_POPUP_TYPE_C. |

For configure, each visible button in the composed result requires text of 2–10 characters and a nonempty destination. Hidden fields are preserved; selecting NOT_USE or USE_ONE does not erase hidden button data. Supplied description newlines are encoded as literal backslash-n before saving. Omitting description preserves its original representation. Reader/completion output decodes it for editor use.

Creation defaults are title TITLE, description DESCRIPTION, title_color and description_color `#FFFFFF`, the source `https://static.publ.site/popup_default_image.png` reference, USE_ONE, button text BUTTON/BUTTON2, https://publ.co destinations with WEB_LINK, and GLOBAL_POPUP_TYPE_B. No source-default image is fetched or uploaded by the bridge. These defaults never repair malformed stored data.

Text is not trimmed. For description, empty clears, one UTF-16 code unit is rejected and 2–100 units are accepted. A newline counts before encoding; an astral character such as an emoji occupies two units. The registered parser enforces this bound in addition to the exported maxLength:100; a JSON Schema validator alone can count Unicode differently. Other text fields retain their own bounds. Supplied links are limited to 4,096 characters before removing all whitespace and adding https:// when no HTTP(S) prefix exists. Nonempty results require HTTP(S), a hostname, no embedded credentials and the source domain-pattern match. This is validation/normalization, not URL fetching or destination availability verification. WEB_LINK and INTERNAL_LINK retain the source's new-tab/current-tab choice; INTERNAL_LINK does not accept a separate page-ID field.

### Images

| Field | Contract |
| --- | --- |
| images.bg_image_src | Omit to preserve; null removes the reference; otherwise an original PNG/JPEG object. |
| images.bg_image_src.filename | Basename, 1–240 characters, PNG/JPG/JPEG extension; no path, URL, colon or control characters. |
| images.bg_image_src.base64 | Canonical original-file base64, 4–6,990,508 characters; no data URL or whitespace. |

All replacement files together may contain at most 5 MiB of original bytes. This aggregate cap is a bridge policy for its original 8 MiB request envelope; the later 16 MiB transport ceiling does not enlarge this file limit, not a source-widget file-size rule. Extension matching is case-insensitive at runtime but the exported regex omits the flag; use lowercase filenames for both. Parsed bytes must match PNG/JPEG structure and extension. The bridge does not resize or render them. null stores an empty image reference and sends no upload; arbitrary replacement image URLs are not accepted.

Do not retry a pending image with a new request ID.

## Input examples

These illustrate dry-run inputs, not observed service results. Replace the channel/revision and choose the user's intended content.

```json
{
  "channelCode": "selected-channel-code",
  "requestId": "popup-create-plan-001",
  "expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
  "action": "create",
  "dryRun": true
}
```

```json
{
  "channelCode": "selected-channel-code",
  "requestId": "popup-visibility-plan-001",
  "expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
  "action": "visibility",
  "dryRun": true,
  "visible": false
}
```

```json
{
  "channelCode": "selected-channel-code",
  "requestId": "popup-configure-plan-001",
  "expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
  "action": "configure",
  "dryRun": true,
  "settings": {
    "title": "Welcome",
    "description": "Latest news"
  }
}
```

```json
{
  "channelCode": "selected-channel-code",
  "requestId": "popup-image-plan-001",
  "expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
  "action": "configure",
  "dryRun": true,
  "images": {
    "bg_image_src": {
      "filename": "popup.png",
      "base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aDWQAAAAASUVORK5CYII="
    }
  }
}
```

## Effects and results

A new dry run returns MARKETING_WIDGET_PLAN, noop, normalized changes and plannedStages, with no task creation or business writes. Image bytes are replaced with metadata in the planned changes. With an existing matching task, dryRun returns historical saved progress as a plan rather than a fresh business preview.

A real no-op writes a completed journal without business requests. For a change, the workflow can initialize a missing custom record with a bodyless HTTP200 POST to `/seller/api/v1/channel-management/custom`, retain its observed full baseline, issue/upload each replacement image, then PUT `/seller/api/v1/channel-management/custom/experimentals` with one nested `globalPopup` object. That object merges requested values with preserved fields; it is not a replacement of the whole experimentals object. The save expects HTTP200 without a response-body contract. There is no recache step.

The final readback must preserve every unrelated business setting, the other widget, unknown nested JSON, record ID, channel ID and insertedAt. updatedAt may change during the save. Only top-level experimentals.id/exp_id are excluded. The workflow checkpoints before each effect, but does not use a server-side compare-and-swap or transaction. Failed uploads/saves can leave initialized records or orphaned objects; no rollback or automatic deletion occurs.

MARKETING_WIDGET_COMPLETE includes widget, revision, phase done, historical, initializationAcknowledged, settingsSaveAcknowledged and uploads. Fresh completion confirms stored readback only. A no-op has no save acknowledgement; readback-only reconciliation can also complete without a save acknowledgement or proven causality. writesPerformed is false for a no-op or untouched prepared phase, true when an initialization/save or uploaded-object receipt exists, and otherwise null.

MARKETING_WIDGET_PENDING retains phase, uploads, lastResult and inspect_task guidance. journalPersistence.saved:false means the error checkpoint was not saved. Authentication/context errors can suppress channel/navigation metadata. Image receipts contain generated storage coordinates and checksums, not raw bytes or temporary credentials.

## Task recovery

Use taskType marketing-widget and the original taskId with [status](publ_task_status.md) or [resume](publ_task_resume.md). The task binds widget kind, channel/context, original revision/plan, normalized input fingerprint, record snapshots and image metadata. The same requestId cannot switch between banner and popup or change the input. Unknown or inconsistent journals fail validation; do not edit or rename them to bypass it.

| Phase | Recovery behavior |
| --- | --- |
| prepared | Recheck the original snapshot/plan, then start untouched work. |
| init-sent | Read back the custom record only; observed initialization can advance without an acknowledgement. Never resend initialization. |
| initialized | Recheck the full baseline, handle any untouched image stages, then save. |
| Per-image prepared | Recall the original apply with identical bytes if they are no longer in memory; resume alone returns MARKETING_IMAGE_REQUIRED. |
| Per-image grant-sent / upload-sent | Hold with MARKETING_UPLOAD_UNKNOWN for inspection; never reissue the grant or repeat upload. |
| Per-image uploaded | Retain the verified receipt and continue untouched work. |
| save-sent | Reconcile from full readback only. Match can complete with settingsSaveAcknowledged:false; mismatch holds with MARKETING_SAVE_UNKNOWN. Never resend save. |
| done | Return historical:true and verification.confirmed:false after authority checks. Read the screen for current state. |

Status never advances effects. Resume may run untouched stages; no sent boundary is automatically reset, even after definite failure. A rejected continuation of a completed task returns its error without writing an error detail into the completed journal.

The marketing-widget namespace uses the selected task store. For a deliberate later restoration, obtain a fresh reader revision and separately authorized input; retain uncertain tasks.

## Saved record identity

A missing record differs from an existing malformed record. Stored JSON null,
false, zero or an empty string is rejected with `TASK_STATE_UNVERIFIED` by the
existing record validator. Apply/dry-run/status/resume must not overwrite it or
treat it as a fresh task. Preserve the file for inspection; do not delete/edit it
or replace the request ID to bypass the error. Existing valid task recovery and
genuinely missing-record behavior remain unchanged.