content_settings_apply
The article_product_*, CREATE_ARTICLE_CATALOG, DELETE_ARTICLE_CATALOG and other existing action/confirmation names remain compatibility identifiers; they do not restrict those shared functions to Article. Article manager-comment notifications, category controls, conversion and assignment remain Article-only. Timeline timeline_notifications reads its two post-create events, with no save or mutation revision. Source commit references below record introduction history; current support follows this family policy.
Save explicitly chosen app settings or original-article manager-comment notification preferences, or use the source-supported Article/Timeline catalog membership, original-article pricing, catalog-detail, creation and deletion actions below, or create/edit an shared Article/Timeline app-unlock product.
Read the shared content contract for authorization, app identity, result interpretation and recovery.
Inputs
For ordinary settings, require channelCode, pAppCode, requestId, expectedRevision from content_settings_get, and settings containing 1–30 public names returned by that read. dryRun is optional and defaults to false. Use desired values rather than toggles. Unknown names, unsupported choices and invalid combinations are refused.
| Controls | Values and limits |
|---|---|
| writing / reactions | SELLER_USER or SELLER_ONLY; REPEATABLE_REACTION, SINGLE_RECOMMENDATION or NONE. |
| postsPerPage | 5, 10, 20 or 30 where offered. |
| tabOrder | Every offered tab exactly once. Article: HOME/POSTS/SERIES/CATEGORIES; photobook: HOME/SERIES; resource: POSTS/SERIES. Ordering CATEGORIES does not enable that tab. |
| Visitor tab labels | Usually 1–12 UTF-16 units; timeline seller/subscriber and question labels 2–12. Source punctuation restrictions apply. Notice section titles allow 1–30. |
| guidelineHtml | 2–2000 serialized UTF-16 units; conservative HTML bound, not visible-text equivalence. |
| watermark / watermarkOpacity | Photobook CENTER/CORNER/MIXED and 0.2/0.45/0.7. |
| Other advertised switches | Exact booleans for comments, reactions, visibility, protection, moderation or zoom. |
At least one relevant visitor tab must remain visible. Missing stored keys are not assumed enabled. Outlink/welcome have no editable settings. Paid extensions and plans cannot be activated here.
Save and recovery
For the ordinary settings path, the source PUT contains only configOverride keys mapped from the requested public settings. Readback compares the expected merged configuration and the remaining installed-app record. A match returns CONTENT_SETTINGS_SAVED, taskId and a new revision. This does not prove actual visitor layout, access protection or restoration of previously absent keys.
Use taskType content-settings. The sent boundary is saved before transport. An uncertain result stays CONTENT_SETTINGS_OUTCOME_UNVERIFIED; identical apply or task_resume performs readback only, never another PUT. Completed replay is historical. A dry run prepares no save; a dry run against an existing task is not a new current-state verification.
Input example
This is an input illustration, not a recorded call. Replace the channel, app, observed IDs and all-zero revision with values from the matching authorized read. A dry run does not save changes.
{
"channelCode": "selected-channel",
"pAppCode": "A00002",
"requestId": "example-request-001",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"dryRun": true,
"settings": {
"comments": true,
"postsPerPage": 20
}
}
Article category-tab visibility
Supply false to show the tab or true to hide it. Both transitions require a freshly read nonempty published-category inventory; the prerequisite is checked at dispatch and readback. It also applies when categories would become the only visible tab. The last visible tab remains protected. Missing discovery yields ARTICLE_CATEGORY_DISCOVERY_REQUIRED; no published category yields ARTICLE_PUBLISHED_CATEGORY_REQUIRED. Do not infer a default from an absent setting.
New categories start unpublished and the source publication selector is disabled. This writer does not publish categories, change the fixed category title, assign posts or upload covers. Continue using the original settings revision and content-settings task. A sent uncertainty only reads back.
Illustrative plan, usable only after a current read confirms an existing published category:
{
"channelCode": "selected-channel",
"pAppCode": "B00001",
"requestId": "article-category-tab-001",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"settings": {
"hideCategories": false
},
"dryRun": true
}
Article catalog membership actions
Do not supply both. Require the selected channel, exact B00001/B9xx01 or A00001/A9xx01 instance, requestId, fresh catalog-membership expectedRevision and optional dryRun (default false). OWNER is required; delegates remain unsupported pending a separately verified general-permission/content-only policy. Linking or unlinking can immediately change access for existing subscribers when executed.
Set action.kind to catalog_memberships, catalogId to an observed POST catalog's business ccid and confirmation to CHANGE_CATALOG_MEMBERSHIPS. Then select one change:
| change.kind | Fields and behavior |
|---|---|
| link | membershipIds: 1–100 distinct current channel membership mids. Already connected choices are omitted from the write; an entirely connected selection is a verified no-op. |
| unlink | membershipId: one currently connected mid. The source permits unlinking the final connection; no messenger-room last-membership restriction is copied. |
Both catalog and membership business IDs accept 1–128 ASCII letters, digits, underscores or hyphens. No catalog/product/membership creation, subscriber enrollment, individual-price editing, content assignment/publication or notification is added. The existing catalog reader must first return complete scoped inventories.
The closed checker permits only the bounded article/OWNER contract; shared locks, current credential/profile, authorization and exact installation are verified before dispatch, with authentication checked again after it and domain context checks around each call. Source-required channel headers and consumer queries are retained. GET is 200/data; mutation success requires HTTP 204 with no data body. Ordinary settings keep their existing response handling. Redirects and automatic POST retries are disallowed. Older hosts missing the port fail closed with CONTENT_CATALOG_TRANSPORT_REQUIRED, including valid dry runs.
With that host dependency satisfied, dryRun returns PLAN_READY with beforeMembershipIds, afterMembershipIds, noop and writesPerformed:false. Before a non-dry-run dispatch, the durable catalog guard and sent journal bind the original request, connection, channel, exact installation and catalog. A fresh requestId cannot bypass an unresolved catalog change; missing/corrupt guard targets fail closed. There is no automatic guard deletion or rollback.
Use the returned taskType content-catalog, not content-settings. Status reports historical saved progress. Repeating the identical sent apply or using resume only reads back, never repeats POST. Verified replay is historical and must not be presented as current access verification. Keep the original task through a lost response or uncertain persistence; do not issue a new mutation to guess the result.
CONTENT_CATALOG_MEMBERSHIPS_SAVED requires the exact expected mid set plus unchanged retained membership rows, catalog/sibling fields, candidate metadata, individual pricing and connected target content. Unexplained computed pricing-flag or metadata changes remain CONTENT_CATALOG_OUTCOME_UNVERIFIED; membership count is not a rule for inventing those flags. Readback compares stored linkage, not visitor access, billing, exclusive causality or restoration. Separate reads are not an atomic upstream transaction.
Illustrative link plan, after a complete authorized catalog-membership read and host integration:
{
"channelCode": "selected-channel",
"pAppCode": "B00001",
"requestId": "catalog-link-001",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"action": {
"kind": "catalog_memberships",
"catalogId": "observed_catalog_ccid",
"confirmation": "CHANGE_CATALOG_MEMBERSHIPS",
"change": {"kind": "link", "membershipIds": ["observed_membership_mid"]}
},
"dryRun": true
}
Illustrative unlink plan; the membership must currently be connected:
{
"channelCode": "selected-channel",
"pAppCode": "B00001",
"requestId": "catalog-unlink-001",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"action": {
"kind": "catalog_memberships",
"catalogId": "observed_catalog_ccid",
"confirmation": "CHANGE_CATALOG_MEMBERSHIPS",
"change": {"kind": "unlink", "membershipId": "observed_membership_mid"}
},
"dryRun": true
}
Original article individual pricing
It is OWNER-only for the exact installed article or timeline instance (B00001/B9xx01 or A00001/A9xx01), without alias remapping or new grants. Read catalog_pricing, retain its fresh revision, and supply channelCode, pAppCode, requestId, expectedRevision and action:{kind:catalog_pricing,catalogId,confirmation:CHANGE_CATALOG_PRICING,change}. Do not combine action with ordinary settings. dryRun defaults to false; use true to preview before an authorized change. Executed configuration can affect access to existing content.
change.kind:save creates an absent price or edits an existing one with a full fields object:
| Field | Input contract |
|---|---|
| representationalName | Trimmed string, 2–40 UTF-16 units. |
| currency | Optional consistency check. Omit to use the freshly verified channel KRW/USD currency; an explicit mismatch is rejected. |
| sellingPrice | Plain currency string in whole units, optionally ending in .00; no grouping, exponent or leading zeros. Supply currency units, not stored CP. |
| originalPrice | Same representation and limits; omitted or null means sellingPrice, matching an empty source form field. Must not be less than sellingPrice. |
| unlockActionExpiryPeriodInDays | 1, 3, 7, 15, 30, 60, 90, 180, 365, or null for unlimited. |
| isPurchaseAvailable | Required boolean. |
| availablePurchaseMethodsType | CASH_ONLY or CASH_AND_CP. The latter requires fresh verified point branding. |
Both amounts must meet the returned priceLimits. KRW defaults to 300–100,000,000 in units of 100. Active original-screen exceptions set the minimum to 15,000 for channel 3200, 5,000 for 93, 20,000 for 5814 and 100 for 887/17727; use the returned limit rather than constructing a channel code from these numbers. The commented 4860 exception is inactive. USD uses 5–100,000 in whole-dollar units. Membership-product maxima and USD exceptions are different and must not be reused. A wrong current currency returns CONTENT_CATALOG_PRICE_CURRENCY; invalid amount/original-price rules return CONTENT_CATALOG_PRICE_INVALID.
The source writer converts KRW by 1 and USD by 1000 into CP amounts. Returned individualPricing and preview afterPricingFields retain those raw stored units. Original USD console display parity is unverified; do not relabel CP amounts as currency display values. Missing/invalid branding blocks CASH_AND_CP with CONTENT_CATALOG_BRANDING_UNVERIFIED. Transport/authentication errors never become permission for CASH_ONLY. Currency and branding are revision-bound and rechecked before dispatch and during preservation verification.
change.kind:discard removes an existing price, not the catalog or linked posts. Missing pricing returns CONTENT_CATALOG_PRICE_MISSING before dispatch. There is no automatic restoration or guarantee that recreating a price restores its identity. Save/discard use the existing authenticated closed catalog transport: save expects HTTP 200 and ignores its body; discard sends an empty body and expects 204 with no body. Membership-link and content-link receipt contracts remain separate.
A successful non-dry-run result is CONTENT_CATALOG_PRICING_SAVED. Save that changes no requested fields performs readback with noop:true and no POST. An edit must preserve price identity, internal name, deactivation state, insertedAt, sales windows, extension fields and unknown metadata; only price updatedAt is excluded from that comparison. Catalog/sibling records, memberships, target rows, linked post business fields, currency and branding are also preserved, excluding only the established post counters/updatedAt. Creating an absent price verifies the attached price at the known catalog, not exclusive causality under concurrent changes.
No automatic catalog pricing-flag change is established by this contract. Unexplained flag changes after save/create/discard remain unverified, even if they appear server-derived. They are not silently normalized. A lost or unexpected receipt, changed preserved state or failed readback returns CONTENT_CATALOG_OUTCOME_UNVERIFIED. The shared persistent catalog guard blocks fresh membership/content/pricing requests until the original task is reconciled; CONTENT_CATALOG_PENDING is not permission to use a new request ID. Sent retries/resume only read back and never repeat save/discard. Completed replay is historical, not fresh verification. Preserve the original input, request and task identity.
The exact destination is the selected app catalog's pricing-policy/individual-pricing screen, bound to content-catalog, catalogId and catalogView:pricing. Use returned validated navigation rather than building a URL or selecting an alias. Actual purchases, settlement and service billing retain their human-console boundary.
Illustrative full-form preview; use limits, catalog and revision from the fresh read. Currency may be omitted because the channel read already supplies it. The all-zero revision is a placeholder and cannot authorize a change:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-price-save-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","dryRun":true,"action":{"kind":"catalog_pricing","catalogId":"observed_catalog_ccid","confirmation":"CHANGE_CATALOG_PRICING","change":{"kind":"save","fields":{"representationalName":"Article access","sellingPrice":"20000","originalPrice":"30000","unlockActionExpiryPeriodInDays":null,"isPurchaseAvailable":true,"availablePurchaseMethodsType":"CASH_ONLY"}}}}
Illustrative removal preview for an existing price, using its fresh revision:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-price-discard-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","dryRun":true,"action":{"kind":"catalog_pricing","catalogId":"observed_catalog_ccid","confirmation":"CHANGE_CATALOG_PRICING","change":{"kind":"discard"}}}
Original article catalog details
OWNER is required. Read catalog_details, then pass its fresh revision as expectedRevision with the same channel/app/catalog, requestId and confirmation:CHANGE_CATALOG_DETAILS. Use action instead of ordinary settings; dryRun defaults to false.
Choose exactly one change per request:
| change.kind | Required field | Behavior |
|---|---|---|
| rename | title | Trimmed string, 2–40 UTF-16 units; changes the selected catalog title. |
| set_active | isActive | Explicit true or false; sets the desired state rather than toggling it. |
A body with both fields, a target-model change or an alias app is rejected. This detail action excludes creation, deletion, bulk activation and filtered search. The later catalog_create option handles creation separately. Activating or deactivating a catalog can immediately affect access to linked content, but does not publish posts, change a price form, purchase a product, enroll a member or send a notification.
The existing closed transport sends one PUT to the selected catalog with its exact consumer query, authenticated channel header and one-field body. It requires a data receipt containing the original contentCatalog identity. It does not invent a fixed success status: the original update contract leaves it unspecified, while common HTTP still rejects redirects and non-2xx results. A valid receipt alone is insufficient; fresh catalog list/detail and linked-post references must agree on the requested value.
CONTENT_CATALOG_DETAILS_SAVED requires preservation of every other catalog/sibling field, individual price, membership choice, target row, other post catalog reference and unrequested post business field. The established post counters/updatedAt remain excluded, but unexplained catalog metadata or timestamp changes are not silently accepted. A no-op performs verification without PUT and returns noop:true. Currency and branding are not read for these detail operations.
Details, membership links, content links and pricing share the same persistent per-catalog guard. Uncertainty in any kind blocks fresh requests of the other kinds. Keep the original task/request/input; sent retries and task_resume only read back and never resend PUT. Mismatched receipt/readback or changed preserved fields remain CONTENT_CATALOG_OUTCOME_UNVERIFIED. Completed replay is historical, not current confirmation or successful restoration.
Use the validated returned catalog detail link with catalogView:details. Existing upload holds and the other unfinished B00001 options remain separate.
Illustrative rename preview; replace the all-zero revision placeholder with the details read's revision:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-catalog-rename-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","dryRun":true,"action":{"kind":"catalog_details","catalogId":"observed_catalog_ccid","confirmation":"CHANGE_CATALOG_DETAILS","change":{"kind":"rename","title":"Article collection"}}}
Illustrative single-catalog deactivation preview with its own fresh revision:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-catalog-state-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","dryRun":true,"action":{"kind":"catalog_details","catalogId":"observed_catalog_ccid","confirmation":"CHANGE_CATALOG_DETAILS","change":{"kind":"set_active","isActive":false}}}
Original article catalog creation
Read catalog_creation options, then supply its revision as expectedRevision, the same channel/app, a unique requestId and optional dryRun, which defaults to false. Use action instead of ordinary settings. This uses the existing content-catalog task; no method or task type is added.
| Action field | Input contract |
|---|---|
| kind | catalog_create. |
| confirmation | CREATE_ARTICLE_CATALOG. |
| title | Trimmed string, 2–40 UTF-16 units. |
| membershipIds | Optional array, default []; at most 100 distinct existing business mid values from the fresh candidates. Each accepts 1–128 ASCII letters, digits, underscores or hyphens. |
| pricing | Optional complete individual-price fields, directly in this object, without change or fields wrappers. |
Choose at least one membership or pricing, or both. No catalogId, caller-selected targetModel, initial post IDs, active-state setting, arbitrary sales windows or raw CP input is accepted. The target is POST. Unknown membership IDs fail with CONTENT_MEMBERSHIP_UNKNOWN; a full 2000-catalog inventory fails with CONTENT_CATALOG_LIMIT. Catalogs, membership choices, currency and branding must still match the creation revision; a mismatch during initial revalidation returns CONTENT_CONFLICT before dispatch. This option does not create a membership product, enroll a subscriber or purchase anything.
For pricing, supply representationalName, sellingPrice, unlockActionExpiryPeriodInDays, isPurchaseAvailable and availablePurchaseMethodsType; currency is an optional match check against the verified channel value; originalPrice is optional and omitted/null means sellingPrice. Use the fresh channel's KRW/USD limits. CASH_AND_CP requires valid current point branding, and cash-only or membership-only creation cannot bypass failed currency/branding reads. Stored CP units and the unverified USD console-display conversion remain as described in the price section.
The outgoing body derives membership/individual-price flags from those choices, includes only selected membership IDs and omits individualCatalogPricing without pricing. A supplied price receives source defaults isDeactivated:false and null salesStartAt/salesEndAt. availablePurchaseMethodsType is the selected price method or CASH_ONLY without pricing. Other new catalog defaults, including activation, are observed from the response rather than promised by this input.
A fresh dryRun returns PLAN_READY, plannedCatalog and writesPerformed:false without creation. A non-dry-run writes the durable instance guard and sent journal before one POST through the existing closed common catalog transport. It sends the selected channel header and exact consumer query and requires a data receipt containing contentCatalog. There is no invented fixed success status; common HTTP rejects redirects and non-2xx responses. Currency/branding are checked again before dispatch. Currency/branding drift after journaling retains the original task as unverified. A recorded sent phase alone does not prove that an effect occurred.
The receipt must identify a correctly scoped POST catalog with the requested title and both a new numeric ID and business ccid absent from the before inventory. The identity must be durably saved before recovery can use it. A lost, malformed, foreign or existing-ID receipt, or a failed durable save, produces CONTENT_CATALOG_CREATION_UNVERIFIED with createdIdentityKnown:false and writesPerformed:unknown. Do not infer an ID from a title or list difference, submit another creation, or claim no catalog was created. Preserve the original task for reconciliation.
With a durably saved identity, readback checks that exact catalog, selected membership IDs, requested price/default fields, listed access flags and empty target/post sets. It also preserves older catalog records, membership candidates, currency and branding and checks agreement with the receipt's base catalog state. Changed preserved data or failed readback remains unverified; no rollback, cleanup or atomicity is promised.
CONTENT_CATALOG_CREATED returns taskId, catalogId, the verified snapshot and writesPerformed:true, with readback, preservation and createdIdentity verification true; console, visitor, restoration and atomicSnapshot remain false. It links to the returned catalog's exact detail screen with catalogView:details. It does not return a follow-up mutation revision: read catalog_details, catalog_pricing, catalog_memberships or catalog_contents as appropriate before another change. No posts are connected during creation.
An unresolved creation blocks fresh creation and fresh membership/content/pricing/detail mutations across the same connection, channel and exact installed app, returning CONTENT_CATALOG_CREATION_PENDING. A new request ID does not bypass it. Existing catalog tasks remain observable/reconcilable under their original per-catalog guards. Task status reports stored historical progress; task resume and identical apply retries only read back after a creation attempt. They never repeat POST. Without a saved identity, even readback recovery cannot discover one. Reusing an ID for different input returns REQUEST_ID_REUSED; completed replay is historical and writesPerformed:false, not fresh confirmation.
The later catalog_delete option is separate from creation. Bulk activation and unrelated pApp connections remain separate implementation work.
Illustrative membership-only preview. Replace the candidate mid and all-zero revision with the fresh creation-options result:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-catalog-create-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","dryRun":true,"action":{"kind":"catalog_create","confirmation":"CREATE_ARTICLE_CATALOG","title":"Member articles","membershipIds":["observed_membership_mid"]}}
Illustrative pricing-only preview. The membership list defaults empty; use the amounts and revision from the authorized read and its limits. Currency can be omitted:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-catalog-create-002","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","dryRun":true,"action":{"kind":"catalog_create","confirmation":"CREATE_ARTICLE_CATALOG","title":"Premium articles","pricing":{"representationalName":"Article access","sellingPrice":"20000","unlockActionExpiryPeriodInDays":null,"isPurchaseAvailable":true,"availablePurchaseMethodsType":"CASH_ONLY"}}}
Original article catalog deletion
It requires OWNER, the exact installed article or timeline instance (B00001/B9xx01 or A00001/A9xx01) and an existing POST catalog. B9xx01 aliases are not remapped. Read catalog_details and pass its fresh revision as expectedRevision with the same channel/app, the observed business ccid as action.catalogId, a unique requestId and action.confirmation:DELETE_ARTICLE_CATALOG. There is no change wrapper, title match, numeric-ID substitution or bulk selector. dryRun defaults to false.
The initial snapshot includes the full catalog inventory/detail, membership choices, individual pricing, targets and linked posts. Bounds remain 2000 catalogs/membership candidates and 200 targets. Changed state returns CONTENT_CONFLICT before a new dispatch. This action needs no additional currency/branding read. Review the selected catalog and access relationships before authorizing deletion; existing linked posts are to remain, with only this catalog association removed.
A fresh dryRun returns PLAN_READY with catalogToDelete, linkedPostIds, connectedMembershipIds, individualPricing, noop:false and writesPerformed:false. It exposes catalogDeletionTarget, not deletedCatalogId, and does not claim absence. The preview uses the same full snapshot and revision as the subsequent authorized apply.
The durable catalog guard and sent task are saved before a single DELETE to /seller/api/v2/channel-management/profitable/content-catalogs/{ccid}, using the selected channel header and exact consumer query. The closed contract requires no request body and HTTP 204 with no body; a 200 response, child path, alternate app or supplied body is rejected. The existing common adapter supports the verb, so no new port, method or task namespace is needed.
A receipt alone cannot confirm deletion. Fresh readback must establish all of the following:
- The complete catalog list excludes both the original numeric ID and ccid, with all other catalog rows unchanged.
- The exact original detail read returns UPSTREAM_HTTP_404.
- Complete channel membership candidates remain unchanged.
- Every previously linked post still exists with its original identity, business fields and other catalog references. Only the deleted catalog reference is removed. Established post activity counters and updatedAt retain their existing exclusions; other metadata changes remain unverified.
CONTENT_CATALOG_DELETED returns taskId, catalogDeletionTarget, deletedCatalogId, deletedCatalog:{id,ccid,title}, the remaining catalogs, preservedPostIds and writesPerformed:true. Verification records readback, preservation, detailAbsent and postReferencesRemoved as true. console, visitor, restoration, physicalPriceCleanup, purchaseRights and atomicSnapshot remain false. A fresh request for an already missing catalog is not an idempotent deletion preview; use the original saved task to resolve an earlier attempt.
Lost or invalid acknowledgements, incomplete absence or failed preservation produce CONTENT_CATALOG_OUTCOME_UNVERIFIED with writesPerformed:unknown and no deletedCatalogId. Retain the task and input. Resume or identical sent apply performs only absence/preservation reads; it never sends another DELETE, completes a partial deletion or recreates the catalog. A later read may verify the original outcome without repeating the effect. Changed input cannot reuse the request ID. Completed replay and status remain historical rather than fresh verification.
Deletion shares the existing per-catalog guard with membership/content/pricing/detail changes: unresolved work blocks fresh actions for that target. An unresolved catalog creation also blocks fresh deletion across the same installed instance. New request IDs cannot bypass either guard; existing original tasks remain reconcilable.
The validated deletion destination is the surviving selected-app monetization/catalogs list with consoleDestination:parent. It has no catalogId or detail marker. Planned/uncertain results use catalogDeletionTarget; only verified absence adds deletedCatalogId. A parent link is not evidence of deletion, and historical completed tasks do not gain fresh navigation. Use the returned link and explanation rather than constructing a link to the deleted detail.
Removing an explicitly allocated test catalog may be cleanup, but it is not restoration of an existing catalog identity, associations or purchase history. There is no automatic rollback or recreation. Existing upload HOLD and bulk activation remain separate. The later catalog_search read supplies filtered pages without a mutation revision; deletion still requires fresh catalog_details.
Illustrative deletion preview only. Replace the catalog and all-zero revision with an authorized fresh catalog_details result:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-catalog-delete-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","dryRun":true,"action":{"kind":"catalog_delete","catalogId":"observed_catalog_ccid","confirmation":"DELETE_ARTICLE_CATALOG"}}
Original article product creation and editing
Require OWNER on the exact installed article or timeline instance (B00001/B9xx01 or A00001/A9xx01) and a revision from the matching product preparation. Use action instead of ordinary settings. Exact installed B9xx01/A9xx01 aliases are supported. Delegate configuration and caller-selected consumer/origin are not supported.
| Action | Required fields outside action.fields |
|---|---|
| article_product_create | confirmation:CREATE_ARTICLE_PRODUCT; no productId. |
| article_product_edit | An observed numeric productId and confirmation:CHANGE_ARTICLE_PRODUCT. |
Both require channelCode, pAppCode, requestId and expectedRevision. requestId is 8–80 ASCII letters, digits, underscores or hyphens; expectedRevision is the 64-character lowercase hexadecimal preparation revision. dryRun is optional and defaults to false. Keep the complete original input for recovery. Different input, including switching between product and catalog actions, cannot reuse a saved request ID.
Article product fields and prices
| action.fields | Creation | Editing |
|---|---|---|
| name | Required internal name, 4–20 UTF-16 units. | Not accepted; preserved. |
| presentationalName | Required visible name, 4–20 UTF-16 units. | Required complete replacement value. |
| currency | Optional KRW/USD consistency assertion; omission uses the freshly checked channel currency. | Optional assertion matching the channel and existing selling-price currency; omission derives the checked currency and never converts it. |
| sellingPrice | Required decimal string obeying creation minimum/maximum and selling unit. | Not accepted; existing selling price is preserved. |
| originalPrice | Optional decimal string; omission uses sellingPrice. | Optional decimal string; omission preserves the existing original price. |
| expiryPeriodInDays | Required 1, 3, 7, 15, 30, 60, 90, 180 or 365. | Not accepted; preserved. |
| isDeactivated | Required boolean; true deactivates this product. | Required desired boolean, not a toggle. |
| availablePurchaseMethodsType | Required CASH_ONLY or CASH_AND_CP. | Required explicit choice, even if old metadata is absent. |
Amounts are nonnegative decimal strings with at most nine integer digits and two fractional digits, without exponents, signs or extra leading zeros. The schema is only the first check: the prepared currency rules below also apply. Null/empty originalPrice is rejected for both actions. In particular, empty original-price input in the source edit form means omission/preservation, not clearing. These product rules differ from catalog individual-price forms, where original-price null has a separate meaning.
| Currency | Creation selling price | Original price |
|---|---|---|
| KRW | Multiples of 100; default minimum 300, with source channel-specific minimum overrides. Maximum 100,000,000. | Whole KRW units, not necessarily multiples of 100. At least the selling price and no more than 100,000,000. |
| USD | Whole dollars, minimum 5 and maximum 100,000. | Up to two decimal places, at least the selling price and no more than 100,000. |
Read inputLimits.createSellingMin rather than assuming the default or borrowing membership-product rules. Creation originalPrice also stays within the creation minimum/maximum. An original-price edit uses the existing selling price as its lower bound and the source maximum; creation minimum and selling-unit restrictions do not apply to that edit. KRW output is formatted as whole-unit strings and USD as two-decimal strings. There is no conversion to catalog CP units. No free-price exception is exposed.
CASH_AND_CP requires currently verified point branding. The shared store summary must explicitly confirm disabled point setup before CASH_ONLY preparation may omit branding. A configured store requires valid branding; malformed data, failed reads, authorization and changed context stop preparation/saving. Currency and branding are reread before dispatch. Both create prices use the same observed currency. An omitted currency stays absent in stored input and the request fingerprint; adding or removing an explicit currency under an existing request ID is different input. The fields object accepts no recurring, raw price object, caller-defined product identity, privileged profile, arbitrary metadata, subscriber data, search or deletion control. Editing cannot change internal name, selling price, period or recurring state.
Product save, identity and recovery
Fresh apply checks the complete inventory/form against the revision, rereads it before journaling and checks current currency/branding again before a business write. These reads are not an atomic compare-and-swap. A new dryRun returns PLAN_READY with plannedBody, noop and writesPerformed:false, without a product write or saved task. An unchanged edit is a read-verified no-op with no PUT; required explicit purchase-method metadata can still make a previously absent value a real change.
Creation sends one POST to the product root; edit sends one PUT to the exact numeric product route. Both use the selected channel header and no query. Creation derives the two currency price objects and adds the exact selected pAppCode; edit sends only presentationalName, isDeactivated, availablePurchaseMethodsType and originalPrice when supplied. Neither sends recurring. The closed common transport expects a data receipt containing pAppUnlockProduct without inventing one fixed successful POST/PUT status. Existing common HTTP rules reject redirects and non-success responses.
The durable article-product guard is scoped to the connection, channel and exact installed app. Guard and sent journal precede POST/PUT. Any unresolved product creation or edit blocks fresh product writes in that scope with ARTICLE_PRODUCT_PENDING, including attempts with a new request ID. This product guard does not imply that all unrelated catalog actions share its lock. Product and catalog journals still occupy the existing content-catalog namespace and cannot reuse one another's request IDs.
A creation receipt must have a new numeric ID and business distinctId, valid source product/app shape and matching optional scope fields. Store that identity durably before readback or recovery can use it. A lost, missing, reused, foreign or unpersisted identity returns ARTICLE_PRODUCT_OUTCOME_UNVERIFIED with createdIdentityKnown:false and writesPerformed:unknown. Even if a similar product appears, never adopt it by name or list difference, resend creation or report that no product was created. Keep the original task for inspection. A durably stored identity allows failed readback to be retried without another POST.
Completion rereads complete inventory and exact detail. Creation must match its receipt and all requested fields, with every pre-existing product preserved. Editing must match the requested editable fields while preserving internal name, selling price, period, recurring state, unrequested price metadata and all other product fields. Every unselected product, currency and branding must remain unchanged. Only the selected product's updatedAt is excluded; decimal formatting is normalized without hiding changed precision. Partial effects or collateral changes remain unverified, with no rollback.
ARTICLE_PRODUCT_CREATED or ARTICLE_PRODUCT_SAVED returns taskId, productId, product, a fresh revision and writesPerformed. Its verification confirms readback/preservation but leaves atomicSnapshot, console and restoration false. The saved no-op returns writesPerformed:false.
Status is historical. Resume and identical non-dry-run retries read only; they never resend POST/PUT. With no durable creation identity, even inventory rereads cannot discover one for recovery. A saved dry-run retry returns a historical stored plan without changing the journal; completed retries are historical with writesPerformed:false. Do not replace an uncertain task with a new request. Inspect fresh preparation for current state or a later separately authorized change.
Product restoration limits
There is no automatic rollback, product deletion or recreation. After resolving the original task, a separately authorized fresh edit may restore supported editable values, but cannot guarantee byte-for-byte restoration of the original raw record. The edit always supplies purchase methods and cannot restore a previously missing purchase-method key. It cannot clear originalPrice, restore old updatedAt/amount formatting, change read-only fields or undo purchase history. Creation cleanup is not supplied by this option. Actual same-target before/after/readback/restoration remains unverified.
Article product console destination
| Result | Validated location below the exact channel and selected p-apps/{pAppCode}/monetization/ | Required interpretation |
|---|---|---|
| Creation preparation/new preview; unknown creation identity | p-app-unlock-products | Parent list. Use its create button to open the original modal; no create URL or prefilled form is promised. For uncertainty, inspect without resubmitting or adopting a row. |
| Edit preparation/preview, verified created/saved product, or uncertainty with a known target | p-app-unlock-products/{productId}/edit | Exact existing or durably identified product editor. A link on an uncertain result is for inspection and is not save confirmation. |
| Status or historical replay | No fresh verified navigation | Stored results do not confirm current product state. |
Use the returned single labeled URL and consoleNextStep. Navigation checks the article-product marker, task type, exact app/product identity and result kind; it does not accept invented /create or ?create=true routes. Product page/size and modal state are separate console interactions. This configures an app-unlock product without purchasing, assigning subscribers, publishing articles or sending messages.
Complete KRW creation preview. Omitted originalPrice becomes the selling price; replace the revision with the creation preparation result.
{
"channelCode": "selected-channel",
"pAppCode": "B00001",
"requestId": "article-product-create-001",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"dryRun": true,
"action": {
"kind": "article_product_create",
"confirmation": "CREATE_ARTICLE_PRODUCT",
"fields": {
"name": "Article access",
"presentationalName": "Monthly articles",
"currency": "KRW",
"sellingPrice": "20000",
"expiryPeriodInDays": 30,
"isDeactivated": true,
"availablePurchaseMethodsType": "CASH_ONLY"
}
}
}
Complete USD creation preview, only for an observed USD channel with verified point branding. Original price accepts cents while selling price uses whole dollars.
{
"channelCode": "selected-channel",
"pAppCode": "B00001",
"requestId": "article-product-create-002",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"dryRun": true,
"action": {
"kind": "article_product_create",
"confirmation": "CREATE_ARTICLE_PRODUCT",
"fields": {
"name": "Article access",
"presentationalName": "Monthly articles",
"currency": "USD",
"sellingPrice": "10",
"expiryPeriodInDays": 30,
"isDeactivated": true,
"availablePurchaseMethodsType": "CASH_AND_CP",
"originalPrice": "20.25"
}
}
}
Complete edit preview preserving the current original price by omission. Replace 123 and the revision using article_product_edit.
{
"channelCode": "selected-channel",
"pAppCode": "B00001",
"requestId": "article-product-edit-001",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"dryRun": true,
"action": {
"kind": "article_product_edit",
"productId": 123,
"confirmation": "CHANGE_ARTICLE_PRODUCT",
"fields": {
"presentationalName": "Monthly articles",
"currency": "KRW",
"isDeactivated": false,
"availablePurchaseMethodsType": "CASH_ONLY"
}
}
}
Complete USD original-price edit preview, assuming the existing selling price is at most 10.01. It cannot change that selling price or the period.
{
"channelCode": "selected-channel",
"pAppCode": "B00001",
"requestId": "article-product-edit-002",
"expectedRevision": "0000000000000000000000000000000000000000000000000000000000000000",
"dryRun": true,
"action": {
"kind": "article_product_edit",
"productId": 123,
"confirmation": "CHANGE_ARTICLE_PRODUCT",
"fields": {
"presentationalName": "Monthly articles",
"currency": "USD",
"originalPrice": "10.01",
"isDeactivated": false,
"availablePurchaseMethodsType": "CASH_ONLY"
}
}
}
Original article manager-comment notification preferences
This connects the existing settings methods and canonical recovery in source.
Prepare with the dedicated notification view, then keep the same authorized channel and original B00001 installation. Supply its expectedRevision, a stable requestId and action:{kind:article_manager_comment_notifications,active,confirmation:CHANGE_ARTICLE_MANAGER_COMMENT_NOTIFICATIONS}. active is an explicit boolean. The existing 64-lowercase-hex revision and 8–80 ASCII request-ID rules apply; dryRun defaults to false. This alternative strict input accepts no ordinary settings object, view, event selector, receiver policy, delivery flags, URL or credential.
A fresh dryRun:true returns PLAN_READY with noop, requestedActive, transition.before/requested, availableChanges and writesPerformed:false, without a saved task or PUT. The full context/configuration revision must match, and repeated complete reads must stay stable through preflight. Choose the explicit ACTIVE value after examining all four transition fields; the setting is not a reversible delivery toggle.
A real OFF change sets isActivated, isSendEmail, isSendAppPush and isSendPwaPush false together. ON changes only isActivated and preserves the current three delivery flags. Already matching ACTIVE is a no-op: it neither sends PUT nor normalizes latent delivery or receiver values. A real change requires existing receiverType:SELLER and exactly one uniReceiverTypes entry SOURCE_DETAIL_META_SUBSCRIBER_DISTINCT_ID; otherwise ARTICLE_NOTIFICATION_RECEIVERS_UNVERIFIED prevents replacement. There are no independent EMAIL/app/PWA controls, receiver normalization or send actions.
The host sends one full PUT to the separate notification service at /seller/api/v1/configurations/sending-configurations/SELLER_P_APP_B00001_POST_MNG_COMMENT_CREATE, using the selected channel header and no query. Its body contains the four activation/delivery fields, preserved receiverType/uniReceiverTypes/isSendRealtime and customSubsMsgBodyKey when originally present. It is separate from ordinary configOverride PUT and content consumer-header requests. Unknown business fields are not added to the source body, but must survive the fresh readback.
Readback and uncertain outcomes
No invented exact HTTP success status is required by this notification hook. A receipt, including a successful or empty response, cannot establish completion by itself. Fresh complete reads must match all four requested values and every unrelated business field. Only server updatedAt may change; a previously present timestamp must not disappear. Stable preflight uses the entire record, including its timestamp. Identity, receiver policy, realtime, custom message-key absence/null and unknown fields remain protected.
ARTICLE_MANAGER_COMMENT_NOTIFICATIONS_SAVED includes transition, previousActive, current revision, observedAt, requestAttempted and readback/preservation verification. No-op success has writesPerformed:false. This is not atomicity, causality, original-console verification, other-event inspection or notification delivery. The host checks exact installation/credentials/authorization before and after the notification request; a post-dispatch change cannot turn its response into confirmed success.
ARTICLE_NOTIFICATION_OUTCOME_UNVERIFIED retains status:action_required, snapshotHistorical:true and the original task. requestAttempted marks the durable attempted boundary, not proof that a remote write completed. An accessible unresolved result may include the exact /notice settings inspection link and a notice that operation completion is not verified. The link does not change the outcome or prove a save. Revoked/foreign context, forged destinations and historical terminal results do not carry fresh navigation.
A known rejected/not-sent transport outcome becomes ARTICLE_NOTIFICATION_REJECTED only after complete unchanged readback when the requested change is absent. That permits fresh preparation; an unknown network/server outcome does not. Do not reinterpret a lost response as a safe resend or replace the original requestId to bypass pending work.
Original task, marker and restoration
Use existing taskType:content-settings and the original taskId. A channel/event marker is keyed to connection and channel, not installation ID, so reinstalling B00001 cannot evade an unresolved attempted change. A missing/incompatible marked journal or an attempted nonterminal task blocks a new request with ARTICLE_NOTIFICATION_PENDING. Ordinary app settings and their existing recovery remain independent; their request IDs cannot be reused for this action.
Prepared input and its marker are saved before the attempted boundary. Once a task exists, identical apply and task_resume only observe unfinished work, including prepared-but-unsent work. They never dispatch or replay PUT. An unattempted non-no-op stays ARTICLE_NOTIFICATION_NOT_SENT. Keep the exact saved context; reinstall, credential or permission changes can block recovery and suppress old preference disclosure. Preserve journals/markers after persistence failure. A saved dry-run replay is historical, not fresh planning; verified/rejected terminal replays are historical too.
Returning ACTIVE to a previous value requires a fresh read/revision and a new explicit change after uncertainty is resolved. The ACTIVE-only control cannot restore delivery flags cleared by OFF. There is no automatic rollback, full delivery restoration, notification recall or proof of recipient delivery.
Notification dry-run examples
These are input illustrations, not actual calls. Replace the all-zero revision with the matching notification preparation revision. Read afresh before a later change; do not reuse an OFF revision for ON.
Prepare turning ACTIVE off, including its compound delivery changes:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-notification-off-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","action":{"kind":"article_manager_comment_notifications","active":false,"confirmation":"CHANGE_ARTICLE_MANAGER_COMMENT_NOTIFICATIONS"},"dryRun":true}
Prepare turning ACTIVE on while preserving the current delivery flags:
{"channelCode":"selected-channel","pAppCode":"B00001","requestId":"article-notification-on-001","expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","action":{"kind":"article_manager_comment_notifications","active":true,"confirmation":"CHANGE_ARTICLE_MANAGER_COMMENT_NOTIFICATIONS"},"dryRun":true}