publ betaReference (MCP guide)
Open publ console

membership_create

# membership_create

Create one GENERAL membership with the original wizard's title, card styling, five optional benefit statements, optional original card images and installed-feature selection in one operation. Artwork implementation:

## Prepare the choices

Call [memberships_get](publ_memberships_get.md) without an existing membership:

```json
{"channelCode":"selected-channel-code","view":{"kind":"create-options"}}
```

The response includes built-in `cardDefaults` and `accessOptions`. Choose installed
numeric IDs whose `canSelect` is true and copy `accessOptions.revision`. This is a
separate installed-feature revision, not an existing membership revision. An inactive
eligible feature can be selected without being activated. Filtered features cannot
be newly granted; do not copy display names or store codes into the ID array.

## Compose and submit

Replace the example IDs and revision with returned values. Without image uploads,
`dryRun:true` returns the complete planned request. With uploads, it returns the
pre-upload plan described below; no grant, upload, POST, new task record or reservation is made.
After reviewing the plan, send the same choices with `dryRun:false` or omit it.
Keep the same `requestId` for the logical operation.

The trimmed title must contain 4–50 UTF-16 code units. Five optional statements
(`benefitAlpha` through `benefitEcho`) each allow up to 50 units. Empty or omitted
statements are omitted from the source POST and must read back as empty strings.
Benefit text alone grants no feature access. Omitted image slots keep the source built-in
background and logo; explicit uploads or default-logo removal are supported below.
Omitted card styling uses A with SOLID `#455EEF4D`; selectable designs are A/B.
Overlays are NONE, SOLID with `primary`, or GRADIENT with both `primary` and
`secondary`; colors use six/eight-digit hex. Arbitrary image URLs remain unsupported.

`access.installedPAppIds` is the complete desired set. An intentionally empty set
requires `confirmEmpty:true`. Missing, duplicate, filtered or changed choices fail
before dispatch. OWNER or DELEGATEE with MANAGE_MONETIZATION is required in the
exact selected channel. The original create control adds no paid-plan or active-
monetization prerequisite, so the bridge does not invent one. Backend rejection
still fails; it is not converted into completion.

After any requested image uploads are acknowledged, the workflow rechecks context/options,
records dispatch, sends one complete POST, saves the validated receipt and independently
reads its exact membership mid. Success is
MEMBERSHIP_CREATED only when identity, title, all card/benefit fields and exact
installed ID/code associations agree. It creates no subscription product, price,
purchase, payment, feature activation or ACL change. Use
[membership_update](publ_membership_update.md) for later supported edits and
[membership_display_apply](publ_membership_display_apply.md) for a separate display choice.

## Create with original card artwork

The image fields belong directly inside card, not inside an existing-membership patch. They can accompany card styling, benefits and the complete access selection in the same creation request.

| Input | Accepted value and effect |
| --- | --- |
| card.backgroundImage | Omit for the built-in background, use `"reset-default"` to select it explicitly, or `{kind:"upload",file:{filename,base64}}` for original PNG/JPEG/GIF up to and including 5 MiB (5242880 bytes). |
| card.logoImage | Omit for the built-in logo, use `"remove"` to create with an empty logo reference, or `{kind:"upload",file:{filename,base64}}` for original PNG/SVG up to 1 MiB (1048576 bytes). |

The background limit follows the source chooser; the logo limit is bridge transport policy. Supply a matching basename and canonical base64 without a local path, URL, data-URL prefix or whitespace. Original bytes are preserved without resizing. The shared parser checks bounded file/container structure, not complete decoding, SVG sanitization or browser rendering. The host must configure the channel-bound resource gateway, bucket and region; the caller supplies no credentials or guessed pApp consumer. Missing configuration returns UPLOAD_NOT_CONFIGURED.

This example reuses tiny illustrative PNG/SVG files to show the input shape. Replace them with the intended original artwork, and replace the installed IDs/options revision from create-options. It is a dry-run example, not evidence of an upload or a rendered membership card.

```json
{
  "requestId": "membership-create-artwork-001",
  "channelCode": "selected-channel-code",
  "title": "Community membership",
  "access": {
    "installedPAppIds": [
      17,
      24
    ],
    "expectedOptionsRevision": "0000000000000000000000000000000000000000000000000000000000000000"
  },
  "dryRun": true,
  "card": {
    "designSchema": "B",
    "backgroundImage": {
      "kind": "upload",
      "file": {
        "filename": "example-background.png",
        "base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGNwjXv/HwAFEAKS9fMIJwAAAABJRU5ErkJggg=="
      }
    },
    "logoImage": {
      "kind": "upload",
      "file": {
        "filename": "example-logo.svg",
        "base64": "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxIiBoZWlnaHQ9IjEiPjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9IiM0NTVFRUYiLz48L3N2Zz4="
      }
    }
  },
  "benefits": {
    "benefitAlpha": "Member news"
  }
}
```

