# Shop API Reference

**Last Updated:** 2025-11-23
**API Version:** v1
**Base Path:** `/api/v1/shop`

---

## Overview

The Shop API provides customer-facing endpoints for browsing products, managing cart, placing orders, and customer authentication. All endpoints use **middleware-based vendor context** - no vendor ID in URLs!

### Key Features

✅ **Automatic Vendor Detection** - Vendor extracted from Referer header via middleware
✅ **Multi-Tenant** - Each vendor has isolated customer data
✅ **Session-Based Cart** - No authentication required for browsing/cart
✅ **Secure Authentication** - JWT tokens with HTTP-only cookies (path=/shop)
✅ **RESTful Design** - Standard HTTP methods and status codes

---

## How Vendor Context Works

All Shop API endpoints automatically receive vendor context from the `VendorContextMiddleware`:

1. **Browser makes API call** from shop page (e.g., `/vendors/wizamart/shop/products`)
2. **Browser automatically sends Referer header**: `http://localhost:8000/vendors/wizamart/shop/products`
3. **Middleware extracts vendor** from Referer path/subdomain/domain
4. **Middleware sets** `request.state.vendor = <Vendor: wizamart>`
5. **API endpoint accesses vendor**: `vendor = request.state.vendor`
6. **No vendor_id needed in URL!**

### Supported Vendor Detection Methods

- **Path-based**: `/vendors/wizamart/shop/products` → extracts `wizamart`
- **Subdomain**: `wizamart.platform.com` → extracts `wizamart`
- **Custom domain**: `customshop.com` → looks up vendor by domain

---

## Authentication

### Public Endpoints (No Auth Required)

- Product catalog
- Product details
- Product search
- Cart operations
- CMS content pages

### Authenticated Endpoints (Customer Token Required)

- Place order
- Order history
- Order details

### Authentication Headers

For authenticated requests, include the JWT token:

```http
Authorization: Bearer <customer_token>
```

Or use the HTTP-only cookie (automatically sent by browser):

```http
Cookie: customer_token=<jwt_token>
```

---

## Products

### Get Product Catalog

Get paginated list of products for current vendor.

**Endpoint:** `GET /api/v1/shop/products`

**Query Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `skip` | integer | 0 | Number of products to skip (pagination) |
| `limit` | integer | 100 | Maximum products to return (max 1000) |
| `search` | string | null | Search products by name/description |
| `is_featured` | boolean | null | Filter by featured products only |

**Request Example:**

```http
GET /api/v1/shop/products?skip=0&limit=20&is_featured=true
Referer: http://localhost:8000/vendors/wizamart/shop/products
```

**Response (200 OK):**

```json
{
  "products": [
    {
      "id": 1,
      "vendor_id": 1,
      "product_id": "PROD-001",
      "price": 29.99,
      "sale_price": null,
      "currency": "EUR",
      "availability": "in stock",
      "is_featured": true,
      "is_active": true,
      "marketplace_product": {
        "title": "Sample Product",
        "description": "Product description...",
        "image_link": "https://example.com/image.jpg",
        "brand": "Brand Name"
      }
    }
  ],
  "total": 50,
  "skip": 0,
  "limit": 20
}
```

---

### Get Product Details

Get detailed information for a specific product.

**Endpoint:** `GET /api/v1/shop/products/{product_id}`

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `product_id` | integer | Product ID |

**Request Example:**

```http
GET /api/v1/shop/products/1
Referer: http://localhost:8000/vendors/wizamart/shop/products
```

**Response (200 OK):**

```json
{
  "id": 1,
  "vendor_id": 1,
  "product_id": "PROD-001",
  "price": 29.99,
  "sale_price": 24.99,
  "currency": "EUR",
  "availability": "in stock",
  "condition": "new",
  "is_featured": true,
  "is_active": true,
  "min_quantity": 1,
  "max_quantity": 10,
  "total_inventory": 100,
  "available_inventory": 95,
  "marketplace_product": {
    "title": "Sample Product",
    "description": "Full product description...",
    "image_link": "https://example.com/image.jpg",
    "brand": "Brand Name",
    "gtin": "1234567890123"
  }
}
```

**Error Responses:**

- `404 Not Found` - Product not found or not active
- `404 Not Found` - Vendor not found (missing/invalid Referer)

---

## Shopping Cart

Cart operations use session-based tracking (no authentication required).

### Get Cart

Retrieve cart contents for a session.

**Endpoint:** `GET /api/v1/shop/cart/{session_id}`

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `session_id` | string | Unique session identifier |

**Request Example:**

