Skip to main content

Cart API

The Cart API manages shopping cart sessions for multi-artist purchases. Cart items are stored server-side and associated with a session ID, allowing users to browse and checkout across multiple artists.

Overview

Gallery carts work differently from single-vendor carts:

  • Session-based: No account required, uses session cookie
  • Multi-artist: Items automatically grouped by artist
  • Persistent: Cart saved server-side, survives page refreshes
  • Validation: Real-time checking of product availability and gallery eligibility

Session Management

Cart sessions are identified by a UUID stored in a cookie:

Cookie Name: gallery_cart_session Expiry: 30 days Storage: HttpOnly, Secure (production), SameSite=Lax

If no session exists, one is created automatically when first adding items.


Endpoints

Get Cart

Retrieves all items in the current cart session, grouped by artist.

Endpoint: GET /api/gallery/cart

Authentication: None (uses session cookie)

Response:

{
"cart": {
"sessionId": "cart_abc123",
"items": [
{
"id": "item_001",
"productId": "prod_001",
"quantity": 2,
"product": {
"id": "prod_001",
"name": "Abstract Painting #5",
"price": 150.00,
"images": ["https://..."],
"org_id": "org_abc",
"organizations": {
"name": "Jane Smith Studio",
"slug": "jane-smith"
}
}
},
{
"id": "item_002",
"productId": "prod_002",
"quantity": 1,
"product": {
"id": "prod_002",
"name": "Ceramic Vase",
"price": 80.00,
"images": ["https://..."],
"org_id": "org_def",
"organizations": {
"name": "Bob's Pottery",
"slug": "bobs-pottery"
}
}
}
],
"subtotal": 380.00,
"artistCount": 2,
"itemCount": 3
}
}

Response Fields:

FieldTypeDescription
cart.sessionIdstringCart session identifier
cart.itemsarrayAll cart items with product details
cart.items[].idstringCart item ID
cart.items[].productIdstringProduct ID
cart.items[].quantitynumberQuantity (1-99)
cart.items[].productobjectFull product details
cart.subtotalnumberTotal before fees
cart.artistCountnumberNumber of unique artists
cart.itemCountnumberTotal quantity across all items

Example Request:

curl https://gallery.artbase.studio/api/gallery/cart \
-H "Cookie: gallery_cart_session=cart_abc123"

Empty Cart Response:

{
"cart": {
"sessionId": "cart_abc123",
"items": [],
"subtotal": 0,
"artistCount": 0,
"itemCount": 0
}
}

Add to Cart

Adds a product to the cart. If the product already exists, increases quantity.

Endpoint: POST /api/gallery/cart/add

Authentication: None (creates session if needed)

Request Body:

{
"productId": "prod_001",
"quantity": 1
}

Request Parameters:

FieldTypeRequiredDescription
productIdstringYesProduct UUID
quantitynumberNoQuantity to add (default: 1, max: 99)

Response:

{
"success": true,
"item": {
"id": "item_001",
"productId": "prod_001",
"quantity": 1,
"sessionId": "cart_abc123"
}
}

Example Request:

curl -X POST https://gallery.artbase.studio/api/gallery/cart/add \
-H "Content-Type: application/json" \
-d '{
"productId": "prod_001",
"quantity": 2
}'

Error Responses:

StatusErrorDescription
400Product ID requiredMissing productId
404Product not foundInvalid product ID
400Product not gallery eligibleProduct removed from gallery
400Product not activeProduct deactivated by artist

Behavior:

  • If product exists in cart: quantity += request.quantity
  • If product is new: Creates new cart item
  • Automatically creates session if none exists
  • Returns updated item with new quantity

Update Cart Item

Updates the quantity of an existing cart item.

Endpoint: POST /api/gallery/cart/update

Authentication: None (uses session cookie)

Request Body:

{
"itemId": "item_001",
"quantity": 3
}

Request Parameters:

FieldTypeRequiredDescription
itemIdstringYesCart item UUID
quantitynumberYesNew quantity (1-99, or 0 to remove)

Response:

{
"success": true,
"item": {
"id": "item_001",
"productId": "prod_001",
"quantity": 3
}
}

Example Request:

curl -X POST https://gallery.artbase.studio/api/gallery/cart/update \
-H "Content-Type: application/json" \
-H "Cookie: gallery_cart_session=cart_abc123" \
-d '{
"itemId": "item_001",
"quantity": 3
}'

Error Responses:

StatusErrorDescription
400Item ID and quantity requiredMissing parameters
404Cart item not foundInvalid item ID or wrong session

