mcpField
S
shoplazza-discounts

shoplazza-discounts

github.com/shoplazzaMarketing & growth1746 installsUpdated 6d ago
Repository

About shoplazza-discounts

shoplazza-discounts is a skill for the discounts module of the shoplazza CLI, covering automatic discounts, discount codes, and coupon campaigns. It guides creation and lifecycle operations, and requires clarification for missing required values rather than guessing terms that affect merchant revenue.

Key features

  1. 01Create automatic discounts and discount codes
  2. 02Manage coupon campaigns
  3. 03Search and retrieve discounts
  4. 04Update, cancel, or delete activities
  5. 05Clarify missing required fields

Use cases

  1. 01Set up flash sales or tiered rebates
  2. 02Create product- or order-level discount codes
  3. 03Find and manage paused or finished discounts

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/shoplazza/shoplazza-cli --skill shoplazza-discounts

After installing, type/shoplazza-discountsin Claude Code to confirm it loaded.

Details

From the project README

shoplazza CLI — discounts module

CRITICAL — before anything else, use Read on ../shoplazza-common/SKILL.md. It owns every cross-cutting mechanic: the three access tiers, the output envelope (.data), --dry-run, --jq (incl. "don't pass -r"), --fields, schema, api rest, auth / profiles, and the safety protocol. This file covers only the discounts domain and never repeats them.

Overview

The discounts module manages discount activities (automatic ones, and code-based ones) plus coupon (voucher) campaigns. All three access tiers apply (tier-selection rule is in shoplazza-common — prefer +shortcut).

There is no +update, +cancel, or +restart shortcut — those lifecycle ops are spec leaves.

Command map

Intent → command. The authoritative flags/params live in shoplazza discounts <cmd> --help and shoplazza schema discounts.<cmd>, not this table.

User intentCommand
% off (with coupon code)discounts +percent-code --target order|product --percent <1-99>
Fixed amount off (with coupon code)discounts +amount-code --target order|product --off <amt>
Free shipping coupondiscounts +free-shipping-code
Buy X get Y (with coupon code)discounts +bxgy-code (buy-side scope + get-side scope)
Flash sale (auto-applied, no code)discounts +flashsale --value ...
Buy N, Nth-item % off (auto, no code)discounts +mn-discount --tiers "n:%,..."
Spend / buy-N threshold → auto discountdiscounts +rebate --target order|product --tiers "th:disc,..."
Search / filter discountsdiscounts +search --query ... --progress ongoing
Get one discount by IDdiscounts get --params '{"id":"<id>"}'
Get one discount by codediscounts get-by-code --params '{"discount_code":"<CODE>"}'
Cancel discount(s)discounts cancel --data '{"ids":["<id>",...]}'
Restart a paused discountdiscounts restart --data '{"id":"<id>"}'
Delete one finished discountdiscounts delete --params '{"id":"<id>"}'
Delete many finished discountsdiscounts batch-delete --data '{"ids":[...]}'
Update an automatic discountdiscounts update-automatic --params '{"id":"<id>"}' --data '<body>'
Update a non-automatic (code) discountdiscounts update-non-automatic --params '{"id":"<id>"}' --data '<body>'
Configure cross-type combination rulesdiscounts combine --data '<rules>'
Coupon campaigns (vouchers)discounts coupons {create,get,update}

Acting on a request

