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:
| Field | Type | Description |
|---|---|---|
cart.sessionId | string | Cart session identifier |
cart.items | array | All cart items with product details |
cart.items[].id | string | Cart item ID |
cart.items[].productId | string | Product ID |
cart.items[].quantity | number | Quantity (1-99) |
cart.items[].product | object | Full product details |
cart.subtotal | number | Total before fees |
cart.artistCount | number | Number of unique artists |
cart.itemCount | number | Total 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:
| Field | Type | Required | Description |
|---|---|---|---|
productId | string | Yes | Product UUID |
quantity | number | No | Quantity 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:
| Status | Error | Description |
|---|---|---|
| 400 | Product ID required | Missing productId |
| 404 | Product not found | Invalid product ID |
| 400 | Product not gallery eligible | Product removed from gallery |
| 400 | Product not active | Product 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:
| Field | Type | Required | Description |
|---|---|---|---|
itemId | string | Yes | Cart item UUID |
quantity | number | Yes | New 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:
| Status | Error | Description |
|---|---|---|
| 400 | Item ID and quantity required | Missing parameters |
| 404 | Cart item not found | Invalid 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:
| Status | Error | Description |
|---|---|---|
| 400 | Item ID required | Missing itemId |
| 404 | Cart item not found | Invalid 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_idingallery_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
- Add items: POST to
/api/gallery/cart/addwith product IDs - Verify cart: GET
/api/gallery/cartto see items - Update quantity: POST to
/api/gallery/cart/update - Remove item: POST to
/api/gallery/cart/remove - 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.