Search Documentation

Search for a documentation page...

Coupons API

REST API endpoints for managing checkout discount codes.

Coupons are discount codes customers enter at checkout. Each coupon takes a percentage off, a fixed amount off, or gives free shipping, and can be limited by date range, total redemptions, cart product count, a minimum order value, specific products, single variants of a product, collections, categories, or brands. A coupon can skip products already on sale, can be limited to one-time purchases, and can carry extra rules: percentages on narrower scopes, with an optional minimum-quantity condition, and buy X get Y offers.

List Coupons

GET /api/v1/coupons

Query Parameters

ParameterTypeDefaultDescription
limitnumber10Coupons per page (1-100)
offsetnumber0Coupons to skip
querystring–Search by coupon code

Response

{
"data": [
{
"id": "0191abc0-1234-7def-8000-000000000001",
"code": "WELCOME10",
"type": "percentage",
"value": 10,
"freeShipping": false,
"timesRedeemed": 42,
"maxUses": 1000,
"oncePerCustomer": false,
"oncePerCustomer": false,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-12-31T23:59:59.000Z",
"minProductCount": 1,
"maxProductCount": null,
"productIds": [],
"variantIds": [],
"collectionIds": [],
"categoryIds": [],
"brandIds": [],
"currencyAmounts": {},
"minSubtotal": null,
"currencyMinSubtotals": {},
"excludeSaleItems": false,
"appliesOnSubscription": true,
"additionalRules": [],
"createdAt": "2024-05-20T10:30:00.000Z",
"updatedAt": "2024-05-20T10:30:00.000Z"
}
],
"meta": {
"count": 1,
"offset": 0,
"limit": 10
}
}

Create Coupon

POST /api/v1/coupons

Creates a discount code. Codes are matched case-insensitively and are unique per store, so creating save10 when SAVE10 exists returns a 409. For fixed coupons on multi-currency stores, supply per-currency amounts via currencyAmounts.

Request Body

FieldTypeRequiredDescription
codestringYesThe code customers enter at checkout. Letters A to Z, digits, and hyphens, up to 64 characters, matched case-insensitively
type"percentage" | "fixed" | "free_shipping"YesA percentage off, a fixed amount off, or free delivery
valuenumberConditionalPercentage (e.g. 10 = 10%, max 100) or fixed amount in store currency (min 1). Required for percentage and fixed; omit it for free_shipping
freeShippingbooleanNoAlso make delivery free on top of a percentage or fixed discount (default false). Ignored for free_shipping coupons, which always free delivery and read back as true
startDatestringNoISO 8601 datetime the coupon becomes valid
endDatestringNoISO 8601 datetime the coupon expires. Must be after startDate when both are set
maxUsesnumberNoMaximum total redemptions across all customers
oncePerCustomerbooleanNoLimit to one use per customer (default false). Each checkout email, matched case-insensitively, can place one order with the code; a cancelled order gives the use back. Combine with maxUses for a code capped both in total and per customer. Applying the code to a cart whose email already used it fails with couponAlreadyUsed
minProductCountnumberNoMinimum cart product count required
maxProductCountnumberNoMaximum cart product count the coupon applies to. Must be >= minProductCount when both are set
productsstring[]NoProduct UUIDs the coupon is restricted to, covering every variant of each product – including variants added later (empty = no product restriction)
variantsstring[]NoVariant UUIDs the coupon is restricted to, when only some variants of a product should be discounted. Other variants of the same product pay full price. A variant whose product is also in products is dropped, since the product already covers it
collectionsstring[]NoCollection UUIDs the coupon is restricted to; membership is evaluated at checkout
categoriesstring[]NoCategory UUIDs the coupon is restricted to; membership is evaluated at checkout
brandsstring[]NoBrand UUIDs the coupon is restricted to; membership is evaluated at checkout
currencyAmountsobjectNoPer-currency fixed amounts for multi-currency stores, keyed by currency code (fixed coupons only)
minSubtotalnumber | nullNoMinimum order value in the store currency, greater than 0. YNS compares it with the cart's products at their gross price after promotions, before a subscription plan discount or the coupon. Shipping is not counted. Below it, applying the code returns 422 with reason: "couponMinSubtotalNotMet". Omit it or send null for no minimum
currencyMinSubtotalsobjectNoMinimum order values per currency, keyed by currency code, each greater than 0. When minSubtotal is set, set one for every other currency the store sells in. In a currency without its own minimum, applying the code returns 422 with reason: "couponIsNotApplicable"
excludeSaleItemsbooleanNoSkip cart lines whose price an active promotion lowered (default false). Those lines keep the promotional price. Volume pricing, subscription plan discounts, and bundles are not a sale
appliesOnSubscriptionbooleanNoPurchase type (default true). true also takes the coupon off the plan price on the first order of a subscription. false limits the coupon to one-time purchases, so subscription lines get only their plan discount. Renewals never get a coupon
additionalRulesobject[]NoUp to 10 extra rules, tried in order before the main rule (default []). See Extra rules
curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"code": "WELCOME10", "type": "percentage", "value": 10, "maxUses": 1000}' \
https://your-store.yns.store/api/v1/coupons

