Let customers choose their free items from a promotion's pool
A free item promotion can offer the customer a choice of products instead of a single fixed one. The promotion defines a pool of products on offer, a total quantity the customer may take across the pool in any combination, and optionally a maximum quantity per product. When the promotion is applied, Uniqodo adds a default free item to the order and returns the whole pool, so your platform can let the customer choose. A product bundle whose reward is a free item works the same way.
Choosing between several free items has to be enabled for your merchant account; contact Uniqodo to turn it on. Uniqodo provides the data and validates the choice, but your integration needs to build the selection UI that shows the pool to the customer and collects their choice.
How it works
-
Apply the promotion. Call
/v2/applywith the order and the coupon code (or promotion ID). The response adds the default free item toorder.productsas a line withprice: 0andfreeItem: true. The applied promotion'sappliedRewardcarriesproducts(what the customer currently has) andfreeItemPool(everything on offer, each with an optionalmaxQuantity). -
Present the choice. In your selection UI, show the products in
freeItemPool, with the items inproductspre-selected, and respectquantityandmaxQuantityso invalid choices are not sent. Product names, images and prices come from your own catalogue; Uniqodo returns product IDs only. -
Send the choice. On the next
/v2/applycall, send the order exactly as it was returned, plus the top-levelfreeItemSelection:{ "order": { "...": "the order as returned by the last /v2/apply call" }, "freeItemSelection": { "promotionId": "8f0a6c3e-3b61-4c1e-9d0a-6a1f2b7c9e10", "products": [ { "id": "pump", "quantity": 1 }, { "id": "lights", "quantity": 1 } ] } }The response replaces the default free item with the chosen ones, and
appliedReward.productsnow holds the customer's choice. -
Keep it in step. On every later
/v2/applycall, sendappliedPromotionsback unchanged. The choice travels inappliedReward.products, so you only sendfreeItemSelectionagain when the customer changes their mind. -
Complete the order. On
/v2/transactions, send the applied promotion withappliedReward.productsas the last/v2/applycall returned it, so the customer's choice is recorded.
For automatic promotions, /v2/applicable also returns reward.freeItemPool, so you can show what is on offer in advance.
What makes a choice valid
A freeItemSelection is accepted only if:
- every chosen product is in the promotion's
freeItemPool, - the chosen quantities add up to no more than the reward's
quantity, and - no product exceeds its own
maxQuantity.
The customer may pick the same product more than once, and may take fewer items than the total allows.
A rejected choice costs the customer the choice, not the promotion. The promotion stays applied with the free items it was granting before, and the reason is returned in errors.
Rules for your integration
- Echo applied promotions unchanged, including the whole
appliedReward. If it is left out, the customer's choice is lost and the promotion falls back to its default. - Send free item lines back with
freeItem: true. A line without it is treated as a product the customer pays for, so the customer would end up with that product twice. - Change the choice only with
freeItemSelection. Do not editappliedReward.productsor the free item lines. - Name the promotion when there is more than one. If the order carries two or more free item promotions, set
freeItemSelection.promotionId. - Choose after applying. A choice cannot target the promotion that the same call applies: apply first, then send the choice on the next call. A choice for one promotion can travel with a code that applies another.
- Read
errorson every response. Free item errors are returned alongside a successful order.
Free items never help an order qualify for another promotion; only products the customer pays for count.
Errors
Error id | What happened | What to do |
|---|---|---|
invalid_free_item_selected | A chosen product is not in the pool. | Refresh the choice from freeItemPool. |
free_item_quantity_exceeded | The chosen quantities add up to more than the reward's quantity. | Enforce the total in your UI and ask the customer to choose again. |
free_item_product_quantity_exceeded | A chosen product exceeds its maxQuantity. | Enforce maxQuantity in your UI and ask the customer to choose again. |
free_item_selection_target_not_applied | The choice targets a promotion that is not in the echoed appliedPromotions. | Apply the promotion first, then send the choice on the next call. |
ambiguous_free_item_selection | The order carries more than one free item promotion and the choice has no promotionId. | Set freeItemSelection.promotionId. |
free_item_grant_replaced_with_default | The promotion was changed and the customer's free items no longer fit it, so an automatic promotion moved them to its new default. | Tell the customer their free item changed and show the new pool. |
The first three errors are also returned if the promotion was changed after the customer chose and their free items no longer fit it. A promotion applied by code is then removed from the order, and the customer can re-apply the code to see the current pool. See API Errors for the general error format.