```http
GET /api/v1/shop/cart/session-abc-123
Referer: http://localhost:8000/vendors/wizamart/shop/cart
```

**Response (200 OK):**

```json
{
  "vendor_id": 1,
  "session_id": "session-abc-123",
  "items": [
    {
      "product_id": 1,
      "product_name": "Sample Product",
      "quantity": 2,
      "price": 29.99,
      "line_total": 59.98,
      "image_url": "https://example.com/image.jpg"
    }
  ],
  "subtotal": 59.98,
  "total": 59.98,
  "item_count": 1
}
```

**Response Schema:** `CartResponse`

| Field | Type | Description |
|-------|------|-------------|
| `vendor_id` | integer | Vendor ID |
| `session_id` | string | Shopping session ID |
| `items` | array | List of cart items (see CartItemResponse below) |
| `subtotal` | float | Subtotal of all items |
| `total` | float | Total amount (currently same as subtotal) |
| `item_count` | integer | Total number of unique items in cart |

**CartItemResponse Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `product_id` | integer | Product ID |
| `product_name` | string | Product name |
| `quantity` | integer | Quantity in cart |
| `price` | float | Price per unit (captured when added to cart) |
| `line_total` | float | Total for this line (price × quantity) |
| `image_url` | string \| null | Product image URL |

---

### Add to Cart

Add a product to the cart. If the product already exists in the cart, the quantity will be incremented.

**Endpoint:** `POST /api/v1/shop/cart/{session_id}/items`

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `session_id` | string | Unique session identifier |

**Request Body Schema:** `AddToCartRequest`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `product_id` | integer | Yes | Product ID to add (must be > 0) |
| `quantity` | integer | No | Quantity to add (default: 1, must be >= 1) |

**Request Example:**

```json
{
  "product_id": 1,
  "quantity": 2
}
```

**Response (200 OK):** `CartOperationResponse`

```json
{
  "message": "Product added to cart",
  "product_id": 1,
  "quantity": 2
}
```

**Response Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `message` | string | Operation result message |
| `product_id` | integer | Product ID affected |
| `quantity` | integer | New quantity in cart |

**Error Responses:**

- `404 Not Found` - Product not found or vendor not found
  ```json
  {
    "error_code": "PRODUCT_NOT_FOUND",
    "message": "Product with ID '123' not found in vendor 1 catalog",
    "status_code": 404,
    "details": {
      "product_id": 123,
      "vendor_id": 1
    }
  }
  ```

- `400 Bad Request` - Insufficient inventory
  ```json
  {
    "error_code": "INSUFFICIENT_INVENTORY_FOR_CART",
    "message": "Insufficient inventory for product 'Sample Product'. Requested: 10, Available: 5",
    "status_code": 400,
    "details": {
      "product_id": 1,
      "product_name": "Sample Product",
      "requested_quantity": 10,
      "available_quantity": 5
    }
  }
  ```

- `422 Unprocessable Entity` - Validation error
  ```json
  {
    "error_code": "INVALID_CART_QUANTITY",
    "message": "Quantity must be at least 1",
    "status_code": 422,
    "details": {
      "field": "quantity",
      "quantity": 0,
      "min_quantity": 1
    }
  }
  ```

**Implementation Notes:**

- Cart items are stored in the database with persistent storage
- Price is captured at time of adding to cart (uses `sale_price` if available, otherwise `price`)
- If product already exists in cart, quantity is incremented (duplicate prevention)
- Inventory is validated before adding items

---

### Update Cart Item

Update the quantity of an existing item in the cart.

**Endpoint:** `PUT /api/v1/shop/cart/{session_id}/items/{product_id}`

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `session_id` | string | Unique session identifier |
| `product_id` | integer | Product ID to update (must be > 0) |

**Request Body Schema:** `UpdateCartItemRequest`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `quantity` | integer | Yes | New quantity (must be >= 1) |

**Request Example:**

```json
{
  "quantity": 3
}
```

**Response (200 OK):** `CartOperationResponse`

```json
{
  "message": "Cart updated",
  "product_id": 1,
  "quantity": 3
}
```

**Error Responses:**

- `404 Not Found` - Cart item not found
  ```json
  {
    "error_code": "CART_ITEM_NOT_FOUND",
    "message": "Product 123 not found in cart",
    "status_code": 404,
    "details": {
      "product_id": 123,
      "session_id": "session-abc-123"
    }
  }
  ```

- `400 Bad Request` - Insufficient inventory for new quantity
- `422 Unprocessable Entity` - Invalid quantity (less than 1)

---

### Remove from Cart

Remove a specific item from the cart.

