> ## Documentation Index
> Fetch the complete documentation index at: https://docs.henrylabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Universal Cart

> Create and manage multi-merchant carts: add, update, remove items, and get a hosted checkout URL in one call

## Overview

The Henry cart is a persistent server-side object that holds items from **any merchant** in a single checkout experience. Creating a cart immediately returns both a `cartId` and a `checkoutUrl` - there's no separate step to generate a checkout link.

***

## Prerequisites

```typescript theme={null}
import HenrySDK from '@henrylabs/sdk';

const henry = new HenrySDK({
	apiKey: process.env.HENRY_SDK_API_KEY!,
});
```

***

## Create a cart

`cart.create` is the starting point. Pass an array of items using product `link` URLs (from search results or any product page), and optionally configure cart settings and tags. Each item can also include an `affiliateProductLink`, selected options, shipping, coupons, and metadata.

```typescript theme={null}
const cart = await henry.cart.create({
  items: [
    {
      link: "https://www.nike.com/u/custom-nike-ja-3-by-you-10002205",
      affiliateProductLink:
        "https://partner.example.com/click?url=https%3A%2F%2Fwww.nike.com%2Fu%2Fcustom-nike-ja-3-by-you-10002205",
      quantity: 2,
      selectedOptions: ["regular", "black", "10-w"],
      metadata: { creatorSource: "Frank Herbert" },
    },
  ],
  settings: {
    options: {
      allowPartialPurchase: true,
      collectBuyerEmail: "required",
      collectBuyerAddress: "optional",
      collectBuyerPhone: "off",
    },
    serviceFeeFixed: { value: 1.99, currency: "USD" },
    serviceFeePercent: 10,
    events: [],
  },
  tags: { region: "us-ny" },
});

const { cartId, checkoutUrl } = cart.data;
console.log("Cart ID:", cartId);
console.log("Checkout URL:", checkoutUrl);
```

<Info>The `checkoutUrl` is your hosted checkout link - share it with your user directly. See [Checkout](/v1/sdk/server/guides/checkout) for how to embed it.</Info>

### Cart creation response

```json theme={null}
{
  "success": boolean,
  "status": "string",
  "message": "string",
  "data": {
    "cartId": "string",
    "checkoutUrl": "https://example.com",
    "data": {
      "items": [
        {
          "link": "https://www.nike.com/u/custom-nike-ja-3-by-you-10002205",
          "affiliateProductLink": "https://partner.example.com/click?url=https%3A%2F%2Fwww.nike.com%2Fu%2Fcustom-nike-ja-3-by-you-10002205",
          "quantity": 2,
          "selectedOptions": ["regular", "black", "10-w"],
          "selectedShipping": {
            "value": "standard"
          },
          "coupons": ["SUMMER26", "SAVE10"],
          "metadata": {
            "creatorSource": "Frank Herbert"
          }
        }
      ],
      "settings": {
        "options": {
          "allowPartialPurchase": true,
          "collectBuyerEmail": "required",
          "collectBuyerAddress": "optional",
          "collectBuyerPhone": "off"
        },
        "serviceFeeFixed": {
          "value": 1.99,
          "currency": "USD"
        },
        "serviceFeePercent": 10,
        "events": [
          {
            "type": "order",
            "conditional": {
              "type": "tier",
              "operator": "equals",
              "value": "string"
            },
            "data": [
              {
                "type": "give_points_fixed",
                "points": 1
              }
            ]
          }
        ]
      },
      "tags": {
        "region": "us-ny"
      }
    },
    "metadata": {}
  }
}
```

***

## Manage cart items

### Add an item

Add a product to an existing cart. Use the product `link` URL as the identifier.

```typescript theme={null}
const updated = await henry.cart.item.add({
  cartId,
  item: {
    link: "https://www.adidas.com/us/ultraboost-22-shoes/GX5591.html",
    affiliateProductLink:
      "https://partner.example.com/click?url=https%3A%2F%2Fwww.adidas.com%2Fus%2Fultraboost-22-shoes%2FGX5591.html",
    quantity: 1,
    selectedOptions: ["9.5", "Core Black"],
    selectedShipping: { value: "standard" },
    coupons: ["SAVE10"],
    metadata: { giftWrap: true },
  },
});

console.log(updated.data.data.items.length); // cart now has 2 items
```

### Update an item quantity

```typescript theme={null}
const updated = await henry.cart.item.update(cartId, {
	item: {
		link: 'https://www.nike.com/t/air-max-270-shoes/AH8050-002',
		quantity: 3, // set new quantity
	},
});
```

<Info>Setting `quantity` to `0` via `cart.item.update` also removes the item from the cart.</Info>

### Remove an item

```typescript theme={null}
const updated = await henry.cart.item.remove(cartId, {
	link: 'https://www.nike.com/t/air-max-270-shoes/AH8050-002',
});

console.log(updated.data.data.items.length); // one fewer item
```

***

## Fetch carts

Henry exposes two retrieval methods with different shapes:

