Skip to main content

The basket model

Two halves: what you sent, and what it costs​

The single most useful thing to understand about UBM is that a basket has two distinct halves, and the API exposes both:

HalfWhat it isWho writes it
The input basketWhat you asked for: items and quantities, coupons applied, the loyalty membership, the customer, fulfillments, tendersYou, through the endpoints
The calculated resultWhat it costs: priced lines, unit discounts, the promotions that fired, coupon outcomes, loyalty outcomes, and tax groupsThe calculation engine, whenever a price-affecting part of the basket changes

🛑 Tax groups are empty on business units configured for the PROMOTION engine — it returns price without tax. See How pricing works.

You never write the calculated result, and you should never derive one yourself. If a total looks wrong, the question is which engine produced it and from what input — not whether UBM added it up correctly.

The calculated result lists the coupons the engine accepted, and the promotions and discounts that actually applied. A coupon that is in the input basket but missing from the calculated result was not accepted, for example because it has expired, and took nothing off. That is the signal to look for, rather than assuming a coupon in the basket means a discount was given. What an accepted coupon took off shows on the lines, as their promotions and actual price.

A basket has two halves. The input basket is what you asked for, including the coupon WELCOME5. The calculated result is what the engine says it costs: the socks promotion applied, a total of 280.00, and no coupons accepted. WELCOME5 is in the input basket but not in the calculated result, so the engine did not accept it, for example because it has expired, and it took nothing off.

Status​

StatusMeaning
ACTIVEThe basket can be read and changed. Every basket starts here
COMPLETEDThe basket has been checked out and a customer order exists

🛑 A COMPLETED basket is locked, not merely discouraged. Every modifying operation — items, coupons, customer, loyalty, fulfillments, tenders, recalculate, a further checkout — returns 400 Bad Request saying the basket is completed: "Cannot modify a completed basket" or "Basket is COMPLETED or does not exist and cannot be modified.". Every read still works, but the basket can never be revived. To sell the same goods again, create a new basket.

A completed basket cannot be deleted either ("Basket is COMPLETED or does not exist and cannot be deleted."). It is the record that ties the purchase to its customer order, and it is removed automatically, like any other basket — see How long a basket lives.

There is no cancelled or expired status. A basket you no longer want is deleted.

A basket starts ACTIVE, where it can be read and changed. Checkout creates the order and makes it COMPLETED, which locks it for good: any change returns 400 Bad Request and it cannot be deleted, while reading it still works; it is removed automatically 18 months after its last change. There is no cancelled or expired status.

How long a basket lives​

A basket is not automatically expired or cleaned up. There is no abandoned-cart sweep and no tenant setting that removes baskets, so an unfinished basket stays ACTIVE and fully usable until you delete it.

Storage is reclaimed only after 18 months without modification, and that period is sliding — most calls restart it. A cart a shopper returns to every few weeks therefore persists indefinitely, and a long-lived webshop basket never needs recreating.

Checkout does not restart the period, so a completed basket is removed 18 months after the last change made to it before checkout.

⚠ Treat 18 months as the retention period today, not a guarantee to build against. It may change. If your integration depends on baskets surviving a particular length of time, tell us rather than inferring it from this number.

What restarts each clock​

Two independent clocks run on a basket, and the same call does not necessarily restart both. Retention restarts when a call writes the basket record itself; the two-hour price clock restarts when a call produces a new calculated price. Creating or replacing the basket restarts retention; beyond that:

CallRestarts 18-month retentionResets the two-hour price clock
Recalculateyesyes
Add, change or remove an itemyesyes
Add a couponyesyes
Remove a couponyesyes
Add or change loyaltyyesyes
Remove loyaltyyesyes
Set or clear the customeryesno
Any fulfillment callnono
Any tender callnono

The rows that restart neither are narrow: a basket would have to sit untouched for 18 months except for those calls before retention were affected. For a cart being built or revisited, every call a storefront normally makes restarts it.

🛑 A long-lived basket still meets the two-hour price clock. The basket survives; its calculated price does not. A shopper returning to an old cart can read it, but checkout fails until you recalculate — and the total can legitimately have changed in the meantime. See Checkout.

Two clocks run on a basket. A cart filled on 2 March is still there, active and intact, when the shopper comes back on 12 October, because a basket is kept for 18 months after its last change. Its price was good for only two hours, so the basket must be recalculated before checkout.

Sub-resources​

Each of these is separately addressable. Only items, coupons and loyalty reprice the basket — fulfillments, customer and tenders are stored without calling an engine, because none of them changes what the goods cost.

Items​

Lines carry the item, quantity, unit of measure, and — after calculation — the resolved name, identifier, regular and actual unit prices, tax group, and any deposit or fee rules that apply. Quantity is fractional, so weight- and measure-based items work without a separate mechanism.

There are two ways to write lines, and they behave very differently:

  • One line at a time — PUT …/items/{itemId} changes that line and leaves the rest alone.
  • The whole set at once — PUT …/items takes the complete item set the basket should hold.

🛑 The bulk call is a replace, not an append. The array you send is the basket afterwards: an item you leave out is deleted, and an empty array clears the basket. It is the desired end state, not a list of changes.

PUT on the items collection replaces the whole item set. A basket with socks ×2, a mug and a tea towel, sent socks ×3 and the mug, ends with socks ×3 and the mug: the tea towel is deleted, and the quantity 3 replaces 2 rather than adding to it.

Prefer it when you hold the whole cart anyway — a storefront usually does. It is also markedly faster, because it works out what actually changed and only touches those lines: re-sending a 20-line cart with one quantity edited costs a single engine call, not twenty.

⚠ Quantity is replaced, never accumulated — on both endpoints. Sending quantity: 3 for a line holding 5 leaves it at 3, not 8. Worth pausing on, because "add to cart" in a storefront usually means increment.

The basket-level PUT still carries no lines. See Integrating with UBM.

Resolution of names, identifiers and prices against the catalogue is described in How pricing works. The short version: what you send is used, and the catalogue fills in what you omit — so send the price your storefront displayed.

Coupons​

Applied and removed individually, and each reprices. Whether a coupon actually discounts anything is decided by the engine and visible in the calculated result, not in the input basket.

Loyalty​

The loyalty membership the basket is priced for. Registering loyalty can change prices, so it reprices; it is not merely an attribute recorded for later.

Customer​

Who is buying. Held separately from loyalty, because a basket can have a customer without a membership and a membership without full customer details. Does not reprice — customer details do not affect price; loyalty membership is what can.

Fulfillments​

How the basket reaches the customer. A basket can have several fulfillments — for example part collected and part delivered — and each can carry its own destination business unit and its own line items. Does not reprice. Fulfillments matter at checkout, where tenders are allocated to them.

One basket can have several fulfillments. The Webshop basket prices both lines, then the socks are collected at the Stockholm store, a fulfillment of its own with Stockholm as its destination, and the mug is delivered home from the warehouse.

Tenders​

The means of payment, with authorization data where the tender type needs it (card and gift-card authorizations are held separately). Card and gift-card tenders must carry authorization data, and other tender types must not; a tender that breaks this is refused with 400 when it is added. Does not reprice. Tenders are validated at checkout against the business unit's active tender configurations, and where the basket also has fulfillments their total must match the order total — see Checkout.

External identifiers​

A basket and its fulfillments can carry external identifiers — your own references, kept alongside UBM's ids so you can correlate a basket with an order in your own systems. UBM stores and returns them without interpreting them.

Additional properties​

Baskets accept additional properties for data the model does not have a field for. They are carried through and returned, not interpreted, and they do not influence pricing.