Special Cases:

  • quantity: 0 - Deletes the item (same as remove endpoint)
  • quantity > 99 - Capped at 99

Remove from Cart

Removes a product from the cart.

Endpoint: POST /api/gallery/cart/remove

Authentication: None (uses session cookie)

Request Body:

{
"itemId": "item_001"
}

Response:

{
"success": true
}

Example Request:

curl -X POST https://gallery.artbase.studio/api/gallery/cart/remove \
-H "Content-Type: application/json" \
-H "Cookie: gallery_cart_session=cart_abc123" \
-d '{
"itemId": "item_001"
}'

Error Responses:

StatusErrorDescription
400Item ID requiredMissing itemId
404Cart item not foundInvalid item ID or wrong session

Cart Grouping

The gallery UI groups cart items by artist for clarity:

Example Grouped Cart

const groupedCart = {
"org_abc": {
artist: "Jane Smith Studio",
items: [
{ name: "Abstract Painting #5", price: 150, qty: 2 }
],
subtotal: 300
},
"org_def": {
artist: "Bob's Pottery",
items: [
{ name: "Ceramic Vase", price: 80, qty: 1 }
],
subtotal: 80
}
};

Client-Side Grouping Logic

function groupCartByArtist(items) {
return items.reduce((groups, item) => {
const orgId = item.product.org_id;
if (!groups[orgId]) {
groups[orgId] = {
artist: item.product.organizations.name,
items: [],
subtotal: 0
};
}
groups[orgId].items.push(item);
groups[orgId].subtotal += item.product.price * item.quantity;
return groups;
}, {});
}

Validation Rules

Product Eligibility

Products must meet these criteria to be added:

  • is_active: true - Product not deleted by artist
  • gallery_eligible: true - Product curated for gallery
  • org_id in gallery_eligible_orgs - Artist approved for gallery

Quantity Limits

  • Minimum: 1
  • Maximum: 99 per item
  • Cart limit: No limit on total items or artists

Session Expiry

  • Duration: 30 days from last activity
  • Cleanup: Expired carts auto-deleted by database trigger
  • Recovery: No recovery after expiry (start new cart)

Cart Persistence

Cart data persists across:

  • ✅ Page refreshes
  • ✅ Browser restarts (cookie persists)
  • ✅ Multiple devices (if cookie manually copied - not recommended)
  • ❌ Different browsers (separate sessions)
  • ❌ Incognito/private browsing (cookies cleared on close)

Database Schema

CREATE TABLE gallery_cart_items (
id UUID PRIMARY KEY,
session_id TEXT NOT NULL,
product_id UUID NOT NULL REFERENCES products(id),
quantity INTEGER NOT NULL CHECK (quantity > 0 AND quantity <= 99),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(session_id, product_id)
);

CREATE INDEX idx_cart_session ON gallery_cart_items(session_id);
CREATE INDEX idx_cart_product ON gallery_cart_items(product_id);

Client Integration

React Example

import { useState, useEffect } from 'react';

export function useCart() {
const [cart, setCart] = useState(null);
const [loading, setLoading] = useState(true);

const fetchCart = async () => {
const res = await fetch('/api/gallery/cart');
const data = await res.json();
setCart(data.cart);
setLoading(false);
};

const addToCart = async (productId: string, quantity = 1) => {
const res = await fetch('/api/gallery/cart/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ productId, quantity }),
});
await fetchCart(); // Refresh cart
return res.ok;
};

const updateQuantity = async (itemId: string, quantity: number) => {
const res = await fetch('/api/gallery/cart/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ itemId, quantity }),
});
await fetchCart();
return res.ok;
};

const removeItem = async (itemId: string) => {
const res = await fetch('/api/gallery/cart/remove', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ itemId }),
});
await fetchCart();
return res.ok;
};

useEffect(() => {
fetchCart();
}, []);

return {
cart,
loading,
addToCart,
updateQuantity,
removeItem,
refresh: fetchCart,
};
}

Usage

function ProductCard({ product }) {
const { addToCart } = useCart();

return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
<button onClick={() => addToCart(product.id)}>
Add to Cart
</button>
</div>
);
}

Testing

Test Flow

  1. Add items: POST to /api/gallery/cart/add with product IDs
  2. Verify cart: GET /api/gallery/cart to see items
  3. Update quantity: POST to /api/gallery/cart/update
  4. Remove item: POST to /api/gallery/cart/remove
  5. Check grouping: Ensure items from same artist grouped together

Example Test Data

{
"productId": "prod_001",
"quantity": 2
}

Use products from seed data with gallery_eligible: true.


Next Steps