| Method                           | Returns        | Use when                                                      |
| -------------------------------- | -------------- | ------------------------------------------------------------- |
| `cart.list({ cartId?, tags? })`  | Array of carts | Looking up by tags, or paging through many carts              |
| `cart.fetch(cartId, { buyer? })` | A single cart  | You know the `cartId` and optionally want to prefill checkout |

### List carts

```typescript theme={null}
// Fetch a specific cart by ID
const { data: carts } = await henry.cart.list({ cartId });
const cart = carts[0];
console.log(cart.data.items);
console.log(cart.checkoutUrl); // still valid - use it any time

// Filter carts by tags
const { data: userCarts } = await henry.cart.list({
	tags: { userId: 'user_123' },
});
```

### Fetch a single cart (with optional buyer prefill)

`cart.fetch` returns one cart by id. Pass an optional `buyer` to prefill the hosted checkout - the returned `checkoutUrl` will embed the buyer details as query params so fields render pre-filled when the user opens it.

```typescript theme={null}
// Plain fetch by id
const { data: cart } = await henry.cart.fetch(cartId);
console.log(cart.data.items, cart.checkoutUrl);

// Fetch + prefill the hosted checkout
const { data: prefilled } = await henry.cart.fetch(cartId, {
	buyer: {
		name: { firstName: 'Jane', lastName: 'Doe' },
		email: 'jane@example.com',
		phone: '+15551234567',
		shippingAddress: {
			name: { firstName: 'Jane', lastName: 'Doe' },
			line1: '350 5th Ave',
			city: 'New York',
			province: 'NY',
			postalCode: '10118',
			countryCode: 'US',
		},
	},
});

// prefilled.checkoutUrl now renders with Jane's details pre-populated
```

