🏠

Home

Home page and health check endpoints

GET /api/health PUBLIC
Check API health status and server availability
📀 Response Example
{
  "status": "healthy"
}
📱

Introduction Pages

Onboarding/introduction pages for mobile app first-time users

GET /api/v1/introduction-pages PUBLIC
Get all active introduction pages for app onboarding. Returns pages ordered by page_number (1, 2, 3). Only active pages are returned. Use this endpoint to display introduction/onboarding screens in the mobile app.
🔍 Query Parameters
📀 Response Example
[
  {
    "id": 1,
    "page_number": 1,
    "image": "https://teamhirfa.com/static/uploads/introduction_pages/intro_page_1_abc123.jpg",
    "text1": "Welcome to our app",
    "text2": "Discover amazing products",
    "text3": "Start shopping now",
    "is_active": true,
    "created_at": "2024-12-25T00:00:00Z",
    "updated_at": "2024-12-25T00:00:00Z"
  },
  {
    "id": 2,
    "page_number": 2,
    "image": "https://teamhirfa.com/static/uploads/introduction_pages/intro_page_2_def456.jpg",
    "text1": "Fast delivery",
    "text2": "Get your orders quickly",
    "text3": "Track in real-time",
    "is_active": true,
    "created_at": "2024-12-25T00:00:00Z",
    "updated_at": "2024-12-25T00:00:00Z"
  },
  {
    "id": 3,
    "page_number": 3,
    "image": "https://teamhirfa.com/static/uploads/introduction_pages/intro_page_3_ghi789.jpg",
    "text1": "Secure payments",
    "text2": "Your data is safe",
    "text3": "Shop with confidence",
    "is_active": true,
    "created_at": "2024-12-25T00:00:00Z",
    "updated_at": "2024-12-25T00:00:00Z"
  }
]
🔐

Authentication

User registration, login, and token management

POST /api/v1/auth/register PUBLIC
Register a new user account
📊 Body Parameters
  • email
  • username
  • password
  • full_name (optional)
  • phone (optional)
📀 Response Example
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 86400,
  "user": {
    "id": 1,
    "email": "user@example.com",
    "username": "johndoe",
    "full_name": "John Doe",
    "phone": "+1234567890",
    "user_type": "normal_user",
    "store_id": null,
    "selected_currency_id": null,
    "is_active": true,
    "is_verified": false,
    "avatar": null,
    "last_login": null,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
}
POST /api/v1/auth/login PUBLIC
Login with email/username and password. Users can login with either their email address or username.
📊 Body Parameters
  • email (or username)
  • password
📀 Response Example
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 86400,
  "user": {
    "id": 1,
    "email": "user@example.com",
    "username": "johndoe",
    "full_name": "John Doe",
    "phone": "+1234567890",
    "user_type": "normal_user",
    "store_id": null,
    "is_active": true,
    "is_verified": false,
    "avatar": null,
    "last_login": "2024-12-08T10:30:00Z",
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
}
POST /api/v1/auth/refresh PUBLIC
Refresh access token using refresh token
📊 Body Parameters
  • refresh_token
📀 Response Example
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 86400
}
POST /api/v1/auth/logout PUBLIC
Logout user (client-side token removal)
📀 Response Example
{
  "message": "Logged out successfully"
}
POST /api/v1/auth/forgot-password PUBLIC
Request password reset. User submits their email address. The system generates a secure password reset token and sends it via email to the user's registered email address. The token expires in 1 hour. For security reasons, the API always returns a success message regardless of whether the email exists (prevents email enumeration attacks). The reset link is sent to the user's email only - no token is returned in the API response.
📊 Body Parameters
  • email
📀 Response Example
{
  "message": "If the email exists, a password reset link has been sent to your email.",
  "success": true
}
ℹ Note
SECURITY: The reset token is NEVER returned in the API response. It is sent ONLY via email to the registered email address. This prevents unauthorized access to password reset tokens. Always check the user's email inbox for the reset link.
POST /api/v1/auth/verify-reset-token PUBLIC
Verify password reset token. Checks if the token is valid and not expired. Returns success if token is valid. Use this before showing the password reset form to ensure the token is still valid.
📊 Body Parameters
  • token
📀 Response Example
{
  "message": "Reset token is valid",
  "success": true
}
POST /api/v1/auth/reset-password PUBLIC
Reset password using reset token. Resets the user's password and invalidates the reset token. Password must be at least 8 characters. After successful reset, user can login with the new password.
📊 Body Parameters
  • token
  • new_password (minimum 8 characters)
📀 Response Example
{
  "message": "Password has been reset successfully",
  "success": true
}
👀

Profile

User profile management and information

GET /api/v1/auth/me PRIVATE
Get current authenticated user profile. Returns user information including user type (normal_user or shop_owner), store_id if the user is a shop owner, and opening_hours for shop owners.
🔒 Auth: Bearer Token (JWT)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "id": 1,
  "email": "user@example.com",
  "username": "johndoe",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "user_type": "normal_user",
  "store_id": null,
  "selected_currency_id": null,
  "is_active": true,
  "is_verified": true,
  "avatar": "https://example.com/avatar.jpg",
  "banner": "https://example.com/banner.jpg",
  "opening_hours": null,
  "last_login": "2024-12-08T10:30:00Z",
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T10:30:00Z"
}
ℹ Note
opening_hours and platform_fee are only returned for shop owners (user_type='shop_owner'). For regular users, these will be null. platform_fee contains the fee details (type='global' or 'custom', percentage, flags, and label='VIP/Normal Seller'). The opening_hours object contains all 7 days of the week (monday through sunday), each with is_closed (bool), open_time (HH:MM format, 24-hour, UTC+3), and close_time (HH:MM format, 24-hour, UTC+3). All times are in UTC+3 (Bahrain Time). Use is_closed=true to mark a day as closed. See opening_hours_example above for the structure.
PUT /api/v1/auth/me PRIVATE
Update current user profile with file upload support. Use multipart/form-data for avatar upload. You can update full_name, phone, upload avatar image file, and opening_hours (for shop owners only).
🔒 Auth: Bearer Token (JWT)
📋 Headers
  • Authorization: Bearer {token}
📎 Form Data (multipart/form-data)
  • full_name (optional, string)
  • phone (optional, string)
  • avatar (optional, image file)
  • banner (optional, image file - jpg, jpeg, png, gif, svg, webp)
  • opening_hours (optional, JSON string) - Opening hours for shop owners. Format: {"monday": {"is_closed": false, "open_time": "09:00", "close_time": "17:00"}, "tuesday": {"is_closed": true}, ...}. Days: monday, tuesday, wednesday, thursday, friday, saturday, sunday. Each day has is_closed (bool), open_time (HH:MM format, 24-hour), close_time (HH:MM format, 24-hour).
📀 Response Example
{
  "id": 1,
  "email": "user@example.com",
  "username": "johndoe",
  "full_name": "Jane Doe Updated",
  "phone": "+1987654321",
  "user_type": "shop_owner",
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "is_active": true,
  "is_verified": true,
  "avatar": "https://teamhirfa.com/static/uploads/users/user_1_a3b5c7d9.png",
  "opening_hours": {
    "monday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "tuesday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "wednesday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "thursday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "friday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "saturday": {
      "is_closed": true,
      "open_time": null,
      "close_time": null
    },
    "sunday": {
      "is_closed": true,
      "open_time": null,
      "close_time": null
    }
  },
  "last_login": "2024-12-08T10:30:00Z",
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T11:45:00Z"
}
ℹ Note
opening_hours is only applicable for shop owners (user_type='shop_owner'). For regular users, opening_hours will be null. Use is_closed=true to mark a day as closed. Times are in 24-hour format (HH:MM) and are in UTC+3 (Bahrain Time).
PUT /api/v1/auth/me/json PRIVATE
Update current user profile (JSON format). Use this for text fields only. For avatar file upload, use PUT /me with multipart/form-data. You can update full_name, phone, avatar (as URL string), and opening_hours (for shop owners only).
🔒 Auth: Bearer Token (JWT)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • full_name (optional, string)
  • phone (optional, string)
  • avatar (optional, URL string)
  • opening_hours (optional, object) - Opening hours for shop owners. Format: {"monday": {"is_closed": false, "open_time": "09:00", "close_time": "17:00"}, "tuesday": {"is_closed": true}, ...}. Days: monday, tuesday, wednesday, thursday, friday, saturday, sunday. Each day has is_closed (bool), open_time (HH:MM format, 24-hour), close_time (HH:MM format, 24-hour).
📀 Response Example
{
  "id": 1,
  "email": "user@example.com",
  "username": "johndoe",
  "full_name": "Jane Doe Updated",
  "phone": "+1987654321",
  "user_type": "shop_owner",
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "is_active": true,
  "is_verified": true,
  "avatar": "https://example.com/avatar.jpg",
  "opening_hours": {
    "monday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "tuesday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "wednesday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "thursday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "friday": {
      "is_closed": false,
      "open_time": "09:00",
      "close_time": "17:00"
    },
    "saturday": {
      "is_closed": true,
      "open_time": null,
      "close_time": null
    },
    "sunday": {
      "is_closed": true,
      "open_time": null,
      "close_time": null
    }
  },
  "last_login": "2024-12-08T10:30:00Z",
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T11:45:00Z"
}
ℹ Note
opening_hours is only applicable for shop owners (user_type='shop_owner'). For regular users, opening_hours will be null. Use is_closed=true to mark a day as closed. Times are in 24-hour format (HH:MM) and are in UTC+3 (Bahrain Time).
POST /api/v1/auth/currency PRIVATE
Update user's selected currency preference. The selected currency will be stored in the user's profile and remembered for future sessions. Currency must be active.
🔒 Auth: Bearer Token (JWT)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • currency_id
📀 Response Example
{
  "id": 1,
  "email": "user@example.com",
  "username": "johndoe",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "user_type": "normal_user",
  "store_id": null,
  "selected_currency_id": 2,
  "is_active": true,
  "is_verified": true,
  "avatar": null,
  "last_login": "2024-12-08T10:30:00Z",
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-09T12:00:00Z"
}
ℹ Note
Returns updated user profile with selected_currency_id. Use this ID to convert prices in the app. Check /api/v1/settings endpoint to get currency details including exchange rates.
GET /api/v1/auth/me/banner PRIVATE
Get current authenticated user's profile/shop banner URL.
🔒 Auth: Bearer Token (JWT)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "banner": "https://teamhirfa.com/static/uploads/banners/user_banner_1_b4c6d8.jpg",
  "success": true
}
POST /api/v1/auth/me/banner PRIVATE
Upload or update user profile/shop banner. Use multipart/form-data. This banner is shown on the shop page and in the mobile app profile header.
🔒 Auth: Bearer Token (JWT)
📋 Headers
  • Authorization: Bearer {token}
📎 Form Data (multipart/form-data)
  • banner (required, image file - jpg, jpeg, png, gif, svg, webp)
📀 Response Example
{
  "banner": "https://teamhirfa.com/static/uploads/banners/user_banner_1_b4c6d8.jpg",
  "message": "Banner uploaded successfully",
  "success": true
}
DELETE /api/v1/auth/me/banner PRIVATE
Remove the user's profile/shop banner image.
🔒 Auth: Bearer Token (JWT)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "message": "Banner removed successfully",
  "success": true
}
🎚

Branding

Branding and site configuration endpoints including dynamic app color customization (read-only)

GET /api/v1/branding PUBLIC
Get current branding information including favicon, logos, copyright text, social media links, and dynamic app color customization. The response includes a 'colors' object with all customizable app colors for both light and dark modes. This endpoint enables dynamic theming - the Flutter app can fetch these colors and apply them automatically. All color fields are optional (nullable) - if a color is null or not provided, the app will use default fallback colors. This is a read-only endpoint. To update branding, use the admin panel at /admin/branding. To customize app colors, use the admin panel at /admin/app-colors.
📀 Response Example
{
  "id": 1,
  "app_name": "My Application",
  "favicon": "https://teamhirfa.com/static/uploads/branding/favicon.ico",
  "logo_light": "https://teamhirfa.com/static/uploads/branding/logo_light.png",
  "logo_dark": "https://teamhirfa.com/static/uploads/branding/logo_dark.png",
  "copyright_text": "© 2024 SaaS E-commerce Platform. All rights reserved.",
  "social_media": [
    {
      "name": "Facebook",
      "url": "https://facebook.com/yourcompany",
      "icon": "https://teamhirfa.com/static/uploads/branding/social_facebook.png"
    },
    {
      "name": "Twitter",
      "url": "https://twitter.com/yourcompany",
      "icon": "https://teamhirfa.com/static/uploads/branding/social_twitter.png"
    },
    {
      "name": "Instagram",
      "url": "https://instagram.com/yourcompany",
      "icon": "https://teamhirfa.com/static/uploads/branding/social_instagram.png"
    }
  ],
  "colors": {
    "button_color_light": "#76974C",
    "button_color_dark": "#A3D97A",
    "light_button_color_light": "#EEF6E4",
    "light_button_color_dark": "#1D231C",
    "bg_color_light": "#FAFAFA",
    "bg_color_dark": "#161616",
    "card_color_light": "#FFFFFF",
    "card_color_dark": "#1E1E1E",
    "primary_text_light": "#000000",
    "primary_text_dark": "#F2F2F2",
    "secondary_text_light": "#808080",
    "secondary_text_dark": "#B8BFC7",
    "sub_title_color_light": "#808080",
    "sub_title_color_dark": "#A7AFB8",
    "border_color_light": "#DFDFDF",
    "border_color_dark": "#2A2A2A",
    "divider_color_light": "#F1F1F1",
    "divider_color_dark": "#212121",
    "regular_black_light": "#000000",
    "regular_black_dark": "#F2F2F2",
    "regular_white_light": "#FFFFFF",
    "regular_white_dark": "#1E1E1E",
    "animation_base_color_light": "#808080",
    "animation_base_color_dark": "#FFFFFF",
    "animation_highlight_color_light": "#FAFAFA",
    "animation_highlight_color_dark": "#161616",
    "error_color": "#FF6565",
    "success_color": "#4CAF50",
    "warning_color": "#FF9800",
    "info_color": "#2196F3",
    "chat_other_side_color": "#2196F3"
  },
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T11:45:00Z"
}
ℹ Note
DYNAMIC COLORS API: The 'colors' object enables dynamic app theming. Colors are organized by category: 📌 PRIMARY COLORS: - button_color_light/dark: Main button colors (default: #76974C/#A3D97A) - light_button_color_light/dark: Light button backgrounds (default: #EEF6E4/#1D231C) 🎚 BACKGROUND COLORS: - bg_color_light/dark: Main background (default: #FAFAFA/#161616) - card_color_light/dark: Card/surface colors (default: #FFFFFF/#1E1E1E) 📝 TEXT COLORS: - primary_text_light/dark: Primary text (default: #000000/#F2F2F2) - secondary_text_light/dark: Secondary text (default: #808080/#B8BFC7) - sub_title_color_light/dark: Subtitle text (default: #808080/#A7AFB8) 🔲 BORDER & DIVIDER COLORS: - border_color_light/dark: Border colors (default: #DFDFDF/#2A2A2A) - divider_color_light/dark: Divider colors (default: #F1F1F1/#212121) ⚫ STANDARD COLORS: - regular_black_light/dark: Black variant (default: #000000/#F2F2F2) - regular_white_light/dark: White variant (default: #FFFFFF/#1E1E1E) ✹ ANIMATION COLORS (for shimmer effects): - animation_base_color_light/dark: Base shimmer color (default: #808080/#FFFFFF) - animation_highlight_color_light/dark: Highlight shimmer (default: #FAFAFA/#161616) 🚊 STATUS COLORS (same for both modes): - error_color: Error/red (default: #FF6565) - success_color: Success/green (default: #4CAF50) - warning_color: Warning/orange (default: #FF9800) - info_color: Info/blue (default: #2196F3) - chat_other_side_color: Chat message color (default: #2196F3) 📋 COLOR FORMAT: - Colors are in hex format: #RRGGBB (6 digits) or #AARRGGBB (8 digits with alpha) - The # prefix is optional (both '#76974C' and '76974C' are accepted) - All color fields are optional (nullable) - partial customization is supported - If a color is null or empty, the app uses default fallback colors 💡 USAGE: - Fetch colors from this endpoint on app startup - Cache colors locally for offline use - Colors automatically adapt to light/dark mode - Changes in admin panel (/admin/app-colors) are reflected after app refresh
⚙

Settings

General platform settings and feature toggles. Use this endpoint to check if features like Events or Blogs are enabled before making API calls.

GET /api/v1/settings PUBLIC
Get general platform settings including feature toggles and currencies. Returns events_enabled (shows if Events feature is active), blogs_enabled (shows if Blogs feature is active), courses_enabled (shows if Courses feature is active), top_sellers_enabled (shows if Top Sellers feature is active), shops_enabled (shows if Shops feature is active), chat_enabled (shows if Chat feature is active), support_enabled (shows if Support System feature is active), master_categories_enabled (shows if Master Categories feature is active), platform_fees_enabled, platform_fees_percentage, and currencies array with exchange rates. Always check these feature toggles before calling respective endpoints to know if features are available. Currencies are returned with their exchange rates against BHD (default currency).
📀 Response Example
{
  "events_enabled": true,
  "blogs_enabled": true,
  "courses_enabled": true,
  "top_sellers_enabled": true,
  "shops_enabled": true,
  "chat_enabled": true,
  "support_enabled": true,
  "master_categories_enabled": true,
  "maintenance_mode": false,
  "platform_fees_enabled": false,
  "platform_fees_percentage": 0.0,
  "delivery_internal_fallback_cost": 2.0,
  "delivery_internal_profit": 0.2,
  "delivery_external_fallback_cost": 5.0,
  "delivery_external_profit": 0.5,
  "currencies": [
    {
      "id": 1,
      "symbol": "BHD",
      "rate": 1.0,
      "flag_image": "https://teamhirfa.com/static/uploads/currencies/flag_1_abc123.jpg",
      "is_default": true,
      "is_active": true,
      "created_at": "2024-12-09T10:30:00Z",
      "updated_at": "2024-12-09T10:30:00Z"
    },
    {
      "id": 2,
      "symbol": "SAR",
      "rate": 10.0,
      "flag_image": "https://teamhirfa.com/static/uploads/currencies/flag_2_def456.jpg",
      "is_default": false,
      "is_active": true,
      "created_at": "2024-12-09T10:35:00Z",
      "updated_at": "2024-12-09T10:35:00Z"
    }
  ]
}
ℹ Note
If events_enabled is false, Events API endpoints return empty arrays or 404 errors. If blogs_enabled is false, Blogs API endpoints return empty arrays or 404 errors. If courses_enabled is false, Courses API endpoints return empty arrays or 403 errors. If top_sellers_enabled is false, Top Sellers feature should be hidden in the app. If shops_enabled is false, Shops feature should be hidden in the app. If chat_enabled is false, Chat API endpoints will be unavailable. If support_enabled is false, Support System API endpoints will be unavailable. If master_categories_enabled is false, Master Categories feature should be hidden in the app and master categories API endpoints may return empty arrays. If maintenance_mode is true, the platform is in maintenance mode and users should see a maintenance message. Always check these settings first before displaying features in your app. Currencies array contains all active currencies with their exchange rates against BHD. The rate represents how many units of the currency equal 1 BHD (e.g., rate: 10 means 10 SAR = 1 BHD). The default currency (is_default: true) is BHD. Delivery settings: delivery_internal_fallback_cost and delivery_internal_profit apply to Bahrain deliveries. delivery_external_fallback_cost and delivery_external_profit apply to international shipping. The system automatically adds profit to calculated costs (e.g., if API calculates 1 BHD and profit is 0.2 BHD, users see 1.2 BHD). If API fails, fallback cost is used instead.
🖌

Banners

Home page banners management endpoints

GET /api/v1/banners PUBLIC
Get all home page banners. Returns banners ordered by display order (ascending). By default, only active banners within their scheduled time range are returned. Use this endpoint to display banners on the home page.
🔍 Query Parameters
  • is_active (optional, boolean) - Filter by active status. If not provided, returns only active banners.
📀 Response Example
[
  {
    "id": 1,
    "title": "Summer Sale 2024",
    "image": "https://teamhirfa.com/static/uploads/banners/banner_1_a1b2c3d4.png",
    "link": "https://teamhirfa.com/sale",
    "targetType": "product",
    "targetId": "987",
    "url": null,
    "order": 0,
    "isActive": true,
    "startAt": null,
    "endAt": null,
    "createdAt": "2024-12-08T10:30:00Z",
    "updatedAt": "2024-12-08T10:30:00Z"
  },
  {
    "id": 2,
    "title": "Visit Our Store",
    "image": "https://teamhirfa.com/static/uploads/banners/banner_2_e5f6g7h8.png",
    "link": null,
    "targetType": "store",
    "targetId": "store_a1b2c3d4e5f6g7h8",
    "url": null,
    "order": 1,
    "isActive": true,
    "startAt": null,
    "endAt": null,
    "createdAt": "2024-12-08T11:00:00Z",
    "updatedAt": "2024-12-08T11:00:00Z"
  },
  {
    "id": 3,
    "title": "Special Promotion",
    "image": "https://teamhirfa.com/static/uploads/banners/banner_3_i9j0k1l2.png",
    "link": null,
    "targetType": "url",
    "targetId": "https://example.com/promotion",
    "url": "https://example.com/promotion",
    "order": 2,
    "isActive": true,
    "startAt": "2024-12-10T00:00:00Z",
    "endAt": "2024-12-31T23:59:59Z",
    "createdAt": "2024-12-08T12:00:00Z",
    "updatedAt": "2024-12-08T12:00:00Z"
  },
  {
    "id": 4,
    "title": "Upcoming Event",
    "image": "https://teamhirfa.com/static/uploads/banners/banner_4_m3n4o5p6.png",
    "link": null,
    "targetType": "event",
    "targetId": "42",
    "url": null,
    "order": 3,
    "isActive": true,
    "startAt": null,
    "endAt": null,
    "createdAt": "2024-12-08T13:00:00Z",
    "updatedAt": "2024-12-08T13:00:00Z"
  }
]
🎯

Sale Page Banners

Sale/Offers page promotional banners with deep linking

GET /api/v1/sale-banners PUBLIC
Get all active sale page banners. Returns banners specifically for the Sale/Offers screen, ordered by display order (ascending). Only active banners within their scheduled time range are returned. These banners are managed separately from home page banners.
📀 Response Example
[
  {
    "id": 1,
    "title": "Summer Sale - Up to 60% Off!",
    "image": "https://teamhirfa.com/static/uploads/sale_banners/sale_banner_1_abc123.jpg",
    "target_type": "category",
    "target_id": "5",
    "url": null,
    "order": 0
  },
  {
    "id": 2,
    "title": "Flash Deal",
    "image": "https://teamhirfa.com/static/uploads/sale_banners/sale_banner_2_def456.jpg",
    "target_type": "url",
    "target_id": "https://example.com/flash-deal",
    "url": "https://example.com/flash-deal",
    "order": 1
  },
  {
    "id": 3,
    "title": "Top Seller Products",
    "image": "https://teamhirfa.com/static/uploads/sale_banners/sale_banner_3_ghi789.jpg",
    "target_type": "product",
    "target_id": "123",
    "url": null,
    "order": 2
  }
]
🔔

Popups

Popup images with deep linking support for products, stores, events, and external URLs

GET /api/v1/popups PUBLIC
Get all active popups. Returns popups ordered by display order (ascending). Only active popups within their scheduled time range are returned by default. Use this endpoint to display popups in the mobile app.
🔍 Query Parameters
  • is_active (optional, boolean) - Filter by active status. If not provided, returns only active popups.
📀 Response Example
[
  {
    "id": 12,
    "image": "https://teamhirfa.com/static/uploads/popups/popup_abc123.jpg",
    "targetType": "product",
    "targetId": "987",
    "url": null,
    "title": "Limited offer",
    "isActive": true,
    "isFullScreen": false,
    "showOnPage": "home",
    "repeatInterval": 24,
    "order": 0,
    "startAt": null,
    "endAt": null,
    "createdAt": "2024-12-09T10:30:00Z",
    "updatedAt": "2024-12-09T10:30:00Z"
  },
  {
    "id": 13,
    "image": "https://teamhirfa.com/static/uploads/popups/popup_def456.jpg",
    "targetType": "url",
    "targetId": "https://example.com/promotion",
    "url": "https://example.com/promotion",
    "title": "Special Promotion",
    "isActive": true,
    "isFullScreen": true,
    "showOnPage": "sales",
    "repeatInterval": 12,
    "order": 1,
    "startAt": "2024-12-10T00:00:00Z",
    "endAt": "2024-12-31T23:59:59Z",
    "createdAt": "2024-12-09T11:00:00Z",
    "updatedAt": "2024-12-09T11:00:00Z"
  }
]
🏪

Shops

Shop owners and stores management endpoints

GET /api/v1/shops PUBLIC
List all shop owners (stores). Filter by master_category_id to get shops that sell products in a specific master category (e.g., shops selling Crochet or Embroidery products). All responses include share_url for easy sharing.
🔍 Query Parameters
  • master_category_id (optional, integer - filter by master category)
  • skip (optional, default: 0) - Number of items to skip for pagination
  • limit (optional, default: 20, max: 100) - Number of items per page. Use pagination: page 1 = skip=0&limit=20, page 2 = skip=20&limit=20, etc.
📀 Response Example
[
  {
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "store_name": "John's Shop",
    "email": "shop@example.com",
    "phone": "+1234567890",
    "avatar": "https://teamhirfa.com/static/uploads/users/avatar_1.png",
    "banner": "https://teamhirfa.com/static/uploads/banners/shop_banner_1.jpg",
    "opening_hours": [
      "Ellipsis"
    ],
    "share_url": "https://teamhirfa.com/shops/store_a1b2c3d4e5f6g7h8"
  },
  {
    "store_id": "store_h9i0j1k2l3m4n5o6",
    "store_name": "Sarah Store",
    "email": "sarah@example.com",
    "phone": "+1987654321",
    "avatar": null,
    "banner": null,
    "opening_hours": [
      "Ellipsis"
    ],
    "share_url": "https://teamhirfa.com/shops/store_h9i0j1k2l3m4n5o6"
  }
]
GET /api/v1/shops/{store_id} PUBLIC
Get shop details by store_id. Returns complete shop information including creation date.
📍 Path Parameters
  • store_id (required, string)
📀 Response Example
{
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "store_name": "John's Shop",
  "email": "shop@example.com",
  "phone": "+1234567890",
  "avatar": "https://teamhirfa.com/static/uploads/users/avatar_1.png",
  "banner": "https://teamhirfa.com/static/uploads/banners/shop_banner_1.jpg",
  "opening_hours": [
    "Ellipsis"
  ],
  "share_url": "https://teamhirfa.com/shops/store_a1b2c3d4e5f6g7h8",
  "created_at": "2024-12-08T10:30:00Z"
}
GET /api/v1/shops/{store_id}/categories PUBLIC
List all categories for a specific shop. Returns categories created by the shop owner.
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
📍 Path Parameters
  • store_id (required, string)
📀 Response Example
[
  {
    "id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "name_en": "Electronics",
    "name_ar": "إلكترونيات",
    "image": "https://teamhirfa.com/static/uploads/categories/category_1.png",
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
]
GET /api/v1/shops/{store_id}/products PUBLIC
List all products for a specific shop. **Supports pagination for infinite scroll** - use skip and limit parameters. Returns products created by the shop owner. Can be filtered by category_id and subcategory_id.
🔍 Query Parameters
  • category_id (optional, integer)
  • subcategory_id (optional, integer)
  • skip (optional, default: 0) - Number of items to skip for pagination
  • limit (optional, default: 20, max: 100) - Number of products per page. Use pagination: page 1 = skip=0&limit=20, page 2 = skip=20&limit=20, etc.
📍 Path Parameters
  • store_id (required, string)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Smartphone Pro",
    "title_ar": "هاتف ذكي ؚرو",
    "description_en": "Latest smartphone with advanced features",
    "description_ar": "أحدث هاتف ذكي ؚميزات متقدمة",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_1_1.png"
    ],
    "weight": 0.5,
    "sku": "SKU-1",
    "is_active": true,
    "variants": [],
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
]
🏷

