publ betaReference (MCP guide)
Open publ console

design_menu_layout_apply — publish one device's menu connections

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

# design_menu_layout_apply — publish one device's menu connections

Set one device's template and both menu slots together, then read the stored result. First call [design_menu_layout_get](publ_design_menu_layout_get.md) for the same channel and device. Prepare menu definitions and their contents through their own workflows before connecting them here.

## Input

| Field | Type | Required | Constraint |
| --- | --- | --- | --- |
| `requestId` | string | Yes | 8–80 ASCII letters, digits, underscores or hyphens. Preserve it for this operation. |
| `channelCode` | string | Yes | Nonempty code of the channel just read. |
| `device` | enum string | Yes | `W`, `M` or `A`, matching the read. |
| `expectedRevision` | string | Yes | Lowercase 64-character hexadecimal revision from that read. |
| `menuTemplateType` | string | Yes | Nonempty `type` from get's available templates. |
| `primaryMenuId` | positive integer | Yes | Candidate menu ID, or the existing ID in this same slot. |
| `secondaryMenuId` | positive integer or `null` | Yes | Candidate menu ID, the existing ID in this same slot, or explicit `null` to clear. |
| `dryRun` | boolean | No | Defaults to `false`; `true` prepares a plan without publishing. |

The input is strict; extra fields are rejected. Both slots must be explicit even when unchanged. Omission is not preservation, `0` is not a clearing value, and menu-item IDs cannot substitute for menu IDs. A template with `secondaryMenu:false` requires `secondaryMenuId:null`.

```json
{
  "requestId":"example-menu-layout-001",
  "channelCode":"selected-channel-code",
  "device":"W",
  "expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000",
  "menuTemplateType":"W1_0003",
  "primaryMenuId":101,
  "secondaryMenuId":102,
  "dryRun":true
}
```

This is an illustrative input, not an executed transcript. Replace the channel, zero revision, template and numeric IDs with the same get result. This template supports a secondary menu only if it is available on the selected channel/device. To retain an existing out-of-list menu, keep its ID in its original slot; moving that unlisted ID to another slot is rejected.

For a template-only change, explicitly carry forward both current slot IDs. If the new template does not support Secondary, clearing that connection is an additional effect of choosing it; send `null` only when that matches the intended change. Each device change requires its own read, revision and request ID.

## Plan and publication

The get tool's connection, selected-channel, role and origin requirements apply. A new request reads the state, compares the layout revision and validates the template/slot choices. A new dry run returns `success/PLAN_READY`, environment, channel, device and `body:{menuTemplateType,primaryMenuId,secondaryMenuId}`. It creates no task and sends no write.

Real execution saves a `menu-design` task and reads again. If the same layout already has the requested body, it can complete without writing. Otherwise it requires the original revision, revalidates choices, records `phase:sending` and sends one PUT:

`/seller/api/v1/channel-management/design-preferences/menu-design-layouts/{layoutId}`

The layout ID comes from get's verified state, not a caller-supplied input. Menu transport checks connection and the exact selected-channel credential before and after the request; a post-dispatch context change remains an unknown write outcome. The request uses the general API origin, selected-channel bearer and channel-ID header. Its body includes the template and both slots. It creates no menu, edits no menu items, refreshes no credentials and sends no separate site-launch or publication call beyond this layout update.

After the PUT, the workflow rereads menus/templates/layout and requires the same layout ID and all three requested body values. These read/compare/write steps are not an atomic server transaction; they cannot rule out a competing write after the final check. There is no automatic rollback. Separate device operations can finish independently.

## Completion and saved plans

Completion returns `success/MENU_DESIGN_APPLIED`, task ID, `taskType:menu-design`, environment, channel, device, `layout:{id,menuTemplateType,primaryMenuId,secondaryMenuId}`, revision, and `verification:{confirmed:true,method:menu_design_read}`. There is **no `changed` field**. Success can mean the intended layout was already present or a sent change was later observed; it does not prove which actor produced it.

Replaying a completed apply/resume checks current channel context but returns the saved historical result without freshly reading the layout. Use get to establish present state. Stored readback does not prove desktop/mobile/app rendering, visitor access or a visual preference was satisfied.