**Endpoint:** `DELETE /api/v1/shop/cart/{session_id}/items/{product_id}`

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `session_id` | string | Unique session identifier |
| `product_id` | integer | Product ID to remove (must be > 0) |

**Response (200 OK):** `CartOperationResponse`

```json
{
  "message": "Item removed from cart",
  "product_id": 1
}
```

**Error Responses:**

- `404 Not Found` - Cart item not found (product not in cart)

---

### Clear Cart

Remove all items from the cart.

**Endpoint:** `DELETE /api/v1/shop/cart/{session_id}`

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `session_id` | string | Unique session identifier |

**Response (200 OK):** `ClearCartResponse`

```json
{
  "message": "Cart cleared",
  "items_removed": 3
}
```

**Response Schema:**

| Field | Type | Description |
|-------|------|-------------|
| `message` | string | Operation result message |
| `items_removed` | integer | Number of items that were removed from cart |

---

## Orders

Order endpoints require customer authentication.

### Place Order

Create a new order (authenticated).

**Endpoint:** `POST /api/v1/shop/orders`

**Authentication:** Required (customer token)

**Request Body:**

```json
{
  "session_id": "session-abc-123",
  "shipping_address": {
    "street": "123 Main St",
    "city": "City",
    "postal_code": "12345",
    "country": "US"
  },
  "billing_address": {
    "street": "123 Main St",
    "city": "City",
    "postal_code": "12345",
    "country": "US"
  },
  "payment_method": "stripe",
  "notes": "Please deliver before 5pm"
}
```

**Response (201 Created):**

```json
{
  "id": 123,
  "order_number": "ORD-2025-001",
  "vendor_id": 1,
  "customer_id": 456,
  "total": 59.98,
  "status": "pending",
  "created_at": "2025-11-22T10:00:00Z",
  "items": [...],
  "shipping_address": {...},
  "billing_address": {...}
}
```

---

### Get Order History

Get list of customer's orders (authenticated).

**Endpoint:** `GET /api/v1/shop/orders`

**Authentication:** Required

**Query Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `skip` | integer | 0 | Pagination offset |
| `limit` | integer | 20 | Max orders to return |

**Response (200 OK):**

```json
{
  "orders": [
    {
      "id": 123,
      "order_number": "ORD-2025-001",
      "total": 59.98,
      "status": "completed",
      "created_at": "2025-11-22T10:00:00Z"
    }
  ],
  "total": 5,
  "skip": 0,
  "limit": 20
}
```

---

### Get Order Details

Get details of a specific order (authenticated).

**Endpoint:** `GET /api/v1/shop/orders/{order_id}`

**Authentication:** Required

**Response (200 OK):**

```json
{
  "id": 123,
  "order_number": "ORD-2025-001",
  "vendor_id": 1,
  "customer_id": 456,
  "total": 59.98,
  "status": "completed",
  "created_at": "2025-11-22T10:00:00Z",
  "items": [...],
  "shipping_address": {...},
  "billing_address": {...},
  "tracking_number": "TRACK-123"
}
```

**Error Responses:**

- `404 Not Found` - Order not found or doesn't belong to customer

---

## Authentication

### Register Customer

Create a new customer account.

**Endpoint:** `POST /api/v1/shop/auth/register`

**Request Body:**

```json
{
  "email": "customer@example.com",
  "password": "SecurePass123!",
  "first_name": "John",
  "last_name": "Doe",
  "phone": "+1234567890"
}
```

**Response (201 Created):**

```json
{
  "id": 456,
  "email": "customer@example.com",
  "first_name": "John",
  "last_name": "Doe",
  "phone": "+1234567890",
  "is_active": true,
  "vendor_id": 1
}
```

**Error Responses:**

- `400 Bad Request` - Email already registered
- `422 Unprocessable Entity` - Validation errors

---

### Customer Login

Authenticate a customer and receive JWT token.

**Endpoint:** `POST /api/v1/shop/auth/login`

**Request Body:**

```json
{
  "email_or_username": "customer@example.com",
  "password": "SecurePass123!"
}
```