Master Categories

Master categories - top level category grouping (e.g., Crochet, Knitting, etc.)

GET /api/v1/master-categories PUBLIC
List all active master categories. Returns master categories ordered by order field.
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 1000, max: 1000)
📀 Response Example
[
  {
    "id": 1,
    "name_en": "Crochet",
    "name_ar": "كرو؎يه",
    "icon": "https://teamhirfa.com/static/uploads/master_categories/master_category_1.png",
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
]
GET /api/v1/master-categories/{master_category_id} PUBLIC
Get master category details by ID.
📍 Path Parameters
  • master_category_id (required, integer)
📀 Response Example
{
  "id": 1,
  "name_en": "Crochet",
  "name_ar": "كرو؎يه",
  "icon": "https://teamhirfa.com/static/uploads/master_categories/master_category_1.png",
  "is_active": true,
  "order": 0,
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T10:30:00Z"
}
GET /api/v1/master-categories/{master_category_id}/categories PUBLIC
List categories for a specific master category. Can be filtered by store_id.
🔍 Query Parameters
  • store_id (optional, string - filter by shop)
  • skip (optional, default: 0) - Number of items to skip for pagination
  • limit (optional, default: 20, max: 100) - Number of items per page. Use pagination: page 1 = skip=0&limit=20, page 2 = skip=20&limit=20, etc.
📍 Path Parameters
  • master_category_id (required, integer)
📀 Response Example
[
  {
    "id": 1,
    "master_category_id": 1,
    "store_id": null,
    "name_en": "Electronics",
    "name_ar": "إلكترونيات",
    "image": "https://teamhirfa.com/static/uploads/categories/category_1.png",
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
]
GET /api/v1/master-categories/{master_category_id}/subcategories PUBLIC
List all subcategories for a specific master category. Returns all subcategories from all categories under this master category.
🔍 Query Parameters
  • store_id (optional, string - filter by shop)
  • skip (optional, default: 0) - Number of items to skip for pagination
  • limit (optional, default: 20, max: 100) - Number of items per page. Use pagination: page 1 = skip=0&limit=20, page 2 = skip=20&limit=20, etc.
📍 Path Parameters
  • master_category_id (required, integer)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "name_en": "Mobile Phones",
    "name_ar": "هواتف محمولة",
    "image": "https://teamhirfa.com/static/uploads/subcategories/subcategory_1.png",
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
]
GET /api/v1/master-categories/{master_category_id}/products PUBLIC
List all products for a specific master category. **Supports pagination for infinite scroll** - use skip and limit parameters. Returns all products from all categories under this master category.
🔍 Query Parameters
  • store_id (optional, string - filter by shop)
  • skip (optional, default: 0) - Number of items to skip for pagination
  • limit (optional, default: 20, max: 100) - Number of items per page. Use pagination: page 1 = skip=0&limit=20, page 2 = skip=20&limit=20, etc.
📍 Path Parameters
  • master_category_id (required, integer)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Premium Product",
    "title_ar": "منتج ممتاز",
    "description_en": "High quality product",
    "description_ar": "منتج عالي الجودة",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_1.jpg"
    ],
    "weight": 0.5,
    "sku": "SKU-1",
    "is_active": true,
    "variants": []
  }
]
GET /api/v1/master-categories/{master_category_id}/shops PUBLIC
List all shops that have products in a specific master category. Returns shops that sell products under this master category.
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 1000, max: 1000)
📍 Path Parameters
  • master_category_id (required, integer)
📀 Response Example
[
  {
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "store_name": "Crochet Paradise Shop",
    "email": "shop@example.com",
    "phone": "+1234567890",
    "avatar": "https://teamhirfa.com/static/uploads/users/avatar_1.png",
    "opening_hours": [
      "Ellipsis"
    ],
    "share_url": "https://teamhirfa.com/shop/store_a1b2c3d4e5f6g7h8"
  }
]
GET /api/v1/master-categories/{master_category_id}/products/top-sellers PUBLIC
Get top 10 selling products for a specific master category. Products are ranked by order quantity (more orders = higher rank). Returns random products if no order data exists.
🔍 Query Parameters
  • limit (optional, default: 10, max: 100) - Number of top selling products to return
📍 Path Parameters
  • master_category_id (required, integer)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Premium Cotton Yarn",
    "title_ar": "خيوط قطنية عالية الجودة",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_1_1.jpg"
    ],
    "variants": []
  }
]
📁

Categories

Product categories management endpoints

GET /api/v1/categories PUBLIC
List categories. Filter by master_category_id to get categories for a specific master category (e.g., all Crochet or all Embroidery categories). Can also filter by store_id.
🔍 Query Parameters
  • master_category_id (optional, integer - filter by master category)
  • store_id (optional, string - filter by shop)
  • skip (optional, default: 0)
  • limit (optional, default: 1000, max: 1000)
📀 Response Example
[
  {
    "id": 1,
    "master_category_id": 1,
    "store_id": null,
    "name_en": "Electronics",
    "name_ar": "إلكترونيات",
    "image": "https://teamhirfa.com/static/uploads/categories/category_1.png",
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  },
  {
    "id": 2,
    "master_category_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "name_en": "Fashion",
    "name_ar": "أزياء",
    "image": "https://teamhirfa.com/static/uploads/categories/category_2.png",
    "is_active": true,
    "order": 1,
    "created_at": "2024-12-08T11:00:00Z",
    "updated_at": "2024-12-08T11:00:00Z"
  }
]
GET /api/v1/categories/{category_id} PUBLIC
Get category details by ID. Returns complete category information.
📍 Path Parameters
  • category_id (required, integer)
📀 Response Example
{
  "id": 1,
  "master_category_id": 1,
  "store_id": null,
  "name_en": "Electronics",
  "name_ar": "إلكترونيات",
  "image": "https://teamhirfa.com/static/uploads/categories/category_1.png",
  "is_active": true,
  "order": 0,
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T10:30:00Z"
}
GET /api/v1/categories/{category_id}/subcategories PUBLIC
List subcategories for a specific category. Can be filtered by store_id.
🔍 Query Parameters
  • store_id (optional, string)
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
📍 Path Parameters
  • category_id (required, integer)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "name_en": "Mobile Phones",
    "name_ar": "هواتف محمولة",
    "image": "https://teamhirfa.com/static/uploads/subcategories/subcategory_1.png",
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
]
GET /api/v1/categories/{category_id}/shops PUBLIC
List all shops that have products in a specific category. Returns shops that sell products in this category.
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
📍 Path Parameters
  • category_id (required, integer)
📀 Response Example
[
  {
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "store_name": "Crochet Paradise Shop",
    "email": "shop@example.com",
    "phone": "+1234567890",
    "avatar": "https://teamhirfa.com/static/uploads/users/avatar_1.png",
    "opening_hours": [
      "Ellipsis"
    ],
    "share_url": "https://teamhirfa.com/shop/store_a1b2c3d4e5f6g7h8"
  },
  {
    "store_id": "store_h9i0j1k2l3m4n5o6",
    "store_name": "Yarn World Store",
    "email": "yarn@example.com",
    "phone": "+1987654321",
    "avatar": null,
    "opening_hours": [
      "Ellipsis"
    ],
    "share_url": "https://teamhirfa.com/shop/store_h9i0j1k2l3m4n5o6"
  }
]
GET /api/v1/categories/{category_id}/products/top-sellers PUBLIC
Get top 10 selling products for a specific category. Products are ranked by order quantity (more orders = higher rank). Returns random products if no order data exists.
🔍 Query Parameters
  • limit (optional, default: 10, max: 100) - Number of top selling products to return
📍 Path Parameters
  • category_id (required, integer)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Premium Cotton Yarn",
    "title_ar": "خيوط قطنية عالية الجودة",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_1_1.jpg"
    ],
    "variants": []
  }
]
📂

Subcategories

Product subcategories management endpoints

GET /api/v1/subcategories PUBLIC
List subcategories with optional filters. Filter by master_category_id to get all subcategories for a master category (e.g., all Crochet or all Embroidery subcategories). Can also filter by category_id and/or store_id.
🔍 Query Parameters
  • master_category_id (optional, integer - filter by master category)
  • category_id (optional, integer)
  • store_id (optional, string)
  • skip (optional, default: 0)
  • limit (optional, default: 1000, max: 1000)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "name_en": "Mobile Phones",
    "name_ar": "هواتف محمولة",
    "image": "https://teamhirfa.com/static/uploads/subcategories/subcategory_1.png",
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  },
  {
    "id": 2,
    "category_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "name_en": "Laptops",
    "name_ar": "أجهزة كمؚيوتر محمولة",
    "image": "https://teamhirfa.com/static/uploads/subcategories/subcategory_2.png",
    "is_active": true,
    "order": 1,
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
]
GET /api/v1/subcategories/{subcategory_id}/products/top-sellers PUBLIC
Get top 10 selling products for a specific subcategory. Products are ranked by order quantity (more orders = higher rank). Returns random products if no order data exists.
🔍 Query Parameters
  • limit (optional, default: 10, max: 100) - Number of top selling products to return
📍 Path Parameters
  • subcategory_id (required, integer)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Premium Cotton Yarn",
    "title_ar": "خيوط قطنية عالية الجودة",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_1_1.jpg"
    ],
    "variants": []
  }
]
GET /api/v1/subcategories/{subcategory_id} PUBLIC
Get subcategory details by ID. Returns complete subcategory information.
📍 Path Parameters
  • subcategory_id (required, integer)
📀 Response Example
{
  "id": 1,
  "category_id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "name_en": "Mobile Phones",
  "name_ar": "هواتف محمولة",
  "image": "https://teamhirfa.com/static/uploads/subcategories/subcategory_1.png",
  "is_active": true,
  "order": 0,
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T10:30:00Z"
}
🛍

Products

Product management endpoints with variants support

GET /api/v1/products/sale PUBLIC
Get a list of all products that have an active store discount (Sale). Includes detailed category info and seller info for display. Perfect for a 'Sale' or 'Offers' screen.
🔍 Query Parameters
  • master_category_id (optional, integer - filter by master category)
  • category_id (optional, integer)
  • skip (optional, default: 0)
  • limit (optional, default: 20)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 5,
    "subcategory_id": 12,
    "store_id": "store_a1b2",
    "title_en": "Sale Item",
    "title_ar": "عنصر تخفيضات",
    "price": 50.0,
    "is_discounted": true,
    "discount_percentage": 20.0,
    "images": [
      "image_url.jpg"
    ],
    "category": {
      "id": 5,
      "name_en": "Yarn",
      "name_ar": "خيوط",
      "slug": "yarn"
    },
    "master_category": {
      "id": 1,
      "name_en": "Crochet",
      "name_ar": "كرو؎يه",
      "slug": "crochet"
    },
    "seller": {
      "store_id": "store_a1b2",
      "full_name": "Crafty Store",
      "avatar": "avatar_url.jpg",
      "username": "crafty_store"
    }
  }
]
GET /api/v1/products PUBLIC
List products with optional filters. **Supports pagination for infinite scroll** - use skip and limit parameters. Filter by master_category_id to get all products for a master category (e.g., all Crochet or all Embroidery products). Can also filter by category_id, subcategory_id, and/or store_id.
🔍 Query Parameters
  • master_category_id (optional, integer - filter by master category)
  • category_id (optional, integer)
  • subcategory_id (optional, integer)
  • store_id (optional, string)
  • skip (optional, default: 0) - Number of items to skip for pagination
  • limit (optional, default: 20, max: 100) - Number of products per page. Use pagination: page 1 = skip=0&limit=20, page 2 = skip=20&limit=20, etc.
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Smartphone Pro",
    "title_ar": "هاتف ذكي ؚرو",
    "description_en": "Latest smartphone with advanced features",
    "description_ar": "أحدث هاتف ذكي ؚميزات متقدمة",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_1_1.png",
      "https://teamhirfa.com/static/uploads/products/product_1_2.png"
    ],
    "weight": 0.5,
    "sku": "SKU-1",
    "is_active": true,
    "is_discounted": true,
    "discount_percentage": 15.0,
    "variants": [
      {
        "id": 1,
        "product_id": 1,
        "color_en": "Black",
        "color_ar": "أسود",
        "size_en": "128GB",
        "size_ar": "128 جيجاؚايت",
        "quantity": 50,
        "price": 999.99,
        "is_active": true,
        "created_at": "2024-12-08T10:30:00Z",
        "updated_at": "2024-12-08T10:30:00Z"
      },
      {
        "id": 2,
        "product_id": 1,
        "color_en": "White",
        "color_ar": "أؚيض",
        "size_en": "256GB",
        "size_ar": "256 جيجاؚايت",
        "quantity": 30,
        "price": 1199.99,
        "is_active": true,
        "created_at": "2024-12-08T10:30:00Z",
        "updated_at": "2024-12-08T10:30:00Z"
      }
    ],
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  }
]
GET /api/v1/products/{product_id} PUBLIC
Get product details by ID with all variants. Returns complete product information including all active variants with pricing.
📍 Path Parameters
  • product_id (required, integer)
