Tools
Discounts
Thirteen tools for Shopify checkout discounts, managed as campaigns with one discount per store: read campaigns, capabilities, and exports; edit write drafts that never reach Shopify; and the publish-tier commands that change live discounts. They call the same validation, revisions, and publish locks as the Discounts pages, and are available only to accounts with the Discounts feature.
Plan name → tool
Tool names follow the catalog's <verb>_<subject>_v1 convention. The approved plan named the operations differently; this table maps them.
| Plan name | Tool | Tier |
|---|---|---|
discount_list | list_discounts_v1 | read |
discount_get | get_discount_v1 | read |
| — | get_discount_capabilities_v1 | read |
discount_create | create_discount_v1 | write |
discount_update_store | update_discount_store_v1 | write |
discount_update_shared | update_discount_shared_v1 | write |
discount_add_store | add_discount_store_v1 | write |
discount_duplicate | duplicate_discount_v1 | write |
discount_publish | publish_discount_v1 | publish |
discount_lifecycle | set_discount_lifecycle_v1 | publish |
discount_recreate | recreate_discount_v1 | publish |
discount_delete | delete_discount_v1 | publish |
| export tools | export_discounts_v1 | read |
delete_discount_v1 removes store discounts from Shopify — the same kind of live store write as turning a discount off or replacing it — and deletes no product, collection, or other catalog item. It requires pim:publish, confirmed: true, and an explicit store list (or, for a campaign with no store left, delete_empty_campaign). The pim:delete permission stays ungrantable and still gates every catalog delete. The in-app assistant always asks before any publish-tier discount tool runs, and waits for the merchant's explicit confirmation, naming the stores, before it removes a discount. It treats these tools as available only where list_stores_v1 reports the discounts capability.
Customer and segment eligibility is returned as Shopify GIDs only. Looking up customer names is an in-app action, and its route is not reachable with an MCP token.
list_discounts_v1
list_discounts_v1pim:read
List the account's discount campaigns, one row per store with its method (code or automatic), type, title, Shopify status, publish state, dates, usage, code count, and ownership (native or external_app — another app's discount, read-only).
| Argument | Type | Description |
|---|---|---|
search | string | Campaign name or store title. |
method | string[] | code, automatic. |
family | string[] | basic, bxgy, free_shipping, app (another app's discount). |
shopify_status | string[] | ACTIVE, SCHEDULED, EXPIRED. |
publish_status | string[] | never_published, pending, published, failed. |
channel_ids | string[] | Only campaigns on these stores. |
page, page_size | integer | Paging; page_size is clamped at 100. |
get_discount_v1
get_discount_v1pim:readRead one campaign and each store's version: its revision (the token every edit sends back), configuration fields, typed selections with whether Shopify confirmed them, buyer context as Shopify GIDs only (never customer names or emails), Shopify facts (status, usage, sales, summary, share links), whether it is editable, and its publish blockers.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
get_discount_capabilities_v1
get_discount_capabilities_v1pim:read
Per store: missing discount or customer permissions, shop currency and time zone, Shopify Plus, active automatic discounts against Shopify's limit, and customer lookup availability. Advisory only. Open world: refresh: true reads Shopify live; otherwise a snapshot up to 15 minutes old answers.
| Argument | Type | Description |
|---|---|---|
channel_ids | string[], required | The stores to check. |
refresh | boolean | Read Shopify live. |
create_discount_v1
create_discount_v1pim:write
Create a campaign as a DRAFT on the listed stores, all of one method and type. A store whose draft is invalid is reported in failed with its field errors; nothing reaches Shopify until publish_discount_v1.
| Argument | Type | Description |
|---|---|---|
name | string, required | The campaign name (internal). |
channel_ids | string[], required | The stores. |
method | string, required | code or automatic. |
family | string, required | basic, bxgy, or free_shipping. Another app's type (app) is refused. |
changes | object | Fields, {key: value}: Title, Starts At / Ends At, and the JSON documents Discount Value, Minimum Requirement, Combines With, and the rest. |
selection_modes | object | get, buy, buyer. |
relationships | object | {role: [item ids]}, e.g. discount_get_product. |
external_context | object | {kind, ids}: customer or segment GIDs, one store only. |
preferred_codes | object | Code per store. |
schedule | object | Same instant, same local time, or store local. |
update_discount_store_v1
update_discount_store_v1pim:write
Save draft changes to one store's discount, validated exactly as the discount page does. revision is required; a stale one is refused with current_revision, and an invalid save is refused with field_errors and writes nothing. Listed selection roles replace that role's list. A method or type change on a discount already on Shopify needs recreate_discount_v1.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
channel_id | string, required | The store id; the discount must have a version on it. |
revision | integer, required | From get_discount_v1. |
changes, selection_modes, relationships, external_context, schedule | object | As for create_discount_v1. |
currency_resolution | string | preserve_nominal keeps amounts as written when currencies differ. |
update_discount_shared_v1
update_discount_shared_v1pim:writeApply one draft edit to several stores of a campaign at once. Each store saves on its own; the result lists stores saved and stores refused with reasons, so partial success is normal.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
master_revision | integer, required | The campaign revision. |
channel_ids | string[] | The stores to edit. Leave empty to change only the campaign name or defaults; selection modes, selections, and a schedule need stores. |
changed_keys, values | string[], object | The fields to change and their values. |
expected_revisions | object | Each store version's revision. |
name, selection_modes, relationships, schedule, update_defaults | Optional parts of the edit. |
add_discount_store_v1
add_discount_store_v1pim:write
Add a draft version on another store, copied from one of the campaign's stores (selections remapped to that store's items; customer and segment choices never copied) or started from the campaign defaults. Selected items missing on the new store are refused with missing_targets unless accept_missing_targets is true.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
master_revision | integer, required | The campaign revision. |
channel_id | string, required | The store to add. |
copy_from_channel_id | string | The store to copy. |
method, family, changes, preferred_code | Without a copy: the starting configuration (family as for create_discount_v1). | |
accept_missing_targets | boolean | Create it anyway; the missing items become publish blockers. |
duplicate_discount_v1
duplicate_discount_v1pim:writeCopy a campaign into a new draft campaign on the same stores (or a subset), without its Shopify identity, statistics, or codes.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
channel_ids | string[] | Source stores to copy (default all). |
name | string | New name (default "Copy of …"). |
preferred_codes | object | Code per store. |
publish_discount_v1
publish_discount_v1pim:publish
Publish a campaign's drafts LIVE on the listed stores (default every store): creates the Shopify discount the first time and updates it after, then confirms it by reading it back. A store with blockers, a missing permission, an unpublished selected item, or changes made in Shopify since the last publish is refused on its own. Runs as a background operation; poll get_publish_operation_v1.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
channel_ids | string[] | Restrict to these stores. |
set_discount_lifecycle_v1
set_discount_lifecycle_v1pim:publishActivate or deactivate a published discount on the listed stores. Shopify has no paused state: deactivating ends it now. Background operation.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
action | string, required | activate or deactivate. |
channel_ids | string[], required | At least one store. |
recreate_discount_v1
recreate_discount_v1pim:publishChange the method or type of a discount already on Shopify by replacing it: the new discount is created, the old one deleted, and its main code carried over. A code discount with more than one code in Shopify is refused: manage extra codes in Shopify. Usage history and share links are lost, with a short gap while the replacement takes over. Refused before anything changes when the draft is invalid or the revision is stale. Background operation.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
channel_id | string, required | The store id; the discount must have a version on it. |
revision | integer, required | The store version's revision. |
method, family | string, required | The new type; family is basic, bxgy, or free_shipping. |
changes, selection_modes, relationships, external_context, preferred_code | The replacement's configuration. | |
confirmed | boolean, required | Must be true. |
delete_discount_v1
delete_discount_v1pim:publish
Delete a campaign's discount from the listed stores: each store's Shopify discount, its codes, and its usage history are deleted, then the store version is removed from Peak PIM. It deletes no catalog item. Another app's discount is refused. A campaign with no store version left (stores is empty in get_discount_v1) is removed with delete_empty_campaign; nothing changes in Shopify. Background operation: poll get_publish_operation_v1.
| Argument | Type | Description |
|---|---|---|
id | string, required | The discount campaign id. |
channel_ids | string[] | At least one store, unless delete_empty_campaign is true; never "all stores" by omission. |
confirmed | boolean, required | Must be true. |
delete_master_when_empty | boolean | Also remove the campaign when no store is left. |
delete_empty_campaign | boolean | Remove a campaign that has no store version left. Takes no channel_ids; refused while any store version remains. |
export_discounts_v1
export_discounts_v1pim:readThe discount configuration as a CSV file (one row per store discount), with the list filters. Redeem codes are not included; extra codes are managed in Shopify.
| Argument | Type | Description |
|---|---|---|
search, method, family, shopify_status, publish_status, channel_ids | As for list_discounts_v1. |