# membership_product_create
Create one product under an existing membership from the complete source wizard
choices. This configures a sale or subscription offer; it does not purchase it,
charge funds, issue serial keys or enroll a member.
## Observe the parent membership
Use [memberships_get](publ_memberships_get.md) detail and copy its `revision`.
This is the membership revision, not the product-list page revision. Choose the
parent by its `mid`, not its numeric row ID. The selected-channel owner or a
delegate with MANAGE_MONETIZATION is required.
```json
{"channelCode":"selected-channel-code","view":{"kind":"detail","membershipMid":"returned-membership-mid"}}
```
## Compose the wizard
```json
{
"requestId":"product-create-001",
"channelCode":"selected-channel-code",
"membershipMid":"returned-membership-mid",
"expectedMembershipRevision":"0000000000000000000000000000000000000000000000000000000000000000",
"title":"Monthly access","alias":"One month access",
"availablePurchaseMethodsType":"CASH_ONLY",
"offer":{"kind":"paid-once","periodInDays":30,"feeAmount":"5000"},
"dryRun":true
}
```
Replace the revision with the current observed value. Title and displayed alias
are trimmed and checked against the active source 4–20 UTF-16-unit bounds. Two
supplementary-plane emoji occupy four units. The older generic form defaults use
different limits; the active screen controls determine this contract.
| offer.kind | periodInDays | feeAmount |
| --- | --- | --- |
| paid-once |30,90,180,365 or null for unlimited | Required |
| subscription |30,90,180,365 | Required; period maps to the recurring type |
| free |1,3,7,14,30 | Omit |
| coupon-paid |30,90,180,365 or null | Required |
| coupon-free |1,3,7,14,30,90,120,180,365 or null, subject to eligibility | Omit |
There is no ordinary FREE_INFINITE choice in the active wizard. A finite offer
sends expiryPeriodInDays; a recurring offer encodes its period in its type. The
bridge composes the same type/title/alias/purchase-method/fee/period source body
instead of exposing raw API fields or step-by-step Next calls.
Amounts are decimal strings without grouping and use the observed channel base
currency. KRW accepts 300–10,000,000 in 100-unit steps; ordinary USD accepts 5–10,000
in whole-dollar units after the source formatting check. Exact source channel
exceptions change minima; the two source USD exception channels allow cents.
Creation intentionally follows its active legacy validation, while extension
pricing follows the newer currency policy and ordinarily allows USD cents.
The body formats USD to two decimals and KRW to an integer. Other base currencies
are outside the current membership contract. Free offers omit fee; readback must
confirm zero in the parent currency.
CASH_ONLY is the default. Only ordinary paid-once permits CASH_AND_CP; fresh valid
Channel Point branding is required during preparation and immediately before
submission. No branding is created, and no points are issued or charged.
Coupon offers require the source coupon-capable plans: NORMAL_BUSINESS,
NORMAL_PRO, NORMAL_FROM_ENTERPRISE, ENTERPRISE_TRIAL, ENTERPRISE_ENTERPRISE,
PARTNER_TRIAL, PARTNER_PARTNER or INTERNAL_INTERNAL. Other/future plans are not
assumed eligible. Ordinary free/paid/recurring creation does not gain an invented
selling-plan or monetization-activation gate.
Production coupon-free periods above seven days or unlimited require current
source manual-action permission for the selected channel or authenticated
operator. The runtime reads its configured public primary/fallback resource with
no credentials, identity query or channel header; only the derived boolean enters
the task. Missing/unavailable policy is not permission. Nonproduction unlimited
free coupons remain unavailable for FREE_/NORMAL_ plans. Environment comes from
the registered API environment, never caller arguments. There is no bypass flag.
## Apply, verify and recover
A fresh dry run returns PLAN_READY, body and derived policy without a POST or new
task. Submit the same values with dryRun false to create. The parent revision,
operator, channel and relevant permission/branding are rechecked before one POST.
A validated receipt ID is saved, followed by an independent read of that exact ID
under the same parent route. Requested title/alias/type/fee/explicit period and
purchase methods must agree; the parent metadata and access associations must be
preserved. Equivalent decimal trailing zeros do not change an amount. The source
receipt's observed selling state is returned, not silently replaced or activated.
MEMBERSHIP_PRODUCT_CREATED confirms that bounded readback. The result includes
subscriptionProduct, the exact created ID, taskId and taskType
membership-product-create. This does not prove checkout, visitor access, causal
ownership or an atomic snapshot. Benefits and installed-feature association come
from the existing parent; attaching individual content has its own workflow.
Keep the requestId. task_status reads its saved progress. task_resume only reads:
prepared work needs the identical original create call, sent creation is never
repeated, and a missing receipt ID remains MEMBERSHIP_PRODUCT_CREATE_ID_UNRESOLVED.
Never adopt a same-title/alias/price row or issue another create to bypass unknown
outcome. A known receipt can recover after a temporary read outage. Completed
replays are historical, not a fresh current product check.
One console destination opens this membership's creation wizard for the plan or the exact created product editor after confirmation. Use the returned link. Source has no product delete/reset operation. Suspending selling with [membership_product_apply](membership_product_apply.md) retains the new record and is not an undo of creation. Existing base title/price/type/period remain locked; only source-supported alias, purchase methods, selling state and extension settings are editable through that method.
## Inputs
| Field | Required here | Contract |
| --- | --- | --- |
| `membershipMid` | Yes | string; min length 1; max length 512; Copy membership.mid from the selected memberships_get detail, not a channel code, numeric membership ID or member/profile ID. |
| `title` | Yes | string; min length 2; max length 20 |
| `alias` | Yes | string; min length 2; max length 20 |
| `availablePurchaseMethodsType` | No | `CASH_ONLY`, `CASH_AND_CP`; default `CASH_ONLY` |
| `offer` | Yes | one of: `paid-once`, `subscription`, `free`, `coupon-paid`, `coupon-free` (by `kind`) |
| `offer.periodInDays` | Yes | When `offer.kind` is `paid-once`: `30` or `90` or `180` or `365` or null |
| `offer.feeAmount` | Yes | When `offer.kind` is `paid-once`: string; pattern `^(?:0\|[1-9]\d{0,7})(?:\.\d{1,2})?$` |
| `offer.periodInDays` | Yes | When `offer.kind` is `subscription`: `30` or `90` or `180` or `365` |
| `offer.feeAmount` | Yes | When `offer.kind` is `subscription`: string; pattern `^(?:0\|[1-9]\d{0,7})(?:\.\d{1,2})?$` |
| `offer.periodInDays` | Yes | When `offer.kind` is `free`: `1` or `3` or `7` or `14` or `30` |
| `offer.periodInDays` | Yes | When `offer.kind` is `coupon-paid`: `30` or `90` or `180` or `365` or null |
| `offer.feeAmount` | Yes | When `offer.kind` is `coupon-paid`: string; pattern `^(?:0\|[1-9]\d{0,7})(?:\.\d{1,2})?$` |
| `offer.periodInDays` | Yes | When `offer.kind` is `coupon-free`: `1` or `3` or `7` or `14` or `30` or `90` or `120` or `180` or `365` 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. |
| `irreversibleConfirmed` | No | `true`; true only after the person confirmed again, when the preview has requiresReconfirmation: the action cannot be undone. |