📀 Response Example
{
  "id": 1,
  "category_id": 1,
  "subcategory_id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "title_en": "Smartphone Pro",
  "title_ar": "هاتف ذكي ؚرو",
  "description_en": "Latest smartphone with advanced features",
  "description_ar": "أحدث هاتف ذكي ؚميزات متقدمة",
  "images": [
    "https://teamhirfa.com/static/uploads/products/product_1_1.png",
    "https://teamhirfa.com/static/uploads/products/product_1_2.png"
  ],
  "weight": 0.5,
  "sku": "SKU-1",
  "is_active": true,
  "is_discounted": true,
  "discount_percentage": 15.0,
  "variants": [
    {
      "id": 1,
      "product_id": 1,
      "color_en": "Black",
      "color_ar": "أسود",
      "size_en": "128GB",
      "size_ar": "128 جيجاؚايت",
      "quantity": 50,
      "price": 999.99,
      "is_active": true,
      "created_at": "2024-12-08T10:30:00Z",
      "updated_at": "2024-12-08T10:30:00Z"
    }
  ],
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T10:30:00Z"
}
POST /api/v1/products PRIVATE
Create a new product. Requires authentication as a shop owner. Supports multiple image uploads and product variants. **SKU is auto-generated by the backend (format: SKU-{product_id}) and cannot be provided by the user.** **Pricing: Two options available - (1) Use variants_json for products with variants (colors, sizes), or (2) Use single_price and single_quantity for simple products without variants. At least one pricing method must be provided.**
🔒 Auth: Bearer Token (Shop Owner Required)
📎 Form Data (multipart/form-data)
  • category_id (required, integer) - Must belong to your store
  • subcategory_id (required, integer) - Must belong to the selected category
  • title_en (required, string)
  • title_ar (required, string)
  • description_en (optional, string)
  • description_ar (optional, string)
  • weight (optional, float) - Weight in kg
  • is_active (optional, boolean, default: true)
  • images (optional, array of files) - Product images (jpg, jpeg, png, gif, svg, webp). Files are automatically compressed and uploaded to AWS S3.
  • variants_json (optional, string) - JSON array of variants. Use this for products with variants (colors, sizes). If provided, it takes precedence over single_price/single_quantity. Example: [{"color_en":"Black","color_ar":"أسود","size_en":"Large","size_ar":"كؚير","quantity":50,"price":99.99,"is_active":true}]
  • single_price (optional, float) - Single price for products without variants. Must be provided with single_quantity if variants_json is not used. Must be greater than 0.
  • single_quantity (optional, integer) - Single quantity for products without variants. Must be provided with single_price if variants_json is not used. Must be 0 or greater.
🎚 Variants Format
[
  {
    "color_en": "Black",
    "color_ar": "أسود",
    "size_en": "Large",
    "size_ar": "كؚير",
    "quantity": 50,
    "price": 99.99,
    "is_active": true
  }
]
📀 Response Example
{
  "id": 1,
  "category_id": 1,
  "subcategory_id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "title_en": "Premium Cotton Yarn",
  "title_ar": "خيوط قطنية عالية الجودة",
  "description_en": "Soft cotton yarn for amigurumi",
  "description_ar": "خيوط قطنية ناعمة للأميغورومي",
  "images": [
    "https://teamhirfa.com/static/uploads/products/product_1_1.jpg"
  ],
  "weight": 0.3,
  "sku": "SKU-1",
  "is_active": true,
  "share_url": "https://teamhirfa.com/product/1",
  "variants": [
    {
      "id": 1,
      "product_id": 1,
      "color_en": "White",
      "color_ar": "أؚيض",
      "size_en": null,
      "size_ar": null,
      "quantity": 50,
      "price": 12.99,
      "is_active": true,
      "created_at": "2024-12-08T10:30:00Z",
      "updated_at": "2024-12-08T10:30:00Z"
    }
  ],
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T10:30:00Z"
}
PUT /api/v1/products/{product_id} PRIVATE
Update an existing product. Only the shop owner who created the product can update it. All fields are optional - only include fields you want to update. **You can remove individual images and add new ones.** **SKU cannot be updated - it is auto-generated and read-only.** **Pricing: Two options available - (1) Use variants_json for products with variants, or (2) Use single_price and single_quantity for simple products. If neither is provided, variants remain unchanged.**
🔒 Auth: Bearer Token (Shop Owner Required)
📍 Path Parameters
  • product_id (required, integer)
📎 Form Data (multipart/form-data)
  • category_id (optional, integer) - Must belong to your store
  • subcategory_id (optional, integer) - Must belong to the selected category
  • title_en (optional, string)
  • title_ar (optional, string)
  • description_en (optional, string)
  • description_ar (optional, string)
  • weight (optional, float) - Weight in kg
  • is_active (optional, boolean)
  • images (optional, array of files) - New product images to add. Files are automatically compressed and uploaded to AWS S3. Existing images are kept unless removed via images_to_remove.
  • images_to_remove (optional, string) - JSON array of image URLs to remove (e.g., ["https://bucket.s3.region.amazonaws.com/products/image1.jpg"]). These images will be deleted from S3.
  • variants_json (optional, string) - JSON array of variants (replaces all existing variants). Use this for products with variants. If provided, it takes precedence over single_price/single_quantity.
  • single_price (optional, float) - Single price for products without variants. Must be provided with single_quantity. Must be greater than 0.
  • single_quantity (optional, integer) - Single quantity for products without variants. Must be provided with single_price. Must be 0 or greater.
📀 Response Example
{
  "id": 1,
  "category_id": 1,
  "subcategory_id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "title_en": "Premium Cotton Yarn - Updated",
  "title_ar": "خيوط قطنية عالية الجودة - محدث",
  "description_en": "Soft cotton yarn for amigurumi - Updated description",
  "description_ar": "خيوط قطنية ناعمة للأميغورومي - وصف محدث",
  "images": [
    "https://teamhirfa.com/static/uploads/products/product_1_1.jpg",
    "https://teamhirfa.com/static/uploads/products/product_1_2.jpg"
  ],
  "weight": 0.35,
  "sku": "SKU-1",
  "is_active": true,
  "share_url": "https://teamhirfa.com/product/1",
  "variants": [],
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T11:45:00Z"
}
GET /api/v1/products/top-sellers PUBLIC
Get top selling products based on order quantities. Returns the top 10 products (by default) sorted by total quantity sold from most selling to least. Uses order items as indicator of sales. If no orders exist, returns random products.
🔍 Query Parameters
  • limit (optional, default: 10, max: 100) - Number of top selling products to return
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Premium Cotton Yarn",
    "title_ar": "خيوط قطنية عالية الجودة",
    "description_en": "Soft cotton yarn for amigurumi",
    "description_ar": "خيوط قطنية ناعمة للأميغورومي",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_1_1.jpg"
    ],
    "weight": 0.3,
    "sku": "SKU-1",
    "is_active": true,
    "variants": [
      {
        "id": 1,
        "product_id": 1,
        "color_en": "White",
        "color_ar": "أؚيض",
        "size_en": null,
        "size_ar": null,
        "quantity": 50,
        "price": 12.99,
        "is_active": true,
        "created_at": "2024-12-08T10:30:00Z",
        "updated_at": "2024-12-08T10:30:00Z"
      }
    ],
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  },
  {
    "id": 2,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Acrylic Yarn Bundle",
    "title_ar": "حزمة خيوط الأكريليك",
    "description_en": "Colorful acrylic yarn bundle",
    "description_ar": "حزمة خيوط أكريليك ملونة",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_2_1.jpg"
    ],
    "weight": 0.5,
    "sku": "SKU-2",
    "is_active": true,
    "variants": [],
    "created_at": "2024-12-08T11:00:00Z",
    "updated_at": "2024-12-08T11:00:00Z"
  }
]
📰

Blogs

Blog posts and articles management endpoints. Note: Description fields accept and return HTML formatted content from a rich text editor. When displaying in Flutter, use packages like 'flutter_html' or 'html_widget' to render the HTML content properly. Blogs feature can be enabled/disabled by admin. When disabled, API endpoints return empty results or 404 errors. Check /api/v1/settings endpoint to see if blogs feature is enabled.

GET /api/v1/blogs PUBLIC
List all active blogs. Filter by master_category_id to get blogs for a specific master category. Returns blogs ordered by creation date (newest first). Description fields contain HTML formatted content from the rich text editor. If blogs feature is disabled by admin, returns an empty list [].
🔍 Query Parameters
  • master_category_id (optional, integer - filter by master category)
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
  • is_active (optional, default: true, boolean)
