Product Management

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-07-07
Version: v3.0

Short Description: The Product Management API provides comprehensive functionality for managing products within shops on the NextGate platform. It supports group buying, installment payment plans, color variations, specifications, digital product file delivery, product preview media (video, PDF, 3D, image), and comprehensive search/filter capabilities with role-based access control.

Hints:

All product media (product images/videos, color images, digital files, previews) go through FileThunder — no raw URLs are ever sent by the client:

  1. Client calls POST api/v1/files/request-upload with a context (PRODUCT_IMAGE, PRODUCT_VIDEO, DIGITAL_PRODUCT, PRODUCT_PREVIEW_IMAGE/_VIDEO/_DOCUMENT) → gets back a presigned upload URL and a fileId
  2. Client PUTs the raw file bytes directly to that URL (not through this API)
  3. Client references that fileId in the relevant product field (mediaFileIds, colors[].imageFileIds, digitalFileId, preview's ftFileId, ...) — the file is still processing (virus scan + variant generation) at this point, so validation only checks Redis-cached ownership/context, not readiness
  4. FileThunder finishes processing asynchronously and webhooks back into the product/preview/digital-file "updater" services, which upgrade the stored record to READY with resolved variants (a map of variant name → CDN URL)

Endpoints

1. Create Product

Purpose: Creates a new product in a shop, supporting group buying, color variations, specifications, and digital product rules.

Endpoint: POST api/v1/e-commerce/shops/{shopId}/products

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Request Headers:

Header Type Required Description
Authorization string Yes Bearer token
Content-Type string Yes application/json

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Query Parameters:

Parameter Type Required Default Description
action ReqAction Yes SAVE_DRAFT or SAVE_PUBLISH

Request Body Parameters:

Parameter Type Required Description Validation
productType ProductType Yes PHYSICAL or DIGITAL Required
productName string Yes Unique name within shop Min: 2, Max: 100 chars
productDescription string Yes Detailed description Min: 10, Max: 1000 chars
price decimal Yes Selling price Min: 0.01, max 8 digits + 2 decimal places
stockQuantity integer Yes Available stock Min: 0
categoryId UUID Yes Product category Must exist and be active
comparePrice decimal No Original price for discount display Must be > price if provided
lowStockThreshold integer No Low stock alert threshold Min: 1, Max: 1000, Default: 5
condition ProductCondition No Product condition NEW, USED_LIKE_NEW, USED_GOOD, USED_FAIR, REFURBISHED, FOR_PARTS
status ProductStatus No Initial status (overridden by action) Default: ACTIVE
mediaFileIds array<UUID> Yes FileThunder file IDs for product images/videos, in display order Not null; each ID must belong to the caller and have been uploaded with context PRODUCT_IMAGE or PRODUCT_VIDEO
specifications object No Key-value specs Key max 100 chars, Value max 500 chars
colors array No Color variations See Color object below
minOrderQuantity integer No Minimum order qty Min: 1, Default: 1
maxOrderQuantity integer No Maximum order qty per order Min: 1, must be ≥ minOrderQuantity
groupBuyingEnabled boolean No Enable group buying Default: false
groupMaxSize integer No Maximum group participants Min: 2, required if groupBuyingEnabled
groupPrice decimal No Discounted group price Must be < price
groupTimeLimitHours integer No Group formation time limit Min: 1, Max: 8760
digitalFileId UUID Conditional FileThunder file ID of the downloadable file Required when productType=DIGITAL; uploaded with context DIGITAL_PRODUCT
previewFileId UUID No FileThunder file ID of a public preview (DIGITAL only) Uploaded with context PRODUCT_PREVIEW_IMAGE/_VIDEO/_DOCUMENT
previewDownloadable boolean No Whether buyers can download the preview file itself Default: false
downloadExpiryDays integer No Download link expiry (DIGITAL only) Min: 1, Default: 7
maxDownloadsPerBuyer integer No Download attempts per buyer (DIGITAL only) Min: 1
maxQuantityForDigital integer No Purchase cap per buyer (DIGITAL only) Min: 1

Color Object:

Field Type Required Validation
name string Yes Max: 50 chars
hex string Yes Valid #RRGGBB format
imageFileIds array<UUID> No FileThunder file IDs for this color's images — stored as-is, not validated/resolved against FileThunder (see top-of-doc note)
priceAdjustment decimal No Min: 0.0, Default: 0

Request JSON Sample (PHYSICAL):

{
  "productType": "PHYSICAL",
  "productName": "iPhone 15 Pro Max 256GB",
  "productDescription": "The most advanced iPhone featuring the A17 Pro chip and titanium design.",
  "price": 1199.00,
  "comparePrice": 1299.00,
  "stockQuantity": 25,
  "lowStockThreshold": 5,
  "categoryId": "123e4567-e89b-12d3-a456-426614174000",
  "condition": "NEW",
  "mediaFileIds": [
    "f11e4567-e89b-12d3-a456-426614174111",
    "f22e4567-e89b-12d3-a456-426614174222"
  ],
  "specifications": {
    "Display": "6.7-inch Super Retina XDR OLED",
    "Chip": "A17 Pro"
  },
  "colors": [
    {
      "name": "Natural Titanium",
      "hex": "#F5F5DC",
      "imageFileIds": ["f33e4567-e89b-12d3-a456-426614174333"],
      "priceAdjustment": 0.00
    }
  ],
  "minOrderQuantity": 1,
  "maxOrderQuantity": 3,
  "groupBuyingEnabled": true,
  "groupMaxSize": 50,
  "groupPrice": 1099.00,
  "groupTimeLimitHours": 72
}

Request JSON Sample (DIGITAL):

{
  "productType": "DIGITAL",
  "productName": "UI Design Kit Pro",
  "productDescription": "A comprehensive Figma component library with 500+ components.",
  "price": 49.00,
  "stockQuantity": 1000,
  "categoryId": "123e4567-e89b-12d3-a456-426614174000",
  "mediaFileIds": ["f44e4567-e89b-12d3-a456-426614174444"],
  "digitalFileId": "f55e4567-e89b-12d3-a456-426614174555",
  "previewFileId": "f66e4567-e89b-12d3-a456-426614174666",
  "previewDownloadable": false,
  "downloadExpiryDays": 30,
  "maxDownloadsPerBuyer": 5,
  "maxQuantityForDigital": 1
}

Response JSON Sample:

{
  "success": true,
  "message": "Product created successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productSlug": "iphone-15-pro-max-256gb",
    "productName": "iPhone 15 Pro Max 256GB",
    "status": "ACTIVE",
    "shopSlug": "techstore-pro"
  }
}

