Basket
General
Customers must place items in the basket before proceeding to the checkout and completing a purchase. Included products can be reviewed, edited, or moved to the customer's wishlist at any point in time.
Products in the basket always include the respective variant since this is the entity to be sold. So, whether you are adding, updating, or deleting items, you deal with the individual variant and its availability, without exception.
The products are kept for 60 days in the basket and this time is reset in case any change is made within the basket entity. After this period, the products are deleted from the basket.
You can apply one or more promotions to a basket item through the promotions list. Each entry is a promotion id with an optional code. The applied promotions appear on the item as appliedReductions (one entry per promotion, each with category: "promotion" and its promotionId), so several promotions can stack on the same item.
For every applied promotion, its validity (promotion.isValid) and any failed conditions (promotion.failedConditions) depend on whether the promotion meets its conditions. If the conditions are met, isValid is true and failedConditions is empty; otherwise isValid is false and the failed conditions are listed. If no promotions are applied, the item's promotion list is empty.
The single-value promotionId / promotionCode fields are deprecated. They cannot express more than one promotion per item. Use the promotions list instead. The deprecated fields still work for backward compatibility.
Retrieve all promotions that could be applied to the basket by including the ?with=applicablePromotions query parameter. Applicable promotions are returned for items that do not already have promotions attached. If you attach one or more promotions to an item, those become directly associated with it, and no further applicable promotions are returned for that item.
For example, to get basket items together with their applicable promotions: GET /v1/baskets/{basket_key}?with=applicablePromotions
You need a configured Storefront API client to:
- get basket items
- get basket items with applicable promotions
- get basket items for a campaign (
campaignKey), returning campaign-discounted prices - add items to the basket
- add items to the basket with one or more promotions (
promotionslist) - add items to the basket with a price promotion key (PPK)
- add items to the basket combining PPK,
campaignKeyandpromotions - add a configurable item to the basket (
itemGroup) - update items in the basket
- update a basket item's promotions (
promotionslist) - change item quantity
- bulk-update the promotions applied across basket items
- delete basket item
Storefront API
When selling products, remember that you are always working with its respective variants.
The number of items you can add to the basket depends on how many product variants are in stock.
If you add a number higher than what is in stock, the system will automatically add the maximum number of what is available. If a product is not available at all, the request will fail. If a variant already in the basket goes out of stock, it is flagged as sold out.
Item quantity
addItem() adds a variant that is not yet in the basket. If the variant is already there, the request returns 409 and the SDK gives you a failure result with kind: "VariantAlreadyPresent".
To change the amount of a variant that is already in the basket, update the quantity, or use addOrUpdateItems(), which resolves add-versus-update per item.
Add basket item
We are going to add our first product variant to the basket of a customer. In this example it is about customer 1234 who wants 3 pieces of a specific shirt with variant ID 1.
Each basket has a specific key which we can freely come up with. Here we can use the pattern myshop_customer_$customerId. Whenever we want to work with this basket, we need to refer to that specific key. We also request some product and variant related information, e.g. product name and variant sizes.
addItem() adds a single variant. It returns 409 with kind: "VariantAlreadyPresent" if that variant is already in the basket, so it is not safe to call twice for the same variant.
The NodeJS SDK also provides addOrUpdateItems(), which takes an array of items and resolves each one for you: it adds the item when the variant is not in the basket, and updates the existing item when it is. The existingItemHandling option controls how an existing item is treated (KEEP_EXISTING, ADD_QUANTITY_TO_EXISTING, REPLACE_EXISTING, or the default REPLACE_EXISTING_WITH_COMBINED_QUANTITY).
| Parameter | Type | Required | Description |
|---|---|---|---|
basketKey | string | true | Key of the basket. |
variantId | integer | true | ID of the variant to be added to the basket. |
quantity | integer | true | Quantity of variants to be added to the basket. |
promotions | array | false | Promotions to apply to the item. Each entry is a promotion id with an optional code for code-based promotions. |
pricePromotionKey | string | false | Applies a price promotion to the item. The SDK writes it into the item's customData, so it is the key applied during checkout: it changes the price charged for the item and the basket total, and it persists with the order. |
campaignKey | string | false | Applies campaign pricing to the item's product and variant data. Passed as a query parameter. |
displayData | object | false | Additional display information (for example name, identifier, and up to three attributes) attached to the item. It stays on the item throughout checkout and the order. |
itemGroup | object | false | Groups the item with others as a configurable product: { id, isMainItem, isRequired }. |
customData | object | false | For each item, you can add an additional custom data object. The data remains attached to the basket item and you may retrieve it at any time during or after the checkout and order process. Setting |
with | object | false | Defines which optional fields should be included in the response. Contained as with inside the options object. |
There are two ways to use a price promotion key:
Persist it on the item. Pass pricePromotionKey to addItem(), addOrUpdateItems() or updateItem(). The SDK writes it into the item's customData, so it is the key applied during checkout. It changes the price charged, the basket total, and it stays with the order.
Preview it for a single request. Pass pricePromotionKey to bulkUpdatePromotions() or getApplicablePromotionsByCode(), or send it as a pricePromotionKey query parameter on any basket request. It only re-prices the product and variant data returned under each item, so you can show a promotional price without committing to it. The basket total and the price charged at checkout stay unchanged.
Response
To add several items in one call, and let the SDK decide per item whether to add or update:
Add or update items with one or more promotions
Pass a promotions list to apply one or several promotions to an item in a single request. Each entry is a promotion id, with an optional code for code-based promotions. To apply a promotion by code, include the code alongside its id in the list. To change the promotions on an existing item, send the item's full desired promotions list on the update request, or use the bulk endpoint to update promotions across many items at once.
Response
Add items to the basket with PPK
It is possible to add an item to the basket with a PPK (Price Promotion Key). The key is stored in the item's customData and is the key applied during checkout, so it changes the price charged for the item and the basket total. Below is an example of how this works:
Example:
- Product original price: $999.00
- After applying the PPK: $777.00
The final payable amount after the PPK discount is $777.00.
Response
Combine PPK, campaignKey and promotions
A price promotion key, a campaign, and item promotions are independent and can be used together on the same request. campaignKey is a query parameter, while pricePromotionKey (PPK) and the promotions list go in the item body.
Response
Add a configurable item
Configurable products are added as grouped basket items. Each item in a group carries an itemGroup object with a group id that you define, a flag for whether it is the group's main item (isMainItem), and a flag for whether it is required within the group (isRequired). Add the main item and its members with the same itemGroup.id.
Removing a required member of a group (including the main item) removes the entire group from the basket. A member marked as not required can be removed on its own, leaving the rest of the group in place. Quantities are managed per item and are not linked across the group. Each item carries its itemGroup in basket responses (null for items that are not part of a group).
Response
Get all basket items
We can request the current state of a basket by passing the basket key. Here we can also define which attributes of the product and its variant we want to retrieve.
A basket key that has never been used is not an error. The request returns 200 with an empty basket ("items": []). Baskets are created on demand when the first variant is added.
Note that you need to have an item in the basket.
Optionally you can add a campaignKey to retrieve updated discounted prices.
Response
Get basket items with applicable promotions
Retrieve all applicable promotions alongside the basket items by including the ?with=applicablePromotions query parameter.
Response
Get basket items with applicable promotions by promotion code
Send a promotion code to retrieve the promotions it unlocks for the basket. The matched promotions are returned under applicablePromotions, each with the itemId it applies to. Add ?with=applicablePromotions to include them in the response.
Response
Update basket item with PPK
We can update the pricePromotionKey (PPK) for the basket item by passing the item key, like so:
Response
Change quantity
Sometimes our customers change their mind and change quantity. We can update basket items by passing the item key and the updated quantity.
| Parameter | Type | Required | Description |
|---|---|---|---|
basketKey | string | true | Key of the basket. |
itemKey | string | true | Key of the basket item. |
quantity | integer | true | New quantity of the item. |
promotions | array | false | Promotions to apply to the item. Each entry is a promotion id with an optional code. The submitted list replaces the item's current promotions, so send the complete list of active promotions to avoid unintended removals. |
pricePromotionKey | string | false | Passed as a query parameter. Adjusts the product and variant price shown under the item to the matching price promotion, falling back to the default price if none matches. This is display only: it does not change the basket total or the price charged at checkout. To apply a price promotion to the charged price, set pricePromotionKey inside customData instead. |
campaignKey | string | false | Applies campaign pricing to the item's product and variant data. Passed as a query parameter. |
displayData | object | false | Additional display information (for example name, identifier, and up to three attributes) attached to the item. It stays on the item throughout checkout and the order. |
itemGroup | object | false | Groups the item with others as a configurable product: { id, isMainItem, isRequired }. |
customData | object | false | Additional custom data stored on the basket item. The data remains attached to the item during and after the checkout and order process. Setting |
with | object | false | Defines which optional fields should be included in the response. Contained as with inside the options object. |
Response
Bulk-update promotions across items
Use the bulk endpoint to set the promotions on several basket items in one request. The submitted list is treated as the complete set of promotions for each item, so any promotion you do not include is removed. To avoid unintended removals, always send the full active list per item. Items you omit from the payload keep their existing promotions unchanged.
Response
Delete basket item
To delete an item from the basket we need to pass the item key.
deleteItem() returns the basket directly. Unlike get(), addItem() and updateItem(), the result is not wrapped in a basket key and carries no type or statusCode. Read response.items, and narrow with 'code' in response to detect an error result.
| Parameter | Type | Required | Description |
|---|---|---|---|
basketKey | string | true | Unique identifier of the basket. |
itemKey | string | true | Key of the item to remove. Returned as key on each basket item. |
campaignKey | string | false | Applies campaign pricing to the product and variant data in the returned basket. Passed as a query parameter. |
with | object | false | Defines which optional fields should be included in the response. Contained as with inside the options object (third parameter). |
Response
Bulk add, update and remove basket items
Sometimes a customer changes many items at once, for example when a returning customer reorders a past order with 40 items. Instead of sending one request per item, you can send all the changes in one request to POST /v1/baskets/{basketId}/bulk, where {basketId} is the basket key. You get the updated basket back once, plus one result for each change.
Each change in the request is called an operation. An operation adds, updates or removes one item.
| Parameter | Type | Required | Description |
|---|---|---|---|
basketKey | string | true | Key of the basket. |
operations | array | true | The changes to apply, in the order you send them. Up to 100 operations per request. The fields of each operation are listed in the next table. |
campaignKey | string | false | Applies campaign pricing to the product and variant data in the returned basket. Passed as a query parameter. |
with | string | false | Defines which optional fields should be included in the returned basket. Passed as a query parameter. |
Each operation has an action and the same fields as the matching single-item request, except that the item key is sent as key instead of itemKey:
| Action | Same as | Required fields | Optional fields |
|---|---|---|---|
add | Add basket item | variantId, quantity | promotions, customData, displayData, itemGroup |
update | Change quantity | key | quantity, promotions, customData, displayData, itemGroup |
remove | Delete basket item | key |
key is the key that the basket returns on each item. See Get all basket items.
To apply a price promotion key, set pricePromotionKey inside the operation's customData.
update and remove only work on items that were in the basket before the request. An item added in the same request gets its key only in the response, so send a second request to change it.
The operations run in the order you send them, and the API handles each one separately. If one fails, the others still apply. results[0] describes operations[0], results[1] describes operations[1], and so on.
Always check results, even when the status is 200. An operation can succeed with fewer units than you asked for. See Less stock than requested.
In this example, one request adds five T-shirts and a hoodie, sets the jeans to two pairs and removes the socks. Three of them don't go to plan:
- Only three T-shirts are left, so the basket gets three.
- The hoodie sold out a minute ago.
- The customer had already removed the jeans in another browser tab.
The response status is 207 because one is only partly added and two fail. The basket holds the sneakers that were already in it and the three new T-shirts.
Response
Read the results
Check results on every response, including a 200. The status code only tells you whether any operation failed:
| Status | Meaning | What to do |
|---|---|---|
200 OK | Every operation succeeded. | Check requestedQuantity on add and update results, see Less stock than requested. |
207 Multi-Status | At least one operation failed. The rest went through. | Find the results with "status": "failed" and handle each error.code. |
400 Bad Request | The request is invalid, so no operation ran. | Fix the fields listed in details and send the request again. |
423 Locked | Another request is changing the same basket. | Retry after a short wait. |
500 Internal Server Error | The request didn't finish. Some operations may have gone through. | Get the basket before you retry, so you only resend what didn't go through. |
A failed result carries an error with a code and a message. The message is for developers, so don't show it to customers. These are the codes for each action:
| Code | Actions | Meaning |
|---|---|---|
VARIANT_NOT_FOUND | add | The variant doesn't exist in this shop. |
ITEM_NOT_FOUND | update, remove | No item with this key is in the basket. |
ITEM_ALREADY_EXISTS | add, update | For add, the basket already holds this variant, in the same itemGroup if you sent one. For update, the change moves the item into an itemGroup that already holds the same variant. |
OUT_OF_STOCK | add, update | The variant is sold out. |
QUANTITY_LIMIT_EXCEEDED | add, update | The item's maximum quantity was reached, or for update, the basket's maximum number of units. The operation fails: for add, the item isn't added; for update, it keeps its previous quantity. |
ITEM_LIMIT_EXCEEDED | add | The basket already holds the maximum number of items or units. |
CATEGORY_LIMIT_EXCEEDED | add, update | The item went over a shop category's basket limit. For add, the item isn't added. For update, the item is removed from the basket, including the quantity it had before. For example, your shop allows one pair of sneakers per basket and the basket already holds one. See Basket item limits by shop category. |
INTERNAL_ERROR | all | The operation failed for another reason. Get the basket to see its current state. |
Less stock than requested
If fewer units are available than an add or update asks for, the operation still succeeds with the units that are left. The same happens when a shop category's basket limit leaves room for fewer units. The result then has two quantities:
quantityis what went into the basket.requestedQuantityis what you asked for. It only appears in this case.
In the example above, the T-shirt result has "quantity": 3 and "requestedQuantity": 5. Use the difference to tell the customers that only three were added. The single-item requests answer 206 when they add fewer units than requested. The bulk endpoint keeps the status at 200 if nothing else failed.
Limits
- A request can hold up to 100 operations by default. With more, you get a
400whosedetails[0].descriptionstarts withTOO_MANY_OPERATIONS. - If the basket doesn't exist yet, the request creates it, as a single add does. An
updateorremoveon the new, empty basket fails withITEM_NOT_FOUND. - A bulk request counts as one request against your rate limit, however many operations it holds.
If one operation is invalid, for example a quantity of 0, the whole request fails with 400 and no operation runs. details names each problem by its position in operations:
Replace a loop of single requests
If your shop adds items one request at a time, for a reorder or a "complete the look" button, you can move that loop to the bulk endpoint:
- Build one
addoperation per item. - Send all operations in one request. If you have more than 100, split them into chunks of up to 100 and send the chunks one after the other, not in parallel. Two requests on the same basket at the same time can fail with
423. - Match each failed result to its item by position and tell the customers which items couldn't be added.
ITEM_ALREADY_EXISTSmeans the variant is already in the basket, so don't report it as missing. To change its quantity, send anupdatewith the item'skey.