<Info>`buyer` is purely a UX convenience for hosted checkout - it does not persist on the cart. To actually commit buyer info, use [Headless Checkout](/v1/sdk/server/guides/checkout#headless-checkout) via `cart.checkout.purchase`.</Info>

***

## Delete a cart

Remove a cart entirely when it's no longer needed (e.g. on session logout).

```typescript theme={null}
await henry.cart.delete(cartId);
```

***

## Cart item fields reference

| Field                  | Type              | Required | Description                                                                          |
| ---------------------- | ----------------- | -------- | ------------------------------------------------------------------------------------ |
| `link`                 | `string`          | Y        | Direct product URL from any merchant                                                 |
| `affiliateProductLink` | `string`          | -        | Optional affiliate or redirect URL used for fulfillment. If omitted, `link` is used. |
| `quantity`             | `number`          | -        | Defaults to `1`                                                                      |
| `selectedOptions`      | `string[]`        | -        | Ordered array of option values e.g. `['regular', 'black', '10-w']`                   |
| `selectedShipping`     | `{ id?, value? }` | -        | Preferred shipping method                                                            |
| `coupons`              | `string[]`        | -        | Coupon codes to apply at checkout                                                    |
| `metadata`             | `object`          | -        | Arbitrary key/value data passed through to orders                                    |

***

## Cart tags

Attach key-value `tags` when creating a cart to associate it with your own identifiers (e.g. user IDs, regions, campaigns). You can then filter carts by tags with `cart.list`.

```typescript theme={null}
const cart = await henry.cart.create({
	items: [{ link: '...', quantity: 1 }],
	tags: { userId: 'user_123', campaign: 'summer-sale' },
});

// Later, retrieve all carts for this user
const { data: carts } = await henry.cart.list({
	tags: { userId: 'user_123' },
});
```

***

## Cart settings reference

| Field                          | Type                                | Description                                            |
| ------------------------------ | ----------------------------------- | ------------------------------------------------------ |
| `options.allowPartialPurchase` | `boolean`                           | Let buyers remove items during checkout                |
| `options.collectBuyerEmail`    | `"off" \| "required" \| "optional"` | Email collection behavior                              |
| `options.collectBuyerAddress`  | `"off" \| "required" \| "optional"` | Address collection behavior                            |
| `options.collectBuyerPhone`    | `"off" \| "required" \| "optional"` | Phone collection behavior                              |
| `serviceFeePercent`            | `number`                            | Service fee as a percentage of the order total (0-100) |
| `serviceFeeFixed`              | `{ value, currency }`               | Fixed service fee amount added to the order            |
| `events`                       | `CartEvent[]`                       | Triggers to fire when cart events occur                |

***

## Cart events

`settings.events` lets you attach triggers to cart lifecycle events. Each entry declares an event `type` to listen for, an optional `conditional`, and a list of actions (`data`) to fire when it occurs.

### Supported event types

#### Order events

| Event type                             | Fires when                                        |
| -------------------------------------- | ------------------------------------------------- |
| `order`                                | Any order event                                   |
| `order.purchase`                       | Any purchase event (full or partial, any outcome) |
| `order.purchase.pending`               | Purchase created, awaiting payment confirmation   |
| `order.purchase.processing`            | Payment confirmed, placing with merchants         |
| `order.purchase.complete`              | Purchase completed (full or partial)              |
| `order.purchase.cancelled`             | Purchase cancelled (full or partial)              |
| `order.purchase.full`                  | A full purchase resolves to any outcome           |
| `order.purchase.full.pending`          | Full purchase pending payment confirmation        |
| `order.purchase.full.processing`       | Full purchase confirmed, placing with merchants   |
| `order.purchase.full.complete`         | All items placed successfully                     |
| `order.purchase.full.cancelled`        | Full purchase cancelled                           |
| `order.purchase.partial`               | A partial purchase resolves to any outcome        |
| `order.purchase.partial.pending`       | Partial purchase pending                          |
| `order.purchase.partial.processing`    | Partial purchase processing                       |
| `order.purchase.partial.complete`      | Partial purchase completed                        |
| `order.purchase.partial.cancelled`     | Partial purchase cancelled                        |
| `order.item`                           | Any item-level event                              |
| `order.item.pending`                   | Individual item pending                           |
| `order.item.processing`                | Individual item processing                        |
| `order.item.complete`                  | Individual item placed successfully               |
| `order.item.failed`                    | Individual item failed                            |
| `order.update`                         | Any order update event                            |
| `order.update.3ds-required`            | 3DS authentication required                       |
| `order.update.adding-to-cart`          | Adding item to merchant cart                      |
| `order.update.filling-address-details` | Filling in address details                        |
| `order.update.filling-card-details`    | Filling in card details                           |
| `order.update.submitting-order`        | Submitting the order                              |
| `order.update.changing-quantity`       | Changing item quantity                            |
| `order.update.applying-coupon`         | Applying a coupon code                            |
| `order.update.selecting-shipping`      | Selecting shipping option                         |
| `order.update.selecting-options`       | Selecting product options                         |

#### Points & tier events

| Event type      | Fires when                  |
| --------------- | --------------------------- |
| `points`        | Any points event            |
| `points.give`   | Points awarded to a buyer   |
| `points.remove` | Points removed from a buyer |
| `tier`          | Any tier event              |
| `tier.give`     | A tier assigned to a buyer  |
| `tier.remove`   | A tier removed from a buyer |

### Supported action types

| Action type               | Description                                    | Required fields       |
| ------------------------- | ---------------------------------------------- | --------------------- |
| `send_webhook`            | POST the event payload to a registered webhook | `webhookUUID`         |
| `send_email`              | Send a notification email                      | -                     |
| `give_points_fixed`       | Award a fixed number of points to the buyer    | `points`              |
| `give_points_per_spent`   | Award points proportional to the order amount  | `points`, `perAmount` |
| `remove_points_fixed`     | Deduct a fixed number of points from the buyer | `points`              |
| `remove_points_per_spent` | Deduct points proportional to the order amount | `points`, `perAmount` |
| `give_tier`               | Assign a loyalty tier to the buyer             | `tierUUID`            |
| `remove_tier`             | Remove the buyer's current tier                | -                     |

### Example - webhook on successful purchase

```typescript theme={null}
const cart = await henry.cart.create({
	items: [
		{
			link: 'https://www.nike.com/t/air-max-270-shoes/AH8050-002',
			quantity: 1,
		},
	],
	settings: {
		events: [
			{
				type: 'order.purchase.full.complete',
				data: [
					{
						type: 'send_webhook',
						webhookUUID: 'c5dbd16b-60f1-42c7-8d36-a55c46e7a8c6',
					},
				],
			},
		],
	},
});
```

### Example - award points on purchase

```typescript theme={null}
settings: {
  events: [
    {
      type: 'order.purchase.full.complete',
      data: [
        {
          type: 'give_points_per_spent',
          points: 1,
          perAmount: { value: 1, currency: 'USD' }, // 1 point per $1 spent
        },
      ],
    },
  ],
}
```

### Conditionals

Add a `conditional` to a trigger to gate it on the buyer's current tier or points balance:

```typescript theme={null}
{
  type: 'order.purchase.full.complete',
  conditional: {
    type: 'tier',
    operator: 'equals', // 'equals' | 'not_equals'
    value: 'your-tier-uuid',
  },
  data: [{ type: 'give_points_fixed', points: 500 }],
}
```

<Info>
  Register and manage webhooks in the [Henry Dashboard](https://app.henrylabs.ai). The `webhookUUID` is the identifier assigned when you create a webhook endpoint. See
  [Webhooks](/v1/sdk/server/guides/webhooks) for payload shape, verification, and the full event reference.
</Info>

***

## Error handling

| Error              | Cause                                                 |
| ------------------ | ----------------------------------------------------- |
| `400 Bad Request`  | Missing required fields or invalid URL in `link`      |
| `401 Unauthorized` | Invalid API key                                       |
| `404 Not Found`    | `cartId` does not exist or belongs to a different app |

***

## Next steps

Once your cart is ready, proceed to checkout:

<CardGroup cols={2}>
  <Card title="Checkout" icon="credit-card" href="/v1/sdk/server/guides/checkout">
    Embed the hosted checkout or call the headless purchase API
  </Card>

  <Card title="Order Management" icon="receipt" href="/v1/sdk/server/guides/order-management">
    Track and list orders after checkout completes
  </Card>
</CardGroup>