Business Rules:

Error Responses:


2. Update Product

Purpose: Updates an existing product. Only provided fields are updated.

Endpoint: PUT api/v1/e-commerce/shops/{shopId}/products/{productId}

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product

Query Parameters:

Parameter Type Required Default Description
action ReqAction Yes SAVE_DRAFT or SAVE_PUBLISH

Request Body Parameters (all optional):

Parameter Type Description Validation
productName string Updated name Min: 2, Max: 100 chars
productDescription string Updated description Min: 10, Max: 1000 chars
price decimal Updated selling price Min: 0.01
comparePrice decimal Updated compare price Must be > price
stockQuantity integer Updated stock Min: 0
lowStockThreshold integer Updated low stock threshold Min: 1, Max: 1000
condition ProductCondition Updated condition See enum values
status ProductStatus Updated status Overridden by action
urgencyTag UrgencyTag Urgency badge on product NONE, NEW_ARRIVAL, LIMITED_EDITION, LIMITED_OFFER, FEW_REMAINS
categoryId UUID Updated category Must exist and be active
mediaFileIds array<UUID> Updated list of FileThunder file IDs for product images/videos, in order Same validation as Create; merged, not replaced — see note below
specifications object Updated specifications Completely replaces existing
colors array Updated color variations Completely replaces existing — see Color object in Create Product (same imageFileIds shape)
minOrderQuantity integer Updated min order qty Min: 1
maxOrderQuantity integer Updated max order qty Min: 1, must be ≥ min
groupBuyingEnabled boolean Enable/disable group buying
groupMaxSize integer Updated group max Min: 2
groupPrice decimal Updated group price Must be < price
groupTimeLimitHours integer Updated group time limit Min: 1, Max: 8760
installmentEnabled boolean Enable/disable installment feature toggle
maxQuantityForInstallment integer Max qty a buyer can purchase on installment Min: 1
showStockAvailableToPublic boolean Show available stock count publicly
showSoldCountToPublic boolean Show sold count publicly
clearPreview boolean ⚠️ Currently only clears legacy preview fields — does not remove an active preview (see note below)
previewDownloadable boolean Allow/disallow viewers from downloading the preview file

Notes on mediaFileIds:

Note on clearPreview: this flag only nulls the legacy previewType/previewUrl entity fields, which are already dead (never populated by the current preview pipeline). It does not clear previewFtFileId/previewContext/previewStatus/previewVariants. To actually remove a product's preview, call DELETE .../products/{productId}/preview (17c).

Response JSON Sample:

{
  "success": true,
  "message": "Product updated successfully and published",
  "data": {
    "...": "full ProductDetailedResponse — same shape as Get Product Detailed (#6), rebuilt from the saved product"
  }
}

Error Responses:


3. Publish Product

Purpose: Publishes a draft product making it active and publicly available.

Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/publish

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the draft product

Response JSON Sample:

{
  "success": true,
  "message": "Product 'iPhone 15 Pro Max' published successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max",
    "status": "ACTIVE",
    "publishedAt": "2026-05-19T14:45:00Z"
  }
}

Publishing Requirements:

Error Responses:


4. Delete Product

Purpose: Deletes a product. Draft products are hard-deleted; published products are soft-deleted.

Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product

Response JSON Sample (Soft Delete):

{
  "success": true,
  "message": "Product 'iPhone 15 Pro Max' has been deleted and will be permanently removed after 30 days",
  "data": {
    "productName": "iPhone 15 Pro Max",
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "previousStatus": "ACTIVE",
    "deletedAt": "2026-05-19T15:00:00Z",
    "deletionType": "SOFT_DELETE"
  }
}