**Response (200 OK):**

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 86400,
  "user": {
    "id": 456,
    "email": "customer@example.com",
    "first_name": "John",
    "last_name": "Doe"
  }
}
```

**Cookie Set:**

The endpoint also sets an HTTP-only cookie:

```http
Set-Cookie: customer_token=<jwt>; Path=/shop; HttpOnly; SameSite=Lax; Secure
```

**Error Responses:**

- `401 Unauthorized` - Invalid credentials
- `404 Not Found` - Customer not found for this vendor

---

### Customer Logout

Clear customer session.

**Endpoint:** `POST /api/v1/shop/auth/logout`

**Response (200 OK):**

```json
{
  "message": "Logged out successfully"
}
```

The endpoint clears the `customer_token` cookie.

---

## Content Pages (CMS)

### Get Navigation Links

Get CMS pages for header/footer navigation.

**Endpoint:** `GET /api/v1/shop/content-pages/navigation`

**Response (200 OK):**

```json
[
  {
    "slug": "about",
    "title": "About Us",
    "show_in_header": true,
    "show_in_footer": true,
    "display_order": 1
  },
  {
    "slug": "contact",
    "title": "Contact Us",
    "show_in_header": true,
    "show_in_footer": true,
    "display_order": 2
  }
]
```

---

### Get Content Page

Get full content for a specific page.

**Endpoint:** `GET /api/v1/shop/content-pages/{slug}`

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `slug` | string | Page slug (e.g., "about", "faq") |

**Response (200 OK):**

```json
{
  "slug": "about",
  "title": "About Us",
  "content": "<p>Welcome to our shop...</p>",
  "meta_description": "Learn about our story",
  "is_published": true,
  "show_in_header": true,
  "show_in_footer": true,
  "display_order": 1
}
```

**Error Responses:**

- `404 Not Found` - Page not found

---

## Error Handling

All endpoints follow standard HTTP error responses:

### Common Error Response Format

```json
{
  "error_code": "VALIDATION_ERROR",
  "message": "Request validation failed",
  "status_code": 422,
  "details": {
    "validation_errors": [
      {
        "loc": ["body", "email"],
        "msg": "Invalid email format",
        "type": "value_error.email"
      }
    ]
  }
}
```

### HTTP Status Codes

| Code | Meaning | When Used |
|------|---------|-----------|
| 200 | OK | Successful GET/PUT/DELETE |
| 201 | Created | Successful POST (resource created) |
| 400 | Bad Request | Invalid request data |
| 401 | Unauthorized | Authentication required/failed |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource not found (product, vendor, order) |
| 422 | Unprocessable Entity | Validation errors |
| 500 | Internal Server Error | Server error |

---

## Rate Limiting

All Shop API endpoints are rate limited:

- **Public endpoints**: 100 requests/minute per IP
- **Authenticated endpoints**: 200 requests/minute per customer
- **Search endpoints**: 20 requests/minute per IP

Rate limit headers included in responses:

```http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1700000000
```

---

## Migration from Old API

**Old Pattern (Deprecated):**

```http
GET /api/v1/public/vendors/{vendor_id}/products
POST /api/v1/public/vendors/auth/{vendor_id}/customers/login
```

**New Pattern (Current):**

```http
GET /api/v1/shop/products
POST /api/v1/shop/auth/login
```

**Key Changes:**

- ✅ Removed `{vendor_id}` from URLs
- ✅ Vendor extracted from Referer header automatically
- ✅ Cleaner URLs (~40% shorter)
- ✅ Same functionality, better architecture

**See:** [API Migration Status](../architecture/API_MIGRATION_STATUS.md)

---

## Examples

### Complete Add to Cart Flow

```javascript
// 1. Get session ID (generate or retrieve from localStorage)
const sessionId = localStorage.getItem('session_id') || crypto.randomUUID();
localStorage.setItem('session_id', sessionId);

// 2. Add product to cart
const response = await fetch(`/api/v1/shop/cart/${sessionId}/items`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    product_id: 1,
    quantity: 2
  })
});

const result = await response.json();
console.log('Cart updated:', result.cart);
```

### Complete Checkout Flow

```javascript
// 1. Customer logs in
const loginResponse = await fetch('/api/v1/shop/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email_or_username: 'customer@example.com',
    password: 'password123'
  })
});

const { access_token } = await loginResponse.json();
localStorage.setItem('customer_token', access_token);

// 2. Place order
const orderResponse = await fetch('/api/v1/shop/orders', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${access_token}`
  },
  body: JSON.stringify({
    session_id: sessionId,
    shipping_address: {...},
    billing_address: {...},
    payment_method: 'stripe'
  })
});

const order = await orderResponse.json();
console.log('Order created:', order.order_number);
```

---

## Interactive Documentation

For live API testing and exploration:

- **Swagger UI**: [http://localhost:8000/docs](http://localhost:8000/docs)
- **ReDoc**: [http://localhost:8000/redoc](http://localhost:8000/redoc)
- **OpenAPI Spec**: [http://localhost:8000/openapi.json](http://localhost:8000/openapi.json)

---

**Questions?** See the [API Migration Status](../architecture/API_MIGRATION_STATUS.md) or [Shop Architecture Guide](../frontend/shop/architecture.md).
