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

# Carts

# Carts

> **Endpoint reference: ✅ Verified live against production on 10 May 2026.**
> Carts are per-client. Each client has at most one active cart. Adding items to a cart doesn't reserve inventory — items are only committed when an order is placed via [`POST /dapp/orders`](./orders.md).

***

## Conceptual surprise: the API exposes "clients-with-carts", not "carts directly"

> ⚠️ **Read this before building cart UI.**
>
> `GET /dapp/carts` returns a paginated list of **clients**, each with their `clientCart[]` and `cartItems[]` embedded. The top-level field is **`clients`**, not `carts`. This is a small but real footgun — your UI code expecting `data.carts` will silently render nothing.

If your store has a "carts in progress" view (for the holder, showing all customers who've started a cart), this endpoint feeds it. If you're rendering a single customer's cart, fetch `GET /dapp/clients/{clientId}` instead — `clientCart[]` is included in the detail response.

***

## Endpoints

| Method   | Path                   | Auth          | Description                                                    |
| -------- | ---------------------- | ------------- | -------------------------------------------------------------- |
| `POST`   | `/dapp/carts`          | API-key + sig | Add items to a client's cart (creates the cart if none exists) |
| `GET`    | `/dapp/carts`          | API-key + sig | List clients with carts in progress                            |
| `DELETE` | `/dapp/carts/{cartId}` | API-key + sig | Delete a cart                                                  |

***

## `POST /dapp/carts` — add items

Adds one or more strain items to a client's cart. Creates the cart on first call.

### Request body

The shape is `CreateCartItemsDto` per the live OpenAPI spec — full schema not formally declared, but the working pattern is:

```json theme={null}
{
  "clientId": "2cbab026-9dc6-46fd-8580-77e3dfd2f086",
  "cartItems": [
    { "strainId": "c77dd198-ec9a-4ced-8105-937fde104424", "quantity": 5 },
    { "strainId": "<other-strain-id>", "quantity": 2 }
  ]
}
```

| Field                  | Type          | Required | Notes                             |
| ---------------------- | ------------- | -------- | --------------------------------- |
| `clientId`             | string (UUID) | ✅        | Must belong to the calling holder |
| `cartItems`            | array         | ✅        | At least one item                 |
| `cartItems[].strainId` | string (UUID) | ✅        | From [Strains](./strains.md)      |
| `cartItems[].quantity` | integer       | ✅        | Positive integer (units of grams) |

### Canonical payload (for signing)

`JSON.stringify(body)` — compact, no whitespace. **Send the same exact string as the request body.**

### Response

`201 Created` with the cart's `id`.

```json theme={null}
{
  "success": true,
  "statusCode": 201,
  "message": "Success",
  "data": { "id": "<cart-uuid>" }
}
```

### Idempotency

`POST /dapp/carts` is **not idempotent** server-side. Calling it twice with the same payload will add the items twice. If the customer hits the "Add to cart" button rapidly, debounce client-side. There's no `Idempotency-Key` header support yet (see [04-errors.md § Idempotency](../04-errors.md#idempotency)).

### Errors

| Status                                                               | Cause                                                       | Fix                                                   |
| -------------------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------- |
| `400` `["clientId must be a UUID", "cartItems should not be empty"]` | Validation failure                                          | Validate input client-side first                      |
| `403` "Client does not belong to this holder"                        | `clientId` is for another holder's customer                 | Re-fetch the holder's client list and pick from there |
| `404` "Strain not found"                                             | `strainId` was soft-deleted between catalogue fetch and add | Refresh the catalogue, drop stale items               |

***

## `GET /dapp/carts` — clients with carts

Returns a paginated list of clients who have an active cart, with the cart and items embedded.

### Query parameters

| Name     | Type    | Default |
| -------- | ------- | ------- |
| `page`   | integer | `1`     |
| `limit`  | integer | `10`    |
| `search` | string  | —       |

### Canonical payload

`urlencode(query)` if any present, else `"{}"`.

### Response shape (verified)

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": {
    "clients": [
      {
        "id": "715b0db6-02e9-46b4-8b1a-eb6cac47f9d1",
        "firstName": "Ricardo",
        "lastName": "Capone",
        "email": "ricardo.capone@protonmail.ch",
        "shippings": [ { "countryCode": "ZAF", "currency": null } ],
        "clientCart": [
          {
            "id": "6712f17e-678e-419c-9790-27dff0de7ed2",
            "cartItems": [
              {
                "quantity": 5,
                "createdAt": "2026-05-08T11:46:12.738Z",
                "strain": { /* embedded strain summary */ }
              }
            ]
          }
        ]
      }
    ],
    "pageMetaDto": {
      "page": "1", "take": 10, "itemCount": 1,
      "pageCount": 1, "hasPreviousPage": false, "hasNextPage": false
    }
  }
}
```

> 🪲 **`clientCart` is an array** even though each client has at most one active cart. Take `[0]` and treat the rest defensively.

> 🪲 **Cart items have no `id` in the embedded list** — only `quantity`, `createdAt`, and the embedded `strain`. To remove a single item, you delete the whole cart and recreate. There's no "remove item from cart" endpoint in the current API. 🔒

### Worked example (Python)

```python theme={null}
def carts_in_progress(page=1, limit=10):
    query = {"page": str(page), "limit": str(limit)}
    payload = canonical_payload("GET", query=query)
    headers = auth_headers(API_KEY, SECRET_KEY, payload)
    url = f"https://api.drgreennft.com/api/v1/dapp/carts?{urlencode(query)}"
    r = httpx.get(url, headers=headers)
    r.raise_for_status()
    clients = r.json()["data"]["clients"]
    # Flatten to a "cart progress" list
    return [
        {"client": c, "cart": (c["clientCart"][0] if c["clientCart"] else None)}
        for c in clients
    ]