When the user says "帮我创建一个X营销活动" / "create a flash sale" / "建个优惠码" / similar — that is an action, not a question:

  1. Match intent to a shortcut via the trigger table below.
  2. Check required fields (each shortcut's no-default flags). If any is missing or ambiguous → ask with AskUserQuestion, never fabricate. Real discounts cost the merchant money; "I'll just default to 满100减10" is the wrong call.
  3. For these required, no-default fields, this skill overrides session-level "no clarifying questions" preferences. Usage caps are NOT in this set (see below).
  4. All required present → run it (the +shortcuts are designed to be safe; run --dry-run first only if the user explicitly asks).
  5. Never ask about a flag with a CLI default. Absolute. If <shortcut> --help shows (default …) / auto-generated if omitted / omit-to-disable, that flag MUST NOT appear in any AskUserQuestion. Let the default win; override only if the user's wording disambiguates (e.g. "8折" → --type=amount-percent).

Usage caps are optional (omit = no limit). --limit-max / --limit-user (and flashsale's --limit-user-variant/-product/-all) have no default to set — omit them and the discount is unlimited. Do NOT ask about caps and do NOT pass -1 (the CLI rejects -1: "must be > 0"). One soft exception: for an open-ended, money-costing code/coupon campaign (no end date + no caps), it's fine to mention in your reply that redemptions will be unlimited — mention, don't block, don't turn it into an AskUserQuestion.

Never-ask list (all have CLI defaults or omit-to-disable — never a question): --start (default now) · --end (default -1/forever) · --name (auto-generated) · --code (auto-generated) · --combines (default empty = no stacking) · --limit-max / --limit-user (omit = no limit) · --limit-user-variant / --limit-user-product / --limit-user-all (flashsale; omit = no limit) · --limit-order (+bxgy-code, default 1) · --limit-order-once (+amount-code / +rebate, default true) · --type (default amount-off for +rebate, percent for +flashsale) · --scope (default highest for +mn-discount) · --price-rule (default price for +flashsale) · --price-sort (default desc for +mn-discount) · --min-amount (default 0) · --min-quantity (default 1) · --countries (default all) · --stock (omit = follow product stock) · --exclude (default off) · --customer-segments (omit = all customers eligible).

When in doubt, run <shortcut> --help: any flag whose line contains "(default ...)", "auto-generated", or "omit …" goes to defaults, not the question list.

Trigger phrase → shortcut
User saysShortcutHow to extract values
满X减Y / 订单满X减Y / spend X get Y off+rebate --target order --type amount-off--tiers "X:Y"; multi-tier "满200减20,满500减50" → "200:20,500:50"
商品满X减Y / product spend X get Y+rebate --target product --type amount-offneeds --products or --collections or --variants
满N件减Y元 / buy N get Y off+rebate --type qty-off--tiers "N:Y"
满X享Y折 / 满X打Y折 / spend X get Y% off+rebate --type amount-percentY is the discount %, e.g. "8折" → 20 (i.e. 20% off) — confirm if ambiguous
满N件享Y折 / buy N get Y% off+rebate --type qty-percent
第N件Y折 / 买N件第N件Y折 / Nth item Y% off+mn-discount--tiers "N:Y" where Y is % off
闪购 / 限时秒杀 / 限时折扣 / flash sale+flashsaleauto-applied, no code; --value + --type; scope via --variants/--collections (omit = all products)
优惠码 + X% off / 折扣码立减Y% / X% off coupon+percent-code--percent is the percent (1-99)
优惠码立减Y元 / 折扣码减¥Y / $Y off coupon+amount-code--off is the amount
包邮码 / 免邮券 / free shipping code+free-shipping-codeoptional --off (partial), --countries
买X送Y / 买X赠Y / buy X get Y free+bxgy-codebuy-side scope + --buy-quantity/--buy-amount; get-side scope + --get-quantity; --get-free / --get-percent / --get-off

If multiple rows match, ask which one. Don't guess.

Required-vs-ask matrix

Only the no-default flags are askable. Caps are NOT here — they default to no-limit.

ShortcutMust ASK if user did not specifyInfer if possibleDefault silently
+percent-code--percent (the %), --target, --products/--variants/--collections if --target=product--target from "订单/商品" wording--min-amount, --min-quantity, caps, --combines, --code, --name, time, --customer-segments, --exclude
+amount-code--off (¥), --target, scope if --target=product--target from wordingsame as +percent-code, plus --limit-order-once
+free-shipping-code— (nothing strictly required)--off only if user said "立减Y元运费"--off, --min-amount, --min-quantity, --countries, caps, --combines, --code, --name, time
+bxgy-codebuy-side scope, --buy-quantity or --buy-amount, get-side scope, --get-quantity, one of --get-free/--get-percent/--get-offbuy/get qty from "买N件送M件"; --get-percent/--get-off from "打折"/"立减" wording (neither = --get-free)--limit-order (default 1), caps, --combines, --code, --name, time, --exclude
+flashsale--value only (never ask --type — it defaults to percent)--type from wording (percent for "X折/X%off", fixed-price for "一口价Y元", off for "立减Y元"); scope from named products--type (default percent), scope (omit = all products), --price-rule, --stock, per-user caps, --combines, --name, time
+mn-discount--tiers--scope ("最高档"=highest, "全部档"=all; also highest-all)--scope, --price-sort, scope flags, caps, --combines, --name, time, --exclude
+rebate--target, --tiers--type from wording — see trigger table abovescope if --target=order, --limit-order-once, caps, --combines, --type if user didn't disambiguate, --name, time
Decision examples
User saysVerdict
"帮我建个满减活动"ASK — tiers + target missing
"创建一个满减活动,满200减20"ASK — target unclear (订单 or 商品?)
"创建一个满200减20的订单级满减活动,永不过期"CREATE — all required present (+rebate --target order --tiers "200:20" --end forever); caps omitted = unlimited
"Create a 15% off coupon code"ASK — target unclear (order or product?)
"Create a 15% off site-wide coupon code, max 100 uses, 1 per customer"CREATE (+percent-code --target order --percent 15 --limit-max 100 --limit-user 1)
"创建闪购活动 50% off"CREATE — --value 50 is enough (+flashsale --value 50); scope optional. Optionally mention it applies store-wide unless products are specified.
How to ask

Use AskUserQuestion with concrete numbered options. Bundle related fields into one batch (≤4 questions per call). The complete set of askable questions is:

Question typeWhen askedSample options
Target scope (--target)For +percent-code/+amount-code/+rebate when user didn't say "订单/商品""订单级"、"商品级"
Tiers (--tiers)For +rebate/+mn-discount when not givenfree-form: list of 阈值:折扣 pairs
Discount value (--percent / --off / --value)When user gave only the type ("打个折") without a numberfree-form numeric
Product targeting (--products/--collections/--variants)When target=product / a shortcut needs scopefree-form ID list
BXGY buy/get sidesFor +bxgy-code if scope or trigger qty not givenfree-form ID list + quantities

That's it. Do not bundle anything not in this table — every other flag (including usage caps) has a CLI default or omit-to-disable. Re-read the user's message before asking, and skip any question whose answer is already in it.

Creating: minimum required flags

Required = flags the CLI rejects the command without (no default). Usage caps are not required — omit for no limit.

ShortcutRequired flags
+percent-code--target --percent (+ a scope flag if --target=product)
+amount-code--target --off (+ a scope flag if --target=product)
+free-shipping-code(none)
+bxgy-codebuy scope · (--buy-quantity | --buy-amount) · get scope · --get-quantity · (--get-free | --get-percent | --get-off)
+flashsale--value
+mn-discount--tiers
+rebate--target --tiers (+ a scope flag if --target=product)

"Scope flag" = one of --products / --variants / --collections (mutually exclusive).

Compact pair / list syntaxes:

FlagFormatExample
+rebate --tiersthreshold:discount,..."200:20,500:50" (满200减20,满500减50)
+mn-discount --tiersNth-item:percent,..."3:50,5:70" (买3件第3件50%off,买5件第5件70%off)
+bxgy-code buy side--products P1,P2 --buy-quantity 2 (or --buy-amount 100)qty/amount across the buy-side items that triggers the offer
+bxgy-code get side--get-products P3 --get-quantity 1 --get-freehow many get-side items are discounted, and how

Time forms for --start / --end (any shortcut):

FormMeaning
nowcurrent time
+30d / +2w / +12hoffset from now
2026-11-01UTC midnight on that date
2026-11-01T08:00:00explicit UTC datetime
<unix-int>raw Unix seconds
forever / -1no expiry (--end only)

Targeting / eligibility helpers (all optional, on most shortcuts):

  • --exclude — turns the scope IDs into a blocklist (apply to all products EXCEPT those listed). Needs a scope flag to have effect.
  • --customer-segments <ids> — restrict the discount to customers in the given segment IDs (omit = all customers eligible).

Discount stacking: --combines controls whether this discount may stack with others. Default is empty (no stacking). To allow: --combines order,product,shipping (subset). For global cross-type rules (independent of --combines), use discounts combine.

Discovering

Prefer +search over list. Both hit GET /discounts, but +search has named flags; list only takes --params JSON.

discounts +search --progress ongoing --discount-type flashsale --page-limit 250
discounts +search --query "spring" --discount-method discount_code

Multi-value filters (--progress, --discount-type, --discount-method, --discount-target) take comma-separated lists.

The discount object is nested at .data.discount.{discount_info,discount_layer,discount_rule,entitled_product,...} (the general "jq starts at .data" rule is in shoplazza-common):

discounts get-by-code --params '{"discount_code":"CODE123"}' \
  --jq '.data.discount.discount_info.id'

discount_type enum

These are the internal API names — use them with --discount-type / inside --params. They are NOT user-facing labels.

flashsale                m_n_discount
rebate_cta_otr           rebate_ctq_otr           rebate_cta_otp           rebate_ctq_otp
code_percent             code_fix_price_reduction code_bxgy                code_free_shipping

Mapping back to shortcuts:

Shortcut writesdiscount_type
+percent-codecode_percent
+amount-codecode_fix_price_reduction
+free-shipping-codecode_free_shipping
+bxgy-codecode_bxgy
+flashsaleflashsale
+mn-discountm_n_discount
+rebate --target=order --type=amount-offrebate_cta_otr
+rebate --target=product --type=qty-offrebate_ctq_otr
+rebate ... --type=amount-percent / qty-percentrebate_cta_otp / rebate_ctq_otp

Filtering with --discount-type rebate returns nothing — that label doesn't exist; use one of the four rebate_* enums.

Lifecycle — body shapes are asymmetric

Watch the singular vs plural. This is the most common spec-leaf footgun.

discounts cancel        --data '{"ids":["A","B"]}'   # ARRAY (one or many)
discounts batch-delete  --data '{"ids":["A","B"]}'   # ARRAY (must be finished)
discounts restart       --data '{"id":"A"}'          # SINGULAR — one id only
discounts delete        --params '{"id":"A"}'        # path param, no body, one id

delete and batch-delete only work on discounts whose progress == finished. To delete an active one: cancel it first, then delete.

Updating

There is no +update shortcut. Compose the update body from a get and feed it back:

discounts get --params '{"id":"<id>"}' --jq '.data.discount' > /tmp/d.json
# edit /tmp/d.json …
discounts update-automatic --params '{"id":"<id>"}' --data @/tmp/d.json --dry-run

Use update-automatic for flash sales / rebates / mn-discounts (no code), update-non-automatic for code-bearing discounts. The body shape matches the corresponding create-* endpoint — run schema discounts.update-automatic for fields.

Boundaries

Reads like discounts, actually belongs elsewhere:

Sounds like discountsActually belongs toCommand
礼品卡 / 储值卡 / gift card (redeemable as store credit at checkout)shoplazza-productsproducts gift-cards
首页限时促销卡片展示某个活动 / show a flash sale in a theme cardshoplazza-themes (creating the flash sale itself stays HERE)themes +edit

Coupon (voucher) vs discount code — both live in this module, don't confuse them:

  • Discount codes (the +*-code shortcuts): public codes shoppers type at checkout.
  • Coupons (discounts coupons …): vouchers assigned to specific customers, distributed by marketing campaigns.

Default to the +*-code shortcuts. Only reach for coupons when the user explicitly says "coupon campaign", "voucher", "assign to customer", or similar.

Permissions · Scope

Authorization is by domain (the unit of --domain), not per command. Authorization flow is in shoplazza-common → Authentication.

OperationNeedsGrant
read (get / get-by-code / list / +search)read_price_rulesauth login --domain discounts
write (create-* / +* / cancel / restart / delete / batch-delete / update-* / combine / coupons)write_price_rulesauth login --domain discounts

The discounts scopes are named read_price_rules / write_price_rules — there is no scope with "discount" in its name. Check granted_scopes in shoplazza auth scopes for those two; --domain discounts expands into them.

Gotchas

Domain-specific pitfalls only (generic ones — .data prefix, --jq without -r — are in shoplazza-common).

SymptomCauseFix
403 PermissionDenied "check if a store is a new marketing store" on every discounts call, reads includedA store-side check, not a scope: this store isn't enabled for the discounts API. The envelope's generic re-login hint does not applyDon't re-login or retry. Confirm read_price_rules / write_price_rules are in auth scopes → granted_scopes (the discounts scopes; none is named "discount"); if they are, tell the user discounts can't be managed through the CLI on this store and stop
null after get / get-by-codediscount object is nested under .data.discountStart jq at .data.discount.discount_info.…
--discount-type rebate matches nothing"rebate" is not an enumUse rebate_cta_otr / rebate_ctq_otr / rebate_cta_otp / rebate_ctq_otp
--limit-max must be > 0 (got -1)Caps are positive-only; -1 is rejectedOmit the cap entirely for "no limit" — don't pass -1
unknown flag: --off on +percent-codepercent-code uses --percent, not --off--percent 15 (1-99)
unknown flag: --discount on +flashsaleflashsale uses --value--value 50 (+ --type)
+bxgy-code --buy/--get rejectedThose flags don't existBuy side: scope flag + --buy-quantity/--buy-amount; get side: --get-* scope + --get-quantity + --get-free/--get-percent/--get-off
restart --data '{"ids":["X"]}' failsrestart body is singular--data '{"id":"X"}'
delete returns 4xx with progress messageDiscount is not finishedcancel first, then delete
--target=product rejected "requires one of --products/--collections/--variants"Product scope is mandatory when target=productPass a scope flag
display_name looks truncated in responseServer auto-truncates to ~20 charsPass --name explicitly if it matters
Created two discounts with the same --codeCodes are unique per storePass an explicit unique --code, or omit to auto-generate CLI-XXXXXX
Wanted to update via shortcutThere is no +updateUse update-automatic / update-non-automatic with a full body

Recipes

# 1. Spend $200 → $20 off, auto-applied, no expiry, unlimited
discounts +rebate --target order --tiers "200:20" --end forever

# 2. 15% off site-wide coupon, max 100 redemptions, 1 per shopper
discounts +percent-code --target order --percent 15 --limit-max 100 --limit-user 1 --code SPRING15

# 3. Buy 2 of P1, get 1 of P2 free, single set per order
discounts +bxgy-code --products P1 --buy-quantity 2 --get-products P2 --get-quantity 1 --get-free

# 4. Find all ongoing flash sales
discounts +search --progress ongoing --discount-type flashsale --page-limit 250 \
  --jq '.data[] | {id: .discount_info.id, name: .discount_info.discount_name}'

# 5. Cancel a code-bearing discount by its code
ID=$(discounts get-by-code --params '{"discount_code":"SPRING15"}' --jq '.data.discount.discount_info.id')
discounts cancel --data "{\"ids\":[\"$ID\"]}" --dry-run     # check first
discounts cancel --data "{\"ids\":[\"$ID\"]}"               # only after the user agrees (next turn)

# 6. Hard-delete the now-finished discount
discounts delete --params "{\"id\":\"$ID\"}"

References

  • Per-command flags: shoplazza discounts <cmd> --help
  • Spec-leaf params / body / response: shoplazza schema discounts.<cmd>
  • Cross-cutting mechanics (auth / tiers / output envelope / --dry-run / --jq / safety): ../shoplazza-common/SKILL.md
  • Shortcut source of truth: shortcuts/discounts/*.go

Comments

Markdown supported