Put a paid membership on sale or take it off
PATCH /program/memberships/{membership_id}/sale| Scope | program:write |
| Customer session token | Not accepted: call from your server with an API key |
| Rate limit class | write_shopify (limits) |
| Idempotency key | Required (idempotency) |
| MCP tool | charm_update_membership_sale |
Puts one membership on sale (a purchase of its product grants it) or takes it off sale, through the save the Memberships page uses, logged in the program changes. Going on sale needs the product active in Shopify. Every real change is refused with 409 confirmation_required until the call carries confirm_switch: true; the dry run and the refusal carry impact (the product, its price, current members). Show it to the merchant and get a clear yes.
Parameters
Section titled “Parameters”| Name | In | Type | Description |
|---|---|---|---|
membership_id | path | string | The membership’s id. |
Request body
Section titled “Request body”| Field | Type | Description |
|---|---|---|
on_sale | boolean | |
confirm_switch | boolean | Required on the real call, after the merchant saw impact and said yes. (optional) |
dry_run | boolean | Preview only: validate, resolve references and return the diff and summary, but write nothing. Costs no idempotency key and is limited as a read. Call with this first, show the merchant, then repeat without it. (optional) |
if_revision | integer | The program revision you last read (from GET /program or a previous write). When the program changed since, the write is refused with 409 revision_conflict instead of overwriting a concurrent admin edit. (optional) |
Response 200
Section titled “Response 200”Returns MembershipSaleWriteResult.
| Field | Type | Description |
|---|---|---|
dry_run | boolean | |
revision | integer | |
membership | object | |
operation | string: update, none | |
impact | object or null | (nullable) |
{ "dry_run": false, "revision": 1, "membership": { "id": "id", "name": "Gold", "on_sale": false }, "operation": "update", "impact": { "product": { "id": "id", "title": "title", "status": "active", "url": "https://example.com/charm-hook" }, "price": "price", "members": 1, "notes": [ "notes" ] }}Example
Section titled “Example”# 1. Dry run: writes nothing and needs no Idempotency-Key. Show the merchant the result.curl -X PATCH "https://charm.appfleece.app/api/v1/program/memberships/<membership_id>/sale" \ -H "Authorization: Bearer chrm_live_..." \ -H "Content-Type: application/json" \ -d '{"on_sale":false,"dry_run":true}'
# 2. After the merchant says yes: the real call with the confirmation.curl -X PATCH "https://charm.appfleece.app/api/v1/program/memberships/<membership_id>/sale" \ -H "Authorization: Bearer chrm_live_..." \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"on_sale":false,"confirm_switch":true}'Errors
Section titled “Errors”unauthorized · invalid_token · insufficient_scope · access_required · not_found · validation_error · rate_limited · idempotency_conflict · shopify_unavailable
Every error body carries code, message, request_id and a doc_url pointing at the matching entry in the error catalog.