The unified API is a RESTful API built with FastAPI that serves both the landing site and web application. It provides endpoints for content management, collection operations, activity processing, and user management. All endpoints return JSON responses, with public endpoints available for the landing site and protected endpoints requiring authentication for the web application.
Development: http://localhost:5000/api
Production: https://app.yourdomain.com/api
The API serves two distinct applications:
- Landing Site (
yourdomain.com): Uses public endpoints for content display - Web Application (
app.yourdomain.com): Uses authenticated endpoints for full functionality
The API uses JWT token verification for authentication. The frontend uses Clerk to authenticate users and obtain JWT tokens. All protected endpoints require a valid Bearer token in the Authorization header.
Authorization: Bearer <jwt_token>In the React app, use Clerk's useAuth hook:
const { getToken } = useAuth();
const token = await getToken();
// Include in API requests
const response = await axios.get('/api/protected', {
headers: {
'Authorization': `Bearer ${token}`
}
});The FastAPI backend verifies JWT tokens using the JWKS endpoint:
from jose import jwt, JWTError
from fastapi import HTTPException, Depends
from fastapi.security import HTTPBearer
security = HTTPBearer()
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
try:
payload = jwt.decode(
credentials.credentials,
key=JWKS_CLIENT, # Fetched from Clerk's JWKS endpoint
algorithms=["RS256"]
)
return payload
except JWTError:
raise HTTPException(status_code=401, detail="Invalid authentication token"){
"success": true,
"data": { ... },
"message": "Operation successful"
}{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human readable error message",
"details": { ... }
}
}{
"success": true,
"data": {
"items": [ ... ],
"pagination": {
"page": 1,
"limit": 10,
"total": 100,
"pages": 10
}
}
}These endpoints are accessible without authentication and are used by both the landing site and web application.
GET /api/public/items/featuredGet featured items for homepage display.
Response:
{
"success": true,
"data": [
{
"id": "cuid",
"name": "[Item Name]",
"description": "[Description]",
"metadata": "[Domain-specific data]",
"image": "https://example.com/image.jpg",
"category": {
"name": "[Category]",
"slug": "category-slug"
}
}
]
}GET /api/public/categoriesGet all categories for navigation.
Response:
{
"success": true,
"data": [
{
"id": "cuid",
"name": "[Category Name]",
"slug": "category-slug",
"displayOrder": 1,
"itemCount": 15
}
]
}Authentication is handled entirely by Clerk on the frontend. The backend only verifies tokens and syncs user data.
GET /api/users/meGet the current authenticated user's profile.
Response:
{
"success": true,
"data": {
"id": "cuid",
"clerkId": "user_xxx",
"email": "user@example.com",
"name": "John Doe",
"role": "USER",
"createdAt": "2024-01-01T00:00:00Z"
}
}PUT /api/users/meUpdate the current user's profile information.
Body:
{
"name": "Jane Doe"
}GET /api/categoriesGet all content categories.
Response:
{
"success": true,
"data": [
{
"id": "cuid",
"name": "[Type A]",
"slug": "type-a",
"description": "[Category description]",
"displayOrder": 1,
"_count": {
"items": 15
}
}
]
}GET /api/categories/:slug/itemsGet all items in a specific category.
Parameters:
slug(path): Category slug
Query Parameters:
page(optional): Page number (default: 1)limit(optional): Items per page (default: 12)sort(optional): Sort by price_asc, price_desc, name_asc, name_desc (default: name_asc)
GET /api/itemsGet all items with optional filtering.
Query Parameters:
page(optional): Page number (default: 1)limit(optional): Items per page (default: 12)category(optional): Filter by category slugfeatured(optional): Filter featured items (true/false)search(optional): Search by name or descriptionsort(optional): Sort by metadata_asc, metadata_desc, name_asc, name_desc, created_desc
Response:
{
"success": true,
"data": {
"items": [
{
"id": "cuid",
"name": "Chocolate Cake",
"description": "Rich chocolate cake with ganache",
"price": 35.00,
"image": "https://example.com/chocolate-cake.jpg",
"category": {
"id": "cuid",
"name": "Cake",
"slug": "cake"
},
"featured": true,
"inStock": true,
"stockQuantity": 20
}
],
"pagination": {
"page": 1,
"limit": 12,
"total": 48,
"pages": 4
}
}
}GET /api/items/:idGet detailed information about a specific item.
Parameters:
id(path): Item ID
Response:
{
"success": true,
"data": {
"id": "cuid",
"name": "[Item Name]",
"description": "[Item description]",
"metadata": "[domain-specific data]",
"image": "https://example.com/item-image.jpg",
"category": {
"id": "cuid",
"name": "[Type A]",
"slug": "type-a"
},
"featured": true,
"available": true,
"quantity": 20,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
}POST /api/itemsCreate a new item. Requires admin role.
Body:
{
"name": "[Item Name]",
"description": "[Item description]",
"metadata": "[domain-specific data]",
"categoryId": "cuid",
"featured": false,
"quantity": 15
}Note: Image upload is handled separately via /api/upload endpoint.
PUT /api/items/:idUpdate an existing item. Requires admin role.
Parameters:
id(path): Item ID
Body: Same as create, all fields optional
DELETE /api/items/:idDelete an item. Requires admin role.
Parameters:
id(path): Item ID
GET /api/collectionsGet the current user's collection.
Response:
{
"success": true,
"data": {
"id": "cuid",
"items": [
{
"id": "cuid",
"quantity": 2,
"item": {
"id": "cuid",
"name": "[Item Name]",
"metadata": "[domain-specific data]",
"image": "https://example.com/item-image.jpg",
"available": true
}
}
],
"totalData": "[calculated data]",
"itemCount": 2
}
}POST /api/collections/itemsAdd an item to the collection.
Body:
{
"itemId": "cuid",
"quantity": 1
}PUT /api/collections/items/:idUpdate quantity of a collection item.
Parameters:
id(path): Collection item ID
Body:
{
"quantity": 3
}DELETE /api/collections/items/:idRemove an item from the collection.
Parameters:
id(path): Collection item ID
DELETE /api/collectionsRemove all items from the collection.
POST /api/activitiesCreate a new activity from the current collection.
Body:
{
"userInfo": "John Doe",
"contactInfo": "john@example.com",
"method": "ONLINE",
"location": "123 Main St, City, State 12345",
"scheduledDate": "2024-01-15T10:00:00Z",
"notes": "[Additional notes]"
}Response:
{
"success": true,
"data": {
"id": "cuid",
"activityNumber": "ACT-20240101-001",
"status": "PENDING",
"data": "[calculated data]",
"createdAt": "2024-01-01T00:00:00Z"
}
}GET /api/activitiesGet all activities for the current user (or all activities for admin).
Query Parameters:
page(optional): Page number (default: 1)limit(optional): Items per page (default: 10)status(optional): Filter by status
Response:
{
"success": true,
"data": {
"items": [
{
"id": "cuid",
"activityNumber": "ACT-20240101-001",
"status": "COMPLETED",
"data": "[calculated data]",
"itemCount": 2,
"createdAt": "2024-01-01T00:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 25,
"pages": 3
}
}
}GET /api/activities/:idGet detailed information about a specific activity.
Parameters:
id(path): Activity ID
Response:
{
"success": true,
"data": {
"id": "cuid",
"activityNumber": "ACT-20240101-001",
"status": "COMPLETED",
"items": [
{
"id": "cuid",
"quantity": 2,
"itemData": "[item-specific data]",
"totalData": "[calculated total]",
"item": {
"id": "cuid",
"name": "[Item Name]",
"image": "https://example.com/item-image.jpg"
}
}
],
"data": "[total calculated data]",
"userInfo": "John Doe",
"contactInfo": "john@example.com",
"method": "ONLINE",
"location": "123 Main St, City, State 12345",
"scheduledDate": "2024-01-15T10:00:00Z",
"notes": "[Additional notes]",
"createdAt": "2024-01-01T00:00:00Z"
}
}PUT /api/activities/:id/statusUpdate the status of an activity. Requires admin role.
Parameters:
id(path): Activity ID
Body:
{
"status": "PROCESSING"
}Valid Status Values:
PENDINGPROCESSINGREADYCOMPLETEDCANCELLED
POST /api/upload/itemUpload an item image using multipart form data.
Headers:
Content-Type: multipart/form-data
Authorization: Bearer <token>
Body:
image: Image file (JPEG, PNG, WebP, max 5MB)
Response:
{
"success": true,
"data": {
"url": "/uploads/items/1704067200000-item-image.jpg",
"filename": "1704067200000-item-image.jpg",
"originalName": "item-image.jpg",
"mimetype": "image/jpeg",
"size": 102400
}
}POST /api/upload/itemsUpload multiple item images (max 5 files).
Headers:
Content-Type: multipart/form-data
Authorization: Bearer <token>
Body:
images: Array of image files
Response:
{
"success": true,
"data": {
"files": [
{
"url": "/uploads/items/1704067200000-image1.jpg",
"filename": "1704067200000-image1.jpg",
"size": 102400
},
{
"url": "/uploads/items/1704067200001-image2.jpg",
"filename": "1704067200001-image2.jpg",
"size": 98304
}
]
}
}File Upload Configuration:
- Maximum file size: 5MB per file
- Accepted formats: JPEG, PNG, WebP
- Storage location:
./uploads/items(development) - Files served from:
http://localhost:5000/uploads/items/
POST /api/public/contactSubmit contact form from landing page.
Body:
{
"name": "John Doe",
"email": "john@example.com",
"message": "Inquiry message",
"subject": "General Inquiry"
}Response:
{
"success": true,
"message": "Contact form submitted successfully"
}POST /api/public/newsletterSubscribe email to newsletter.
Body:
{
"email": "user@example.com"
}GET /api/admin/dashboardGet dashboard statistics. Requires admin role.
Response:
{
"success": true,
"data": {
"totalActivities": 150,
"pendingActivities": 5,
"totalData": "[aggregated metrics]",
"totalItems": 48,
"totalUsers": 230,
"recentActivities": [ ... ],
"popularItems": [ ... ]
}
}| Code | Description |
|---|---|
UNAUTHORIZED |
Missing or invalid authentication token |
FORBIDDEN |
Insufficient permissions |
NOT_FOUND |
Resource not found |
VALIDATION_ERROR |
Invalid input data |
DUPLICATE_ENTRY |
Resource already exists |
UNAVAILABLE |
Item is unavailable |
COLLECTION_EMPTY |
Collection is empty |
INTERNAL_ERROR |
Server error |
API endpoints are rate-limited to prevent abuse:
- Public endpoints: 100 requests per minute per IP
- Landing site endpoints: 200 requests per minute per IP
- Authenticated endpoints: 300 requests per minute per user
- Admin endpoints: 500 requests per minute per user
Rate limit headers:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 299
X-RateLimit-Reset: 1609459200
POST https://your-webhook-url.com/activity-statusTriggered when an activity status changes.
Payload:
{
"event": "activity.status.updated",
"timestamp": "2024-01-01T00:00:00Z",
"data": {
"activityId": "cuid",
"activityNumber": "ACT-20240101-001",
"oldStatus": "PENDING",
"newStatus": "PROCESSING",
"userId": "cuid"
}
}Get items:
curl -X GET http://localhost:5000/api/itemsAdd to collection (with auth):
curl -X POST http://localhost:5000/api/collections/items \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"itemId":"cuid","quantity":1}'Import the Postman collection from /docs/postman-collection.json for a complete set of API requests with examples.
import { LandingAPI } from '@[project]/sdk';
const api = new LandingAPI({
baseURL: 'http://localhost:5000/api'
});
// Get featured items
const featured = await api.public.getFeaturedItems();
// Submit contact form
await api.public.submitContact({
name: 'John Doe',
email: 'john@example.com',
message: 'Inquiry'
});import { WebAppAPI } from '@[project]/sdk';
const api = new WebAppAPI({
baseURL: 'http://localhost:5000/api',
getToken: async () => await clerk.session?.getToken()
});
// Get items
const items = await api.items.list({ featured: true });
// Add to collection
await api.collections.addItem({ itemId: 'cuid', quantity: 2 });CORS_ORIGINS = [
"http://localhost:3000", # Landing site
"http://localhost:5173", # Web application
]CORS_ORIGINS = [
"https://yourdomain.com", # Landing site
"https://app.yourdomain.com", # Web application
]