Cart integration
The agent can read the shopper's cart and change it: add an item in a specific size, change a quantity, switch a line to a different size, or remove a line. It never writes to your store itself. The shopper's cart session belongs to your storefront, so the agent tells your client what to change, and your client makes the change and shows the result.
How it works
- Before each chat request, your client reads the current cart from the storefront.
- It sends that snapshot as
cartin the request body. - Receiving a snapshot adds the cart tools to the agent for that turn.
- When the agent reads or changes the cart, the stream sends a
cart_actionevent. - For each change, your client updates the storefront cart and shows whether it worked.
- Optionally, your client reports the outcome using the
intenttoken from the event. - On the next turn, your client sends a fresh snapshot that reflects the changes.
The cart snapshot
{
"itemCount": 2,
"total": 168,
"currency": "USD",
"items": [
{
"key": "43210987654321:8f2c1a9e",
"title": "Coastal Linen Shirt",
"variant": "M / Navy",
"quantity": 1,
"price": 89,
"sku": "CLS-NVY-M",
"image": "https://cdn.example.com/products/coastal-linen-shirt-navy.jpg"
},
{
"key": "43210987654322:1b7e33c0",
"title": "Everyday Chino",
"variant": "32 / Stone",
"quantity": 1,
"price": 79,
"sku": "EDC-STN-32",
"image": "https://cdn.example.com/products/everyday-chino-stone.jpg"
}
]
}| Field | Type | Description |
|---|---|---|
itemCount |
integer | Total units across all lines. |
total |
number | Cart total in major currency units (89.00, not cents). |
currency |
string | ISO 4217 code for total and each line's price. |
items |
array | The cart lines. Can be empty. |
items[].key |
string | Your storefront's stable id for the line. The agent sends it back as lineKey, so use whatever your cart API needs to find the line. |
items[].title |
string | The product name as the shopper sees it. |
items[].variant |
string or null | The variant label, or null for single-variant products. |
items[].quantity |
integer | Units of this line. |
items[].price |
number | The line total in major currency units. |
items[].sku |
string or null | The SKU of the variant in the cart. Changing a line's size needs this to find the product in your catalogue. Without it, a size change can only match on the exact product name. |
items[].image |
string or null | An absolute image URL, used for receipts. It is never sent to the model. |
The server does not validate the snapshot. Send the shape shown above: if a field is missing or wrong, the cart tools report errors to the agent instead of doing anything useful.
Enabling the cart tools
Add cart to the chat request:
curl https://stylor.ai/api/v2/agent/chat \
-H "Authorization: Bearer $STYLOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [{ "role": "user", "content": "Add the navy linen shirt in a medium." }],
"sessionId": "3b0d4a1e-6f52-4b8a-9d3c-7e2f1a0b9c8d",
"cart": { "itemCount": 0, "total": 0, "currency": "USD", "items": [] }
}'- Sending a snapshot, even an empty one, enables
get_cart,add_to_cart,set_cart_quantity,change_cart_sizeandremove_from_cartfor that turn. - Omit
cart(or sendnull) on storefronts where your client cannot change the cart. The agent then has no cart tools at all, so it never offers something it cannot do. - Send a fresh snapshot with every request. The agent answers "what's in my cart?" from the snapshot, and uses the line
keyvalues in it to target changes. - Keep streaming on. With
stream: false, cart actions are not returned, so the agent would ask for changes your client never receives. - Send
sessionId, thesession.sessionIdfrom the widget preflight. Without it,addandswapactions carryintent: nulland their outcomes cannot be recorded.
The agent changes the cart only when the shopper clearly asks it to. It asks which size before adding anything that comes in more than one.
Cart events
Every cart tool call that has something to show sends one cart_action event. The action field tells you which kind:
action |
Sent by | Meaning | Your client must |
|---|---|---|---|
read |
get_cart |
The agent read the snapshot | Show it as a receipt. Nothing to change. |
add |
add_to_cart |
Add a variant | Add quantity of sku from the product at url. |
quantity |
set_cart_quantity |
Set a line's quantity | Set line lineKey to quantity (0 removes it). |
swap |
change_cart_size |
Change a line's size | Add sku at the line's quantity, then remove line lineKey. |
remove |
remove_from_cart |
Remove a line | Remove line lineKey. |
By the time an event is sent, the server has already checked everything it can. For add and swap that means the item exists in your catalogue, the size is one of its variants, and the variant is in stock. For line actions it means lineKey is in your snapshot. Requests that fail those checks never produce an event: the agent is told why, and usually tells the shopper or asks a follow-up question. No event is sent either when the change would do nothing, such as setting a quantity to its current value or switching to the size the line already has.
The agent is told not to claim a change worked. Your interface is where the shopper sees the confirmation.
read
{
"action": "read",
"itemCount": 2,
"total": 168,
"currency": "USD",
"items": [
{ "name": "Coastal Linen Shirt", "size": "M / Navy", "quantity": 1, "price": 89, "image": "https://cdn.example.com/products/coastal-linen-shirt-navy.jpg" },
{ "name": "Everyday Chino", "size": "32 / Stone", "quantity": 1, "price": 79, "image": "https://cdn.example.com/products/everyday-chino-stone.jpg" }
]
}This is your own snapshot with the fields renamed for display (title becomes name, variant becomes size). It is already complete, so show it as a finished receipt.
add
{
"action": "add",
"itemId": "8f2c1a9e-4b7d-4c1e-9a3f-2d6b7e5c1a90",
"name": "Coastal Linen Shirt",
"url": "https://shop.example.com/products/coastal-linen-shirt",
"sku": "CLS-NVY-M",
"size": "M / Navy",
"quantity": 1,
"price": 89,
"currency": "USD",
"image": "org_5f3a/ds_9c21/8f2c1a9e-4b7d/0.jpg",
"intent": "eyJ2IjoxLCJqdGkiOiI3ZjFlIn0.q8vT2rN9..."
}Find the product on your storefront using url, then add quantity (between 1 and 10) of the variant with sku. price is the variant price, falling back to the product price. image is a storage key, not a URL: build the URL as https://stylor.ai/api/v1/image/<key>. url can be an empty string when the catalogue has no URL for the item.
quantity
{
"action": "quantity",
"lineKey": "43210987654322:1b7e33c0",
"name": "Everyday Chino",
"size": "32 / Stone",
"from": 1,
"quantity": 2,
"price": null,
"image": "https://cdn.example.com/products/everyday-chino-stone.jpg"
}Set the line to quantity (between 0 and 50); from is the quantity before the change. price is always null, because the line total changes with the quantity.
swap
{
"action": "swap",
"lineKey": "43210987654321:8f2c1a9e",
"name": "Coastal Linen Shirt",
"fromSize": "M / Navy",
"size": "L / Navy",
"sku": "CLS-NVY-L",
"url": "https://shop.example.com/products/coastal-linen-shirt",
"quantity": 1,
"image": "org_5f3a/ds_9c21/8f2c1a9e-4b7d/0.jpg",
"intent": "eyJ2IjoxLCJqdGkiOiI0YzJhIn0.Zt7mK1pQ..."
}Add quantity of the new sku first. Remove line lineKey only after the add succeeds. This way a failed add leaves the shopper with the item they already had, and the agent has already told them this is how size changes work. image is the product's storage key, or the line's own image URL when the product has no stored image.
remove
{
"action": "remove",
"lineKey": "43210987654322:1b7e33c0",
"name": "Everyday Chino",
"size": "32 / Stone",
"quantity": 1,
"price": 79,
"image": "https://cdn.example.com/products/everyday-chino-stone.jpg"
}Remove the line. price is the line total being removed.
Handling cart events in the client
Show the receipt as soon as the event arrives. Do the change in the background, and mark the receipt as done or failed when the storefront responds. When several changes arrive in one turn, apply them in the order they arrived.
async function handleCartAction(action) {
const receipt = showReceipt(action, action.action === 'read' ? 'done' : 'pending');
if (action.action === 'read') return;
try {
switch (action.action) {
case 'add':
await storefront.addToCart({ url: action.url, sku: action.sku, quantity: action.quantity });
break;
case 'quantity':
await storefront.setQuantity({ lineKey: action.lineKey, quantity: action.quantity });
break;
case 'swap':
await storefront.addToCart({ url: action.url, sku: action.sku, quantity: action.quantity });
await storefront.removeLine({ lineKey: action.lineKey });
break;
case 'remove':
await storefront.removeLine({ lineKey: action.lineKey });
break;
}
receipt.markDone();
await reportOutcome(action, null);
} catch (err) {
receipt.markFailed(err.message);
await reportOutcome(action, err.message);
}
}Reporting outcomes with intent tokens
add and swap events include intent, a signed, single-use token covering what the server verified: the item, the size and the price. Report whether the change succeeded by sending it to the v1 Track events endpoint, under the event name that matches the action:
action |
Event name |
|---|---|
add |
cart.item_added |
swap |
cart.size_changed |
The product, size and price are read from the token. Your client only reports the outcome:
curl https://stylor.ai/api/v1/events \
-H "X-Stylor-Session: $STYLOR_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"events": [{
"eventId": "2e3f4a5b-6c7d-4e8f-8a9b-0c1d2e3f4a5b",
"name": "cart.item_added",
"occurredAt": "2026-09-12T14:05:02.118Z",
"seq": 9,
"intent": "eyJ2IjoxLCJqdGkiOiI3ZjFlIn0.q8vT2rN9...",
"data": { "outcome": "ok", "error": null }
}]
}'- The events endpoint is authenticated with the session token from the widget preflight, not with your API key.
- A token is tied to the
sessionIdyou sent with the chat request, is valid for five minutes, and can be used only once. - When
intentisnull(nosessionIdwas sent), skip the report. quantity,removeandreadactions have no intent and nothing to report.
See the v1 Track events reference for the full event schema and rejection reasons.
After the turn
The snapshot you sent is from before the agent's changes. Read the cart again after your client has applied them, and send the new snapshot with the shopper's next message. The agent then works from the real cart, including any change that failed.