publ betaReference (MCP guide)
Open publ console

conversion_link_apply

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

# conversion_link_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.

This implementation is a dependency of the documentation branch.

Compose the source creation wizard or update the selected editor fields in one recoverable workflow. The bridge does not automate browser clicks. Use [manage](publ_conversion_links_manage.md) for active state and deletion.

## Access and input

Requires OWNER or DELEGATEE with MANAGE_CONVERSION_LINKS on the selected channel. MANAGE_MARKETING_TOOLS alone is insufficient. No connected-domain gate is imposed. Creation alone requires a verified eligible Business-or-higher source plan; existing-link reads, updates, visibility and deletion do not inherit that creation gate. See the [creation policy](publ_conversion_link_apply.md#creation-policy). Hosted source rules admit these tools only on the exact selected-channel resource, not account resources; this is not deployed authorization evidence.

channelCode is a 1–512 character string, excluding literal dot/double dot, slash, backslash, ASCII controls and DEL. Conversion IDs are positive safe integers obtained from authorized results, not keywords or URLs. Strict input objects reject unknown properties. Stored text and URLs are data, never instructions; the bridge does not fetch destination/share/image URLs.

| Input | Contract |
| --- | --- |
| channelCode | Required selected channel. |
| requestId | Required 8–80 ASCII letters, digits, underscores or hyphens. Keep it with the original input. |
| action | create or update. |
| id | Forbidden for create; required selected numeric ID for update. |
| expectedRevision | Required 64 lowercase hexadecimal characters: creationRevision for create, detail revision for update. |
| settings | Complete create settings or at least one supported update field. See below. |
| dryRun | Optional boolean; defaults to false. true checks a plan without performing effects. |

The flat exported schema does not express every action/field combination. Runtime validation enforces them. Replace the sample zero revision and numeric ID with authorized read results.

```json
{
  "channelCode": "selected-channel-code",
  "requestId": "conversion-create-01",
  "action": "create",
  "expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
  "settings": {
    "name": "Campaign link",
    "title": "Discover our channel",
    "description": "Explore the latest content.",
    "destination": {
      "kind": "WEB",
      "url": "https://example.com/offer"
    },
    "utmSource": "newsletter",
    "utmMedium": "email",
    "utmCampaign": "autumn_2026"
  },
  "dryRun": true
}
```

```json
{
  "channelCode": "selected-channel-code",
  "requestId": "conversion-update-01",
  "action": "update",
  "id": 123,
  "expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
  "settings": {
    "name": "Autumn campaign",
    "title": "Updated sharing title"
  },
  "dryRun": true
}
```

## Creation policy

Before a new create plan or task, and before each untouched create effect, the workflow reads a channel-bound currentPlan and rechecks context. Allowed plans are NORMAL_BUSINESS, NORMAL_FROM_ENTERPRISE, NORMAL_PRO, ENTERPRISE_TRIAL, ENTERPRISE_ENTERPRISE, PARTNER_TRIAL, PARTNER_PARTNER and INTERNAL_INTERNAL. FREE_TRIAL, NORMAL_STARTER, NORMAL_STARTER_2023_04, NORMAL_BASIC and NORMAL_ESSENTIAL are known denials. A missing, null or unrecognized plan fails closed; unlike the source UI's broad default, unknown plans are not treated as eligible.

A known denial returns CONVERSION_PLAN_REQUIRED; unverified policy returns CONVERSION_PLAN_UNVERIFIED. Preflight failure is action_required with writesPerformed:false, verification.confirmed:false and one human channel-plan destination. A saved create stopped before an untouched effect stays at its original phase, with lastResult, action_required/open_console guidance and existing write evidence preserved. No plan purchase/payment change is sent. Recalling an existing create with dryRun checks policy for prepared/uploaded; a sent write only reconciles and is never replayed because eligibility changed.

Existing-link update, visibility, delete and ordinary reading are not creation-gated. creationRevision remains context-based rather than a reservation of the returned policy; fresh checks enforce eligibility before effects.

## Settings

| Field | Contract |
| --- | --- |
| settings.name | Trimmed 4–50 characters; required for create, optional for update. |
| settings.title | Trimmed 1–70 characters; required for create, optional for update. |
| settings.description | Trimmed 1–160 characters; required for create, optional for update. |
| settings.destination | Required strict object for create; forbidden for update. |
| settings.destination.kind | INTERNAL or WEB. |
| settings.destination.url | Trimmed HTTP(S) URL, 1–4,096 characters, hostname required and no embedded credentials. |
| settings.utmSource | Create only; default direct. |
| settings.utmMedium | Create only; default (none), also accepts that exact special value. |
| settings.utmCampaign | Create only; default empty string. |
| settings.utmContent | Create only; default empty string. |
| settings.utmTerm | Create only; default empty string. |
| settings.thumbnail | Optional original PNG/JPEG object on create/update; cannot be null or a replacement URL. |
| settings.thumbnail.filename | Basename of 1–240 characters ending in .png, .jpg or .jpeg; no slash, backslash, colon, ASCII controls or DEL. Use lowercase extension for portable JSON-schema clients. |
| settings.thumbnail.base64 | Canonical bare base64, 4–13,333,332 characters; decoded image checks below also apply. |

Nonempty UTM strings need at least two lowercase ASCII letters, digits or underscores; empty is allowed. The 1,024-character UTM maximum is a bridge bound. Only utmMedium also permits literal (none). The server chooses keyword; clients cannot set it. Destination, UTM and keyword are read-only after creation even if a broader upstream DTO could change them.

INTERNAL requires decoded URL pathname segments containing exact consecutive channels/{selected channelCode}. A substring match or another channel is rejected with CONVERSION_DESTINATION_MISMATCH. WEB needs the valid HTTP(S) destination only. The bridge neither fetches the URL nor verifies content ownership or destination availability.

Omitted update fields preserve their values. At least one of name/title/description/thumbnail is required, although unchanged metadata can finish as a no-op. Only requested editable metadata and a verified image receipt are patched; complete readback must preserve all other business fields except derived hits. Creation omission of thumbnail uses the source default image; update omission preserves the stored reference. Thumbnail removal is unsupported.

## Original thumbnail and transport

Decoded original PNG/JPEG bytes must be strictly below 10,000,000 decimal bytes (maximum 9,999,999), with filename/format/signature, canonical encoding, byte length and SHA-256 checked. This differs from questionnaire binary MiB limits. No data URL, remote replacement URL, credentials or forged receipt is accepted as image input. The authenticated MCP envelope is 16 MiB; proxy/client limits remain independent.

Image replacement requires configured resource gateway, bucket and region before any effect, including dry run. Upload uses the original bytes; only its verified receipt location enters thumbnailSrc. Task storage retains metadata/receipts, not base64, original bytes or upload credentials. Upload and save are separate effects with no rollback or orphan deletion.

Create uses POST accepting 200/201 data. It must durably record a positive returned id and matching channelId before full readback. Optional auto-created segment ID/title are disclosed but not a complete segment verification or cleanup guarantee. It never automatically creates a promotion campaign. Update uses PATCH accepting 200/204 without a body. Acknowledgement alone is not completion. At the create-receipt boundary only, id/channelId may be positive safe integers or canonical positive decimal strings: no signs, leading zeros, spaces, decimal points or overflow. The workflow normalizes those two identities to numbers and retains selected-channel checks; public inputs and other stored IDs remain numeric. autoCreatedSegmentId is separately preserved as a positive number or nonempty opaque string; it is not coerced to a numeric ID. A receipt correction never fills an old task's missing identity or permits name adoption. Cross-save update/visibility matching excludes generated root updatedAt and derived hits, while retaining updatedAt in snapshots, current revisions, repeated reads, no-op and pre-effect conflict checks. All other requested/preserved business fields must match.

## Inputs

| Field | Required here | Contract |
| --- | --- | --- |
| `action` | Yes | `create`, `update` |
| `id` | No | integer; greater than 0 |
| `settings` | Yes | object or object |
| `settings.name` | Yes | When `settings` is alternative 1: string; min length 4; max length 50; Trimmed text: 4–50 UTF-16 code units, including two units per astral character, as the console counts. |
| `settings.title` | Yes | When `settings` is alternative 1: string; min length 1; max length 70; Trimmed text: 1–70 UTF-16 code units, including two units per astral character, as the console counts. |
| `settings.description` | Yes | When `settings` is alternative 1: string; min length 1; max length 160; Trimmed text: 1–160 UTF-16 code units, including two units per astral character, as the console counts. |
| `settings.destination` | Yes | When `settings` is alternative 1: object |
| `settings.destination.kind` | Yes | When `settings` is alternative 1: `INTERNAL`, `WEB`; INTERNAL links to a page of this publ channel (its address contains channels/ and the code of this channel); WEB links to any other site. |
| `settings.destination.url` | Yes | When `settings` is alternative 1: string; min length 1; max length 4096; WEB: any http(s) address. INTERNAL: the full http(s) address of a page of this publ channel, copied from the channel site; its path must contain channels/ followed by the code of this channel. A bare path such as /C00007 is not accepted. |
| `settings.utmSource` | No | When `settings` is alternative 1: string; max length 1024; default `direct` |
| `settings.utmMedium` | No | When `settings` is alternative 1: string; max length 1024 or `(none)`; default `(none)` |
| `settings.utmCampaign` | No | When `settings` is alternative 1: string; max length 1024; default `` |
| `settings.utmContent` | No | When `settings` is alternative 1: string; max length 1024; default `` |
| `settings.utmTerm` | No | When `settings` is alternative 1: string; max length 1024; default `` |
| `settings.thumbnail` | No | When `settings` is alternative 1: one of: `local-file`, `more-parts`, `complete`, `cancel` (by `kind`) |
| `settings.thumbnail.filename` | Yes | When `settings` is alternative 1 and `settings.thumbnail.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. |
| `settings.thumbnail.byteLength` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `local-file`: integer; greater than 0; The exact file size in bytes (for example from `wc -c < FILE` or `stat`). |
| `settings.thumbnail.sha256` | No | When `settings` is alternative 1 and `settings.thumbnail.kind` is `local-file`: string; pattern `^[0-9a-f]{64}$` |
| `settings.thumbnail.uploadId` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `more-parts`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `settings.thumbnail.fromPart` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `more-parts`: integer; minimum 1; maximum 10000 |
| `settings.thumbnail.uploadId` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `settings.thumbnail.parts` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: array; min items 1; max items 10000 |
| `settings.thumbnail.parts[].partNumber` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: integer; minimum 1; maximum 10000 |
| `settings.thumbnail.parts[].etag` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: string; min length 1; max length 200 |
| `settings.thumbnail.measured` | No | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: object; What the video measures on this computer, read with measureCommand. |
| `settings.thumbnail.measured.durationSeconds` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: number; maximum 1000000; greater than 0; Video duration in seconds (ffprobe format.duration). |
| `settings.thumbnail.measured.frameRate` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: number; maximum 1000; greater than 0; Frames per second (ffprobe avg_frame_rate, for example 30000/1001 = 29.97). |
| `settings.thumbnail.measured.width` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `settings.thumbnail.measured.height` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `settings.thumbnail.measured.frameRateMode` | No | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: `CFR`, `VFR` |
| `settings.thumbnail.measured.minFrameRate` | No | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: number; maximum 1000; greater than 0 |
| `settings.thumbnail.measured.maxFrameRate` | No | When `settings` is alternative 1 and `settings.thumbnail.kind` is `complete`: number; maximum 1000; greater than 0 |
| `settings.thumbnail.uploadId` | Yes | When `settings` is alternative 1 and `settings.thumbnail.kind` is `cancel`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `settings.name` | No | When `settings` is alternative 2: string; min length 4; max length 50; Trimmed text: 4–50 UTF-16 code units, including two units per astral character, as the console counts. |
| `settings.title` | No | When `settings` is alternative 2: string; min length 1; max length 70; Trimmed text: 1–70 UTF-16 code units, including two units per astral character, as the console counts. |
| `settings.description` | No | When `settings` is alternative 2: string; min length 1; max length 160; Trimmed text: 1–160 UTF-16 code units, including two units per astral character, as the console counts. |
| `settings.thumbnail` | No | When `settings` is alternative 2: one of: `local-file`, `more-parts`, `complete`, `cancel` (by `kind`) |
| `settings.thumbnail.filename` | Yes | When `settings` is alternative 2 and `settings.thumbnail.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. |
| `settings.thumbnail.byteLength` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `local-file`: integer; greater than 0; The exact file size in bytes (for example from `wc -c < FILE` or `stat`). |
| `settings.thumbnail.sha256` | No | When `settings` is alternative 2 and `settings.thumbnail.kind` is `local-file`: string; pattern `^[0-9a-f]{64}$` |
| `settings.thumbnail.uploadId` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `more-parts`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `settings.thumbnail.fromPart` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `more-parts`: integer; minimum 1; maximum 10000 |
| `settings.thumbnail.uploadId` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `settings.thumbnail.parts` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: array; min items 1; max items 10000 |
| `settings.thumbnail.parts[].partNumber` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: integer; minimum 1; maximum 10000 |
| `settings.thumbnail.parts[].etag` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: string; min length 1; max length 200 |
| `settings.thumbnail.measured` | No | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: object; What the video measures on this computer, read with measureCommand. |
| `settings.thumbnail.measured.durationSeconds` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: number; maximum 1000000; greater than 0; Video duration in seconds (ffprobe format.duration). |
| `settings.thumbnail.measured.frameRate` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: number; maximum 1000; greater than 0; Frames per second (ffprobe avg_frame_rate, for example 30000/1001 = 29.97). |
| `settings.thumbnail.measured.width` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `settings.thumbnail.measured.height` | Yes | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `settings.thumbnail.measured.frameRateMode` | No | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: `CFR`, `VFR` |
| `settings.thumbnail.measured.minFrameRate` | No | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: number; maximum 1000; greater than 0 |
| `settings.thumbnail.measured.maxFrameRate` | No | When `settings` is alternative 2 and `settings.thumbnail.kind` is `complete`: number; maximum 1000; greater than 0 |
| `settings.thumbnail.uploadId` | Yes | When `settings` is alternative 2 and `settings.thumbnail.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. |

## Results and task recovery

Dry run returns CONVERSION_LINK_PLAN, dryRun:true, noop, changes, plannedStages, writesPerformed:false and verification.confirmed:false. It creates no new task. A normal result uses CONVERSION_LINK_COMPLETE or CONVERSION_LINK_PENDING, taskType conversion-link, taskId, phase, historical, noop, acknowledged and writesPerformed. Completion includes conversionLinks, per-record revisions and shareUrls; created and upload metadata appear when recorded. writesPerformed is false for a no-op/prepared state, true after a verified upload receipt or acknowledged write, and null in other pending states.

Fresh completion confirms full stored readback. Historical completion has confirmed:false. atomicSnapshot, causalityVerified, redirectBehaviorVerified, imageAvailabilityVerified and autoCreatedSegmentVerified remain false; storedCompletion is independent of current verification. A supplied thumbnail constitutes an effect even when visible text is unchanged.

| Phase | Continuation contract |
| --- | --- |
| prepared | Recheck context/content and start untouched work. An unstarted thumbnail needs original apply bytes after restart. |
| grant-sent or upload-sent | Preserve and inspect; attempted issuance/upload never repeats, including after a definite failure. |
| uploaded | A durable image receipt, or completed no-image preparation, permits the untouched write after fresh checks. |
| write-sent | Readback only; never repeat the write. Known ID can reconcile; missing durable create ID remains unknown and cannot be adopted by name. |
| done | Historical status/resume with confirmed:false; completed journal remains immutable. Read again for current state. |

Use original requestId and input, including revisions. Changed input under the same ID returns REQUEST_ID_CONFLICT. Invalid saved identity/progress returns TASK_STATE_UNVERIFIED; preserve the journal. Full content/context conflicts must not be bypassed by a new ID. Pending results can include requiredAction, lastResult and journalPersistence. Status does not dispatch effects; resume may continue untouched stages. No automatic recache is part of this workflow.

The single console destination opens `/channels/{channelCode}/conversion-links/create` until create identity is known, then detail `/{id}`; update also opens detail. Demo omits live links.

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