```

***

## `DELETE /dapp/carts/{cartId}` — delete a cart

Removes a cart and all its items. Used when the customer abandons or chooses to start over.

### Path parameters

| Name     | Type          | Description                      |
| -------- | ------------- | -------------------------------- |
| `cartId` | string (UUID) | The `id` from `clientCart[0].id` |

### Canonical payload

`{}`

### Response

`200 OK` with `data: null` or a confirmation `id`.

### Errors

| Status                         | Cause                            | Fix                                                         |
| ------------------------------ | -------------------------------- | ----------------------------------------------------------- |
| `404` "Cart not found"         | Already deleted, or wrong holder | Treat as success on the client side; idempotent in practice |
| `401` "User is not authorized" | Signed `""` instead of `"{}"`    | Use the canonical payload `"{}"`                            |

***

## Common patterns

### Resolve a single client's cart

```ts theme={null}
async function clientCart(clientId: string): Promise<Cart | null> {
  const client = await getClient(clientId); // GET /dapp/clients/{clientId}
  return client.clientCart[0] ?? null;
}
```

### Replace cart contents (since there's no "remove item")

```ts theme={null}
async function setCart(clientId: string, items: { strainId: string; quantity: number }[]) {
  const client = await getClient(clientId);
  const existing = client.clientCart[0];
  if (existing) await deleteCart(existing.id);
  await postCart({ clientId, cartItems: items });
}
```

### Cart-to-order

When the customer checks out, you call [`POST /dapp/orders`](./orders.md) with the cart's contents. The order endpoint takes the same shape as the cart contents, with the addition of shipping and payment details. The cart itself isn't automatically cleared on order — clean it up with `DELETE` if you want a fresh slate.

***

## Caching guidance

Don't cache cart responses. Carts are mutable and customers expect immediate feedback after add/remove operations. Always read fresh.

***

## See also

* [Strains](./strains.md) — pricing and strain IDs to populate the cart
* [Clients](./clients.md) — client detail also includes `clientCart[]`
* [Orders](./orders.md) — converting a cart into an order