Response JSON Sample (Hard Delete):

{
  "success": true,
  "message": "Draft product 'iPhone 15 Pro Max' has been permanently deleted",
  "data": null
}

Deletion Logic:

Error Responses:


5. Restore Product

Purpose: Restores a soft-deleted product back to draft status.

Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/restore

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the soft-deleted product

Response JSON Sample:

{
  "success": true,
  "message": "Product 'iPhone 15 Pro Max' has been restored successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max",
    "status": "DRAFT",
    "restoredAt": "2026-05-19T15:30:00Z",
    "note": "Product restored as draft. Publish to make it active again."
  }
}

Error Responses:


6. Get Product Detailed (Owner/Admin View)

Purpose: Retrieves comprehensive product details including all management information.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}/detailed

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product

Response JSON Sample (ProductDetailedResponse):

{
  "success": true,
  "message": "Product details retrieved successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max 512GB",
    "productType": "PHYSICAL",
    "productSlug": "iphone-15-pro-max-512gb",
    "productDescription": "The most advanced iPhone ever...",
    "productMedia": [
      {
        "fileId": "f11e4567-e89b-12d3-a456-426614174111",
        "mediaType": "IMAGE",
        "status": "READY",
        "variants": {
          "original": "https://cdn.example.com/products/f11e4567/original.jpg",
          "thumb": "https://cdn.example.com/products/f11e4567/thumb.jpg"
        },
        "mimeType": "image/jpeg",
        "order": 0
      }
    ],
    "price": 1199.00,
    "comparePrice": 1299.00,
    "discountPercentage": 7.69,
    "isOnSale": true,
    "stockQuantity": 25,
    "lowStockThreshold": 5,
    "isInStock": true,
    "isLowStock": false,
    "stockInfo": {
      "stockTotal": 25,
      "stockHeld": 2,
      "stockAvailable": 23,
      "soldCount": 140,
      "holdBreakdown": {
        "heldForCheckout": 1,
        "heldForGroupPurchase": 1,
        "heldForInstallment": 0,
        "nearestCheckoutExpiry": "2026-07-08T16:00:00"
      },
      "showStockAvailableToPublic": true,
      "showSoldCountToPublic": true
    },
    "condition": "NEW",
    "status": "ACTIVE",
    "urgencyTag": "NONE",
    "shopId": "123e4567-e89b-12d3-a456-426614174000",
    "shopName": "TechStore Pro",
    "shopSlug": "techstore-pro",
    "categoryId": "789e0123-e89b-12d3-a456-426614174002",
    "categoryName": "Smartphones",
    "createdAt": "2026-05-19T10:30:00",
    "updatedAt": "2026-05-19T14:30:00",
    "createdBy": "111e4567-e89b-12d3-a456-426614174aaa",
    "editedBy": "111e4567-e89b-12d3-a456-426614174aaa",
    "specifications": {
      "Display": "6.7-inch Super Retina XDR OLED",
      "Chip": "A17 Pro"
    },
    "hasSpecifications": true,
    "specificationCount": 2,
    "colors": [
      {
        "name": "Natural Titanium",
        "hex": "#F5F5DC",
        "images": ["f33e4567-e89b-12d3-a456-426614174333"],
        "priceAdjustment": 0.00,
        "finalPrice": 1199.00,
        "hasExtraFee": false,
        "extraFeeReason": null
      }
    ],
    "hasMultipleColors": false,
    "colorCount": 1,
    "priceRange": {
      "minPrice": 1199.00,
      "maxPrice": 1199.00,
      "priceStartsFrom": 1199.00,
      "hasPriceVariations": false
    },
    "orderingLimits": {
      "minOrderQuantity": 1,
      "maxOrderQuantity": 3,
      "canOrderQuantity": 3,
      "maxAllowedQuantity": 3,
      "hasOrderingLimits": true
    },
    "groupBuying": {
      "isEnabled": true,
      "maxGroupSize": 50,
      "groupPrice": 1099.00,
      "groupDiscount": 100.00,
      "groupDiscountPercentage": 8.34,
      "timeLimitHours": 72,
      "canJoinGroup": true
    },
    "installmentOptions": {
      "isEnabled": true,
      "isAvailable": true,
      "downPaymentRequired": true,
      "minDownPaymentPercentage": 20.00,
      "plans": [],
      "eligibilityStatus": "ELIGIBLE",
      "creditCheckRequired": false
    },
    "purchaseOptions": {
      "canBuyNow": true,
      "canJoinGroup": true,
      "canPayInstallment": true,
      "recommendedOption": "GROUP_BUYING",
      "bestDeal": { "option": "GROUP_BUYING", "savings": 100.00, "finalPrice": 1099.00 }
    },
    "preview": {
      "type": null,
      "variants": { "thumb": "https://cdn.example.com/preview/f66e4567/thumb.jpg" },
      "downloadable": false
    }
  }
}

Notes on media/preview in this response:

Error Responses:


7. Get Shop Products (Management View)

Purpose: Retrieves all products for a shop with summary statistics.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/all

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Response JSON Sample:

{
  "success": true,
  "message": "Retrieved 47 products from shop: TechStore Pro",
  "data": {
    "shop": {
      "shopId": "123e4567-e89b-12d3-a456-426614174000",
      "shopName": "TechStore Pro",
      "isVerified": true,
      "isMyShop": true
    },
    "summary": {
      "totalProducts": 47,
      "activeProducts": 35,
      "draftProducts": 8,
      "outOfStockProducts": 4,
      "lowStockProducts": 6,
      "productsWithGroupBuying": 18,
      "productsWithInstallments": 25
    },
    "products": [
      {
        "productId": "456e7890-e89b-12d3-a456-426614174001",
        "productName": "iPhone 15 Pro Max 512GB",
        "productType": "PHYSICAL",
        "productSlug": "iphone-15-pro-max-512gb",
        "productPrimaryMedia": {
          "fileId": "f11e4567-e89b-12d3-a456-426614174111",
          "mediaType": "IMAGE",
          "status": "READY",
          "thumbUrl": "https://cdn.example.com/products/f11e4567/thumb.jpg",
          "mimeType": "image/jpeg"
        },
        "price": 1199.00,
        "stockQuantity": 25,
        "status": "ACTIVE",
        "isInStock": true,
        "hasGroupBuying": true,
        "hasInstallments": true,
        "createdAt": "2026-05-19T10:30:00Z"
      }
    ],
    "totalProducts": 47
  }
}

Note: each product card is a ProductSummaryResponseproductPrimaryMedia is a PrimaryMediaResponse (fileId, mediaType, status, thumbUrl, mimeType), or null if no media was uploaded at all. It resolves to the product's first IMAGE item; if the product has no image (e.g. video-only), it falls back to that video's poster/thumb frame instead — so mediaType here can be VIDEO even though thumbUrl is a still image. thumbUrl is only populated once the underlying file's status is READY; while PROCESSING, productPrimaryMedia still comes back (so the client can show a placeholder) but thumbUrl is null. Every response type that surfaces a card thumbnail (ProductSummaryResponse, MarketplaceProductResponse, cart/wishlist items, installment plan's ProductBasicInfo) uses this same shape. Similarly, the accompanying shop logo is always ShopLogoPrimaryMedia (fileId, status, thumbUrl, mimeType) under the field name shopLogoMedia in these same list/card responses — full ShopMedia (with the raw variants map) is reserved for single-entity detail views only (ShopResponse, ProductPublicResponse).

Error Responses:


8. Get Shop Products Paginated (Management View)

Purpose: Retrieves shop products with pagination for management dashboard.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/all-paged

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Query Parameters:

Parameter Type Required Default Description
page integer No 1 Page number (1-indexed)
size integer No 10 Items per page (max: 100)

Response JSON Sample:

{
  "success": true,
  "message": "Retrieved 10 products from shop: TechStore Pro (Page 1 of 5)",
  "data": {
    "contents": { "...same structure as /all..." },
    "currentPage": 1,
    "pageSize": 10,
    "totalElements": 47,
    "totalPages": 5,
    "hasNext": true,
    "hasPrevious": false
  }
}

Error Responses:


9. Get Public Product by ID

Purpose: Retrieves a single active product for public viewing.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}

Access Level: 🌐 Public

Path Parameters:

Parameter Type Required Description
shopId UUID Yes Shop must be active and approved
productId UUID Yes Product must be active

Response JSON Sample (ProductPublicResponse):

{
  "success": true,
  "message": "Product retrieved successfully",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "productName": "iPhone 15 Pro Max 512GB",
    "productSlug": "iphone-15-pro-max-512gb",
    "productDescription": "The most advanced iPhone ever...",
    "productMedia": [
      {
        "fileId": "f11e4567-e89b-12d3-a456-426614174111",
        "mediaType": "IMAGE",
        "status": "READY",
        "variants": {
          "original": "https://cdn.example.com/products/f11e4567/original.jpg",
          "thumb": "https://cdn.example.com/products/f11e4567/thumb.jpg"
        },
        "mimeType": "image/jpeg",
        "order": 0
      }
    ],
    "price": 1199.00,
    "comparePrice": 1299.00,
    "discountPercentage": 7.69,
    "isOnSale": true,
    "groupPurchasePrice": 1099.00,
    "isInStock": true,
    "isLowStock": false,
    "stockInfo": {
      "stockAvailable": 23,
      "soldCount": 140
    },
    "condition": "NEW",
    "urgencyTag": "NONE",
    "shopId": "123e4567-e89b-12d3-a456-426614174000",
    "shopName": "TechStore Pro",
    "shopSlug": "techstore-pro",
    "shopLogoMedia": {
      "fileId": "s11e4567-e89b-12d3-a456-426614174001",
      "status": "READY",
      "variants": { "thumb": "https://cdn.example.com/shops/s11e4567/thumb.jpg" },
      "mimeType": "image/png"
    },
    "categoryId": "789e0123-e89b-12d3-a456-426614174002",
    "categoryName": "Smartphones",
    "specifications": { "Display": "6.7-inch OLED" },
    "hasSpecifications": true,
    "colors": [
      {
        "name": "Natural Titanium",
        "hex": "#F5F5DC",
        "images": ["f33e4567-e89b-12d3-a456-426614174333"],
        "priceAdjustment": 0.00,
        "finalPrice": 1199.00
      }
    ],
    "hasMultipleColors": false,
    "priceRange": {
      "minPrice": 1199.00,
      "maxPrice": 1199.00,
      "hasPriceVariations": false
    },
    "groupBuying": {
      "isAvailable": true,
      "maxGroupSize": 50,
      "groupPrice": 1099.00,
      "groupDiscount": 100.00,
      "groupDiscountPercentage": 8.34,
      "timeLimitHours": 72
    },
    "installmentOptions": {
      "isAvailable": true,
      "downPaymentRequired": true,
      "minDownPaymentPercentage": 20.00,
      "plans": [
        {
          "installmentPlanId": "aa1e4567-e89b-12d3-a456-426614174aaa",
          "duration": 6,
          "interval": "MONTHLY",
          "interestRate": 0.00,
          "description": "6-Month Interest-Free"
        }
      ]
    },
    "previewType": null,
    "previewUrl": null,
    "previewDownloadable": true,
    "isInCart": null,
    "cartQuantity": null,
    "isInWishlist": null,
    "wishlistItemId": null,
    "wishlistGroupId": null,
    "wishlistGroupName": null,
    "createdAt": "2026-05-19T10:30:00"
  }
}