Response (201)

{
"id": "0191abc0-1234-7def-8000-000000000001",
"code": "WELCOME10",
"type": "percentage",
"value": 10,
"freeShipping": false,
"timesRedeemed": 0,
"maxUses": 1000,
"oncePerCustomer": false,
"startDate": null,
"endDate": null,
"minProductCount": null,
"maxProductCount": null,
"productIds": [],
"variantIds": [],
"collectionIds": [],
"categoryIds": [],
"brandIds": [],
"currencyAmounts": {},
"minSubtotal": null,
"currencyMinSubtotals": {},
"excludeSaleItems": false,
"appliesOnSubscription": true,
"additionalRules": [],
"createdAt": "2024-06-15T10:30:00.000Z",
"updatedAt": "2024-06-15T10:30:00.000Z"
}

Free shipping coupons

A free_shipping coupon makes the shopper's chosen delivery method cost nothing (and carry no tax) while product prices stay the same. It works alongside any free-delivery threshold the store already has, and value is reported as 0 when the coupon is read back.

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"code": "FREESHIP", "type": "free_shipping", "maxUses": 500}' \
https://your-store.yns.store/api/v1/coupons

Scope restrictions still apply: with products, variants, collections, categories or brands set, delivery becomes free as soon as any cart line matches one of them.

Discount plus free shipping

To give money off and free delivery with one code, create a percentage or fixed coupon with freeShipping: true. The discount applies to qualifying products as usual, and the delivery method costs nothing, under the same scope and cart-item rules.

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"code": "SUMMER15", "type": "percentage", "value": 15, "freeShipping": true}' \
https://your-store.yns.store/api/v1/coupons

PATCH replaces the coupon's fields, so send freeShipping: true again when updating a coupon that should keep free delivery.

Limit a coupon to specific variants

List variant UUIDs in variants to discount only those variants – for example the "Start" plan of a product sold in several plans. Leave the product itself out of products: a product there covers all its variants. A cart line qualifies if its product is in products or its variant is in variants; if no line in the cart qualifies, applying the code returns 422 with reason: "couponIsNotApplicable".

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"code": "START20", "type": "percentage", "value": 20, "variants": ["0191abc0-1234-7def-8000-0000000000aa"]}' \
https://your-store.yns.store/api/v1/coupons

The response lists the variants in variantIds.

Extra rules

additionalRules gives one code different deals on different products. The coupon's own type, value, and scope are its main rule. Each entry in additionalRules is one of two kinds, set by type.

A scope below means the five arrays products, variants, collections, categories, and brands, each a list of UUIDs (default []). A product in products covers all its variants. Every scope in a rule needs at least one non-empty array.

Percentage rule

FieldTypeRequiredDescription
type"percentage"NoThe default when type is left out
valuenumberYesPercent off, from 0.001 to 100, with up to 3 decimals (33.333 = 33.333%)
products, variants, collections, categories, brandsstring[]One of themThe products the rule discounts
conditionobject | nullNoApply the rule only while the cart holds enough of some products. See below

condition has minQuantity (an integer, at least 1) and its own scope. The rule applies only while the cart holds at least minQuantity units from that scope. The counted products can differ from the discounted ones.

Buy X get Y rule

FieldTypeRequiredDescription
type"buyXGetY"Yes
buysobjectYesquantity (integer, at least 1) and a scope: what the customer buys at full price
getsobjectYesquantity (integer, at least 1), value (percent off, 0.001 to 100, 100 = free) and a scope: what the customer then gets discounted
usesPerOrderLimitnumber | nullNoHow many times the offer can apply in one order. Default null, no limit

The offer applies once for every buys.quantity units from the buy scope plus gets.quantity more units from the get scope. The discounted units are the cheapest ones, the most expensive ones count as bought, and no unit counts as both. With the same product on both sides, buy 2 get 1 gives 1 free unit for 3 in the cart, still 1 for 5, and 2 for 6.

How rules combine

Each cart line takes one rule. YNS tries the extra rules in array order, then the main rule. A line takes the first rule that covers it and is active:

  • A percentage rule is active when it has no condition, or the cart meets it.
  • A buy X get Y rule is active when the cart holds at least one full set. It covers the lines in both its scopes, so those lines take no other discount, including units left over after the last full set.
  • The main rule is always active.

Rules never add up on one line. A line that no rule takes pays full price. The code applies when at least one line gets a discount from it.

With excludeSaleItems: true, lines on sale are left out of every rule. With appliesOnSubscription: false, subscription lines are too. A left-out line is not discounted, does not count as a bought unit, and does not count toward a minQuantity.

Responses list each rule with its scopes as productIds, variantIds, collectionIds, categoryIds, and brandIds. A percentage rule always has condition, null when it has none:

"additionalRules": [
{
"type": "buyXGetY",
"buys": { "quantity": 2, "productIds": ["0191abc0-1234-7def-8000-0000000000c1"], "variantIds": [], "collectionIds": [], "categoryIds": [], "brandIds": [] },
"gets": { "quantity": 1, "value": 100, "productIds": ["0191abc0-1234-7def-8000-0000000000c1"], "variantIds": [], "collectionIds": [], "categoryIds": [], "brandIds": [] },
"usesPerOrderLimit": null
},
{
"type": "percentage",
"value": 10,
"productIds": ["0191abc0-1234-7def-8000-0000000000b1"],
"variantIds": [],
"collectionIds": [],
"categoryIds": [],
"brandIds": [],
"condition": { "minQuantity": 3, "productIds": ["0191abc0-1234-7def-8000-0000000000c1"], "variantIds": [], "collectionIds": [], "categoryIds": [], "brandIds": [] }
}
]

Example: code K3BA

This coupon gives a free tub of creatine (…c1) for every two. A cart with at least three tubs also gets 10% off an add-on product (…b1). The main value is 0, so the code takes nothing off other products.

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"code": "K3BA",
"type": "percentage",
"value": 0,
"additionalRules": [
{
"type": "buyXGetY",
"buys": { "quantity": 2, "products": ["0191abc0-1234-7def-8000-0000000000c1"] },
"gets": { "quantity": 1, "value": 100, "products": ["0191abc0-1234-7def-8000-0000000000c1"] }
},
{
"type": "percentage",
"value": 10,
"products": ["0191abc0-1234-7def-8000-0000000000b1"],
"condition": { "minQuantity": 3, "products": ["0191abc0-1234-7def-8000-0000000000c1"] }
}
]
}' \
https://your-store.yns.store/api/v1/coupons

With 3 tubs and the add-on product, the cart pays for 2 tubs and gets the add-on 10% cheaper. With 2 tubs, neither extra rule is active and the code does not apply.

A value of 0 is allowed only on a coupon with additionalRules: it means no main discount. For "5% on everything, 33% on creatine", leave the main scope empty and set value to 5, then add one percentage rule with "value": 33 on the creatine product. Creatine lines take 33%, and every other line takes 5%.


Get Coupon

GET /api/v1/coupons/:idOrCode

Returns a single coupon, addressable by its UUID or by its code.

Response

{
"id": "0191abc0-1234-7def-8000-000000000001",
"code": "WELCOME10",
"type": "percentage",
"value": 10,
"timesRedeemed": 42,
"maxUses": 1000,
"oncePerCustomer": false,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-12-31T23:59:59.000Z",
"minProductCount": 1,
"maxProductCount": null,
"productIds": [],
"variantIds": [],
"collectionIds": [],
"categoryIds": [],
"brandIds": [],
"currencyAmounts": {},
"minSubtotal": null,
"currencyMinSubtotals": {},
"excludeSaleItems": false,
"appliesOnSubscription": true,
"additionalRules": [],
"createdAt": "2024-05-20T10:30:00.000Z",
"updatedAt": "2024-05-20T10:30:00.000Z"
}

Update Coupon

PATCH /api/v1/coupons/:idOrCode

Replaces the coupon's fields. Uses the same body as Create Coupon. A field you leave out takes its default, so send every field the coupon should keep, including minSubtotal, currencyMinSubtotals, excludeSaleItems, appliesOnSubscription, oncePerCustomer, and additionalRules. Renaming onto a code another coupon already owns returns a 409.

Request Body

Same fields as Create Coupon.

curl -X PATCH \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"code": "WELCOME10", "type": "percentage", "value": 15}' \
https://your-store.yns.store/api/v1/coupons/0191abc0-1234-7def-8000-000000000001

Response (200)

{
"id": "0191abc0-1234-7def-8000-000000000001",
"code": "WELCOME10",
"type": "percentage",
"value": 15,
"timesRedeemed": 42,
"maxUses": 1000,
"oncePerCustomer": false,
"startDate": "2024-06-01T00:00:00.000Z",
"endDate": "2024-12-31T23:59:59.000Z",
"minProductCount": 1,
"maxProductCount": null,
"productIds": [],
"variantIds": [],
"collectionIds": [],
"categoryIds": [],
"brandIds": [],
"currencyAmounts": {},
"minSubtotal": null,
"currencyMinSubtotals": {},
"excludeSaleItems": false,
"appliesOnSubscription": true,
"additionalRules": [],
"createdAt": "2024-05-20T10:30:00.000Z",
"updatedAt": "2024-06-15T11:00:00.000Z"
}

Delete Coupon

DELETE /api/v1/coupons/:idOrCode

Hard-deletes the coupon by UUID or code. A coupon that is currently assigned to the store's enabled welcome offer cannot be deleted – disable the offer or reassign it first.

curl -X DELETE \
-H "Authorization: Bearer your_api_key" \
https://your-store.yns.store/api/v1/coupons/WELCOME10

Response (200)

{
"ok": true,
"deleted": 1
}

Welcome Offer Guard (409)

{
"error": "Coupon is used by the enabled welcome offer -- disable the offer or reassign it first"
}