📀 Response Example
[
  {
    "id": 1,
    "title_en": "Introduction to Crochet",
    "title_ar": "مقدمة في الكرو؎يه",
    "description_en": "

Learn the basics of crochet and start your journey...

  • Step 1: Choose your yarn
  • Step 2: Select hooks
", "description_ar": "

تعلم أساسيات الكرو؎يه واؚدأ رحلتك...

  • الخطوة 1: اختر خيوطك
  • الخطوة 2: اختر الخطافات
", "image": "https://teamhirfa.com/static/uploads/blogs/blog_1_abc123.jpg", "is_active": true, "created_at": "2024-12-08T10:30:00Z", "updated_at": "2024-12-08T10:30:00Z" }, { "id": 2, "title_en": "Best Yarn for Beginners", "title_ar": "أفضل الخيوط للمؚتد؊ين", "description_en": "

Discover the best yarn types for crochet beginners...

Quality yarn makes all the difference!
", "description_ar": "

اكت؎ف أفضل أنواع الخيوط لمؚتد؊ي الكرو؎يه...

الجودة في الخيوط تحدث الفرق!
", "image": "https://teamhirfa.com/static/uploads/blogs/blog_2_def456.jpg", "is_active": true, "created_at": "2024-12-07T14:20:00Z", "updated_at": "2024-12-07T14:20:00Z" } ]
GET /api/v1/blogs/{blog_id} PUBLIC
Get blog details by ID. Returns complete blog information including title, description (HTML formatted), and image in both English and Arabic. Description fields contain HTML content from the rich text editor and should be rendered in your Flutter app using HtmlWidget or similar package. If blogs feature is disabled by admin, returns 404 error with message 'Blogs feature is disabled'.
📍 Path Parameters
  • blog_id (required, integer)
📀 Response Example
{
  "id": 1,
  "title_en": "Introduction to Crochet",
  "title_ar": "مقدمة في الكرو؎يه",
  "description_en": "

Getting Started

Learn the basics of crochet and start your journey into the wonderful world of yarn crafts. This comprehensive guide covers everything you need to know to get started.

What You'll Need

  • Yarn: Choose a medium weight yarn
  • Hook: Size 5.0mm recommended
  • Scissors: Sharp pair for cutting

For more information, visit our website.

", "description_ar": "

الؚدء

تعلم أساسيات الكرو؎يه واؚدأ رحلتك في عالم الحرف اليدوية الرا؊ع. يغطي هذا الدليل ال؎امل كل ما تحتاج لمعرفته للؚدء.

ما ستحتاجه

  • الخيوط: اختر خيوط متوسطة الوزن
  • الخطاف: مقاس 5.0 ملم موصى ØšÙ‡
  • مقص: زوج حاد للقطع

لمزيد من المعلومات، زر موقعنا.

", "image": "https://teamhirfa.com/static/uploads/blogs/blog_1_abc123.jpg", "is_active": true, "created_at": "2024-12-08T10:30:00Z", "updated_at": "2024-12-08T10:30:00Z" }
📄

Custom Pages

Custom content pages management endpoints for creating and managing custom pages

GET /api/v1/pages PUBLIC
List all active custom pages. Returns pages ordered by order field and creation date.
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 100)
  • is_active (optional, default: true, boolean)
📀 Response Example
[
  {
    "id": 1,
    "slug": "about-us",
    "title_en": "About Us",
    "title_ar": "من نحن",
    "description_en": "Learn more about our company and mission...",
    "description_ar": "تعرف على المزيد حول ؎ركتنا ومهمتنا...",
    "image": "https://teamhirfa.com/static/uploads/custom_pages/page_1_abc123.jpg",
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-09T10:30:00Z",
    "updated_at": "2024-12-09T10:30:00Z"
  },
  {
    "id": 2,
    "slug": "terms-and-conditions",
    "title_en": "Terms and Conditions",
    "title_ar": "ال؎روط والأحكام",
    "description_en": "Our terms and conditions of service...",
    "description_ar": "؎روط وأحكام خدمتنا...",
    "image": null,
    "is_active": true,
    "order": 1,
    "created_at": "2024-12-09T11:00:00Z",
    "updated_at": "2024-12-09T11:00:00Z"
  }
]
GET /api/v1/pages/{slug} PUBLIC
Get custom page by slug (URL-friendly identifier). Returns complete page information including title, description, and image in both English and Arabic.
📍 Path Parameters
  • slug (required, string) - URL-friendly page identifier
📀 Response Example
{
  "id": 1,
  "slug": "about-us",
  "title_en": "About Us",
  "title_ar": "من نحن",
  "description_en": "Learn more about our company and mission. We are dedicated to providing quality products and services.",
  "description_ar": "تعرف على المزيد حول ؎ركتنا ومهمتنا. نحن ملتزمون ؚتقديم منتجات وخدمات عالية الجودة.",
  "image": "https://teamhirfa.com/static/uploads/custom_pages/page_1_abc123.jpg",
  "is_active": true,
  "order": 0,
  "created_at": "2024-12-09T10:30:00Z",
  "updated_at": "2024-12-09T10:30:00Z"
}
GET /api/v1/pages/id/{page_id} PUBLIC
Get custom page by ID. Returns complete page information including title, description, and image in both English and Arabic.
📍 Path Parameters
  • page_id (required, integer)
📀 Response Example
{
  "id": 1,
  "slug": "about-us",
  "title_en": "About Us",
  "title_ar": "من نحن",
  "description_en": "Learn more about our company and mission. We are dedicated to providing quality products and services.",
  "description_ar": "تعرف على المزيد حول ؎ركتنا ومهمتنا. نحن ملتزمون ؚتقديم منتجات وخدمات عالية الجودة.",
  "image": "https://teamhirfa.com/static/uploads/custom_pages/page_1_abc123.jpg",
  "is_active": true,
  "order": 0,
  "created_at": "2024-12-09T10:30:00Z",
  "updated_at": "2024-12-09T10:30:00Z"
}
🎉

Events

Events management endpoints for listing, viewing, and registering for events. Users can register for events with a simple click - no additional information needed. Admins can view all registrations for each event. Note: Events feature can be enabled/disabled by admin. When disabled, API endpoints return empty results or 404 errors. Check /api/v1/settings endpoint to see if events feature is enabled.

GET /api/v1/events PUBLIC
List all active events. Filter by master_category_id to get events for a specific master category. Returns events ordered by date_time (upcoming first), then by creation date. Description fields contain HTML formatted content from the rich text editor. Each event includes a registration_count field showing how many users have registered. If events feature is disabled by admin, returns an empty list [].
🔍 Query Parameters
  • master_category_id (optional, integer - filter by master category)
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
  • is_active (optional, default: true, boolean)
📀 Response Example
[
  {
    "id": 1,
    "title_en": "Summer Festival 2024",
    "title_ar": "مهرجان الصيف 2024",
    "description_en": "

Summer Festival

Join us for the biggest summer festival of the year...

  • Live music performances
  • Food vendors
  • Art exhibitions
", "description_ar": "

مهرجان الصيف

انضم إلينا في أكؚر مهرجان صيفي في العام...

  • عروض موسيقية حية
  • ؚا؊عي الطعام
  • معارض فنية
", "image": "https://teamhirfa.com/static/uploads/events/event_1_abc123.jpg", "google_map_url": "https://maps.google.com/?q=25.276987,55.296249", "date_time": "2024-12-15T18:00:00Z", "is_active": true, "created_at": "2024-12-09T10:30:00Z", "updated_at": "2024-12-09T10:30:00Z", "registration_count": 15 }, { "id": 2, "title_en": "Art Workshop", "title_ar": "ور؎ة عمل فنية", "description_en": "

Learn painting techniques from professional artists...

", "description_ar": "

تعلم تقنيات الرسم من فنانين محترفين...

", "image": "https://teamhirfa.com/static/uploads/events/event_2_def456.jpg", "google_map_url": "https://maps.google.com/?q=24.7136,46.6753", "date_time": "2024-12-20T14:00:00Z", "is_active": true, "created_at": "2024-12-08T14:20:00Z", "updated_at": "2024-12-08T14:20:00Z" } ]
GET /api/v1/events/{event_id} PUBLIC
Get event details by ID. Returns complete event information including title, description (HTML formatted), image, Google Map URL, and date/time in both English and Arabic. Description fields contain HTML content from the rich text editor and should be rendered in your Flutter app using HtmlWidget or similar package. If events feature is disabled by admin, returns 404 error with message 'Events feature is disabled'.
📍 Path Parameters
  • event_id (required, integer)
📀 Response Example
{
  "id": 1,
  "title_en": "Summer Festival 2024",
  "title_ar": "مهرجان الصيف 2024",
  "description_en": "

Summer Festival

Join us for the biggest summer festival of the year. This event features live music, delicious food, and amazing art exhibitions.

Event Details

  • Date: December 15, 2024
  • Time: 6:00 PM
  • Location: Downtown Park

For more information, visit our website.

", "description_ar": "

مهرجان الصيف

انضم إلينا في أكؚر مهرجان صيفي في العام. يتضمن هذا الحدث موسيقى حية وطعام لذيذ ومعارض فنية را؊عة.

تفاصيل الحدث

  • التاريخ: 15 ديسمؚر 2024
  • الوقت: 6:00 مساءً
  • المكان: حديقة وسط المدينة

لمزيد من المعلومات، زر موقعنا.

", "image": "https://teamhirfa.com/static/uploads/events/event_1_abc123.jpg", "google_map_url": "https://maps.google.com/?q=25.276987,55.296249", "date_time": "2024-12-15T18:00:00Z", "is_active": true, "created_at": "2024-12-09T10:30:00Z", "updated_at": "2024-12-09T10:30:00Z", "registration_count": 15 }
POST /api/v1/events/{event_id}/register PRIVATE
Register the current authenticated user for an event. Users can simply click to register - no additional information needed. Returns 400 if user is already registered for the event. Returns 404 if event is not found or events feature is disabled.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • event_id (required, integer)
📀 Response Example
{
  "success": true,
  "message": "Successfully registered for event",
  "registration_id": 1,
  "event_id": 1,
  "user_id": 5
}
DELETE /api/v1/events/{event_id}/register PRIVATE
Unregister the current authenticated user from an event. Returns 404 if registration not found.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • event_id (required, integer)
📀 Response Example
{
  "success": true,
  "message": "Successfully unregistered from event"
}
GET /api/v1/events/{event_id}/registrations PRIVATE
Get list of users registered for an event. For regular users, returns only the total count and whether they are registered. For admins/shop owners, returns the full list with user details (name, email, registration date).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • event_id (required, integer)
📀 Response Example
{'example_regular_user': {'event_id': 1, 'total_registrations': 15, 'is_registered': True}, 'example_admin': {'event_id': 1, 'total_registrations': 15, 'registrations': [{'registration_id': 1, 'user_id': 5, 'user_name': 'John Doe', 'user_email': 'john@example.com', 'registered_at': '2024-12-09T10:30:00Z'}, {'registration_id': 2, 'user_id': 7, 'user_name': 'Jane Smith', 'user_email': 'jane@example.com', 'registered_at': '2024-12-09T11:00:00Z'}]}}
GET /api/v1/events/my-events PRIVATE
Get all events the current authenticated user has registered for. Returns a list of events with registration details including registration ID and registration date.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
[
  {
    "id": 1,
    "title_en": "Summer Festival 2024",
    "title_ar": "مهرجان الصيف 2024",
    "description_en": "

Summer Festival

Join us for the biggest summer festival of the year...

", "description_ar": "

مهرجان الصيف

انضم إلينا في أكؚر مهرجان صيفي في العام...

", "image": "https://teamhirfa.com/static/uploads/events/event_1_abc123.jpg", "google_map_url": "https://maps.google.com/?q=25.276987,55.296249", "date_time": "2024-12-15T18:00:00Z", "is_active": true, "created_at": "2024-12-09T10:30:00Z", "updated_at": "2024-12-09T10:30:00Z", "registration_count": 15, "my_registration": { "id": 1, "registered_at": "2024-12-10T09:00:00Z" } }, { "id": 2, "title_en": "Art Workshop", "title_ar": "ور؎ة عمل فنية", "description_en": "

Learn painting techniques from professional artists...

", "description_ar": "

تعلم تقنيات الرسم من الفنانين المحترفين...

", "image": "https://teamhirfa.com/static/uploads/events/event_2_def456.jpg", "google_map_url": "https://maps.google.com/?q=25.276987,55.296249", "date_time": "2024-12-20T14:00:00Z", "is_active": true, "created_at": "2024-12-09T11:00:00Z", "updated_at": "2024-12-09T11:00:00Z", "registration_count": 8, "my_registration": { "id": 5, "registered_at": "2024-12-11T10:30:00Z" } } ]
ℹ Note
Returns only events where the user has an active registration. Each event includes my_registration object with registration ID and registration timestamp. Events are ordered by registration date (most recent first).
📍

Addresses

User address management endpoints for saving and managing shipping addresses. **For Bahrain addresses:** Users MUST select block and city from dropdowns (100% match with Oreem API). **For international addresses:** Users MUST select country from supported countries list, then enter address details (block, road, building, flat, etc.).

GET /api/v1/addresses/options PRIVATE
Get address options (blocks, cities) for address creation/editing. **Use this endpoint BEFORE showing address form to populate dropdowns.** If country is 'Bahrain', returns blocks and cities from database (synced from Oreem API - 100% valid). For other countries, returns empty arrays (user can type freely).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • country (optional, string) - Country name (e.g., 'Bahrain')
📀 Response Example
{
  "country": "Bahrain",
  "blocks": [
    {
      "id": 1,
      "block_number": "101",
      "address": "101 Sharq Al Hidd ؎رق الحد",
      "city_name": "Sharq Al Hidd",
      "latitude": 26.25043,
      "longitude": 50.65685
    }
  ],
  "cities": [
    {
      "id": 1,
      "name": "Manama",
      "block_count": 9
    },
    {
      "id": 2,
      "name": "Isa Town",
      "block_count": 17
    }
  ]
}
ℹ Note
**For Bahrain:** Always use blocks and cities from this endpoint for dropdown selection. This ensures 100% compatibility with Oreem delivery API. **For other countries:** blocks and cities arrays will be empty - user can enter address text freely.
GET /api/v1/addresses/supported-countries PRIVATE
Get list of all supported shipping countries (outside Bahrain). Users outside Bahrain must select a country from this list when creating addresses. This ensures data validation and compatibility with Oreem shipping API. **Use this endpoint to populate country dropdown for international addresses.**
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "status": "success",
  "data": [
    {
      "id": 1,
      "country_code": "KW",
      "country_name": "Kuwait",
      "postal_code": "00000"
    },
    {
      "id": 2,
      "country_code": "SA",
      "country_name": "Saudi Arabia",
      "postal_code": "00000"
    }
  ]
}
ℹ Note
Only active countries are returned. Countries are sorted alphabetically by name. Use country_code for address creation.
GET /api/v1/addresses/supported-countries/{country_code} PRIVATE
Get details of a specific supported shipping country by country code. Returns country information including default postal code. **Use this when user selects a country to auto-fill postal code.**
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • country_code (required, string) - ISO-2 country code (e.g., 'KW', 'SA', 'AE')
📀 Response Example
{
  "status": "success",
  "data": {
    "id": 1,
    "country_code": "KW",
    "country_name": "Kuwait",
    "postal_code": "00000"
  }
}
ℹ Note
Returns 404 if country code is not found or not active. Postal code is auto-filled from country data.
POST /api/v1/addresses PRIVATE
Create a new address for the current user. **FOR BAHRAIN ADDRESSES:** User MUST select block and city from `/addresses/options` endpoint dropdowns to ensure 100% match with Oreem API. Block and city should be selected (not typed). Road and building can be entered manually. **FOR INTERNATIONAL ADDRESSES:** Country MUST be selected from `/addresses/supported-countries` endpoint. Postal code will be auto-filled from supported country data. User can then enter additional address details (block, road, building, flat, etc.).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • label (optional, string) - Address label like 'Home', 'Work', etc.
  • full_name (optional, string)
  • phone (optional, string)
  • address_line1 (required, string) - **Enter normal address text here** (e.g., 'Hamad Town, Building 2001, Road 1500'). The system will automatically extract block, road, building, and flat from this text.
  • address_line2 (optional, string)
  • city (required, string)
  • state (optional, string)
  • country (required, string) - **For international addresses:** Must be a country code from `/addresses/supported-countries` list (e.g., 'KW', 'SA'). **For Bahrain:** Use 'BH' or 'Bahrain'.
  • postal_code (optional, string) - **For international addresses:** Auto-filled from supported country data if not provided. **For Bahrain:** Optional.
  • block (required for Bahrain, optional for others, string) - **For Bahrain:** MUST select from `/addresses/options` dropdown (e.g., '101', '1001'). **For other countries:** Optional.
  • road (required for Bahrain, optional for others, string) - Road number for Bahrain addresses (e.g., '1400'). User must enter manually.
  • building (required for Bahrain, optional for others, string) - Building number/name for Bahrain addresses (e.g., '3547', 'A2', '10A'). User must enter manually.
  • flat (optional, string) - Flat/Apartment number (e.g., '2', 'A1')
  • latitude (optional, float) - GPS latitude for delivery
  • longitude (optional, float) - GPS longitude for delivery
  • is_default (optional, boolean, default: false) - Set as default address
📀 Response Example
{
  "id": 1,
  "user_id": 1,
  "label": "Home",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "address_line1": "Hamad Town, Building 2001, Road 1500",
  "address_line2": "Flat 4B",
  "city": "Hamad Town",
  "state": null,
  "country": "Bahrain",
  "postal_code": null,
  "block": "1002",
  "road": "1500",
  "building": "2001",
  "flat": "4B",
  "latitude": 26.143,
  "longitude": 50.4976,
  "is_default": true,
  "is_active": true,
  "created_at": "2024-12-09T10:30:00Z",
  "updated_at": "2024-12-09T10:30:00Z"
}
ℹ Note
**FOR BAHRAIN ADDRESSES:** User MUST select block and city from `/addresses/options?country=Bahrain` dropdown to ensure 100% match with Oreem API. Road and building must be entered manually. **FOR INTERNATIONAL ADDRESSES:** Country MUST be selected from `/addresses/supported-countries` endpoint. Postal code is auto-filled from country data. User can then enter block, road, building, flat, etc. This ensures 100% compatibility with Oreem shipping API and eliminates errors. **Error:** Returns 400 if country is not in supported list.
GET /api/v1/addresses PRIVATE
List all addresses for the current authenticated user. Returns addresses ordered by default first, then by creation date.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
[
  {
    "id": 1,
    "user_id": 1,
    "label": "Home",
    "full_name": "John Doe",
    "phone": "+1234567890",
    "address_line1": "123 Main Street",
    "address_line2": "Apt 4B",
    "city": "Riyadh",
    "state": "Riyadh Province",
    "country": "Saudi Arabia",
    "postal_code": "12345",
    "block": "1001",
    "road": "1400",
    "building": "865",
    "flat": "4B",
    "latitude": 26.142985,
    "longitude": 50.497627,
    "is_default": true,
    "is_active": true,
    "created_at": "2024-12-09T10:30:00Z",
    "updated_at": "2024-12-09T10:30:00Z"
  },
  {
    "id": 2,
    "user_id": 1,
    "label": "Work",
    "full_name": "John Doe",
    "phone": "+1234567890",
    "address_line1": "456 Business Ave",
    "address_line2": null,
    "city": "Jeddah",
    "state": "Makkah Province",
    "country": "Saudi Arabia",
    "postal_code": "23456",
    "block": null,
    "road": null,
    "building": null,
    "flat": null,
    "latitude": null,
    "longitude": null,
    "is_default": false,
    "is_active": true,
    "created_at": "2024-12-09T11:00:00Z",
    "updated_at": "2024-12-09T11:00:00Z"
  }
]
GET /api/v1/addresses/{address_id} PRIVATE
Get address details by ID. Users can only view their own addresses.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • address_id (required, integer)
📀 Response Example
{
  "id": 1,
  "user_id": 1,
  "label": "Home",
  "full_name": "John Doe",
  "phone": "+1234567890",
  "address_line1": "Hamad Town, Building 2001, Road 1500",
  "address_line2": "Flat 4B",
  "city": "Hamad Town",
  "state": null,
  "country": "Bahrain",
  "postal_code": null,
  "block": "1002",
  "road": "1500",
  "building": "2001",
  "flat": "4B",
  "latitude": 26.143,
  "longitude": 50.4976,
  "is_default": true,
  "is_active": true,
  "created_at": "2024-12-09T10:30:00Z",
  "updated_at": "2024-12-09T10:30:00Z"
}
ℹ Note
Oreem fields are automatically parsed from address_line1 and stored in the database.
PUT /api/v1/addresses/{address_id} PRIVATE
Update an address. Users can only update their own addresses. All fields are optional - only include fields you want to update. Setting is_default to true will automatically unset other default addresses. **IMPORTANT:** If you update `address_line1`, the system will automatically re-parse and update Oreem fields (block, road, building, flat) from the new address text.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • label (optional, string)
  • full_name (optional, string)
  • phone (optional, string)
  • address_line1 (optional, string) - **Enter normal address text** (e.g., 'Isa Town, Building 3001, Road 1600'). If updated, Oreem fields will be auto-re-parsed.
  • address_line2 (optional, string)
  • city (optional, string)
  • state (optional, string)
  • country (optional, string)
  • postal_code (optional, string)
  • block (optional, string) - Block number. **Auto-re-parsed from address_line1 if address_line1 is updated.**
  • road (optional, string) - Road number. **Auto-re-parsed from address_line1 if address_line1 is updated.**
  • building (optional, string) - Building number. **Auto-re-parsed from address_line1 if address_line1 is updated.**
  • flat (optional, string) - Flat/Apartment number. **Auto-re-parsed from address_line1 if address_line1 is updated.**
  • latitude (optional, float) - GPS latitude for delivery
  • longitude (optional, float) - GPS longitude for delivery
  • is_default (optional, boolean)
  • is_active (optional, boolean)
📍 Path Parameters
  • address_id (required, integer)
📀 Response Example
{
  "id": 1,
  "user_id": 1,
  "label": "Home - Updated",
  "full_name": "John Doe",
  "phone": "+97312345678",
  "address_line1": "Isa Town, Building 3001, Road 1600",
  "address_line2": "Flat 4B",
  "city": "Isa Town",
  "state": null,
  "country": "Bahrain",
  "postal_code": null,
  "block": "1003",
  "road": "1600",
  "building": "3001",
  "flat": "4B",
  "latitude": 26.1738,
  "longitude": 50.5477,
  "is_default": true,
  "is_active": true,
  "created_at": "2024-12-09T10:30:00Z",
  "updated_at": "2024-12-09T12:00:00Z"
}
ℹ Note
When address_line1 is updated, Oreem fields (block, road, building, flat) are automatically re-parsed and updated.
DELETE /api/v1/addresses/{address_id} PRIVATE
Soft delete an address. Users can only delete their own addresses. The address will be marked as inactive but not permanently deleted.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • address_id (required, integer)
📀 Response Example
"204 No Content"
🛒

Shopping Cart

Shopping cart management endpoints for adding items, calculating totals, and delivery costs. The cart is stored server-side and automatically calculates subtotal, delivery fee, and total price.

POST /api/v1/cart/items PRIVATE
Add item to cart or update quantity if item already exists. Each user has one cart. If the same product+variant combination is added again, the quantity will be increased. Stock is validated before adding.
🔒 Auth: Bearer Token (User Required)
📊 Body Parameters
  • product_id (required, integer) - Product ID to add
  • variant_id (optional, integer) - Product variant ID (for colors, sizes, etc.)
  • quantity (required, integer, min: 1) - Quantity to add
📀 Response Example
{
  "message": "Item added to cart",
  "cart_item_id": 1
}
GET /api/v1/cart PRIVATE
Get cart with all items, subtotal, and cost calculation. **Terminology**: Inside Bahrain = **Shipping** (local delivery), Outside Bahrain = **Delivery** (international shipping). **Automatic Routing**: System automatically detects recipient country - calculates **shipping cost** for Bahrain addresses (local), **delivery cost** for international addresses. Optionally calculate cost by providing `address_id` (saved address) or `delivery_address` query parameter. **IMPORTANT: All products in cart must be from the same store.** If cart contains products from multiple stores, cost calculation will not be available. **🚚 CRITICAL: Using delivery_fee for Order Creation** When you get the cart response, it includes `delivery_fee` (this field name is used for both shipping inside Bahrain and delivery outside Bahrain). **You MUST use this `delivery_fee` value as `shipping_cost` when creating an order via `POST /api/v1/orders`.** **Example Flow:** ``` 1. GET /api/v1/cart?address_id=123 Response: { "delivery_fee": 1.5, "subtotal": 4.0, "total": 5.5, ... } 2. POST /api/v1/orders Body: { "items": [...], "shipping_cost": 1.5, ... } // Use delivery_fee (1.5) as shipping_cost ```
🔒 Auth: Bearer Token (User Required)
🔍 Query Parameters
  • address_id (optional, integer) - Use saved address ID (preferred - includes country info for automatic routing)
  • delivery_address (optional, string) - JSON string of delivery address for calculating delivery/shipping cost
  • Example: {"building": "3547", "flat": "111", "road": "1400", "block": "1001"}
  • OR: {"address_string": "Address", "latitude": 26.142985, "longitude": 50.497627}
  • instant (optional, integer, default: 1) - 0 for Same day, 1 for Instant delivery (Bahrain only)
  • vehicle (optional, string, default: 'car') - 'car', 'bus', or '6wheel' (Bahrain only)
  • **Note**: For international addresses, instant and vehicle parameters are ignored (shipping API handles this automatically)
📀 Response Example
{
  "cart_id": 1,
  "items": [
    {
      "id": 1,
      "product_id": 72,
      "variant_id": 95,
      "quantity": 2,
      "product_title_en": "Product Name",
      "product_title_ar": "اسم المنتج",
      "product_image": "https://teamhirfa.com/static/uploads/products/image.jpg",
      "variant_color_en": "Red",
      "variant_color_ar": "أحمر",
      "variant_size_en": "Large",
      "variant_size_ar": "كؚير",
      "unit_price": 5.59,
      "original_price": 6.99,
      "is_discounted": true,
      "discount_percentage": 20.0,
      "total_price": 11.18
    }
  ],
  "subtotal": 11.18,
  "vat_amount": 0.0,
  "vat_percentage": 0.0,
  "delivery_fee": 1.3,
  "total": 12.48,
  "currency": "BHD",
  "store_id": "STORE_123",
  "delivery_available": true,
  "delivery_error": null,
  "shipping_companies": [
    {
      "code": "smsa_oreem",
      "name": "SMSA Express",
      "price": 8.5,
      "currency": "BHD",
      "estimated_days": "5-7",
      "is_cod_available": true,
      "description": "Standard international shipping"
    },
    {
      "code": "ups_oreem",
      "name": "UPS Express",
      "price": 15.0,
      "currency": "BHD",
      "estimated_days": "2-3",
      "is_cod_available": false,
      "description": "Express international shipping"
    }
  ]
}
ℹ Note
**shipping_companies** array is only returned for international addresses (non-Bahrain). For Bahrain addresses, only delivery_fee is returned. Each shipping company includes: code, name, price, currency, estimated_days (estimated delivery time in days), is_cod_available (cash on delivery support), and description. Companies are sorted by price (cheapest first).
PUT /api/v1/cart/items/{item_id} PRIVATE
Update cart item quantity. Stock is validated before updating.
🔒 Auth: Bearer Token (User Required)
📊 Body Parameters
  • quantity (required, integer, min: 1) - New quantity
📍 Path Parameters
  • item_id (required, integer) - Cart item ID
📀 Response Example
{
  "message": "Cart item updated",
  "quantity": 3
}
DELETE /api/v1/cart/items/{item_id} PRIVATE
Remove item from cart
🔒 Auth: Bearer Token (User Required)
📍 Path Parameters
  • item_id (required, integer) - Cart item ID to remove
📀 Response Example
{
  "message": "Item removed from cart"
}
DELETE /api/v1/cart PRIVATE
Clear all items from cart
🔒 Auth: Bearer Token (User Required)
📀 Response Example
{
  "message": "Cart cleared"
}
POST /api/v1/cart/checkout PRIVATE
Create order from cart items. This endpoint converts all cart items to an order and automatically clears the cart after successful order creation. You can provide shipping address manually or use a saved address by providing address_id.
🔒 Auth: Bearer Token (User Required)
📊 Body Parameters
  • shipping_address (optional, string) - Shipping address line 1
  • shipping_city (optional, string) - Shipping city
  • shipping_country (optional, string) - Shipping country. **IMPORTANT**: If country is 'Bahrain' (or 'BH'), system calculates **shipping cost** (local delivery inside Bahrain). If any other country, system calculates **delivery cost** (international shipping outside Bahrain). Defaults to 'Bahrain'.
  • shipping_postal_code (optional, string) - Postal/ZIP code
  • address_id (optional, integer) - Use saved address by ID (overrides manual address fields)
  • notes (optional, string) - Order notes or special instructions
  • promo_code (optional, string) - Promo code to apply for discount
📀 Response Example
{
  "message": "Order created from cart",
  "order": {
    "id": 1,
    "order_number": "ORD-20241220-A1B2C3",
    "status": "pending",
    "total_amount": 15.28,
    "items": [
      "Ellipsis"
    ]
  }
}
🛒

Orders

Order management endpoints for users to create and view orders

POST /api/v1/orders PRIVATE
Create a new order. Users can purchase products by providing order items with product_id, variant_id, and quantity. Stock is automatically updated. **IMPORTANT: All products in an order must be from the same store.** If you try to checkout products from multiple stores, the order will be rejected. You must create separate orders for products from different stores. You can apply a promo code by including promo_code in the request body (store_id is optional - it will be auto-detected from products). The discount will be applied to product prices only (not shipping/delivery fees). **Terminology**: Inside Bahrain = **Shipping** (local delivery), Outside Bahrain = **Delivery** (international shipping). **🚚 CRITICAL: Shipping/Delivery Cost Calculation** **You MUST calculate and send `shipping_cost` when creating an order!** (Note: This field represents both shipping inside Bahrain and delivery outside Bahrain) 1. **Before creating the order**, call `GET /api/v1/cart` with the delivery address (either `address_id` or `delivery_address` parameters) 2. The cart API will return `delivery_fee` in the response (this is the shipping cost for Bahrain or delivery cost for international) 3. **You MUST include this `delivery_fee` value as `shipping_cost` in the order creation request** 4. If you don't send `shipping_cost`, the backend will use a fallback value (1.0 BHD for Bahrain, 0.0 for international) which may be incorrect! **Example Flow:** ``` 1. GET /api/v1/cart?address_id=123 Response: { "delivery_fee": 1.5, ... } 2. POST /api/v1/orders Body: { "items": [...], "shipping_cost": 1.5, ... } ```
🔒 Auth: Bearer Token (User Required)
📊 Body Parameters
  • items (required, array) - List of order items
  • - product_id (required, integer)
  • - variant_id (optional, integer)
  • - quantity (required, integer)
  • address_id (optional, integer) - Use saved address (if provided, will override manual address fields)
  • shipping_address (optional, string)
  • shipping_city (optional, string)
  • shipping_country (optional, string) - **IMPORTANT**: If 'Bahrain' (or 'BH'), calculates **shipping cost** (local delivery inside Bahrain). If other country, calculates **delivery cost** (international shipping outside Bahrain).
  • shipping_postal_code (optional, string)
  • **shipping_cost (REQUIRED, float) - 🚚 CRITICAL: For Bahrain addresses, use `/api/v1/cart` endpoint's `delivery_fee` field. For international addresses, use the selected shipping company's `price` from the `shipping_companies` array (NOT the `delivery_fee` which is the cheapest option). Note: This field is called `shipping_cost` but represents both shipping (inside Bahrain) and delivery (outside Bahrain) costs. If not provided, backend will use fallback value (1.0 BHD for Bahrain) which may be incorrect!**
  • **shipping_company_code (optional, string) - REQUIRED for international shipping. The code of the selected shipping company from the `shipping_companies` array (e.g., 'dhl', 'smsa_oreem', 'ups'). This tells Oreem which shipping company to use. For Bahrain addresses, this field is optional and ignored.**
  • notes (optional, string)
  • promo_code (optional, string) - Promo code to apply for discount
  • store_id (optional, string) - Store ID for promo code validation (optional - will be auto-detected from products if not provided)
📀 Response Example
{
  "id": 1,
  "user_id": 1,
  "order_number": "ORD-20241209-A1B2C3D4",
  "status": "pending",
  "total_amount": 90.98,
  "promo_code_id": 1,
  "discount_amount": 20.0,
  "shipping_cost": 5.0,
  "original_total": 105.98,
  "shipping_address": "123 Main Street",
  "shipping_city": "Riyadh",
  "shipping_country": "Saudi Arabia",
  "shipping_postal_code": "12345",
  "notes": "Please deliver before 5 PM",
  "oreem_order_id": "SHIP-1234",
  "delivery_order_id": null,
  "shipping_id": 1234,
  "delivery_method": "oreem_international",
  "tracking_url": "https://oreem.com/track/WB-123456",
  "items": [
    {
      "id": 1,
      "order_id": 1,
      "product_id": 1,
      "variant_id": 1,
      "product_title_en": "Premium Cotton Yarn",
      "product_title_ar": "خيوط قطنية عالية الجودة",
      "product_sku": "SKU-1",
      "product_image": "https://teamhirfa.com/static/uploads/products/product_1_1.jpg",
      "variant_color_en": "White",
      "variant_color_ar": "أؚيض",
      "variant_size_en": null,
      "variant_size_ar": null,
      "quantity": 2,
      "unit_price": 12.99,
      "total_price": 25.98,
      "created_at": "2024-12-09T10:30:00Z"
    }
  ],
  "created_at": "2024-12-09T10:30:00Z",
  "updated_at": "2024-12-09T10:30:00Z"
}
GET /api/v1/orders PRIVATE
List all orders for the current authenticated user. Users can only see their own orders. **Tracking Information**: Each order includes tracking fields - `delivery_order_id` for domestic orders (Bahrain) and `shipping_id` for international orders. Use these IDs with the tracking endpoints to track order status.
🔒 Auth: Bearer Token (User Required)
🔍 Query Parameters
  • status_filter (optional, string) - Filter by order status
  • skip (optional, default: 0)
  • limit (optional, default: 100)
📀 Response Example
[
  {
    "id": 1,
    "user_id": 1,
    "order_number": "ORD-20241209-A1B2C3D4",
    "status": "processing",
    "total_amount": 125.98,
    "promo_code_id": null,
    "discount_amount": 0.0,
    "vat_amount": 11.45,
    "shipping_cost": 8.5,
    "original_total": 106.03,
    "shipping_address": "123 Main Street",
    "shipping_city": "Riyadh",
    "shipping_country": "Saudi Arabia",
    "shipping_postal_code": "12345",
    "notes": "Please deliver before 5 PM",
    "oreem_order_id": "SHIP-1234",
    "delivery_order_id": null,
    "shipping_id": 1234,
    "delivery_method": "oreem_international",
    "tracking_url": "https://oreem.com/track/WB-123456",
    "items": [],
    "created_at": "2024-12-09T10:30:00Z",
    "updated_at": "2024-12-09T10:30:00Z"
  },
  {
    "id": 2,
    "user_id": 1,
    "order_number": "ORD-20241209-B2C3D4E5",
    "status": "processing",
    "total_amount": 55.0,
    "promo_code_id": null,
    "discount_amount": 0.0,
    "vat_amount": 5.0,
    "shipping_cost": 5.0,
    "original_total": 45.0,
    "shipping_address": "Building 2001, Road 1500",
    "shipping_city": "Manama",
    "shipping_country": "Bahrain",
    "shipping_postal_code": null,
    "notes": null,
    "oreem_order_id": "qk-man-3533",
    "delivery_order_id": "qk-man-3533",
    "shipping_id": null,
    "delivery_method": "oreem_local",
    "tracking_url": "https://oreem.com/track/qk-man-3533",
    "items": [],
    "created_at": "2024-12-09T11:00:00Z",
    "updated_at": "2024-12-09T11:00:00Z"
  }
]
GET /api/v1/orders/{order_id} PRIVATE
Get order details by ID. Users can only view their own orders with complete product details and pricing. **Response includes**: `vat_amount` (VAT amount, 0.0 if disabled), `shipping_cost` (delivery/shipping cost), `discount_amount` (promo code discount), and `total_amount` (final total including all fees). **Tracking Information**: The response includes `delivery_order_id` for domestic orders (Bahrain) and `shipping_id` for international orders. Use these IDs with the tracking endpoints (`/api/v1/delivery/track/{oreem_order_id}` or `/api/v1/shipping/track/{shipment_id}`) to track order status.
🔒 Auth: Bearer Token (User Required)
📍 Path Parameters
  • order_id (required, integer)
📀 Response Example
{
  "id": 1,
  "user_id": 1,
  "order_number": "ORD-20241209-A1B2C3D4",
  "status": "processing",
  "total_amount": 125.98,
  "promo_code_id": null,
  "discount_amount": 0.0,
  "vat_amount": 11.45,
  "shipping_cost": 8.5,
  "original_total": 106.03,
  "shipping_address": "123 Main Street",
  "shipping_city": "Riyadh",
  "shipping_country": "Saudi Arabia",
  "shipping_postal_code": "12345",
  "notes": "Please deliver before 5 PM",
  "oreem_order_id": "SHIP-1234",
  "delivery_order_id": null,
  "shipping_id": 1234,
  "delivery_method": "oreem_international",
  "tracking_url": "https://oreem.com/track/WB-123456",
  "items": [
    {
      "id": 1,
      "order_id": 1,
      "product_id": 1,
      "variant_id": 1,
      "product_title_en": "Premium Cotton Yarn",
      "product_title_ar": "خيوط قطنية عالية الجودة",
      "product_sku": "SKU-1",
      "product_image": "https://teamhirfa.com/static/uploads/products/product_1_1.jpg",
      "variant_color_en": "White",
      "variant_color_ar": "أؚيض",
      "variant_size_en": null,
      "variant_size_ar": null,
      "quantity": 2,
      "unit_price": 12.99,
      "total_price": 25.98,
      "created_at": "2024-12-09T10:30:00Z"
    }
  ],
  "created_at": "2024-12-09T10:30:00Z",
  "updated_at": "2024-12-09T10:30:00Z"
}
🚚

Delivery & Shipping

Oreem delivery and shipping integration endpoints. **Automatic Routing**: System automatically uses Delivery API for Bahrain addresses and Shipping API for international addresses. Price calculation, tracking, and status updates are supported for both domestic and international orders.

POST /api/v1/delivery/sync-blocks-cities PRIVATE
Sync blocks and cities from Oreem API to database. Fetches all blocks from Oreem and stores them in database. Cities are extracted from block addresses. **Admin/System Use Only** - Run this periodically to keep data in sync. This ensures 100% match with Oreem API.
🔒 Auth: Bearer Token (Admin/System Use)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "success": true,
  "message": "Blocks and cities synced successfully",
  "blocks": {
    "created": 472,
    "updated": 0,
    "total": 472
  },
  "cities": {
    "created": 166,
    "updated": 0,
    "total": 166
  }
}
GET /api/v1/delivery/bahrain/blocks PRIVATE
Get all blocks for Bahrain from database (for address selection). Optionally filter by city_name. Returns blocks stored in database (synced from Oreem). **Use this for dropdown selection when creating addresses.** This is faster than `/delivery/blocks` as it reads from database instead of calling Oreem API.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • city_name (optional, string) - Filter blocks by city name
📀 Response Example
[
  {
    "id": 1,
    "oreem_id": 101,
    "block_number": "101",
    "address": "101 Sharq Al Hidd ؎رق الحد",
    "city_name": "Sharq Al Hidd",
    "latitude": 26.25043,
    "longitude": 50.65685
  },
  {
    "id": 2,
    "oreem_id": 102,
    "block_number": "102",
    "address": "102 Al Hidd الحد",
    "city_name": "Al Hidd",
    "latitude": 26.2502,
    "longitude": 50.65559
  }
]
GET /api/v1/delivery/bahrain/cities PRIVATE
Get all cities for Bahrain from database (for address selection). Returns cities stored in database (synced from Oreem). **Use this for dropdown selection when creating addresses.** This is faster than `/delivery/cities` as it reads from database instead of calling Oreem API.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
[
  {
    "id": 1,
    "name": "Manama",
    "block_count": 9
  },
  {
    "id": 2,
    "name": "Isa Town",
    "block_count": 17
  },
  {
    "id": 3,
    "name": "Riffa",
    "block_count": 12
  }
]
GET /api/v1/delivery/blocks PUBLIC
Get list of all blocks in Bahrain directly from Oreem API. Returns blocks with id, block number, address, latitude, and longitude. **Note:** For address creation, prefer using `/addresses/options` or `/delivery/bahrain/blocks` which returns data from database (faster and 100% validated).
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
[
  {
    "id": 1001,
    "block": "1001",
    "address": "1001 Al Jasrah",
    "latitude": 26.17155,
    "longitude": 50.45291
  },
  {
    "id": 1002,
    "block": "1002",
    "address": "1002 Al Jasrah",
    "latitude": 26.16629,
    "longitude": 50.46125
  }
]
GET /api/v1/delivery/cities PUBLIC
Get list of all unique cities/areas in Bahrain, extracted from Oreem blocks data. Each city includes its name and the count of blocks associated with it. **Note:** For address creation, prefer using `/addresses/options` or `/delivery/bahrain/cities` which returns data from database (faster and 100% validated).
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
[
  {
    "name": "Manama",
    "block_count": 9
  },
  {
    "name": "Isa Town",
    "block_count": 17
  }
]
POST /api/v1/delivery/price PRIVATE
Calculate delivery/shipping price for a route. **Automatic Routing**: If delivery address country is Bahrain, uses Delivery API. If outside Bahrain, uses Shipping API. Use this endpoint before checkout to show delivery/shipping cost to users. **You can either:** 1) Provide `order_id` to auto-extract addresses from order, OR 2) Provide `pickup` and `delivery` addresses directly. Supports both coordinate-based addresses (address_string, latitude, longitude) and block-based addresses (building, flat, road, block). For international shipping, the system automatically calculates shipping rates based on parcel weight and destination.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • order_id (optional, integer) - Order ID to auto-extract addresses from (if provided, pickup and delivery addresses will be auto-extracted)
  • pickup (optional, object) - Pickup address (required if order_id not provided)
  • - name (required, string)
  • - phone (required, string)
  • - address (required, object) - Either coordinates OR block structure
  • - address_string (string) - Full address (for coordinates)
  • - latitude (float) - Latitude (for coordinates)
  • - longitude (float) - Longitude (for coordinates)
  • - OR building, flat, road, block (for block structure)
  • delivery (optional, object) - Delivery address (required if order_id not provided)
  • - name (required, string)
  • - phone (required, string)
  • - email (optional, string)
  • - address (required, object) - Either coordinates OR block structure
  • instant (optional, integer, default: 1) - 0 = Same day, 1 = Instant
  • vehicle (optional, string, default: 'car') - 'car', 'bus', or '6wheel'
📀 Response Example
{
  "fee": 1.3,
  "currency": "BHD",
  "instant": 1,
  "vehicle": "car"
}
POST /api/v1/delivery/create PRIVATE
Create a delivery/shipping task in Oreem for an existing order. **Automatic Routing**: System automatically detects recipient country - uses Delivery API for Bahrain, Shipping API for international. **NOTE: Delivery/Shipping is automatically created when order is placed, so you usually don't need to call this endpoint manually.** However, you can use it to manually create delivery/shipping if needed. **You can either:** 1) Provide `order_id` only (addresses will be auto-extracted from shop owner and order), OR 2) Provide `order_id` with `pickup` and `delivery` addresses. The order must belong to the authenticated user.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • order_id (required, integer) - Your order ID
  • pickup (optional, object) - Pickup address (auto-extracted from shop owner if not provided)
  • delivery (optional, object) - Delivery address (auto-extracted from order if not provided)
  • cash (optional, float, default: 0.0) - Cash on delivery amount (0 if no COD)
  • instant (optional, integer, default: 1) - 0 = Same day, 1 = Instant
  • vehicle (optional, string, default: 'car') - 'car', 'bus', or '6wheel'
  • items_info (optional, string, max 500 chars) - Information about items (auto-generated from order items if not provided)
  • comments (optional, string, max 250 chars) - Comments for the order
📀 Response Example
{
  "oreem_order_id": "qk-man-3533",
  "fee": 1.3,
  "tracking_url": "https://oreem.com/track/qk-man-3533",
  "message": "Order created successfully"
}
GET /api/v1/delivery/track/{oreem_order_id} PRIVATE
Track a delivery order status. Returns current order status and tracking URL from Oreem.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • oreem_order_id (required, string) - Oreem order ID (returned from create endpoint)
📀 Response Example
{
  "order_status": "pending",
  "tracking_url": "https://oreem.com/track/qk-man-3533",
  "status": 200
}
POST /api/v1/delivery/webhook PUBLIC
Webhook endpoint for Oreem status updates. **This endpoint does NOT require authentication** as it's called by Oreem's servers. Oreem will POST to this endpoint when delivery status changes. The webhook automatically updates your order status based on Oreem delivery status.
🔓 Auth: No authentication (called by Oreem servers)
📊 Body Parameters
  • oreem_order_id (required, string) - Oreem order ID
  • local_order_id (required, string) - Your order number (order_number)
  • status (required, string) - Delivery status from Oreem
📀 Response Example
{
  "status": "ok",
  "message": "Status updated successfully"
}
POST /api/v1/delivery/cancel/{oreem_order_id} PRIVATE
Cancel a delivery order in Oreem. Requires the delivery to belong to an order owned by the authenticated user.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • reason (required, string) - Reason for cancellation
📍 Path Parameters
  • oreem_order_id (required, string) - Oreem order ID
📀 Response Example
{
  "status": "ok",
  "message": "Delivery cancelled successfully"
}
GET /api/v1/shipping/track/{shipment_id} PRIVATE
Track an international shipment status. Returns current shipment status, waybill number, tracking URL, current location, and estimated delivery date. **Note**: Shipment status is automatically updated via webhook when Oreem shipping status changes. You don't need to poll this endpoint unless you want real-time UI updates.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • shipment_id (required, integer) - Shipment ID from Oreem
📀 Response Example
{
  "shipment_id": 1234,
  "status": "in_progress",
  "waybill_number": "WB-123456",
  "tracking_url": "https://oreem.com/track/WB-123456",
  "current_location": "In transit to destination",
  "estimated_delivery": "2024-12-25",
  "message": "Shipment tracked successfully"
}
GET /api/v1/shipping/details/{shipment_id} PRIVATE
Get detailed shipment information including shipper, consignee, parcels, rate, COD, and delivery service information.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • shipment_id (required, integer) - Shipment ID from Oreem
📀 Response Example
{
  "shipment_id": 1234,
  "status": "in_progress",
  "waybill_number": "WB-123456",
  "rate": {
    "value": "8.000",
    "currency": "BHD",
    "status": "Pending"
  },
  "cod": {
    "value": "100.000",
    "currency": "BHD"
  },
  "delivery_service": {
    "code": "smsa_xs4",
    "name": "SMSA Express (Xs4arabia)",
    "method": {
      "name": "ECOM International Economy Delivery",
      "code": "EIED"
    }
  },
  "shipper": {
    "address": {
      "city_name": "Manama",
      "country_code": "BH",
      "country_name": "Bahrain"
    },
    "contact": {
      "name": "Shop Owner",
      "phone": "12345678"
    }
  },
  "consignee": {
    "address": {
      "city_name": "Riyadh",
      "country_code": "SA",
      "country_name": "Saudi Arabia"
    },
    "contact": {
      "name": "Customer Name",
      "phone": "512345678"
    }
  },
  "parcels": [
    {
      "qty": 1,
      "weight": "1.0",
      "weight_unit": "kg"
    }
  ],
  "created_at": "2024-12-20T10:30:00Z",
  "updated_at": "2024-12-20T15:45:00Z"
}
POST /api/v1/shipping/webhook PUBLIC
Webhook endpoint for Oreem shipping status updates. **This endpoint does NOT require authentication** as it's called by Oreem's servers. Oreem will POST to this endpoint when shipment status changes. The webhook automatically updates your order status based on Oreem shipping status.
🔓 Auth: No authentication (called by Oreem servers)
📊 Body Parameters
  • shipment_id (required, integer) - Shipment ID from Oreem
  • local_order_id (optional, string) - Your order number (order_number)
  • status (required, string) - Shipping status from Oreem
  • waybill_number (optional, string) - Waybill number
  • tracking_url (optional, string) - Tracking URL
📀 Response Example
{
  "status": "success",
  "message": "Webhook processed successfully"
}
GET /api/v1/shipping/webhook PUBLIC
Health check endpoint for shipping webhook. Returns OK to confirm the endpoint is accessible.
🔓 Auth: No authentication
📀 Response Example
{
  "status": "ok",
  "message": "Shipping webhook endpoint is active"
}
❀

Favorites

User favorites management endpoints for saving and managing favorite products

POST /api/v1/favorites PRIVATE
Add a product to user's favorites. Users can only add products to their own favorites. Each product can only be added once per user.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • product_id (required, integer) - ID of the product to add to favorites
📀 Response Example
{
  "id": 1,
  "user_id": 1,
  "product_id": 5,
  "created_at": "2024-12-09T10:30:00Z"
}
GET /api/v1/favorites PRIVATE
Get current user's favorite products. Returns products ordered by when they were added (newest first). Only returns active products.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
📀 Response Example
[
  {
    "id": 5,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Premium Cotton Yarn",
    "title_ar": "خيوط قطنية عالية الجودة",
    "description_en": "Soft cotton yarn for amigurumi",
    "description_ar": "خيوط قطنية ناعمة للأميغورومي",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_5_1.jpg"
    ],
    "weight": 0.3,
    "sku": "SKU-5",
    "is_active": true,
    "variants": [
      {
        "id": 1,
        "product_id": 5,
        "color_en": "White",
        "color_ar": "أؚيض",
        "size_en": null,
        "size_ar": null,
        "quantity": 50,
        "price": 12.99,
        "is_active": true,
        "created_at": "2024-12-08T10:30:00Z",
        "updated_at": "2024-12-08T10:30:00Z"
      }
    ],
    "created_at": "2024-12-08T10:30:00Z",
    "updated_at": "2024-12-08T10:30:00Z"
  },
  {
    "id": 3,
    "category_id": 1,
    "subcategory_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "title_en": "Acrylic Yarn Bundle",
    "title_ar": "حزمة خيوط الأكريليك",
    "description_en": "Colorful acrylic yarn bundle",
    "description_ar": "حزمة خيوط أكريليك ملونة",
    "images": [
      "https://teamhirfa.com/static/uploads/products/product_3_1.jpg"
    ],
    "weight": 0.5,
    "sku": "SKU-3",
    "is_active": true,
    "variants": [],
    "created_at": "2024-12-08T11:00:00Z",
    "updated_at": "2024-12-08T11:00:00Z"
  }
]
DELETE /api/v1/favorites/{product_id} PRIVATE
Remove a product from user's favorites. Users can only remove products from their own favorites.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • product_id (required, integer) - ID of the product to remove from favorites
📀 Response Example
"204 No Content"
📚

Courses

Courses management endpoints for shop owners. Shop owners can create courses with flexible or fixed time slot scheduling. Users can register for courses and shop owners can manage registrations. Flexible scheduling: Users register and add preferred times, shop owner discusses timing. Fixed slots: Shop owner creates specific time slots, users select from available slots.

GET /api/v1/courses PUBLIC
List all active courses. Filter by master_category_id to get courses for a specific master category. Returns courses ordered by order field, then by creation date. For fixed_slots courses, includes available time slots. Each course includes registration_count field showing how many users have registered.
🔍 Query Parameters
  • master_category_id (optional, integer - filter by master category)
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
  • store_id (optional, filter by shop/store ID)
  • is_active (optional, default: true, boolean)
  • scheduling_type (optional, 'flexible' or 'fixed_slots')
📀 Response Example
[
  {
    "id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "shop_name": "Crochet Master",
    "shop_avatar": "https://teamhirfa.com/static/uploads/avatars/shop_avatar.jpg",
    "name_en": "Crochet Basics Course",
    "name_ar": "دورة أساسيات الكرو؎يه",
    "description_en": "Learn the fundamentals of crochet in this beginner-friendly course.",
    "description_ar": "تعلم أساسيات الكرو؎يه في هذه الدورة المناسؚة للمؚتد؊ين.",
    "image": "https://teamhirfa.com/static/uploads/courses/course_1_abc123.jpg",
    "price": 50.0,
    "teaching_method": "in_person",
    "scheduling_type": "flexible",
    "is_active": true,
    "time_slots": [],
    "registration_count": 5,
    "created_at": "2024-12-09T10:30:00Z",
    "updated_at": "2024-12-09T10:30:00Z"
  },
  {
    "id": 2,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "shop_name": "Crochet Master",
    "shop_avatar": "https://teamhirfa.com/static/uploads/avatars/shop_avatar.jpg",
    "name_en": "Advanced Knitting Workshop",
    "name_ar": "ور؎ة الحياكة المتقدمة",
    "description_en": "Master advanced knitting techniques and patterns.",
    "description_ar": "أتقن تقنيات وأنماط الحياكة المتقدمة.",
    "image": "https://teamhirfa.com/static/uploads/courses/course_2_def456.jpg",
    "price": 75.0,
    "teaching_method": "both",
    "scheduling_type": "fixed_slots",
    "is_active": true,
    "time_slots": [
      {
        "id": 1,
        "course_id": 2,
        "start_time": "2024-12-15T10:00:00Z",
        "end_time": "2024-12-15T12:00:00Z",
        "max_participants": 10,
        "current_participants": 3,
        "is_available": true,
        "notes": "Morning session",
        "created_at": "2024-12-09T11:00:00Z",
        "updated_at": "2024-12-09T11:00:00Z"
      },
      {
        "id": 2,
        "course_id": 2,
        "start_time": "2024-12-15T14:00:00Z",
        "end_time": "2024-12-15T16:00:00Z",
        "max_participants": 10,
        "current_participants": 2,
        "is_available": true,
        "notes": "Afternoon session",
        "created_at": "2024-12-09T11:00:00Z",
        "updated_at": "2024-12-09T11:00:00Z"
      }
    ],
    "registration_count": 5,
    "created_at": "2024-12-09T11:00:00Z",
    "updated_at": "2024-12-09T11:00:00Z"
  }
]
GET /api/v1/courses/{course_id} PUBLIC
Get course details by ID. Returns complete course information including name, description, image, scheduling type, and available time slots (for fixed_slots courses).
📍 Path Parameters
  • course_id (required, integer)
📀 Response Example
{
  "id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "shop_name": "Crochet Master",
  "shop_avatar": "https://teamhirfa.com/static/uploads/avatars/shop_avatar.jpg",
  "name_en": "Crochet Basics Course",
  "name_ar": "دورة أساسيات الكرو؎يه",
  "description_en": "Learn the fundamentals of crochet in this beginner-friendly course.",
  "description_ar": "تعلم أساسيات الكرو؎يه في هذه الدورة المناسؚة للمؚتد؊ين.",
  "image": "https://teamhirfa.com/static/uploads/courses/course_1_abc123.jpg",
  "price": 50.0,
  "teaching_method": "in_person",
  "scheduling_type": "flexible",
  "is_active": true,
  "time_slots": [],
  "registration_count": 5,
  "created_at": "2024-12-09T10:30:00Z",
  "updated_at": "2024-12-09T10:30:00Z"
}
POST /api/v1/courses/{course_id}/payment PRIVATE
Confirm payment for a course. **This endpoint must be called BEFORE registration for paid courses.** Payment is required for courses with price > 0. For free courses (price = 0), this endpoint is not needed. Currently supports cash payment (payment_method: 'cash'). After payment is confirmed, you can proceed to register using the registration endpoint.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • payment_method (required, string, default: 'cash') - Payment method used ('cash', 'card', 'online', etc. Currently only 'cash' supported, payment gateway coming soon)
  • amount (optional, float) - Amount paid. If not provided, will use course price. Must be >= course price.
📍 Path Parameters
  • course_id (required, integer)
📀 Response Example
{
  "success": true,
  "message": "Payment confirmed successfully. You can now register for the course.",
  "payment_id": 1,
  "course_id": 1,
  "amount_paid": 50.0,
  "payment_method": "cash",
  "next_step": "Call POST /api/v1/courses/{course_id}/register to complete registration"
}
POST /api/v1/courses/{course_id}/register PRIVATE
Register the current user for a course. **Payment must be confirmed first using the payment endpoint for paid courses.** For flexible courses: provide preferred_times (optional). For fixed_slots courses: provide time_slot_id (required). Returns 402 Payment Required if payment has not been confirmed. Returns 400 if already registered or if time slot is full.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • time_slot_id (required for fixed_slots, null for flexible)
  • preferred_times (optional, string - for flexible scheduling)
  • status (optional, default: 'pending')
📍 Path Parameters
  • course_id (required, integer)
📀 Response Example
{
  "success": true,
  "message": "Successfully registered for course",
  "registration_id": 1,
  "course_id": 1,
  "user_id": 5,
  "payment_status": "paid",
  "amount_paid": 50.0
}
DELETE /api/v1/courses/{course_id}/register PRIVATE
Unregister the current user from a course.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • course_id (required, integer)
📀 Response Example
{
  "success": true,
  "message": "Successfully unregistered from course"
}
GET /api/v1/courses/{course_id}/registrations PRIVATE
Get registrations for a course. Returns count only for regular users. Returns full list for shop owners (only if they own the course).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • course_id (required, integer)
📀 Response Example
{
  "count": 5,
  "message": "Only shop owners can view full registration list"
}
GET /api/v1/courses/my-courses PRIVATE
Get all courses the current authenticated user is enrolled in. Returns a list of courses with registration details including status, time slot (if applicable), preferred times, and shop owner notes.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
[
  {
    "id": 1,
    "store_id": "store_abc123",
    "shop_name": "Crochet Master",
    "shop_avatar": "https://teamhirfa.com/static/uploads/avatars/shop_avatar.jpg",
    "name_en": "Advanced Crochet Techniques",
    "name_ar": "تقنيات الكرو؎يه المتقدمة",
    "description_en": "Learn advanced crochet techniques...",
    "description_ar": "تعلم تقنيات الكرو؎يه المتقدمة...",
    "image": "https://teamhirfa.com/static/uploads/courses/course_1.jpg",
    "price": 75.0,
    "teaching_method": "online",
    "scheduling_type": "fixed_slots",
    "is_active": true,
    "time_slots": [
      {
        "id": 1,
        "start_time": "2024-12-15T10:00:00Z",
        "end_time": "2024-12-15T12:00:00Z",
        "max_participants": 10,
        "current_participants": 5,
        "is_available": true
      }
    ],
    "registration_count": 0,
    "created_at": "2024-12-10T10:00:00Z",
    "updated_at": "2024-12-10T10:00:00Z",
    "my_registration": {
      "id": 1,
      "status": "confirmed",
      "payment_status": "paid",
      "payment_method": "cash",
      "amount_paid": 75.0,
      "time_slot": {
        "id": 1,
        "start_time": "2024-12-15T10:00:00Z",
        "end_time": "2024-12-15T12:00:00Z",
        "max_participants": 10,
        "current_participants": 5,
        "is_available": true
      },
      "preferred_times": null,
      "shop_owner_notes": "Welcome to the course!",
      "created_at": "2024-12-11T09:00:00Z",
      "updated_at": "2024-12-11T09:00:00Z"
    }
  }
]
ℹ Note
Returns only courses where the user has an active registration (status != 'cancelled'). Each course includes my_registration object with registration details including payment_status, payment_method, and amount_paid. For flexible courses, preferred_times may contain user's preferred time preferences. For fixed_slots courses, time_slot contains the selected time slot information.
🔔

Notifications

Notifications management endpoints. Admins can create and manage notifications with titles and descriptions in English and Arabic. Notifications can be displayed to users in the mobile app. The system also automatically sends notifications for: order creation, order status changes, event reminders (1 day before), and course reminders (1 day before).

GET /api/v1/notifications PUBLIC
List all active notifications. Returns notifications ordered by order field, then by creation date. Use is_active query parameter to filter by active status.
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
  • is_active (optional, default: true, boolean)
📀 Response Example
[
  {
    "id": 1,
    "titleEn": "New Feature Available",
    "titleAr": "ميزة جديدة متاحة",
    "descriptionEn": "Check out our new courses feature!",
    "descriptionAr": "تحقق من ميزة الدورات الجديدة!",
    "isActive": true,
    "order": 0,
    "createdAt": "2024-12-10T10:00:00Z",
    "updatedAt": "2024-12-10T10:00:00Z"
  }
]
GET /api/v1/notifications/{notification_id} PUBLIC
Get notification details by ID. Returns complete notification information including title and description in both languages.
📍 Path Parameters
  • notification_id (required, integer)
📀 Response Example
{
  "id": 1,
  "titleEn": "New Feature Available",
  "titleAr": "ميزة جديدة متاحة",
  "descriptionEn": "Check out our new courses feature!",
  "descriptionAr": "تحقق من ميزة الدورات الجديدة!",
  "isActive": true,
  "isGlobal": true,
  "order": 0,
  "createdAt": "2024-12-10T10:00:00Z",
  "updatedAt": "2024-12-10T10:00:00Z"
}
GET /api/v1/notifications/my-notifications PRIVATE
Get all notifications for the current authenticated user. Returns both global notifications (sent to all users) and user-specific notifications (sent only to this user). Includes read status for user-specific notifications.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 100, max: 100)
  • is_read (optional, boolean - filter by read status)
  • include_global (optional, default: true, boolean - include global notifications)
📀 Response Example
[
  {
    "id": 1,
    "titleEn": "New Feature Available",
    "titleAr": "ميزة جديدة متاحة",
    "descriptionEn": "Check out our new courses feature!",
    "descriptionAr": "تحقق من ميزة الدورات الجديدة!",
    "isActive": true,
    "isGlobal": true,
    "isRead": null,
    "readAt": null,
    "order": 0,
    "createdAt": "2024-12-10T10:00:00Z",
    "updatedAt": "2024-12-10T10:00:00Z"
  },
  {
    "id": 2,
    "userNotificationId": 123,
    "titleEn": "Special Offer for You",
    "titleAr": "عرض خاص لك",
    "descriptionEn": "You have a special discount code!",
    "descriptionAr": "لديك كود خصم خاص!",
    "isActive": true,
    "isGlobal": false,
    "isRead": false,
    "readAt": null,
    "order": 1,
    "createdAt": "2024-12-10T11:00:00Z",
    "updatedAt": "2024-12-10T11:00:00Z"
  }
]
POST /api/v1/notifications/{notification_id}/mark-read PRIVATE
Mark a user-specific notification as read. Only works for user-specific notifications (not global notifications).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • notification_id (required, integer)
📀 Response Example
{
  "success": true,
  "message": "Notification marked as read",
  "notification_id": 2,
  "is_read": true
}
GET /api/v1/notifications/unread-count PRIVATE
Get count of unread notifications for the current user. Useful for displaying notification badges.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "unread_count": 5
}
PATCH /api/v1/notifications/mark-all-read PRIVATE
Mark all unread notifications as read for the current user.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "success": true,
  "message": "5 notifications marked as read",
  "count": 5
}
DELETE /api/v1/notifications/{user_notification_id} PRIVATE
Delete a specific user notification. Only the user who owns the notification can delete it. Use the userNotificationId from the /my-notifications response.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • user_notification_id (required, integer) - The user notification ID from /my-notifications response
📀 Response Example
{
  "success": true,
  "message": "Notification deleted successfully",
  "user_notification_id": 123
}
DELETE /api/v1/notifications/delete-read PRIVATE
Delete all read notifications for the current user. Useful for clearing old notifications.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "success": true,
  "message": "10 read notifications deleted",
  "count": 10
}
🎟