A dry run with an existing matching job returns `PLAN_READY`, task ID/type, `phase`, `existingPhase` and saved `body`, with the history fields described below. It checks context and input identity, but does not reread or revalidate the layout/candidates. New-plan environment/channel/device fields are omitted.

Non-demo get/completion results with a resolved target return one menu-design destination. A new dry run has no task type/result code identifying that route, while an existing-job dry run has no channel metadata; both therefore return unavailable navigation rather than a substitute home URL. Task status also reports unavailable navigation and removes saved navigation metadata from its direct `lastResult`. The shared wrapper does not change the stored journal. URLs are navigation hints, not completion evidence.

## Recovery

Use [task_status](publ_task_status.md) and [task_resume](publ_task_resume.md) with the original task ID and `taskType:menu-design`. Preserve the connection, channel, device, original revision and all other business inputs. The saved fingerprint ignores only `dryRun`; changing another input under the same request ID produces `TASK_CONTEXT_CHANGED`.

| Saved phase | Behavior |
| --- | --- |
| `prepared` | Read again. Complete without writing if the target is already observed; otherwise require the original revision and current valid choices before sending. |
| `sending` | Read first. Complete if the same layout matches the requested body; otherwise return `MENU_DESIGN_OUTCOME_UNKNOWN` without resending the PUT. |
| `done` | Return the historical saved result. |

A definite rejected/not-sent PUT failure restores `prepared`; unknown or unclassified failures retain `sending`. A read failure after dispatch also leaves the task sent; do not infer from that read error that the write never occurred. Status contains phase and last result, not a step array. `pending` is saved unfinished work, not proof of a background worker. A fresh dry run has no saved task.

| Code | Meaning and next step |
| --- | --- |
| `INVALID_INPUT` | Correct invalid/missing fields; SDK validation may reject earlier. |
| `MENU_TEMPLATE_UNAVAILABLE` | The template is unknown, for another device, or absent from the current candidates. |
| `SECONDARY_MENU_UNSUPPORTED` | Use an intended supported combination; an unsupported template requires `null`. |
| `MENU_UNAVAILABLE` | A chosen ID is neither a candidate nor the existing ID in that same slot. |
| `DESIGN_CONFLICT` | The observed layout differs from the original revision. Read current state and inspect the original task. |
| `TASK_CONTEXT_CHANGED`, `CONNECTION_CHANGED`, `MENU_CONTEXT_CHANGED` | The saved inputs, account or target binding no longer match. |
| `MENU_DESIGN_OUTCOME_UNKNOWN` | A sent update remains unverified and will not be automatically repeated. |
| `MENU_DESIGN_VERIFICATION_MISMATCH` | Post-publication readback does not match the target; the outcome remains unknown. |

Other read/access errors follow get's rules. Internal failures can include task/channel/device metadata and phase; outer failures can omit them. Do not use a new request ID to force a repeat of an uncertain publication. Resume is mutation-capable.

## Saved plan replay

An existing matching task's `dryRun` returns historical saved plan/progress,
`historical:true`, `replayed:true` and false current verification. It has no current
console/action link and does not read present business state, repeat effects or
change the journal. Saved phase/progress and existingPhase aliases remain where
applicable. Follow `nextStep` for a current observation; retain the original task
ID for uncertain-outcome recovery. A new task keeps normal fresh-plan validation.

`nextStep`: design_menu_layout_get retains the original channel and W/M/A device; saved layout body does not validate current candidates.

## Saved record identity

Existing apply or dry-run calls and task status/resume require both the
saved task ID and saved input requestId to match the requested ID. A mismatching
or malformed saved identity returns `TASK_STATE_UNVERIFIED` before workflow
context or business-state reads. Preserve the original record for inspection;
do not rename/edit it, try its embedded ID or create a replacement request to
bypass the error. The workflow does not repair or resend the record. Valid
matching records keep their existing recovery behavior.

## Inputs

| Field | Required here | Contract |
| --- | --- | --- |
| `device` | Yes | `W`, `M`, `A`; W = desktop web, M = mobile web, A = Android/iOS app. |
| `menuTemplateType` | Yes | string; min length 1; A type returned in get.templates. |
| `primaryMenuId` | Yes | integer; greater than 0; An ID from get.menus, or the existing get.layout.primaryMenuId. This is not a menu-item ID. |
| `secondaryMenuId` | Yes | integer; greater than 0 or null |
| `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. |