Notes:

Error Responses:


10. Get Public Shop Products

Purpose: Retrieves all active products from a shop for public browsing.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/public-view/all

Access Level: 🌐 Public

Path Parameters:

Parameter Type Required Description
shopId UUID Yes Shop must be active and approved

Response JSON Sample:

{
  "success": true,
  "message": "Retrieved 23 products from TechStore Pro",
  "data": {
    "shop": {
      "shopId": "123e4567-e89b-12d3-a456-426614174000",
      "shopName": "TechStore Pro",
      "isVerified": true
    },
    "products": [
      {
        "productId": "456e7890-e89b-12d3-a456-426614174001",
        "productName": "iPhone 15 Pro Max",
        "productPrimaryMedia": {
          "fileId": "f11e4567-e89b-12d3-a456-426614174111",
          "mediaType": "IMAGE",
          "status": "READY",
          "thumbUrl": "https://cdn.example.com/products/f11e4567/thumb.jpg",
          "mimeType": "image/jpeg"
        },
        "price": 1199.00,
        "isOnSale": true,
        "isInStock": true,
        "hasGroupBuying": true,
        "hasInstallments": true
      }
    ],
    "totalProducts": 23
  }
}

Error Responses:


11. Get Public Shop Products Paginated

Purpose: Retrieves active products from a shop with pagination for public browsing.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/public-view/all-paged

Access Level: 🌐 Public

Path Parameters:

Parameter Type Required Description
shopId UUID Yes Shop must be active and approved

Query Parameters:

Parameter Type Required Default Description
page integer No 1 Page number (1-indexed)
size integer No 10 Items per page (max: 50)

Error Responses:


12. Search Products

Purpose: Searches products within a shop using multi-word query matching across multiple fields.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/search

Access Level: 🌐 Public (Enhanced features for authenticated users)

Authentication: Optional Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Query Parameters:

Parameter Type Required Default Description
q string Yes Search query (min: 2, max: 100 chars)
status ProductStatus[] No ACTIVE Statuses to search (owners/admins only for non-ACTIVE)
page integer No 1 Page number
size integer No 10 Items per page (max: 50)
sortBy string No relevance relevance, createdAt, updatedAt, productName, price, stockQuantity, brand
sortDir string No desc asc or desc

Search Behavior:

Feature Description
Multi-word Searches for products containing ALL words
Partial match "iph" matches "iPhone"
Cross-field Matches against name, description, brand, tags, specifications
Case-insensitive "APPLE" matches "apple"

User Access:

User Type Searchable Statuses
Public / Authenticated ACTIVE only
Shop Owner / Admin All statuses

Response JSON Sample:

{
  "success": true,
  "message": "Found 12 products matching 'iphone'",
  "data": {
    "contents": {
      "shop": { "shopId": "...", "shopName": "TechStore Pro" },
      "products": [ { "...product summary fields..." } ],
      "totalProducts": 12,
      "searchMetadata": {
        "searchQuery": "iphone",
        "searchedStatuses": ["ACTIVE"],
        "userType": "PUBLIC"
      }
    },
    "currentPage": 1,
    "pageSize": 10,
    "totalElements": 12,
    "totalPages": 2,
    "hasNext": true,
    "hasPrevious": false
  }
}

Error Responses:


13. Advanced Product Filter

Purpose: Filters products using multiple criteria with combined AND/OR logic.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/advanced-filter

Access Level: 🌐 Public (Enhanced features for authenticated users)

Authentication: Optional Bearer Token

Path Parameters:

Parameter Type Required Description
shopId UUID Yes ID of the shop

Query Parameters:

Parameter Type Required Default Description
minPrice decimal No Minimum price
maxPrice decimal No Maximum price (must be ≥ minPrice)
condition ProductCondition No Condition filter
categoryId UUID No Category filter
inStock boolean No Filter by availability
onSale boolean No Filter by sale status
hasGroupBuying boolean No Filter by group buying
hasInstallments boolean No Filter by installments
hasMultipleColors boolean No Filter by color variations
status ProductStatus[] No ACTIVE Status filter (owners/admins for non-ACTIVE)
page integer No 1 Page number
size integer No 10 Items per page (max: 50)
sortBy string No createdAt createdAt, updatedAt, productName, price, stockQuantity
sortDir string No desc asc or desc

Filter Logic:

Filter Type Logic
Price range AND (minPrice AND maxPrice)
Feature flags AND (all must match)
Multiple statuses OR

Error Responses:


14. Get Public Product by Slug

Purpose: Retrieves a single active product by its slug (same response shape as Get Public Product by ID).

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/find-by-slug/{slug}

Access Level: 🌐 Public

Path Parameters:

Parameter Type Required Description
shopId UUID Yes Shop must be active and approved
slug string Yes Product slug

Error Responses:


15. Installment Plan Config

Purpose: CRUD for installment plans attached to a product. Plans are created separately after the product, and linked to it by productId.

Base URL: api/v1/e-commerce/products/{shopId}/{productId}/installment-plans

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters (shared):

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product

15a. Create Installment Plan

Endpoint: POST api/v1/e-commerce/products/{shopId}/{productId}/installment-plans

Request Body:

Parameter Type Required Description Validation
planName string Yes Display name for the plan Min: 3, Max: 100 chars
paymentFrequency PaymentFrequency Yes Payment interval DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS
customFrequencyDays integer Conditional Days between payments Required when paymentFrequency=CUSTOM_DAYS, min: 1
numberOfPayments integer Yes Total number of payments Min: 2, Max: 120
apr decimal Yes Annual percentage rate Min: 0.0, Max: 36.0, 2 decimal places
minDownPaymentPercent integer Yes Minimum down payment % Min: 10, Max: 50
fulfillmentTiming FulfillmentTiming Yes When to ship IMMEDIATE (ship after down payment), AFTER_PAYMENT (layaway — ship after final payment)
displayOrder integer No Sort order in UI Min: 0, Default: 0
isFeatured boolean No Highlight as recommended plan Default: false
isActive boolean No Plan is available to buyers Default: true

Request JSON Sample:

{
  "planName": "6-Month Interest-Free",
  "paymentFrequency": "MONTHLY",
  "numberOfPayments": 6,
  "apr": 0.00,
  "minDownPaymentPercent": 20,
  "fulfillmentTiming": "IMMEDIATE",
  "displayOrder": 1,
  "isFeatured": true,
  "isActive": true
}

Error Responses:


15b. Get All Installment Plans

Endpoint: GET api/v1/e-commerce/products/{shopId}/{productId}/installment-plans

Returns a list of all installment plans for the product.

Error Responses:


15c. Get Installment Plan by ID

Endpoint: GET api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}

Additional Path Parameter:

Parameter Type Description
planId UUID ID of the installment plan

15d. Update Installment Plan

Endpoint: PUT api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}

All fields are optional — only provided fields are updated. Same field structure as Create.


15e. Delete Installment Plan

Endpoint: DELETE api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}


15f. Activate / Deactivate Plan

Activate: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/activate

Deactivate: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/deactivate

Toggles isActive on the plan without changing any other fields.


Endpoint: PATCH api/v1/e-commerce/products/{shopId}/{productId}/installment-plans/{planId}/set-featured

Marks the specified plan as the featured (recommended) plan for this product.


16. Digital File Management

Purpose: Manages the single downloadable file for a DIGITAL product. The client uploads through the generic FileThunder pipeline first (see the file/media note at the top of this doc), then registers the resulting ftFileId with the product here. There is exactly one file per product — registering a new ftFileId replaces the current pending/active file.

Base URL: api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-file

Access Level: 🔒 Protected (Requires shop owner or system admin role)

Authentication: Bearer Token

Path Parameters (shared):

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the digital product

16a. Register File

Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-file/register

Request Body:

Parameter Type Required Description
ftFileId UUID Yes FileThunder file ID from POST api/v1/files/request-upload (context DIGITAL_PRODUCT), owned by the caller

Request JSON Sample:

{
  "ftFileId": "f55e4567-e89b-12d3-a456-426614174555"
}

Response JSON Sample (DigitalFileResponse):

{
  "success": true,
  "message": "File registered — scanning in progress",
  "data": {
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "fileName": null,
    "contentType": null,
    "fileSize": null,
    "fileVersion": 0,
    "status": null,
    "uploadedAt": null,
    "pending": {
      "status": "PROCESSING",
      "failureReason": null,
      "uploadedAt": "2026-07-07T10:45:00"
    }
  }
}

Upload Flow:

  1. POST api/v1/files/request-upload with context: "DIGITAL_PRODUCT" → receive uploadUrl and fileId
  2. PUT {uploadUrl} with the raw file bytes (client-to-storage, not through this API)
  3. POST .../digital-file/register with { "ftFileId": fileId } — do this immediately, you don't need to wait for the upload/scan to finish
  4. Poll GET .../digital-file until status (or pending.status) is READY; FAILED means re-upload and register again

