Promo Codes
Generate and manage promo codes that unlock a promotion, and let customers apply them in the cart.
Promo Codes
A promo code unlocks a promotion whose trigger is Promo code. A promotion can have any number of codes — one public code everyone shares, or thousands of single-use codes to hand out individually.
Generating codes
In Storefront → Promotions → Promotions, open the row menu of a promotion and choose Manage codes.
To create a batch, set How many (up to 1,000), an optional Prefix (e.g. FALL-), the Code length (6–16 random characters) and Uses per code (1 for single-use codes, empty for unlimited).
Or type a specific code such as SUMMER25 to create exactly that code.
Click Generate codes. The new codes appear in the list, ready to copy.
Generated codes use only unambiguous characters (no 0/O or 1/I), are unique across your organization, and are matched case-insensitively — summer25 and SUMMER25 are the same code. Generating codes for an automatic promotion switches it to Promo code.
The list shows how many times each code has been used. Disable a code to stop it working without affecting the promotion's other codes; Enable brings it back. Through the API, codes can also expire at a set time or be restricted to a single customer.
A code is subject to its own uses per code limit and to the promotion's limits (total uses, uses per customer, budget).
Applying codes in the app
Codes are applied to the customer's cart, and codes on the cart are used automatically at checkout:
POST /storefront/v1/carts/{cart}/promo-code
Authorization: Bearer store_your_store_key
Customer-Token: 1|VlKK7lZ...
{ "code": "SUMMER25" }On success the response contains the updated cart (its promo_codes list includes the code) and a preview of the discounts:
{
"cart": { "id": "cart_8Xk2mQ1", "promo_codes": ["SUMMER25"], "...": "..." },
"promotions": {
"discount": 1500,
"discount_subtotal": 1000,
"discount_delivery": 500,
"applied": [
{ "promotion": "promotion_7Hq2xL9", "name": "Summer $10 off", "type": "fixed_amount", "code": "SUMMER25", "amount": 1000, "delivery_amount": 0 },
{ "promotion": "promotion_2Nf8sK1", "name": "Free delivery over $30", "type": "free_delivery", "code": null, "amount": 0, "delivery_amount": 500 }
],
"rejected": []
}
}Amounts are in the currency's minor unit (cents for USD). Remove a code with DELETE /storefront/v1/carts/{cart}/promo-code/{code}, and refresh the preview at any time with GET /storefront/v1/carts/{cart}/promotions?service_quote=... (pass the delivery quote so free delivery is priced). Sending the Customer-Token header lets the preview check customer rules such as first order only.
Checkouts can also receive codes directly with the promo_codes parameter — see Checkout.
Why a code is refused
When a code can't be applied, the API answers 400 with the reason, for example:
{ "error": "Promotion code \"SUMMER25\" cannot be applied (min_subtotal).", "reason": "min_subtotal" }| Reason | Meaning |
|---|---|
invalid_code | The code doesn't exist, is disabled or expired, or belongs to another customer |
not_applicable | The code belongs to a promotion of another store or network |
not_active | The promotion is not active or outside its schedule |
usage_limit_reached | The promotion or the code has been used the maximum number of times |
customer_usage_limit_reached | This customer has used it the maximum number of times |
budget_exhausted | The promotion's budget has been spent |
currency_mismatch | An amount-off promotion in another currency |
first_order_only | The customer has ordered before |
min_subtotal / min_items | The cart doesn't meet the minimum spend or item count |
no_eligible_items | Nothing in the cart is targeted by the promotion |
pickup_order | Free delivery on a pickup order |
no_discount | The promotion would not discount anything on this cart |
A valid code that simply loses to a better combination of promotions is kept on the cart and listed in promotions.rejected with the reason not_combinable — the customer still gets the bigger discount. At checkout, any other refused code fails the checkout with 400 so the customer is never charged without a discount they expected.