Product Management
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:
- Client calls
POST api/v1/files/request-uploadwith acontext(PRODUCT_IMAGE,PRODUCT_VIDEO,DIGITAL_PRODUCT,PRODUCT_PREVIEW_IMAGE/_VIDEO/_DOCUMENT) → gets back a presigned upload URL and afileId - Client
PUTs the raw file bytes directly to that URL (not through this API) - Client references that
fileIdin the relevant product field (mediaFileIds,colors[].imageFileIds,digitalFileId, preview'sftFileId, ...) — the file is still processing (virus scan + variant generation) at this point, so validation only checks Redis-cached ownership/context, not readiness - FileThunder finishes processing asynchronously and webhooks back into the product/preview/digital-file "updater" services, which upgrade the stored record to
READYwith resolvedvariants(a map of variant name → CDN URL)
- Responses never return raw
productImages/previewUrlstrings — image-bearing fields areProductMediaItemobjects (fileId,mediaType,status,variants,mimeType,order) or similarly-shaped preview/file objects, and URLs are assembled fresh on every read viaFileThunderUrlAssembler - Known gap: color
imageFileIdsare stored as raw file IDs and are not validated/resolved through FileThunder like top-level product media — thecolors[].imagesfield in responses currently echoes back the file ID strings as-is, not resolved variant URLs
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:
- Product name must be unique within the shop
comparePricemust be greater thanpricegroupPricemust be less thanprice- All group buying settings (
groupMaxSize,groupPrice,groupTimeLimitHours) required whengroupBuyingEnabled=true maxOrderQuantitymust be ≥minOrderQuantity- Digital download fields (
downloadExpiryDays,maxDownloadsPerBuyer,maxQuantityForDigital) only apply toDIGITALproducts mediaFileIdsare resolved from the FileThunder Redis cache at request time — each file must have been requested viaPOST api/v1/files/request-upload(contextPRODUCT_IMAGE/PRODUCT_VIDEO), be owned by the caller, and be at leastPROCESSING(a still-uploading or expired/never-started file is rejected). Files that finish processing after the product is created are silently upgraded toREADYin place via a FileThunder webhookDIGITALproducts requiredigitalFileId— creation fails immediately if it's missing- After creation, add installment plans via Installment Plan Config; digital files and previews can also be (re)managed later via Digital File Management and Product Preview Management
Error Responses:
400: Validation errors or business rule violations401: Authentication required403: Insufficient permissions404: Shop or category not found, or a referencedmediaFileIds/digitalFileId/previewFileIdwas never uploaded (upload expired or never started)409: Product with same name already exists in shop422: Field-level validation errors
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:
- Unlike Create, Update merges by fileId: any ID already present in the product's stored media that has finished processing (has resolved
variants) is kept as-is and just re-ordered; any new ID is resolved fresh from the FileThunder Redis cache (same ownership/context checks as Create) - To remove an image, omit its fileId from the array — there is no separate "delete image" endpoint
- Digital file and preview are not updatable through this endpoint — there is no
digitalFileId/previewFileIdfield here. Manage them via Digital File Management and Product Preview Management
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:
400: Invalid update data or validation errors401: Authentication required403: Insufficient permissions404: Shop or product not found, or a referencedmediaFileIdsentry was never uploaded409: Updated product name already exists
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:
- Product name, description, price, stock quantity, category, and at least one image must be present
- Group buying settings complete if enabled
- Installment plans present if installment is enabled
Error Responses:
400: Product already published or missing required publish fields401: Authentication required403: Insufficient permissions404: Shop or product not found
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:
- Draft products: Hard delete (permanently removed)
- Published products: Soft delete (status → ARCHIVED, 30-day recovery window)
Error Responses:
401: Authentication required403: Insufficient permissions404: Shop or product not found
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:
400: Product is not deleted401: Authentication required403: Insufficient permissions404: Shop or product not found
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:
productMediaitems may bestatus: "PROCESSING"withvariants: nullright after creation — poll this endpoint (or re-fetch) untilstatusflips to"READY"colors[].imagescurrently holds the raw FileThunder file ID strings supplied at create/update time, not resolved URLs — see the "known gap" note at the top of this docpreview.typeis alwaysnullhere regardless of whether a preview exists — it's wired to a legacy field the current preview pipeline never sets. UseGET .../products/{productId}/preview(17b) for the authoritative previewcontext/status/variants
Error Responses:
401: Authentication required403: Insufficient permissions404: Shop or product not found
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 ProductSummaryResponse — productPrimaryMedia 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:
401: Authentication required403: Insufficient permissions404: Shop not found
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:
401: Authentication required403: Insufficient permissions404: Shop not found
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:
previewTypeandpreviewUrlare wired to legacy fields that the current preview pipeline never populates — they will always benull, even when the product has an active, ready preview. There is currently no public field exposing the resolved preview media/variants;previewDownloadableis the one real preview field here. To surface preview media publicly, the frontend must call the seller-facingGET .../previewendpoint (17b) — note that endpoint currently requires shop-owner/admin auth, so a fully public preview-viewing path doesn't exist yetcolors[].imagesare raw FileThunder file ID strings, not resolved URLs (same gap as the detailed response)isInCart/cartQuantity/isInWishlist/wishlistItemId/wishlistGroupId/wishlistGroupNameare only populated on this single-product view (not on list responses like #10/#11), and only when the request is authenticated
Error Responses:
404: Shop not found/not approved, or product not found/not active
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:
404: Shop not found, not approved, or not active
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:
404: Shop not found, not approved, or not active
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:
400: Query too short or too long404: Shop not found or not accessible
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:
400: Invalid filter values or price range error404: Shop or category not found
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:
404: Shop not found/not approved, or product not found/not active
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:
400: Validation errors404: Shop or product not found
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:
404: Shop or product not found
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.
15g. Set Featured Plan
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
Purpose: Links a FileThunder file (already uploaded with context DIGITAL_PRODUCT via POST api/v1/files/request-upload) to this product. The file starts in a "pending" shadow state and is promoted to the live/active file once FileThunder finishes virus-scanning it (webhook-driven) — or immediately, if it was already READY by the time this call lands (race-condition-safe).
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:
POST api/v1/files/request-uploadwithcontext: "DIGITAL_PRODUCT"→ receiveuploadUrlandfileIdPUT {uploadUrl}with the raw file bytes (client-to-storage, not through this API)POST .../digital-file/registerwith{ "ftFileId": fileId }— do this immediately, you don't need to wait for the upload/scan to finish- Poll
GET .../digital-fileuntilstatus(orpending.status) isREADY;FAILEDmeans re-upload and register again
Notes:
- The top-level
fileName/contentType/fileSize/status/uploadedAtdescribe the active (already-promoted) file;pendingdescribes a file that was just registered and hasn't finished processing/replacing the active one yet. Both can be non-null at once while a replacement is in flight - Can also happen implicitly during Create Product via the
digitalFileIdfield — same underlying registration logic
Error Responses:
400: File not found in Redis (upload expired/never started), or file does not belong to the caller401: Authentication required403: Insufficient permissions, or product is notDIGITAL404: Shop or product not found
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:
401: Authentication required403: Insufficient permissions404: Shop/product not found, or no digital file has ever been registered for this product
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:
401: Authentication required403: Insufficient permissions404: Shop/product not found, or no digital file exists for this product
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
Purpose: Links a FileThunder file (already uploaded via POST api/v1/files/request-upload with context PRODUCT_PREVIEW_IMAGE, PRODUCT_PREVIEW_VIDEO, or PRODUCT_PREVIEW_DOCUMENT) to this product as its preview. The preview's context/type is inferred server-side from the file's MIME type — there is no client-supplied previewType anymore. If the product already has a preview, it's replaced. Processing is async and webhook-driven, with the same immediate-promotion race-condition guard as the digital file.
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:
POST api/v1/files/request-uploadwithcontext: "PRODUCT_PREVIEW_VIDEO"(or_IMAGE/_DOCUMENT) → receiveuploadUrlandfileIdPUT {uploadUrl}with the raw file bytes (client-to-storage, not through this API)POST .../preview/registerwith{ "ftFileId": fileId }- Poll
GET .../preview(17b) untilpreviewStatusisREADY—previewVariantswill 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:
400: File not found in Redis (upload expired/never started), or file does not belong to the caller401: Authentication required403: Insufficient permissions404: Shop or product not found
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:
401: Authentication required403: Insufficient permissions404: Shop or product not found
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:
401: Authentication required403: Insufficient permissions404: Shop or product not found
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:
400: No preview registered, preview is not a document, preview is notREADYyet, orpreviewDownloadableisfalsefor this product401: Authentication required404: Product not found
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
- Dates: ISO 8601 (
2026-05-19T14:30:00Z) - Prices: Decimal with 2 decimal places, stored as BigDecimal
- UUIDs: Standard UUID format
- Pagination: 1-indexed page parameter
- Colors: Hex format
#RRGGBB - Percentages: Decimal format (
20.00= 20%)
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 |
No comments to display
No comments to display