Notes:

Error Responses:


16b. Get Product File

Purpose: Retrieves the current state of the product's digital file (active + any pending replacement).

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-file

Returns the same DigitalFileResponse shape as 16a.

Error Responses:


16c. Delete File

Purpose: Removes the digital file record from the product entirely (both active and pending state).

Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-file

Response JSON Sample:

{
  "success": true,
  "message": "Digital file removed",
  "data": null
}

Error Responses:


17. Product Preview Management

Purpose: Manages a single preview file per product — a teaser shown to buyers before purchase (works for any productType, not just DIGITAL). Distinct from the private Digital File. Supports VIDEO, PDF, 3D model, and IMAGE previews. Like all product media, previews go through the generic FileThunder upload pipeline (see the note at the top of this doc) — there is no preview-specific presign/confirm flow anymore.

Base URL: api/v1/e-commerce/shops/{shopId}/products/{productId}/preview

Access Level: 🔒 Protected (Requires shop owner or system admin role) — except 17d, which only requires authentication (any buyer)

Authentication: Bearer Token

Path Parameters (shared):

Parameter Type Required Description
shopId UUID Yes ID of the shop
productId UUID Yes ID of the product (any type — PHYSICAL or DIGITAL)

17a. Register Preview

Endpoint: POST api/v1/e-commerce/shops/{shopId}/products/{productId}/preview/register

Request Body (RegisterPreviewRequest):

Parameter Type Required Description
ftFileId UUID Yes FileThunder file ID, owned by the caller
previewDownloadable boolean No Whether buyers can download the raw preview file itself. Default: false (view/stream only)

Request JSON Sample:

{
  "ftFileId": "f66e4567-e89b-12d3-a456-426614174666",
  "previewDownloadable": false
}

Response JSON Sample (PreviewResponse):

{
  "success": true,
  "message": "Preview registered — processing in background",
  "data": {
    "previewFtFileId": "f66e4567-e89b-12d3-a456-426614174666",
    "previewContext": "PRODUCT_PREVIEW_VIDEO",
    "previewStatus": "PROCESSING",
    "previewFailureReason": null,
    "previewVariants": null,
    "previewDownloadable": false
  }
}

Upload Flow:

  1. POST api/v1/files/request-upload with context: "PRODUCT_PREVIEW_VIDEO" (or _IMAGE/_DOCUMENT) → receive uploadUrl and fileId
  2. PUT {uploadUrl} with the raw file bytes (client-to-storage, not through this API)
  3. POST .../preview/register with { "ftFileId": fileId }
  4. Poll GET .../preview (17b) until previewStatus is READYpreviewVariants will then hold the resolved, playable CDN URLs (image/video previews only; documents have no variants — see 17d)

Can also happen implicitly during Create Product via the previewFileId/previewDownloadable fields (DIGITAL products only there) — same underlying registration logic.

Error Responses:


17b. Get Preview

Purpose: Retrieves the current preview state for the product.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}/preview

Returns the same PreviewResponse shape as 17a. All fields are null if no preview has ever been registered.

Error Responses:


17c. Remove Preview

Purpose: Fully clears the product's preview — previewFtFileId, previewContext, previewStatus, previewFailureReason, previewVariants, and previewDownloadable are all reset. This is the only way to actually clear a preview (the clearPreview flag on Update Product does not do this — see the note there).

Endpoint: DELETE api/v1/e-commerce/shops/{shopId}/products/{productId}/preview

Response JSON Sample:

{
  "success": true,
  "message": "Preview removed from product",
  "data": null
}

Error Responses:


17d. Get Preview Document URL

Purpose: For document previews only (previewContext = PRODUCT_PREVIEW_DOCUMENT, e.g. a PDF) — generates a short-lived, direct download URL. Documents don't get resolved variants like images/videos do, so this is the only way to actually fetch a document preview's bytes. Access differs from the rest of this section: any authenticated user can call it (no shop-owner/admin check) — it's meant for buyers evaluating the product.

Endpoint: GET api/v1/e-commerce/shops/{shopId}/products/{productId}/preview/document-url

Response JSON Sample (PreviewDocumentUrlResponse):

{
  "success": true,
  "message": "Preview document URL generated — valid for 15 minutes",
  "data": {
    "url": "https://cdn.example.com/preview/f66e4567/sample.pdf?X-Amz-Signature=...",
    "expiresInSeconds": 900
  }
}

Error Responses:


Quick Reference

Common HTTP Status Codes

Code Meaning
200 OK Successful GET/PUT/PATCH
201 Created Successful POST (resource created)
400 Bad Request Invalid data, validation errors, business rule violations
401 Unauthorized Authentication required or invalid token
403 Forbidden Insufficient permissions
404 Not Found Resource not found or not accessible
409 Conflict Duplicate product name or business constraint violation
422 Unprocessable Entity Field-level validation errors
500 Internal Server Error Server error

User Access Levels

