Files
orion/docs/api/storefront-api-reference.md
Samir Boulahtit d648c921b7
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
docs: add consolidated dev URL reference and migrate /shop to /storefront
- Add Development URL Quick Reference section to url-routing overview
  with all login URLs, entry points, and full examples
- Replace /shop/ path segments with /storefront/ across 50 docs files
- Update file references: shop_pages.py → storefront_pages.py,
  templates/shop/ → templates/storefront/, api/v1/shop/ → api/v1/storefront/
- Preserve domain references (orion.shop) and /store/ staff dashboard paths
- Archive docs left unchanged (historical)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 13:23:44 +01:00

912 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Storefront API Reference
**Last Updated:** 2026-01-29
**API Version:** v1
**Base Path:** `/api/v1/storefront`
---
## Overview
The Storefront API provides customer-facing endpoints for browsing products, managing cart, placing orders, and customer authentication. All endpoints use **middleware-based store context** - no store ID in URLs!
### Key Features
**Automatic Store Detection** - Store extracted from Referer header via middleware
**Multi-Tenant** - Each store has isolated customer data
**Session-Based Cart** - No authentication required for browsing/cart
**Secure Authentication** - JWT tokens with HTTP-only cookies (path=/storefront)
**RESTful Design** - Standard HTTP methods and status codes
---
## How Store Context Works
All Storefront API endpoints automatically receive store context from the `StoreContextMiddleware`:
1. **Browser makes API call** from storefront page (e.g., `/platforms/oms/storefront/ORION/products`)
2. **Browser automatically sends Referer header**: `http://localhost:8000/platforms/oms/storefront/ORION/products`
3. **Middleware extracts store** from Referer path/subdomain/domain
4. **Middleware sets** `request.state.store = <Store: ORION>`
5. **API endpoint accesses store**: `store = request.state.store`
6. **No store_id needed in URL!**
### Supported Store Detection Methods
- **Path-based (dev)**: `/platforms/oms/storefront/ORION/products` → extracts `ORION`
- **Subdomain**: `orion.platform.com` → extracts `orion`
- **Custom domain**: `customshop.com` → looks up store 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 store.
**Endpoint:** `GET /api/v1/storefront/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/storefront/products?skip=0&limit=20&is_featured=true
Referer: http://localhost:8000/storefront/orion/products
```
**Response (200 OK):**
```json
{
"products": [
{
"id": 1,
"store_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/storefront/products/{product_id}`
**Path Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `product_id` | integer | Product ID |
**Request Example:**
```http
GET /api/v1/storefront/products/1
Referer: http://localhost:8000/storefront/orion/products
```
**Response (200 OK):**
```json
{
"id": 1,
"store_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` - Store 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/storefront/cart/{session_id}`
**Path Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `session_id` | string | Unique session identifier |
**Request Example:**
```http
GET /api/v1/storefront/cart/session-abc-123
Referer: http://localhost:8000/storefront/orion/cart
```
**Response (200 OK):**
```json
{
"store_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 |
|-------|------|-------------|
| `store_id` | integer | Store 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/storefront/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 store not found
```json
{
"error_code": "PRODUCT_NOT_FOUND",
"message": "Product with ID '123' not found in store 1 catalog",
"status_code": 404,
"details": {
"product_id": 123,
"store_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/storefront/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/storefront/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/storefront/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/storefront/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",
"store_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/storefront/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/storefront/orders/{order_id}`
**Authentication:** Required
**Response (200 OK):**
```json
{
"id": 123,
"order_number": "ORD-2025-001",
"store_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/storefront/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,
"store_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/storefront/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=/storefront; HttpOnly; SameSite=Lax; Secure
```
**Error Responses:**
- `401 Unauthorized` - Invalid credentials
- `404 Not Found` - Customer not found for this store
---
### Customer Logout
Clear customer session.
**Endpoint:** `POST /api/v1/storefront/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/storefront/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/storefront/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 store...</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, store, order) |
| 422 | Unprocessable Entity | Validation errors |
| 500 | Internal Server Error | Server error |
---
## Rate Limiting
All Storefront 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/platform/stores/{store_id}/products
POST /api/v1/platform/stores/auth/{store_id}/customers/login
```
**New Pattern (Current):**
```http
GET /api/v1/storefront/products
POST /api/v1/storefront/auth/login
```
**Key Changes:**
- ✅ Removed `{store_id}` from URLs
- ✅ Store 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/storefront/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/storefront/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/storefront/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 [Storefront Architecture Guide](../frontend/storefront/architecture.md).