To create with the built-in background and no default logo, use this separate request instead of an upload object for those slots:

```json
{
  "requestId": "membership-create-no-logo-001",
  "channelCode": "selected-channel-code",
  "title": "Community membership",
  "access": {
    "installedPAppIds": [
      17,
      24
    ],
    "expectedOptionsRevision": "0000000000000000000000000000000000000000000000000000000000000000"
  },
  "dryRun": true,
  "card": {
    "backgroundImage": "reset-default",
    "logoImage": "remove"
  }
}
```

Removal clears the new card's logo reference; it does not delete a file. Explicit reset-default uses the same built-in background as omission. Neither action adds a follow-up membership update: the intended values belong to the original single creation POST.

## Recovery and destination

Use task_status/task_resume with `taskType:"membership-create"` and the returned
taskId. Status reads the journal. Status/resume never issue image grants, upload or
POST, including prepared tasks with already uploaded files. A prepared task can report
MEMBERSHIP_CREATE_NOT_DISPATCHED; only the identical original create input, including
original image bytes, can continue unattempted effects after rechecking. Acknowledged
uploads are retained; uncertain grant-sent/upload-sent stages are never replayed. A sent task with no validated receipt ID returns
MEMBERSHIP_CREATE_ID_UNRESOLVED. Preserve it and inspect the console; never guess by
title or retry with a different requestId. There is no automatic membership deletion
or rollback. A known receipt permits only that record's fresh read and comparison;
matching recovery returns MEMBERSHIP_CREATE_RECONCILED. Completed replay is historical.

The single canonical console link opens the creation form for a plan, the created membership editor after verified completion, or the membership list when no verified created identity is available. It uses the centrally configured console origin. Demo has no real console destination. Sequential checks do not provide atomic compare-and-swap, causal ownership, rendered appearance or visitor-access proof.

## Inputs

