Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
19 KiB
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:
- Browser makes API call from storefront page (e.g.,
/stores/orion/storefront/products) - Browser automatically sends Referer header:
http://localhost:8000/stores/orion/storefront/products - Middleware extracts store from Referer path/subdomain/domain
- Middleware sets
request.state.store = <Store: orion> - API endpoint accesses store:
store = request.state.store - No store_id needed in URL!
Supported Store Detection Methods
- Path-based:
/stores/orion/storefront/products→ extractsorion - Subdomain:
orion.platform.com→ extractsorion - 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:
Authorization: Bearer <customer_token>
Or use the HTTP-only cookie (automatically sent by browser):
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:
GET /api/v1/storefront/products?skip=0&limit=20&is_featured=true
Referer: http://localhost:8000/stores/orion/shop/products
Response (200 OK):
{
"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:
GET /api/v1/storefront/products/1
Referer: http://localhost:8000/stores/orion/shop/products
Response (200 OK):
{
"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 active404 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:
GET /api/v1/storefront/cart/session-abc-123
Referer: http://localhost:8000/stores/orion/shop/cart
Response (200 OK):
{
"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:
{
"product_id": 1,
"quantity": 2
}
Response (200 OK): CartOperationResponse
{
"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{ "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{ "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{ "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_priceif available, otherwiseprice) - 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:
{
"quantity": 3
}
Response (200 OK): CartOperationResponse
{
"message": "Cart updated",
"product_id": 1,
"quantity": 3
}
Error Responses:
-
404 Not Found- Cart item not found{ "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
{
"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
{
"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:
{
"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):
{
"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):
{
"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):
{
"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:
{
"email": "customer@example.com",
"password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe",
"phone": "+1234567890"
}
Response (201 Created):
{
"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 registered422 Unprocessable Entity- Validation errors
Customer Login
Authenticate a customer and receive JWT token.
Endpoint: POST /api/v1/storefront/auth/login
Request Body:
{
"email_or_username": "customer@example.com",
"password": "SecurePass123!"
}
Response (200 OK):
{
"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:
Set-Cookie: customer_token=<jwt>; Path=/storefront; HttpOnly; SameSite=Lax; Secure
Error Responses:
401 Unauthorized- Invalid credentials404 Not Found- Customer not found for this store
Customer Logout
Clear customer session.
Endpoint: POST /api/v1/storefront/auth/logout
Response (200 OK):
{
"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):
[
{
"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):
{
"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
{
"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:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1700000000
Migration from Old API
Old Pattern (Deprecated):
GET /api/v1/platform/stores/{store_id}/products
POST /api/v1/platform/stores/auth/{store_id}/customers/login
New Pattern (Current):
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
Examples
Complete Add to Cart Flow
// 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
// 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
- ReDoc: http://localhost:8000/redoc
- OpenAPI Spec: http://localhost:8000/openapi.json
Questions? See the API Migration Status or Storefront Architecture Guide.