Discount codes
Define your own promo codes, check one against a cart, and apply it at checkout.
Discount codes are yours — you define them for your own end customers. They never change what you pay Linra: your commission, VAT, and settlement are computed exactly as if the order had no discount at all. A code is purely a display/reporting overlay on top of the price Linra computes.
1. Create a code
Section titled “1. Create a code”POST /api/v1/scent/discount-codes{ "code": "SAVE10", "discountType": "Percent", "value": 10}{ "state": "CREATED", "payload": { "id": "5b2e...", "code": "SAVE10", "discountType": "Percent", "value": 10, "isActive": true, "scopedVariantIds": [], "usageCount": 0 }}Omitting scopedVariantIds (or sending an empty list) applies the code to your whole
catalog. To restrict it to specific products instead, pass the variant IDs explicitly:
POST /api/v1/scent/discount-codes{ "code": "SAUVAGE10", "discountType": "Percent", "value": 10, "scopedVariantIds": ["a1b2c3d4-...", "e5f6a7b8-..."]}The code string is immutable once created, and so is everything else about it — there is no
update endpoint at all. To retire a typo’d or campaign-ended code, deactivate it and create a new
one:
PATCH /api/v1/scent/discount-codes/{id}/active{ "isActive": false }This activate/deactivate toggle is the only mutation a discount code supports after creation — it’s also how you retire a code that has already been redeemed at least once, since a redeemed code can never be deleted (see “Managing usage” below).
Guardrails you can set at create time
Section titled “Guardrails you can set at create time”| Field | Meaning |
|---|---|
validFrom / validTo |
Optional validity window. Both null = open-ended. |
totalUsageCap |
Optional cap on total redemptions across all customers. |
perCustomerCap |
null (no limit) or exactly 1 (at most one redemption per customerRef, ever). No other value is accepted in v1. |
scopedVariantIds |
Present and non-empty ⇒ the code applies only to those variants. Omitted/empty ⇒ applies to your whole catalog. |
minOrderValue |
Optional minimum cart total (VAT-inclusive) for the code to apply. |
Every one of these is fixed for the life of the code — there is no way to edit a term after
create; deactivate and recreate instead. A code is scoped to you — two partners may both
register SAVE10 independently.
2. Validate it against a cart
Section titled “2. Validate it against a cart”Before checkout, check whether a code currently applies — this never consumes a use. customerRef
is a required path segment, identifying which of your carts to check against — pass the same
value you used to build that cart:
POST /api/v1/scent/cart/{customerRef}/discount-codes/validate{ "code": "SAVE10" }{ "state": "SUCCESS", "payload": { "valid": true, "reason": null, "totals": { "originalTotal": 200.00, "discountAmount": 20.00, "discountedTotal": 180.00 } }}(Worked from a cart holding 2 units at 100 SAR each — a 10% code computes a 20 SAR discount,
Math.Round-ed to 4 decimal places and never exceeding the cart total.)
If you’ve requested a supported non-SAR display currency via
X-Partner-Currency, payload.totals additionally carries a nested display object —
{ originalTotal, discountAmount, discountedTotal } — the same additive display-currency pattern
used everywhere else in the API, alongside the canonical SAR figures above (never replacing them).
The code’s own configured value (e.g. a fixed-amount discount) is always SAR-denominated
regardless of your display currency — only the computed result shown back here converts for
display.
If the code doesn’t apply, valid is false and reason is one of:
| Reason | Meaning |
|---|---|
EMPTY_CART |
Your cart has no lines. |
NOT_FOUND |
No such code for your account. |
INACTIVE |
The code exists but is deactivated. |
NOT_YET_VALID / EXPIRED |
Outside the code’s validity window. |
BELOW_MIN_ORDER_VALUE |
Cart total is below the code’s minOrderValue. |
PRODUCT_SCOPE_MISMATCH |
The code has a non-empty scopedVariantIds, and none of your cart lines are in it. |
CAP_REACHED |
The total-usage cap has been reached (advisory here — see below). |
ALREADY_USED_BY_CUSTOMER |
This customerRef has already redeemed a per-customer-capped code. |
totals.originalTotal/totals.discountAmount/totals.discountedTotal are display figures only —
they never change what you are actually charged. discountAmount/discountedTotal (SAR and
display alike) are null whenever valid is false.
This validate call is the only place a discount is ever shown as its own line.
Checkout preview and the price lock it produces stay
discount-free by design — its totals are exactly what Linra charges/nets, which a discount code
never changes (see “How charging works” below). To show the end customer their saving before they
confirm, call validate and render its totals yourself; don’t expect a discount line to appear
anywhere in the cart/preview response.
3. Apply it at order creation
Section titled “3. Apply it at order creation”Pass the SAME code on the order-create call. partnerId is never a request field — it comes from
your bearer token — and lockId is required: every order is placed against a frozen price
lock (the id from a prior POST /api/v1/scent/cart/{customerRef}/price-lock call), never
a live-priced create — see the Cart preview & price locks guide:
POST /api/v1/scent/orders{ "externalReference": "order-42", "lockId": "9f2b3c1a-6e7d-4a12-8b3f-1c2d3e4f5a6b", "discountCode": "SAVE10", "shippingAddress": { "...": "..." }}{ "state": "CREATED", "payload": { "id": "...", "pricing": { "discount": { "code": "SAVE10", "amount": 20.00 }, "...": "..." } }}The code is re-validated at this point (state may have changed since you last validated it — the
usage cap in particular is only advisory at validate time and authoritative here). If it no
longer applies, order creation is rejected with VALIDATION_DISCOUNT_CODE_INVALID,
VALIDATION_DISCOUNT_CODE_EXPIRED, VALIDATION_DISCOUNT_CODE_CAP_REACHED, or
BUSINESS_DISCOUNT_CODE_ALREADY_USED_BY_CUSTOMER — never a silent downgrade to “no discount
applied.” A use is only ever counted when the order itself is actually created: a failed or
cancelled checkout never burns a redemption.
How charging works (the part that never changes)
Section titled “How charging works (the part that never changes)”However a code is defined, it has zero effect on:
- what you pay Linra (wholesale amount, or your commission in commission mode),
- VAT computation and settlement,
- whether a price is available to check out.
Those are all computed on the undiscounted price, exactly as if no code were applied. If you want to fund a promotion, it comes out of your own share of the sale — Linra has no stake in it either way.
Managing usage
Section titled “Managing usage”GET /api/v1/scent/discount-codes and GET /api/v1/scent/discount-codes/{id} include a running
usageCount so you can track redemption progress without a separate report. A code that has never
been redeemed (usageCount == 0) can be deleted outright
(DELETE /api/v1/scent/discount-codes/{id}); once it has at least one redemption, delete is
rejected with BUSINESS_DISCOUNT_CODE_IN_USE — deactivate it instead, so the historical order data
referencing it stays meaningful.

