Skip to main content
start_checkout was the original “find ⇨ buy” path: an agent calls it once with a SKU, gets back a hosted Droplinked checkout URL, and hands the buyer off to pay. That works for single-SKU intent (“buy this thing”) but not for multi-turn agentic composition, where the agent (or buyer) wants to try a cart configuration before paying. The four cart-mutation tools close that loop:
ToolWhat it does
cart.addLineAdd another SKU to an existing cart
cart.updateLineQuantityChange the quantity of a SKU already in the cart
cart.removeLineRemove a SKU from the cart
cart.applyDiscountApply a merchant-issued discount / coupon code
All four expect the encrypted cartId returned by start_checkout — pass it back verbatim; it’s already obfuscated by the backend’s DecryptCartIdPipe.

Canonical multi-line composition flow

1. search_products({ query: "blue sneaker size 10" })       → [skuA, skuB, …]
2. start_checkout({ shopId, skuId: skuA, quantity: 1, … })  → { cartId, checkoutUrl }
3. cart.addLine({ cartId, skuId: skuB, quantity: 2 })        → updated cart
4. cart.updateLineQuantity({ cartId, skuId: skuA, quantity: 3 })
                                                              → bumped cart total
5. cart.applyDiscount({ cartId, discountCode: "WELCOME10" }) → discount applied
6. Buyer (or agent) navigates to `checkoutUrl` and pays
Each step returns the updated cart envelope so the agent can show the running subtotal, line list, and discount before commit. There is no separate “preview” call — every mutation is the preview.

Tool reference

cart.addLine

{
  "cartId": "ENC:...",
  "skuId":  "65ab12cd34ef567890123456",
  "quantity": 2
}
Idempotent on (cartId, skuId): calling twice with the same SKU bumps the quantity on the existing line rather than creating a duplicate. Backs: POST /v2/carts/:cartId/products

cart.updateLineQuantity

{
  "cartId":   "ENC:...",
  "skuId":    "65ab12cd34ef567890123456",
  "quantity": 5
}
Sets the line to the specified quantity. Passing quantity: 0 removes the line — same effect as calling cart.removeLine but in a single call, which is convenient when the agent is decrementing. Backs: PATCH /v2/carts/:cartId/products/:skuId

cart.removeLine

{
  "cartId": "ENC:...",
  "skuId":  "65ab12cd34ef567890123456"
}
Removes the line entirely. The returned cart shows the updated lines + total. Removing the last line leaves the cart empty but still usable — the buyer can continue browsing and the agent can add new lines. Backs: DELETE /v2/carts/:cartId/products/:skuId

cart.applyDiscount

{
  "cartId":       "ENC:...",
  "discountCode": "SAVE20"
}
Validates the discount code at the merchant level, computes the discount, and returns the updated cart with the discount amount + label resolved. The code is case-insensitive on the backend but is preserved on the MCP surface so agents can run idempotency checks against the exact value they sent. An invalid or expired code surfaces as a structured error envelope. Backs: POST /v2/carts/:cartId/coupon

Error envelope

All four tools follow the same envelope shape so agents can branch on JSON instead of catching exceptions:
// Happy path
{ "status": "ok", "cartId": "ENC:...", "...": "..." }

// Failure (backend error, validation error, network error)
{ "status": "error", "reason": "<reason_enum>", "message": "…human-readable…" }
The structured reason enum lets agents branch programmatically (e.g. reason: "INVALID_COUPON" vs reason: "BACKEND_5XX").

When NOT to use the cart-mutation tools

  • Agent only knows the buyer wants one SKU: use start_checkout alone. The mutation tools are pure overhead for single-SKU intent.
  • Agent is composing for an unauthenticated buyer: there is no “guest cart” — the cart belongs to the email passed to start_checkout. Mutations apply to that cart.
  • Agent wants to express discount programmatically: the only discount surface is the discount code. There is no cart.applyPercentageOff or similar — merchants own their promo logic via discount codes.