| Field | Required here | Contract |
| --- | --- | --- |
| `title` | Yes | string; min length 4; max length 50 |
| `card` | No | object |
| `card.designSchema` | No | `A`, `B` |
| `card.overlay` | No | one of: `NONE`, `SOLID`, `GRADIENT` (by `type`) |
| `card.overlay.primary` | Yes | When `card.overlay.type` is `SOLID`: string; pattern `^#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?$` |
| `card.overlay.primary` | Yes | When `card.overlay.type` is `GRADIENT`: string; pattern `^#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?$` |
| `card.overlay.secondary` | Yes | When `card.overlay.type` is `GRADIENT`: string; pattern `^#[0-9a-fA-F]{6}(?:[0-9a-fA-F]{2})?$` |
| `card.backgroundImage` | No | `reset-default` or one of: `local-file`, `more-parts`, `complete`, `cancel` (by `kind`) |
| `card.backgroundImage.filename` | Yes | When `card.backgroundImage.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. |
| `card.backgroundImage.byteLength` | Yes | When `card.backgroundImage.kind` is `local-file`: integer; greater than 0; The exact file size in bytes (for example from `wc -c < FILE` or `stat`). |
| `card.backgroundImage.sha256` | No | When `card.backgroundImage.kind` is `local-file`: string; pattern `^[0-9a-f]{64}$` |
| `card.backgroundImage.uploadId` | Yes | When `card.backgroundImage.kind` is `more-parts`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `card.backgroundImage.fromPart` | Yes | When `card.backgroundImage.kind` is `more-parts`: integer; minimum 1; maximum 10000 |
| `card.backgroundImage.uploadId` | Yes | When `card.backgroundImage.kind` is `complete`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `card.backgroundImage.parts` | Yes | When `card.backgroundImage.kind` is `complete`: array; min items 1; max items 10000 |
| `card.backgroundImage.parts[].partNumber` | Yes | When `card.backgroundImage.kind` is `complete`: integer; minimum 1; maximum 10000 |
| `card.backgroundImage.parts[].etag` | Yes | When `card.backgroundImage.kind` is `complete`: string; min length 1; max length 200 |
| `card.backgroundImage.measured` | No | When `card.backgroundImage.kind` is `complete`: object; What the video measures on this computer, read with measureCommand. |
| `card.backgroundImage.measured.durationSeconds` | Yes | When `card.backgroundImage.kind` is `complete`: number; maximum 1000000; greater than 0; Video duration in seconds (ffprobe format.duration). |
| `card.backgroundImage.measured.frameRate` | Yes | When `card.backgroundImage.kind` is `complete`: number; maximum 1000; greater than 0; Frames per second (ffprobe avg_frame_rate, for example 30000/1001 = 29.97). |
| `card.backgroundImage.measured.width` | Yes | When `card.backgroundImage.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `card.backgroundImage.measured.height` | Yes | When `card.backgroundImage.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `card.backgroundImage.measured.frameRateMode` | No | When `card.backgroundImage.kind` is `complete`: `CFR`, `VFR` |
| `card.backgroundImage.measured.minFrameRate` | No | When `card.backgroundImage.kind` is `complete`: number; maximum 1000; greater than 0 |
| `card.backgroundImage.measured.maxFrameRate` | No | When `card.backgroundImage.kind` is `complete`: number; maximum 1000; greater than 0 |
| `card.backgroundImage.uploadId` | Yes | When `card.backgroundImage.kind` is `cancel`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `card.logoImage` | No | `remove` or one of: `local-file`, `more-parts`, `complete`, `cancel` (by `kind`) |
| `card.logoImage.filename` | Yes | When `card.logoImage.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. |
| `card.logoImage.byteLength` | Yes | When `card.logoImage.kind` is `local-file`: integer; greater than 0; The exact file size in bytes (for example from `wc -c < FILE` or `stat`). |
| `card.logoImage.sha256` | No | When `card.logoImage.kind` is `local-file`: string; pattern `^[0-9a-f]{64}$` |
| `card.logoImage.uploadId` | Yes | When `card.logoImage.kind` is `more-parts`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `card.logoImage.fromPart` | Yes | When `card.logoImage.kind` is `more-parts`: integer; minimum 1; maximum 10000 |
| `card.logoImage.uploadId` | Yes | When `card.logoImage.kind` is `complete`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `card.logoImage.parts` | Yes | When `card.logoImage.kind` is `complete`: array; min items 1; max items 10000 |
| `card.logoImage.parts[].partNumber` | Yes | When `card.logoImage.kind` is `complete`: integer; minimum 1; maximum 10000 |
| `card.logoImage.parts[].etag` | Yes | When `card.logoImage.kind` is `complete`: string; min length 1; max length 200 |
| `card.logoImage.measured` | No | When `card.logoImage.kind` is `complete`: object; What the video measures on this computer, read with measureCommand. |
| `card.logoImage.measured.durationSeconds` | Yes | When `card.logoImage.kind` is `complete`: number; maximum 1000000; greater than 0; Video duration in seconds (ffprobe format.duration). |
| `card.logoImage.measured.frameRate` | Yes | When `card.logoImage.kind` is `complete`: number; maximum 1000; greater than 0; Frames per second (ffprobe avg_frame_rate, for example 30000/1001 = 29.97). |
| `card.logoImage.measured.width` | Yes | When `card.logoImage.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `card.logoImage.measured.height` | Yes | When `card.logoImage.kind` is `complete`: integer; maximum 100000; greater than 0 |
| `card.logoImage.measured.frameRateMode` | No | When `card.logoImage.kind` is `complete`: `CFR`, `VFR` |
| `card.logoImage.measured.minFrameRate` | No | When `card.logoImage.kind` is `complete`: number; maximum 1000; greater than 0 |
| `card.logoImage.measured.maxFrameRate` | No | When `card.logoImage.kind` is `complete`: number; maximum 1000; greater than 0 |
| `card.logoImage.uploadId` | Yes | When `card.logoImage.kind` is `cancel`: string; pattern `^upl_[A-Za-z0-9_-]{22}$` |
| `benefits` | No | object |
| `benefits.benefitAlpha` | No | string; max length 50 |
| `benefits.benefitBravo` | No | string; max length 50 |
| `benefits.benefitCharlie` | No | string; max length 50 |
| `benefits.benefitDelta` | No | string; max length 50 |
| `benefits.benefitEcho` | No | string; max length 50 |
| `access` | Yes | object; The complete intended installed-feature set and current options revision from memberships_get view.kind create-options. Empty requires confirmEmpty:true. |
| `access.installedPAppIds` | Yes | array; items: integer; greater than 0; max items 10000; The complete desired installed-feature ID set, not additions only. Omit access to preserve all associations. Copy IDs from memberships_get detail with includeAccessOptions:true. |
| `access.confirmEmpty` | No | `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. |