User Type Product Management Status Access
Public View active products only ACTIVE only
Authenticated View active products only ACTIVE only
Shop Owner Full CRUD on own shop All statuses
System Admin Full CRUD on all shops All statuses

Product Type Fulfillment Flows

Type Flow
PHYSICAL Payment → PENDING_SHIPMENT → Seller ships → Buyer confirms with 6-digit code → Escrow releases → COMPLETED
DIGITAL Payment → COMPLETED immediately → Escrow released → DigitalDownloadAccess records created → Buyer downloads

Product Status Lifecycle

DRAFT → ACTIVE → INACTIVE → ARCHIVED
  ↑                            ↓
  └───────── RESTORE ──────────┘

OUT_OF_STOCK ←→ ACTIVE (automatic based on inventory)
Status Public Visibility Available Actions
DRAFT Hidden Edit, Publish, Hard Delete
ACTIVE Visible Edit, Deactivate, Soft Delete
INACTIVE Hidden Edit, Activate, Soft Delete
OUT_OF_STOCK Visible (out of stock badge) Restock (auto-activates)
ARCHIVED Hidden Restore

Enums Reference

ProductType: PHYSICAL, DIGITAL

PreviewType: VIDEO, PDF, THREE_D, IMAGE — legacy enum, no longer populated by the preview pipeline (always null in responses). The real preview media type is inferred from FileThunder's FtMediaContext: PRODUCT_PREVIEW_IMAGE, PRODUCT_PREVIEW_VIDEO, PRODUCT_PREVIEW_DOCUMENT (exposed as previewContext — see 17b)

FtFileStatus (file/media processing status — mediaFileIds, digital file, and preview all use this): PENDING, UPLOADING, UPLOADED, SCANNING, PROCESSING, LIVE_PARTIAL, READY, FAILED

ProductMediaType (top-level product media only): IMAGE, VIDEO

ProductCondition: NEW, USED_LIKE_NEW, USED_GOOD, USED_FAIR, REFURBISHED, FOR_PARTS

ReqAction: SAVE_DRAFT (→ DRAFT status), SAVE_PUBLISH (→ ACTIVE status)

UrgencyTag: NONE, NEW_ARRIVAL, LIMITED_EDITION, LIMITED_OFFER, FEW_REMAINS

PaymentFrequency: DAILY, WEEKLY, BI_WEEKLY, SEMI_MONTHLY, MONTHLY, QUARTERLY, CUSTOM_DAYS

FulfillmentTiming: IMMEDIATE (ship after down payment), AFTER_PAYMENT (layaway — ship after final payment)

SKU Format

SHP[8-CHAR-UUID]-[CATEGORY-3]-[BRAND-3]-[ATTRIBUTE-3]-[SEQUENCE-4]

Example: SHP12345678-ELE-APP-512-0001

Data Format Standards

Error Response Format

{
  "success": false,
  "message": "Human-readable error message",
  "error": {
    "code": "ERROR_CODE",
    "details": "Detailed information",
    "field": "fieldName (if field-specific)",
    "timestamp": "2026-05-19T14:30:00Z"
  }
}

Product Creation Flow

0. POST /api/v1/files/request-upload (context=PRODUCT_IMAGE/PRODUCT_VIDEO, one call per file)
   PUT {uploadUrl} (direct to storage, one per file)
   — repeat for the digital file (context=DIGITAL_PRODUCT) and preview (context=PRODUCT_PREVIEW_*) if needed
   — collect the resulting fileIds; no need to wait for processing to finish

1. POST /shops/{shopId}/products?action=SAVE_DRAFT
   body includes mediaFileIds, colors[].imageFileIds, digitalFileId, previewFileId
   — create the product shell; product/digital-file/preview media all start as PROCESSING
   and flip to READY asynchronously once FileThunder finishes scanning/transcoding

2. POST /products/{shopId}/{productId}/installment-plans
   — add installment plans (if installmentEnabled)

3. (optional, if not supplied at create, or to replace later)
   POST /shops/{shopId}/products/{productId}/digital-file/register  { "ftFileId": ... }
   — attach/replace the private digital file (DIGITAL products only)

4. (optional, if not supplied at create, or to replace later)
   POST /shops/{shopId}/products/{productId}/preview/register  { "ftFileId": ..., "previewDownloadable": false }
   — attach/replace the preview teaser (any product type)

5. PATCH /shops/{shopId}/products/{productId}/publish
   — publish when ready

Preview vs Digital File

Aspect Preview Digital File
Who sees it Everyone (before purchase) Buyers only (after payment)
Upload context PRODUCT_PREVIEW_IMAGE/_VIDEO/_DOCUMENT DIGITAL_PRODUCT
Access previewDownloadable gates raw download; documents get a 15-min presigned URL via 17d Buyer-only download, expires per downloadExpiryDays (see order/download endpoints)
Count One per product One per product
Products PHYSICAL or DIGITAL DIGITAL only
Registration endpoint POST .../preview/register POST .../digital-file/register
Purpose Teaser/sample before buying Actual purchased content

Revision #12
Created 23 September 2025 08:15:01 by Admin Qbit
Updated 7 July 2026 16:09:02 by Admin Qbit