Promo Codes

Promo code validation and discount management. Shop owners can create promo codes with discount percentages, date ranges, and usage limits. Users can apply promo codes when creating orders to get discounts on products (discount applies to product prices only, not delivery fees).

POST /api/v1/promo-codes/validate PRIVATE
Validate a promo code for a specific shop. Checks if the promo code exists, is active, within date range, hasn't exceeded max uses, and user hasn't already used it.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • code (required, string) - Promo code to validate
  • store_id (required, string) - Store ID to check if promo code belongs to this shop
📀 Response Example
{
  "valid": true,
  "code": "SAVE20",
  "discount_percentage": 20.0,
  "message": "Promo code is valid",
  "discount_amount": null
}
ℹ Note
Returns validation result. If valid is false, check the message for the reason. discount_amount is null in this endpoint - use /validate-with-total to get calculated discount amount.
POST /api/v1/promo-codes/validate-with-total PRIVATE
Validate a promo code and calculate discount amount based on cart total. This endpoint validates the promo code and also calculates the discount amount based on the provided cart total (sum of product prices, excluding delivery).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • code (required, string) - Promo code to validate
  • store_id (required, string) - Store ID to check if promo code belongs to this shop
🔍 Query Parameters
  • cart_total (required, float) - Total cart amount (sum of product prices, excluding delivery)
