Sales
Automatic discounts with no code involved: a header, its rules and its conditions.
Automatic discounts with no code involved. A sale is a header plus a list of rules that are only meaningful together, so a rule has no route of its own.
List sales
/api/v1/kahunacart/sales
Every sale with its header, its rules and its quantity ladder. A sale is an automatic, scheduled, site-wide discount that needs no code and changes the price the catalog shows, which is what makes it a different thing from a coupon. Rule and tier amounts come back both as amount_minor and as a decimal amount.
Get a sale
/api/v1/kahunacart/sales/{id}
One sale with its rules in order, its quantity ladder lowest rung first, and the translation overrides on its shopper-facing label.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Sale id |
Create a sale
/api/v1/kahunacart/sales
Create an automatic discount. Both name and label are required and they are not the same string: the name is the merchant's handle for the campaign and the label is the badge a shopper reads on the price. Rules resolve most specific first, an exclusion beats everything, and rule order is the order you send them in rather than any position you set. Overlapping sales resolve by priority. Coupons run after sales and work on what is left.
Parameters
| Name | Type | Description |
|---|---|---|
| name required | string | The merchant's handle for the campaign; never shown to shoppers |
| label required | string | The badge a shopper reads beside the sale price |
| starts_at optional | integer or null | Unix timestamp; null for no start date |
| expires_at optional | integer or null | Unix timestamp; null for no end date |
| priority optional | integer | Higher wins when two sales cover the same line |
| enabled optional | boolean | |
| stack_with_coupons optional | string |
What a discount code does on the lines this sale covers. stack (the default): the code comes off the sale price. refuse: the code is refused on those lines with a message. best: the customer gets whichever of the sale or the code saves more on each line, never both, the code worked out on the list price. Read back as 1, 0 or 2; true and false are still accepted for stack and refuse
One of:
stack, refuse, best |
| rules optional | array of object | What this sale touches, resolved in the order given. Keys: `scope`, `scope_id`, `mode`, `percent`, `amount`, `currency_amounts`, `exclude`, `operation_code`, `args` |
| conditions optional | object | When this campaign applies at all, as a nested document — {"container": "all", "children": [{"attribute": "cart_subtotal", "operator": "gte", "value": 5000}]}. Containers are all, any or none and they nest. Attributes come from list_rule_attributes and an unknown one is refused. A sale with conditions is priced at checkout rather than on the catalogue, so it shows no badge and no strike-through price on a product card. A condition on an amount of money (type money) is in one currency: leave `currency` out for the store currency, or name a currency the store sells in, as in {"attribute": "cart_subtotal", "operator": "gte", "value": 4500, "currency": "EUR"}. It is false on an order in any other currency, so cover two currencies with one condition each inside an `any` container. |
| tier_basis optional | string |
What a quantity tier counts. `units` counts every unit across the lines this sale covers, so three of one t-shirt reaches "buy 3". `products` counts distinct products, so three of one t-shirt reaches nothing and one each of three reaches it — the "buy any three of our plugins" offer.
One of:
units, productsDefault:
units |
| tiers optional | array of object | A quantity ladder: "buy 2, save 10%; buy 3, save 15%". The rules still decide what the sale covers; a tier decides what comes off given how many of them are in the cart, and the highest rung the cart reaches replaces the sale's own discount rather than adding to it. A sale with tiers and no base discount takes nothing off below its first rung and shows a "buy 2+ and save" badge instead of a sale price. Counts must be 2 or more, strictly increasing, and one tier per count. Keys: `min_quantity`, `mode`, `percent`, `amount`, `currency_amounts` |
| translations optional | object | Language code to overrides for the shopper-facing label |
Update a sale
/api/v1/kahunacart/sales/{id}
Change a sale's header, its rules, or both. Sending rules replaces the whole set, empty list included, which is how the last rule is removed; omitting it leaves the rules standing, so switching a sale off is one field and not a round trip through every rule. Saving rewrites the rules and tiers, so on a store that sells in more than one currency a rule or tier sent without currency_amounts keeps the ones its predecessor in the same place had, and what is sent wins over what was kept.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Sale id |
| name optional | string | |
| label optional | string | |
| starts_at optional | integer or null | |
| expires_at optional | integer or null | |
| priority optional | integer | |
| enabled optional | boolean | |
| stack_with_coupons optional | string |
stack takes a code off the sale price, refuse refuses codes, best gives whichever saves more; true and false still work
One of:
stack, refuse, best |
| rules optional | array of object | Replaces the whole set when present; an empty list removes every rule. Keys: `scope`, `scope_id`, `mode`, `percent`, `amount`, `currency_amounts`, `exclude`, `operation_code`, `args` |
| conditions optional | object | Replaces the whole condition set when present; a container with no children removes it and puts the campaign back on the catalogue. Omitting it leaves the conditions standing. |
| tier_basis optional | string |
One of:
units, products |
| tiers optional | array of object | Replaces the whole ladder when present; an empty list removes it. Counts must be 2 or more, strictly increasing, and one tier per count. Keys: `min_quantity`, `mode`, `percent`, `amount`, `currency_amounts` |
| translations optional | object |
Delete a sale
/api/v1/kahunacart/sales/{id}
Delete a sale, its rules and the translations of its label. Prices go back to what the catalog says the moment it is gone.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Sale id |
Header fields are name (the merchant's handle), label (the badge a shopper reads), starts_at, expires_at, priority, enabled, stack_with_coupons and translations. A rule takes scope, scope_id, mode, percent, amount and exclude, plus operation_code and args when it prices through an operation other than the two ordinary ones; a rule's position comes from its place in the list, not from a position field. Omitting rules from a PATCH leaves them standing, so switching a sale off is one field. Each rule comes back with both amount_minor and a decimal amount.
A sale also carries conditions, one nested document of containers (all, any, none) and leaves (attribute, operator, value), nesting three levels deep. Sending it replaces the whole set, a container with no children removes it, and omitting the key leaves the conditions standing. An operation code or a condition attribute the store does not publish is refused with a 422 naming it.