📀 Response Example
{
  "valid": true,
  "code": "SAVE20",
  "discount_percentage": 20.0,
  "message": "Promo code is valid",
  "discount_amount": 10.0
}
ℹ Note
Use this endpoint when you have the cart total ready. The discount_amount is calculated as (cart_total * discount_percentage) / 100. Discount applies only to product prices, not delivery fees. When creating an order, include promo_code and store_id in the OrderCreate request body.
💬

Chat API

Real-time chat between users and shop owners using Pusher. All messages are stored in the database. Users can start conversations with shop owners, send text messages, upload media (images, videos, audio, files), record voice messages, and receive real-time updates via Pusher. Shop owners can manage conversations through the Shop Owner API. Supported media types: images (.jpg, .png, .gif, .webp), videos (.mp4, .avi, .mov, .webm), audio files (.mp3, .wav, .ogg, .m4a, .aac), voice recordings (.webm), and documents (.pdf, .doc, .docx). Max file size: 50MB.

POST /api/v1/chat/conversations PRIVATE
Create a new chat conversation with a shop owner. If a conversation already exists with this store, returns the existing one. Optionally include an initial message.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • store_id (required, string)
  • initial_message (optional, string)
📀 Response Example
{
  "id": 1,
  "user_id": 10,
  "user_name": "John Doe",
  "user_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
  "shop_owner_id": 5,
  "shop_owner_name": "Shop Owner Name",
  "shop_owner_avatar": "https://teamhirfa.com/static/uploads/users/user_5_def456.jpg",
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "conversation_type": "user",
  "last_message": {
    "id": 1,
    "conversation_id": 1,
    "sender_id": 10,
    "sender_name": "John Doe",
    "message": "Hello, I have a question about your products",
    "message_type": "text",
    "media_url": null,
    "file_name": null,
    "is_read": false,
    "created_at": "2024-12-12T10:30:00Z"
  },
  "unread_count": 0,
  "last_message_at": "2024-12-12T10:30:00Z",
  "is_active": true,
  "created_at": "2024-12-12T10:30:00Z",
  "updated_at": "2024-12-12T10:30:00Z"
}
ℹ Note
conversation_type indicates the role of the current user in this conversation: 'shop' if the current user is the shop owner (chatting with customers), 'user' if the current user is the customer (chatting with a shop). This field helps filter conversations in the app - shop owners can separate their store conversations from their customer conversations.
GET /api/v1/chat/conversations PRIVATE
List all chat conversations for the current user. Shop owners see conversations for their store (as shop_owner) AND conversations where they are the user (as customer). Each conversation includes a 'conversation_type' field ('shop' or 'user') to help filter conversations in the app.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
📀 Response Example
[
  {
    "id": 1,
    "user_id": 10,
    "user_name": "John Doe",
    "user_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
    "shop_owner_id": 5,
    "shop_owner_name": "Shop Owner Name",
    "shop_owner_avatar": "https://teamhirfa.com/static/uploads/users/user_5_def456.jpg",
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "conversation_type": "user",
    "last_message": {
      "id": 1,
      "conversation_id": 1,
      "sender_id": 10,
      "sender_name": "John Doe",
      "message": "Hello, I have a question",
      "message_type": "text",
      "media_url": null,
      "file_name": null,
      "is_read": true,
      "created_at": "2024-12-12T10:30:00Z"
    },
    "unread_count": 0,
    "last_message_at": "2024-12-12T10:30:00Z",
    "is_active": true,
    "created_at": "2024-12-12T10:30:00Z",
    "updated_at": "2024-12-12T10:30:00Z"
  },
  {
    "id": 2,
    "user_id": 15,
    "user_name": "Jane Smith",
    "user_avatar": "https://teamhirfa.com/static/uploads/users/user_15_xyz789.jpg",
    "shop_owner_id": 10,
    "shop_owner_name": "Current User (Shop Owner)",
    "shop_owner_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
    "store_id": "store_current_user_store",
    "conversation_type": "shop",
    "last_message": {
      "id": 5,
      "conversation_id": 2,
      "sender_id": 15,
      "sender_name": "Jane Smith",
      "message": "When will my order arrive?",
      "message_type": "text",
      "media_url": null,
      "file_name": null,
      "is_read": false,
      "created_at": "2024-12-12T11:00:00Z"
    },
    "unread_count": 1,
    "last_message_at": "2024-12-12T11:00:00Z",
    "is_active": true,
    "created_at": "2024-12-12T09:00:00Z",
    "updated_at": "2024-12-12T11:00:00Z"
  }
]
ℹ Note
conversation_type field: 'shop' = current user is the shop owner (chatting with customers), 'user' = current user is the customer (chatting with a shop). Shop owners who also use the app as customers will see both types. Filter conversations by conversation_type to separate 'My Store' chats from 'My Customer' chats in the app UI.
GET /api/v1/chat/conversations/{conversation_id} PRIVATE
Get a specific conversation. User can only access their own conversations. Shop owners can access conversations for their store (as shop_owner) AND conversations where they are the user (as customer). Response includes conversation_type field ('shop' or 'user') to help filter conversations.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
{
  "id": 1,
  "user_id": 10,
  "user_name": "John Doe",
  "user_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
  "shop_owner_id": 5,
  "shop_owner_name": "Shop Owner Name",
  "shop_owner_avatar": "https://teamhirfa.com/static/uploads/users/user_5_def456.jpg",
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "conversation_type": "user",
  "last_message": {
    "id": 1,
    "conversation_id": 1,
    "sender_id": 10,
    "sender_name": "John Doe",
    "message": "Hello, I have a question",
    "message_type": "text",
    "media_url": null,
    "file_name": null,
    "is_read": true,
    "created_at": "2024-12-12T10:30:00Z"
  },
  "unread_count": 0,
  "last_message_at": "2024-12-12T10:30:00Z",
  "is_active": true,
  "created_at": "2024-12-12T10:30:00Z",
  "updated_at": "2024-12-12T10:30:00Z"
}
GET /api/v1/chat/conversations/{conversation_id}/messages PRIVATE
Get messages for a conversation. Messages are automatically marked as read when fetched. User can only access their own conversations.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
[
  {
    "id": 1,
    "conversation_id": 1,
    "sender_id": 10,
    "sender_name": "John Doe",
    "sender_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
    "message": "Hello, I have a question",
    "message_type": "text",
    "media_url": null,
    "file_name": null,
    "is_read": true,
    "read_at": "2024-12-12T10:31:00Z",
    "created_at": "2024-12-12T10:30:00Z"
  },
  {
    "id": 2,
    "conversation_id": 1,
    "sender_id": 10,
    "sender_name": "John Doe",
    "sender_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
    "message": "Check this out!",
    "message_type": "image",
    "media_url": "https://teamhirfa.com/static/uploads/chat/chat_2_abc123.jpg",
    "file_name": "product.jpg",
    "is_read": false,
    "read_at": null,
    "created_at": "2024-12-12T10:32:00Z"
  },
  {
    "id": 3,
    "conversation_id": 1,
    "sender_id": 5,
    "sender_name": "Shop Owner",
    "sender_avatar": "https://teamhirfa.com/static/uploads/users/user_5_def456.jpg",
    "message": null,
    "message_type": "audio",
    "media_url": "https://teamhirfa.com/static/uploads/chat/chat_3_xyz789.webm",
    "file_name": "voice_1234567890.webm",
    "is_read": false,
    "read_at": null,
    "created_at": "2024-12-12T10:33:00Z"
  }
]
POST /api/v1/chat/conversations/{conversation_id}/messages PRIVATE
Send a message in a conversation. Supports text messages, media uploads (images, videos, audio, files), and voice recordings. Message is broadcasted via Pusher in real-time. User can only send messages in their own conversations. Either 'message' (text) or 'file' (media) is required. Both can be sent together (text with media).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • conversation_id (required, int)
📎 Form Data (multipart/form-data)
  • message (optional, string, max 5000 characters) - Text message content
  • file (optional, file) - Media file to upload. Supported types:
  • • Images: .jpg, .jpeg, .png, .gif, .webp
  • • Videos: .mp4, .avi, .mov, .webm
  • • Audio: .mp3, .wav, .ogg, .m4a, .aac, .webm (for voice recordings)
  • • Documents: .pdf, .doc, .docx
  • • Max file size: 50MB
📀 Response Example
{
  "id": 2,
  "conversation_id": 1,
  "sender_id": 10,
  "sender_name": "John Doe",
  "sender_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
  "message": "Check out this image!",
  "message_type": "image",
  "media_url": "https://teamhirfa.com/static/uploads/chat/chat_2_abc123def.jpg",
  "file_name": "product_image.jpg",
  "file_size": 245760,
  "is_read": false,
  "read_at": null,
  "created_at": "2024-12-12T10:35:00Z"
}
POST /api/v1/pusher/auth PRIVATE
Authenticate Pusher private channel subscription. This endpoint is called automatically by Pusher when subscribing to private channels. Channel format: private-chat-{conversation_id}
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • socket_id (required, string)
  • channel_name (required, string)
📀 Response Example
{
  "auth": "e18960692a059cc4fafb:abc123def456..."
}
🏪

Shop Owner API

Complete store management API for shop owners. Allows shop owners to manage their stores directly from mobile apps. Includes dashboard statistics, products CRUD, orders management, promo codes CRUD, courses CRUD, notifications, and chat. Notifications are automatically created when new orders are placed or course registrations happen. All endpoints require shop owner authentication (Bearer token with user_type='shop_owner').

GET /api/v1/shop-owner/dashboard PRIVATE
Get dashboard statistics for shop owner. Returns product count, order stats, revenue, profit (net profit after removing product cost), stock net worth, promo codes, courses, etc. Profit is calculated as: (selling_price - bought_price) × quantity for each order item.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "products": {
    "total": 45,
    "active": 38,
    "inactive": 7
  },
  "orders": {
    "total": 120,
    "pending": 5,
    "processing": 8,
    "recent_7_days": 15
  },
  "revenue": {
    "total": 12500.5,
    "currency": "BHD"
  },
  "profit": {
    "total": 6250.25,
    "currency": "BHD"
  },
  "today_sales": {
    "revenue": 150.0,
    "profit": 75.0,
    "currency": "BHD"
  },
  "monthly_sales": {
    "revenue": 2500.0,
    "profit": 1250.0,
    "revenue_growth": 15.5,
    "profit_growth": 18.2,
    "currency": "BHD"
  },
  "stock_net_worth": {
    "total": 8750.25,
    "currency": "BHD"
  },
  "promo_codes": {
    "total": 5,
    "active": 3
  },
  "courses": {
    "total": 10,
    "active": 7
  },
  "notifications": {
    "unread_count": 3
  }
}
GET /api/v1/shop-owner/categories PRIVATE
Get all categories. Shop owners can select any category for their products (including global categories). Optionally filter by master_category_id.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • master_category_id (optional, integer) - Filter by master category
📀 Response Example
"Array of CategoryResponse objects with id, name_en, name_ar, image, is_active, order, store_id (can be null for global categories), created_at, updated_at"
GET /api/v1/shop-owner/categories/{category_id}/subcategories PRIVATE
Get subcategories for a specific category. Shop owners can see all subcategories for any category (including global subcategories). Returns subcategories linked via both legacy category_id field and many-to-many relationships.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • category_id (required, integer)
📀 Response Example
[
  {
    "id": 1,
    "category_id": 1,
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "name_en": "fffff",
    "name_ar": "fffff",
    "image": null,
    "is_active": true,
    "order": 0,
    "created_at": "2024-12-24T10:30:00Z",
    "updated_at": "2024-12-24T10:30:00Z"
  }
]
ℹ Note
store_id can be null for global subcategories. Subcategories are returned for the specified category, checking both direct category_id links and many-to-many relationships.
GET /api/v1/shop-owner/products PRIVATE
List all products for the shop owner's store. Can be filtered by is_active and category_id.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
  • is_active (optional, bool)
  • category_id (optional, int)
📀 Response Example
"Array of ProductResponse objects with id, title_en, title_ar, description_en, description_ar, price, images, variants, category_id, subcategory_id, store_id, sku, is_active, created_at, updated_at"
GET /api/v1/shop-owner/products/{product_id} PRIVATE
Get product details by ID. Shop owner can only access their own products.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • product_id (required, int)
📀 Response Example
"ProductResponse object with full product details including variants"
POST /api/v1/shop-owner/products PRIVATE
Create a new product for the shop owner's store. **Shop owners can use any category and subcategory (including global categories/subcategories that don't belong to any specific store).** **Supports real file uploads for images (like avatar uploads) - uploads to AWS S3 automatically.** **SKU is auto-generated by the backend (format: SKU-{product_id}) and cannot be provided by the user.** **Pricing: Two options available - (1) Use variants_json for products with variants (colors, sizes), or (2) Use single_price and single_quantity for simple products without variants. At least one pricing method must be provided.**
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📎 Form Data (multipart/form-data)
  • category_id (required, integer) - Can be any category (including global categories)
  • subcategory_id (required, integer) - Must belong to the selected category (can be global subcategory)
  • title_en (required, string)
  • title_ar (required, string)
  • description_en (optional, string)
  • description_ar (optional, string)
  • weight (optional, float) - Weight in kg
  • is_active (optional, boolean, default: true)
  • images (optional, array of files) - Product images (jpg, jpeg, png, gif, svg, webp). Files are automatically compressed and uploaded to AWS S3.
  • variants_json (optional, string) - JSON array of variants. Use this for products with variants (colors, sizes). If provided, it takes precedence over single_price/single_quantity. Example: [{"color_en":"Black","color_ar":"أسود","size_en":"Large","size_ar":"كؚير","quantity":50,"price":99.99,"is_active":true}]
  • single_price (optional, float) - Single price for products without variants. Must be provided with single_quantity if variants_json is not used. Must be greater than 0.
  • single_quantity (optional, integer) - Single quantity for products without variants. Must be provided with single_price if variants_json is not used. Must be 0 or greater.
🎚 Variants Format
[
  {
    "color_en": "Black",
    "color_ar": "أسود",
    "size_en": "Large",
    "size_ar": "كؚير",
    "quantity": 50,
    "price": 99.99,
    "is_active": true
  }
]
📀 Response Example
{
  "id": 1,
  "category_id": 1,
  "subcategory_id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "title_en": "Premium Cotton Yarn",
  "title_ar": "خيوط قطنية عالية الجودة",
  "description_en": "Soft cotton yarn for amigurumi",
  "description_ar": "خيوط قطنية ناعمة للأميغورومي",
  "images": [
    "https://bucket.s3.region.amazonaws.com/products/product_1_abc123.jpg"
  ],
  "weight": 0.3,
  "sku": "SKU-1",
  "is_active": true,
  "share_url": "https://teamhirfa.com/product/1",
  "variants": [],
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T10:30:00Z"
}
ℹ Note
Images are automatically uploaded to AWS S3 and local temp files are deleted. The response contains S3 URLs for the uploaded images. share_url is automatically generated and included in all product responses for easy sharing.
PUT /api/v1/shop-owner/products/{product_id} PRIVATE
Update a product. Shop owner can only update their own products. **Supports real file uploads for images (like avatar uploads) - uploads to AWS S3 automatically. You can remove individual images and add new ones.** **SKU cannot be updated - it is auto-generated and read-only.** **Pricing: Two options available - (1) Use variants_json for products with variants, or (2) Use single_price and single_quantity for simple products. If neither is provided, variants remain unchanged.**
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • product_id (required, integer)
📎 Form Data (multipart/form-data)
  • category_id (optional, integer) - Must belong to your store
  • subcategory_id (optional, integer) - Must belong to the selected category
  • title_en (optional, string)
  • title_ar (optional, string)
  • description_en (optional, string)
  • description_ar (optional, string)
  • weight (optional, float) - Weight in kg
  • is_active (optional, boolean)
  • images (optional, array of files) - New product images to add. Files are automatically compressed and uploaded to AWS S3. Existing images are kept unless removed via images_to_remove.
  • images_to_remove (optional, string) - JSON array of image URLs to remove (e.g., ["https://bucket.s3.region.amazonaws.com/products/image1.jpg"]). These images will be deleted from S3.
  • variants_json (optional, string) - JSON array of variants (replaces all existing variants). Use this for products with variants. If provided, it takes precedence over single_price/single_quantity.
  • single_price (optional, float) - Single price for products without variants. Must be provided with single_quantity. Must be greater than 0.
  • single_quantity (optional, integer) - Single quantity for products without variants. Must be provided with single_price. Must be 0 or greater.
📀 Response Example
{
  "id": 1,
  "category_id": 1,
  "subcategory_id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "title_en": "Premium Cotton Yarn - Updated",
  "title_ar": "خيوط قطنية عالية الجودة - محدث",
  "description_en": "Soft cotton yarn for amigurumi - Updated description",
  "description_ar": "خيوط قطنية ناعمة للأميغورومي - وصف محدث",
  "images": [
    "https://bucket.s3.region.amazonaws.com/products/product_1_new1.jpg",
    "https://bucket.s3.region.amazonaws.com/products/product_1_new2.jpg"
  ],
  "weight": 0.35,
  "sku": "SKU-1",
  "is_active": true,
  "share_url": "https://teamhirfa.com/product/1",
  "variants": [],
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T11:45:00Z"
}
ℹ Note
When uploading new images, all old images are deleted from S3. New images are automatically uploaded to AWS S3 and local temp files are deleted. share_url is automatically generated and included in all product responses for easy sharing.
DELETE /api/v1/shop-owner/products/{product_id} PRIVATE
Delete a product. Shop owner can only delete their own products.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • product_id (required, int)
📀 Response Example
"204 No Content"
GET /api/v1/shop-owner/orders PRIVATE
List all orders containing products from the shop owner's store. Can be filtered by status.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
  • status_filter (optional, string: pending|processing|out_for_delivery|delivered|cancelled)
📀 Response Example
"Array of OrderResponse objects with id, user_id, user_name (buyer name), user_email (buyer email), user_phone (buyer phone), order_number, status, total_amount, promo_code_id, discount_amount, shipping_cost, original_total, shipping_address, items array, created_at, updated_at"
GET /api/v1/shop-owner/orders/{order_id} PRIVATE
Get order details. Shop owner can only view orders containing their products.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • order_id (required, int)
📀 Response Example
{
  "id": 123,
  "user_id": 10,
  "user_name": "John Doe",
  "user_email": "john@example.com",
  "user_phone": "+1234567890",
  "order_number": "ORD-20241212-ABC123",
  "status": "pending",
  "total_amount": 120.5,
  "promo_code_id": 5,
  "discount_amount": 10.0,
  "shipping_cost": 5.0,
  "original_total": 125.5,
  "shipping_address": "123 Main St",
  "shipping_city": "Manama",
  "shipping_country": "Bahrain",
  "shipping_postal_code": "12345",
  "notes": "Please deliver before 5 PM",
  "items": [
    {
      "id": 1,
      "order_id": 123,
      "product_id": 45,
      "variant_id": 12,
      "product_title_en": "Product Name",
      "product_title_ar": "اسم المنتج",
      "product_sku": "SKU-001",
      "product_image": "https://teamhirfa.com/static/uploads/products/product_45.jpg",
      "variant_color_en": "Red",
      "variant_color_ar": "أحمر",
      "variant_size_en": "Large",
      "variant_size_ar": "كؚير",
      "quantity": 2,
      "unit_price": 50.0,
      "total_price": 100.0,
      "created_at": "2024-12-12T10:30:00Z"
    }
  ],
  "created_at": "2024-12-12T10:30:00Z",
  "updated_at": "2024-12-12T10:30:00Z"
}
PATCH /api/v1/shop-owner/orders/{order_id}/status PRIVATE
Update order status. Shop owner can only update orders containing their products. Valid statuses: pending, processing, out_for_delivery, delivered, cancelled.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • new_status (required, string: pending|processing|out_for_delivery|delivered|cancelled)
📍 Path Parameters
  • order_id (required, int)
📀 Response Example
{
  "success": true,
  "message": "Order status updated successfully",
  "order_id": 123,
  "status": "processing"
}
GET /api/v1/shop-owner/promo-codes PRIVATE
List all promo codes for the shop owner's store. Can be filtered by is_active.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
  • is_active (optional, bool)
📀 Response Example
"Array of PromoCodeResponse objects with id, code, discount_percentage, start_date, end_date, max_uses, current_uses, is_active, allow_multiple_uses_per_user, created_at, updated_at"
GET /api/v1/shop-owner/promo-codes/{promo_code_id} PRIVATE
Get promo code details by ID.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • promo_code_id (required, int)
📀 Response Example
"PromoCodeResponse object with full promo code details"
POST /api/v1/shop-owner/promo-codes PRIVATE
Create a new promo code for the shop owner's store. Code must be unique for the store. End date must be after start date.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • PromoCodeCreate schema: code (required, string), discount_percentage (required, float), start_date (required, datetime), end_date (required, datetime), max_uses (optional, int), allow_multiple_uses_per_user (optional, bool, default: false), is_active (optional, bool, default: true)
📀 Response Example
"PromoCodeResponse object with created promo code details"
PUT /api/v1/shop-owner/promo-codes/{promo_code_id} PRIVATE
Update a promo code. All fields in PromoCodeUpdate are optional.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • PromoCodeUpdate schema: All fields optional - code, discount_percentage, start_date, end_date, max_uses, allow_multiple_uses_per_user, is_active
📍 Path Parameters
  • promo_code_id (required, int)
📀 Response Example
"PromoCodeResponse object with updated promo code details"
DELETE /api/v1/shop-owner/promo-codes/{promo_code_id} PRIVATE
Delete a promo code.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • promo_code_id (required, int)
📀 Response Example
"204 No Content"
GET /api/v1/shop-owner/courses PRIVATE
List all courses for the shop owner's store. Can be filtered by is_active.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
  • is_active (optional, bool)
📀 Response Example
"Array of CourseResponse objects with id, name_en, name_ar, description_en, description_ar, image, price, teaching_method, scheduling_type, is_active, store_id, time_slots, registration_count, created_at, updated_at"
GET /api/v1/shop-owner/courses/{course_id} PRIVATE
Get course details by ID.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • course_id (required, int)
📀 Response Example
"CourseResponse object with full course details including time_slots"
POST /api/v1/shop-owner/courses PRIVATE
Create a new course for the shop owner's store. teaching_method must be: online, in_person, or both. scheduling_type must be: flexible or fixed_slots. **Supports real file upload for image (like avatar uploads) - uploads to AWS S3 automatically.**
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📎 Form Data (multipart/form-data)
  • name_en (required, string)
  • name_ar (required, string)
  • description_en (optional, string)
  • description_ar (optional, string)
  • price (optional, float, default: 0.0)
  • teaching_method (optional, string, default: 'in_person') - Must be: online, in_person, or both
  • scheduling_type (optional, string, default: 'flexible') - Must be: flexible or fixed_slots
  • master_category_id (optional, integer) - Master category ID
  • is_active (optional, boolean, default: true)
  • image (optional, file) - Course image (jpg, jpeg, png, gif, svg, webp). File is automatically compressed and uploaded to AWS S3.
📀 Response Example
{
  "id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "master_category_id": 1,
  "name_en": "Advanced Crochet Techniques",
  "name_ar": "تقنيات الكرو؎يه المتقدمة",
  "description_en": "Learn advanced crochet techniques...",
  "description_ar": "تعلم تقنيات الكرو؎يه المتقدمة...",
  "image": "https://bucket.s3.region.amazonaws.com/courses/course_1_abc123.jpg",
  "price": 50.0,
  "teaching_method": "in_person",
  "scheduling_type": "flexible",
  "is_active": true,
  "time_slots": [],
  "registration_count": 0,
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T10:30:00Z"
}
ℹ Note
Image is automatically uploaded to AWS S3 and local temp file is deleted. The response contains S3 URL for the uploaded image.
PUT /api/v1/shop-owner/courses/{course_id} PRIVATE
Update a course. Shop owner can only update their own courses. **Supports real file upload for image (like avatar uploads) - uploads to AWS S3 automatically. Old image is deleted from S3 when new one is uploaded.**
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • course_id (required, integer)
📎 Form Data (multipart/form-data)
  • name_en (optional, string)
  • name_ar (optional, string)
  • description_en (optional, string)
  • description_ar (optional, string)
  • price (optional, float)
  • teaching_method (optional, string) - Must be: online, in_person, or both
  • scheduling_type (optional, string) - Must be: flexible or fixed_slots
  • master_category_id (optional, integer) - Master category ID
  • is_active (optional, boolean)
  • image (optional, file) - New course image (replaces existing). File is automatically compressed and uploaded to AWS S3. Old image is deleted.
📀 Response Example
{
  "id": 1,
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "master_category_id": 1,
  "name_en": "Advanced Crochet Techniques - Updated",
  "name_ar": "تقنيات الكرو؎يه المتقدمة - محدث",
  "description_en": "Learn advanced crochet techniques - Updated description",
  "description_ar": "تعلم تقنيات الكرو؎يه المتقدمة - وصف محدث",
  "image": "https://bucket.s3.region.amazonaws.com/courses/course_1_new.jpg",
  "price": 75.0,
  "teaching_method": "both",
  "scheduling_type": "fixed_slots",
  "is_active": true,
  "time_slots": [],
  "registration_count": 5,
  "created_at": "2024-12-08T10:30:00Z",
  "updated_at": "2024-12-08T11:45:00Z"
}
ℹ Note
When uploading a new image, the old image is deleted from S3. New image is automatically uploaded to AWS S3 and local temp file is deleted.
DELETE /api/v1/shop-owner/courses/{course_id} PRIVATE
Delete a course. Shop owner can only delete their own courses.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • course_id (required, int)
📀 Response Example
"204 No Content"
GET /api/v1/shop-owner/courses/{course_id}/registrations PRIVATE
Get registrations for a course. Shop owner can only view registrations for their own courses. Can be filtered by status.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
  • status (optional, string: pending|confirmed|cancelled|completed)
📍 Path Parameters
  • course_id (required, int)
📀 Response Example
{
  "course_id": 1,
  "total_registrations": 5,
  "registrations": [
    {
      "id": 1,
      "course_id": 1,
      "user_id": 10,
      "user": {
        "id": 10,
        "username": "johndoe",
        "email": "john@example.com",
        "full_name": "John Doe",
        "phone": "+1234567890"
      },
      "status": "confirmed",
      "payment_status": "paid",
      "payment_method": "card",
      "amount_paid": 50.0,
      "time_slot_id": 1,
      "time_slot": {
        "id": 1,
        "start_time": "2024-12-15T10:00:00Z",
        "end_time": "2024-12-15T12:00:00Z",
        "max_participants": 20,
        "current_participants": 5
      },
      "preferred_times": null,
      "shop_owner_notes": "Regular attendee",
      "created_at": "2024-12-10T08:00:00Z",
      "updated_at": "2024-12-10T08:00:00Z"
    }
  ]
}
PATCH /api/v1/shop-owner/courses/{course_id}/registrations/{registration_id}/status PRIVATE
Update registration status for a course. Shop owner can only update registrations for their own courses. Valid statuses: pending, confirmed, cancelled, completed.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • status (required, string: pending|confirmed|cancelled|completed)
  • shop_owner_notes (optional, string)
📍 Path Parameters
  • course_id (required, int)
  • registration_id (required, int)
📀 Response Example
{
  "success": true,
  "message": "Registration status updated successfully",
  "registration_id": 1,
  "status": "confirmed"
}
GET /api/v1/shop-owner/notifications PRIVATE
Get all notifications for the shop owner. Notifications are automatically created when new orders are placed or course registrations happen. Can be filtered by read status.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
  • is_read (optional, bool)
📀 Response Example
[
  {
    "id": 1,
    "notification_id": 10,
    "title_en": "New Order: ORD-20241212-ABC123",
    "title_ar": "طلؚ جديد: ORD-20241212-ABC123",
    "description_en": "A new order has been placed with order number ORD-20241212-ABC123. Total amount: 125.50 BHD\n\nBuyer Information:\nName: John Doe\nEmail: john@example.com\nPhone: +1234567890",
    "description_ar": "تم تقديم طلؚ جديد ؚرقم ORD-20241212-ABC123. المؚلغ الإجمالي: 125.50 دينار ؚحريني\n\nمعلومات الم؎تري:\nالاسم: John Doe\nالؚريد الإلكتروني: john@example.com\nالهاتف: +1234567890",
    "is_read": false,
    "read_at": null,
    "created_at": "2024-12-12T10:30:00Z"
  },
  {
    "id": 2,
    "notification_id": 11,
    "title_en": "New Course Registration: Sewing Fundamentals",
    "title_ar": "تسجيل جديد في الدورة: أساسيات الخياطة",
    "description_en": "John Doe has registered for the course 'Sewing Fundamentals'. Registration status: pending",
    "description_ar": "سجل John Doe في الدورة 'أساسيات الخياطة'. حالة التسجيل: قيد الانت؞ار",
    "is_read": false,
    "read_at": null,
    "created_at": "2024-12-12T11:15:00Z"
  }
]
GET /api/v1/shop-owner/notifications/unread-count PRIVATE
Get count of unread notifications for the shop owner. Useful for displaying notification badges.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "unread_count": 5
}
PATCH /api/v1/shop-owner/notifications/{notification_id}/read PRIVATE
Mark a specific notification as read.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • notification_id (required, int) - The user notification ID (not the notification_id)
📀 Response Example
{
  "success": true,
  "message": "Notification marked as read",
  "notification_id": 1
}
PATCH /api/v1/shop-owner/notifications/mark-all-read PRIVATE
Mark all unread notifications as read for the shop owner.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📀 Response Example
{
  "success": true,
  "message": "5 notifications marked as read",
  "count": 5
}
GET /api/v1/shop-owner/chat/conversations PRIVATE
List all chat conversations for the shop owner. Shop owner can only see conversations for their store. All conversations returned will have conversation_type='shop' since the shop owner is viewing conversations for their store. If the shop owner also uses the app as a customer, they should use the regular Chat API endpoints to see conversations where conversation_type='user'.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
📀 Response Example
[
  {
    "id": 1,
    "user_id": 10,
    "user_name": "John Doe",
    "user_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
    "shop_owner_id": 5,
    "shop_owner_name": "Shop Owner Name",
    "shop_owner_avatar": "https://teamhirfa.com/static/uploads/users/user_5_def456.jpg",
    "store_id": "store_a1b2c3d4e5f6g7h8",
    "conversation_type": "shop",
    "last_message": {
      "id": 1,
      "conversation_id": 1,
      "sender_id": 10,
      "sender_name": "John Doe",
      "message": "Hello, I have a question",
      "message_type": "text",
      "media_url": null,
      "file_name": null,
      "is_read": false,
      "created_at": "2024-12-12T10:30:00Z"
    },
    "unread_count": 1,
    "last_message_at": "2024-12-12T10:30:00Z",
    "is_active": true,
    "created_at": "2024-12-12T10:30:00Z",
    "updated_at": "2024-12-12T10:30:00Z"
  }
]
ℹ Note
conversation_type will always be 'shop' for shop owner API endpoints since these are conversations for their store. To see conversations where the shop owner is a customer (conversation_type='user'), use the regular Chat API endpoints.
GET /api/v1/shop-owner/chat/conversations/{conversation_id} PRIVATE
Get a specific conversation. Shop owner can only access conversations for their store. Response includes conversation_type='shop' since this is a conversation for their store.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
{
  "id": 1,
  "user_id": 10,
  "user_name": "John Doe",
  "user_avatar": "https://teamhirfa.com/static/uploads/users/user_10_abc123.jpg",
  "shop_owner_id": 5,
  "shop_owner_name": "Shop Owner Name",
  "shop_owner_avatar": "https://teamhirfa.com/static/uploads/users/user_5_def456.jpg",
  "store_id": "store_a1b2c3d4e5f6g7h8",
  "conversation_type": "shop",
  "last_message": {
    "id": 1,
    "conversation_id": 1,
    "sender_id": 10,
    "sender_name": "John Doe",
    "message": "Hello, I have a question",
    "message_type": "text",
    "media_url": null,
    "file_name": null,
    "is_read": false,
    "created_at": "2024-12-12T10:30:00Z"
  },
  "unread_count": 1,
  "last_message_at": "2024-12-12T10:30:00Z",
  "is_active": true,
  "created_at": "2024-12-12T10:30:00Z",
  "updated_at": "2024-12-12T10:30:00Z"
}
GET /api/v1/shop-owner/chat/conversations/{conversation_id}/messages PRIVATE
Get messages for a conversation. Messages are automatically marked as read when fetched. Shop owner can only access conversations for their store.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
"Array of ChatMessageResponse objects"
POST /api/v1/shop-owner/chat/conversations/{conversation_id}/messages PRIVATE
Send a message in a conversation. Supports text messages, media uploads (images, videos, audio, files), and voice recordings. Message is broadcasted via Pusher in real-time. Shop owner can only send messages in conversations for their store. Either 'message' (text) or 'file' (media) is required. Both can be sent together (text with media).
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • conversation_id (required, int)
📎 Form Data (multipart/form-data)
  • message (optional, string, max 5000 characters) - Text message content
  • file (optional, file) - Media file to upload. Supported types:
  • • Images: .jpg, .jpeg, .png, .gif, .webp
  • • Videos: .mp4, .avi, .mov, .webm
  • • Audio: .mp3, .wav, .ogg, .m4a, .aac, .webm (for voice recordings)
  • • Documents: .pdf, .doc, .docx
  • • Max file size: 50MB
📀 Response Example
{
  "id": 5,
  "conversation_id": 1,
  "sender_id": 5,
  "sender_name": "Shop Owner Name",
  "sender_avatar": "https://teamhirfa.com/static/uploads/users/user_5_def456.jpg",
  "message": "Thank you for your message!",
  "message_type": "text",
  "media_url": null,
  "file_name": null,
  "is_read": false,
  "read_at": null,
  "created_at": "2024-12-12T10:40:00Z"
}
🎧

Live Support System

Real-time support chat between users and admin support team using Pusher. Users can create support tickets, send messages with media attachments, and receive real-time responses from admins. Supports conversation status tracking (open, in_progress, resolved, closed), priority levels (low, normal, high, urgent), and unread message counts. All messages are stored in the database. Supported media types: images (.jpg, .png, .gif, .webp), videos (.mp4, .avi, .mov, .webm), audio files (.mp3, .wav, .ogg, .m4a, .aac), and documents (.pdf, .doc, .docx). Max file size: 50MB.

POST /api/v1/support/conversations PRIVATE
Create a new support conversation. Users can only have one open conversation at a time. If an open conversation exists, returns an error. Optionally include a subject and initial message.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • subject (optional, string, max 255 characters) - Conversation subject/title
  • initial_message (optional, string, max 5000 characters) - Initial message
  • priority (optional, string, default: 'normal') - Priority level: 'low', 'normal', 'high', 'urgent'
📀 Response Example
{
  "id": 1,
  "user_id": 123,
  "user_name": "John Doe",
  "user_avatar": "https://...",
  "admin_id": null,
  "admin_name": null,
  "admin_avatar": null,
  "subject": "Order issue",
  "status": "open",
  "priority": "normal",
  "last_message": null,
  "unread_count": 0,
  "message_count": 0,
  "last_message_at": null,
  "is_active": true,
  "created_at": "2024-12-25T10:00:00Z",
  "updated_at": "2024-12-25T10:00:00Z"
}
GET /api/v1/support/conversations PRIVATE
List all support conversations for the current user. Returns conversations sorted by last message time (most recent first).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 50)
📀 Response Example
[
  {
    "id": 1,
    "user_id": 123,
    "user_name": "John Doe",
    "user_avatar": "https://...",
    "admin_id": 5,
    "admin_name": "Support Admin",
    "admin_avatar": "https://...",
    "subject": "Order issue",
    "status": "in_progress",
    "priority": "normal",
    "last_message": {
      "id": 10,
      "conversation_id": 1,
      "sender_id": 5,
      "sender_type": "admin",
      "sender_name": "Support Admin",
      "message": "We're looking into your issue...",
      "message_type": "text",
      "created_at": "2024-12-25T10:30:00Z"
    },
    "unread_count": 2,
    "message_count": 10,
    "last_message_at": "2024-12-25T10:30:00Z",
    "is_active": true,
    "created_at": "2024-12-25T10:00:00Z",
    "updated_at": "2024-12-25T10:30:00Z"
  }
]
GET /api/v1/support/conversations/{conversation_id} PRIVATE
Get details of a specific support conversation. User can only access their own conversations.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
{
  "id": 1,
  "user_id": 123,
  "user_name": "John Doe",
  "user_avatar": "https://...",
  "admin_id": 5,
  "admin_name": "Support Admin",
  "admin_avatar": "https://...",
  "subject": "Order issue",
  "status": "in_progress",
  "priority": "normal",
  "last_message": {
    "id": 10,
    "conversation_id": 1,
    "sender_id": 5,
    "sender_type": "admin",
    "sender_name": "Support Admin",
    "message": "We're looking into your issue",
    "message_type": "text",
    "created_at": "2024-12-25T10:30:00Z"
  },
  "unread_count": 2,
  "message_count": 10,
  "last_message_at": "2024-12-25T10:30:00Z",
  "is_active": true,
  "created_at": "2024-12-25T10:00:00Z",
  "updated_at": "2024-12-25T10:30:00Z"
}
GET /api/v1/support/conversations/{conversation_id}/messages PRIVATE
Get messages for a support conversation. Messages are returned in chronological order (oldest first). Admin messages are automatically marked as read when fetched. User can only access their own conversations.
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
[
  {
    "id": 1,
    "conversation_id": 1,
    "sender_id": 123,
    "sender_type": "user",
    "sender_name": "John Doe",
    "sender_avatar": "https://...",
    "message": "I need help with my order",
    "message_type": "text",
    "media_url": null,
    "file_name": null,
    "file_size": null,
    "is_read": true,
    "read_at": "2024-12-25T10:05:00Z",
    "created_at": "2024-12-25T10:00:00Z"
  },
  {
    "id": 2,
    "conversation_id": 1,
    "sender_id": 5,
    "sender_type": "admin",
    "sender_name": "Support Admin",
    "sender_avatar": "https://...",
    "message": "We're looking into your issue",
    "message_type": "text",
    "media_url": null,
    "file_name": null,
    "file_size": null,
    "is_read": true,
    "read_at": "2024-12-25T10:05:00Z",
    "created_at": "2024-12-25T10:05:00Z"
  }
]
POST /api/v1/support/conversations/{conversation_id}/messages PRIVATE
Send a message in a support conversation. Supports text messages and media uploads (images, videos, audio, files). Message is broadcasted via Pusher in real-time. Either 'message' (text) or 'file' (media) is required. Both can be sent together (text with media).
🔒 Auth: Bearer Token (User Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • conversation_id (required, int)
📎 Form Data (multipart/form-data)
  • message (optional, string, max 5000 characters) - Text message content
  • file (optional, file) - Media file to upload. Supported types:
  • • Images: .jpg, .jpeg, .png, .gif, .webp
  • • Videos: .mp4, .avi, .mov, .webm
  • • Audio: .mp3, .wav, .ogg, .m4a, .aac
  • • Documents: .pdf, .doc, .docx, .txt
  • • Max file size: 50MB
📀 Response Example
{
  "id": 10,
  "conversation_id": 1,
  "sender_id": 123,
  "sender_type": "user",
  "sender_name": "John Doe",
  "sender_avatar": "https://...",
  "message": "Here's a screenshot of the issue",
  "message_type": "image",
  "media_url": "https://...",
  "file_name": "screenshot.jpg",
  "file_size": 245678,
  "is_read": false,
  "read_at": null,
  "created_at": "2024-12-25T10:40:00Z"
}
GET /api/v1/support/admin/conversations PRIVATE
List all support conversations (admin view). Can filter by status and priority. Returns conversations sorted by last message time (most recent first).
🔒 Auth: Bearer Token (Admin Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • status_filter (optional, string) - Filter by status: 'open', 'in_progress', 'resolved', 'closed'
  • priority_filter (optional, string) - Filter by priority: 'low', 'normal', 'high', 'urgent'
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 50)
📀 Response Example
"Array of SupportConversationResponse objects with user and admin information"
GET /api/v1/support/admin/conversations/{conversation_id} PRIVATE
Get a specific support conversation (admin view). Conversation is automatically assigned to the current admin if unassigned, and status is set to 'in_progress'.
🔒 Auth: Bearer Token (Admin Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
"SupportConversationResponse object with full conversation details"
PUT /api/v1/support/admin/conversations/{conversation_id} PRIVATE
Update a support conversation (status, priority, assignment). Admins can change conversation status, priority, assign to another admin, or update the subject.
🔒 Auth: Bearer Token (Admin Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • status (optional, string) - Status: 'open', 'in_progress', 'resolved', 'closed'
  • priority (optional, string) - Priority: 'low', 'normal', 'high', 'urgent'
  • admin_id (optional, int) - Assign conversation to specific admin
  • subject (optional, string, max 255 characters) - Update conversation subject
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
"SupportConversationResponse object with updated conversation details"
GET /api/v1/support/admin/conversations/{conversation_id}/messages PRIVATE
Get messages for a support conversation (admin view). User messages are automatically marked as read when fetched.
🔒 Auth: Bearer Token (Admin Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, int, default: 0)
  • limit (optional, int, default: 100)
📍 Path Parameters
  • conversation_id (required, int)
📀 Response Example
"Array of SupportMessageResponse objects"
POST /api/v1/support/admin/conversations/{conversation_id}/messages PRIVATE
Send a message in a support conversation (admin). Supports text messages and media uploads. Message is broadcasted via Pusher in real-time. Conversation is automatically assigned to current admin if unassigned.
🔒 Auth: Bearer Token (Admin Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • conversation_id (required, int)
📎 Form Data (multipart/form-data)
  • message (optional, string, max 5000 characters) - Text message content
  • file (optional, file) - Media file to upload. Same supported types as user endpoint.
  • • Max file size: 50MB
📀 Response Example
{
  "id": 11,
  "conversation_id": 1,
  "sender_id": 5,
  "sender_type": "admin",
  "sender_name": "Support Admin",
  "sender_avatar": "https://...",
  "message": "We've resolved your issue",
  "message_type": "text",
  "media_url": null,
  "file_name": null,
  "file_size": null,
  "is_read": false,
  "read_at": null,
  "created_at": "2024-12-25T10:45:00Z"
}
POST /api/v1/pusher/auth PRIVATE
Authenticate Pusher private channel subscription for support chat. This endpoint is called automatically by Pusher when subscribing to private channels. Channel format: private-support-{conversation_id}. Supports both user tokens (Bearer) and admin cookies.
🔒 Auth: Bearer Token or Admin Cookie
📋 Headers
  • Authorization: Bearer {token} (for users) or Cookie: admin_access_token (for admins)
📎 Form Data (multipart/form-data)
  • socket_id (required, string) - Pusher socket ID
  • channel_name (required, string) - Channel name: private-support-{conversation_id}
📀 Response Example
{
  "auth": "e18960692a059cc4fafb:abc123def456..."
}
🏷

Store Discounts (Seller Panel)

Exclusive endpoints for shop owners to manage product discounts and sales. Allows creating percentage-based discounts and applying them to specific products. These discounts are automatically reflected in the e-commerce APIs.

GET /api/v1/shop-owner/discounts PRIVATE
List all discount groups created by the shop owner.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
🔍 Query Parameters
  • skip (optional, default: 0)
  • limit (optional, default: 100)
📀 Response Example
[
  {
    "id": 1,
    "store_id": "store_a1b2",
    "name": "Summer Sale",
    "discount_percentage": 15.0,
    "is_active": true,
    "created_at": "2024-12-25T10:00:00Z",
    "updated_at": "2024-12-25T10:00:00Z"
  }
]
POST /api/v1/shop-owner/discounts PRIVATE
Create a new discount group and associate it with products.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • name (required, string) - Name of the discount
  • discount_percentage (required, float) - Percentage value (0-100)
  • is_active (optional, boolean, default: true)
  • product_ids (optional, array of integers) - IDs of products to apply this discount to
📀 Response Example
{
  "id": 1,
  "store_id": "store_a1b2",
  "name": "Summer Sale",
  "discount_percentage": 15.0,
  "is_active": true,
  "created_at": "2024-12-25T10:00:00Z",
  "updated_at": "2024-12-25T10:00:00Z"
}
PUT /api/v1/shop-owner/discounts/{discount_id} PRIVATE
Update an existing discount group. Providing product_ids will overwrite the previous associations.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📊 Body Parameters
  • name (optional, string)
  • discount_percentage (optional, float)
  • is_active (optional, boolean)
  • product_ids (optional, array of integers)
📍 Path Parameters
  • discount_id (required, integer)
📀 Response Example
{
  "id": 1,
  "store_id": "store_a1b2",
  "name": "Updated Sale Name",
  "discount_percentage": 20.0,
  "is_active": true,
  "created_at": "2024-12-25T10:00:00Z",
  "updated_at": "2024-12-25T11:00:00Z"
}
DELETE /api/v1/shop-owner/discounts/{discount_id} PRIVATE
Delete a discount group.
🔒 Auth: Bearer Token (Shop Owner Required)
📋 Headers
  • Authorization: Bearer {token}
📍 Path Parameters
  • discount_id (required, integer)
📀 Response Example
{
  "success": true,
  "message": "Discount deleted successfully"
}