Skip to main content

Product Management

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2026-05-1907-07
Version: v2.1v3.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:

in

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 publiccontext bucket separate from private digital content
  2. Preview supports: (VIDEOPRODUCT_IMAGE, PDFPRODUCT_VIDEO, THREE_DDIGITAL_PRODUCT, IMAGEPRODUCT_PREVIEW_IMAGE/_VIDEO/_DOCUMENT) → gets back a presigned upload URL and a fileId
  3. Client PUTs the raw file bytes directly to that URL (not through this API)
  4. Client references that fileId in the relevant product field (mediaFileIds, colors[].imageFileIds, NulldigitalFileId, meanspreview's noftFileId, preview....) Managed— the file is still processing (virus scan + variant generation) at this point, so validation only checks Redis-cached ownership/context, not readiness
  5. 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)
  • Responses never return raw productImages/previewUrl strings — image-bearing fields are ProductMediaItem objects (fileId, mediaType, status, variants, mimeType, order) or similarly-shaped preview/file objects, and URLs are assembled fresh on every read via presignFileThunderUrlAssembler
  • Known uploadgap: color confirmimageFileIds floware stored as raw file IDs and are not validated/resolved through FileThunder like top-level product media — the colors[].images field 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
productImagesmediaFileIds array<UUID> Yes ProductFileThunder imagefile URLsIDs for product images/videos, in display order ValidNot URLs,null; ateach leastID 1must requiredbelong 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
digitalFileIdUUIDConditionalFileThunder file ID of the downloadable fileRequired when productType=DIGITAL; uploaded with context DIGITAL_PRODUCT
previewFileIdUUIDNoFileThunder file ID of a public preview (DIGITAL only)Uploaded with context PRODUCT_PREVIEW_IMAGE/_VIDEO/_DOCUMENT
previewDownloadablebooleanNoWhether buyers can download the preview file itselfDefault: 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
imagesimageFileIds array<UUID> No ValidFileThunder URLsfile 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",
  "productImages"mediaFileIds": [
    "https://example.com/images/iphone15-main.jpg"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",
      "images"imageFileIds": ["https://example.com/colors/natural-titanium.jpg"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",
  "productImages"mediaFileIds": ["https://example.com/images/design-kit-preview.jpg"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": null{
    "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
  • comparePrice must be greater than price
  • groupPrice must be less than price
  • All group buying settings (groupMaxSize, groupPrice, groupTimeLimitHours) required when groupBuyingEnabled=true
  • maxOrderQuantity must be ≥ minOrderQuantity
  • Digital download fields (downloadExpiryDays, maxDownloadsPerBuyer, maxQuantityForDigital) only apply to DIGITAL products
  • mediaFileIds are resolved from the FileThunder Redis cache at request time — each file must have been requested via POST api/v1/files/request-upload (context PRODUCT_IMAGE/PRODUCT_VIDEO), be owned by the caller, and be at least PROCESSING (a still-uploading or expired/never-started file is rejected). Files that finish processing after the product is created are silently upgraded to READY in place via a FileThunder webhook
  • DIGITAL products require digitalFileId — creation fails immediately if it's missing
  • After creation, add installment plans via Installment Plan Config and; 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 violations
  • 401: Authentication required
  • 403: Insufficient permissions
  • 404: Shop or category not foundfound, or a referenced mediaFileIds/digitalFileId/previewFileId was never uploaded (upload expired or never started)
  • 409: Product with same name already exists in shop
  • 422: 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
productImagesmediaFileIds array<UUID> Updated imagelist URLsof FileThunder file IDs for product images/videos, in order ValidSame URLs,validation replacesas existingCreate; 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 Set⚠️ trueCurrently toonly clears legacy preview fields — does not remove thean product'sactive preview file(see andnote typebelow)
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/previewFileId field 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": {
    "productId"...": "456e7890-e89b-12d3-a456-426614174001"full ProductDetailedResponse — same shape as Get Product Detailed (#6), "productName":rebuilt "iPhonefrom 15the Prosaved Max 512GB",
    "productSlug": "iphone-15-pro-max-512gb",
    "price": 1399.00,
    "status": "ACTIVE",
    "updatedAt": "2026-05-19T14:30:00Z"product"
  }
}

Error Responses:

  • 400: Invalid update data or validation errors
  • 401: Authentication required
  • 403: Insufficient permissions
  • 404: Shop or product not foundfound, or a referenced mediaFileIds entry was never uploaded
  • 409: 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 fields
  • 401: Authentication required
  • 403: Insufficient permissions
  • 404: 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 required
  • 403: Insufficient permissions
  • 404: 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 deleted
  • 401: Authentication required
  • 403: Insufficient permissions
  • 404: 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",
    "productType": "PHYSICAL",
    "productDescription": "The most advanced iPhone ever...",
    "productImages"productMedia": [
      {
        "fileId": "f11e4567-e89b-12d3-a456-426614174111",
        "mediaType": "IMAGE",
        "status": "READY",
        "variants": {
          "original": "https://cdn.example.com/images/iphone15-main.products/f11e4567/original.jpg",
          "thumb": "https://cdn.example.com/products/f11e4567/thumb.jpg"
        },
        "mimeType": "image/jpeg",
        "order": 0
      }
    ],
    "price": 1199.00,
    "comparePrice": 1299.00,
    "discountAmount": 100.00,
    "discountPercentage": 7.69,
    "isOnSale": true,
    "stockQuantity": 25,
    "lowStockThreshold": 5,
    "isInStock": true,
    "isLowStock": false,
    "sku"stockInfo": {
      "stockTotal": 25,
      "stockHeld": 2,
      "stockAvailable": 23,
      "soldCount": 140,
      "holdBreakdown": {
        "heldForCheckout": 1,
        "heldForGroupPurchase": 1,
        "heldForInstallment": 0,
        "nearestCheckoutExpiry": "SHP12345678-ELE-APP-512-0001"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": ["https://example.com/colors/natural-titanium.jpg"f33e4567-e89b-12d3-a456-426614174333"],
        "priceAdjustment": 0.00,
        "finalPrice": 1199.0000,
        "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,
      "groupMaxSize"maxGroupSize": 50,
      "groupPrice": 1099.00,
      "groupDiscount": 100.00,
      "groupDiscountPercentage": 8.34,
      "timeLimitHours": 7272,
      "canJoinGroup": true
    },
    "installmentOptions": {
      "isEnabled": true,
      "isAvailable": true,
      "downPaymentRequired": true,
      "minDownPaymentPercentage": 20.00,
      "plans": [],
      "eligibilityStatus": "ELIGIBLE",
      "creditCheckRequired": false
    },
    "previewType"purchaseOptions": {
      "canBuyNow": true,
      "canJoinGroup": true,
      "canPayInstallment": true,
      "recommendedOption": "VIDEO"GROUP_BUYING",
      "previewUrl"bestDeal": { "option": "GROUP_BUYING", "savings": 100.00, "finalPrice": 1099.00 }
    },
    "preview": {
      "type": null,
      "variants": { "thumb": "https://minio.cdn.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_trailer.mp4"f66e4567/thumb.jpg" },
      "previewDownloadable"downloadable": false,false
    "createdAt": "2026-05-19T10:30:00Z",
    "updatedAt": "2026-05-19T14:30:00Z"}
  }
}

Notes on media/preview in this response:

  • productMedia items may be status: "PROCESSING" with variants: null right after creation — poll this endpoint (or re-fetch) until status flips to "READY"
  • colors[].images currently 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 doc
  • preview.type is always null here regardless of whether a preview exists — it's wired to a legacy field the current preview pipeline never sets. Use GET .../products/{productId}/preview (17b) for the authoritative preview context/status/variants

Error Responses:

  • 401: Authentication required
  • 403: Insufficient permissions
  • 404: 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",
        "primaryImageVariants": {
          "thumb": "https://cdn.example.com/products/f11e4567/thumb.jpg"
        },
        "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 — primaryImageVariants is the resolved variant map (e.g. thumb/original → CDN URL) of the product's first productMedia item, or null if that image hasn't finished processing yet or no media was uploaded.

Error Responses:

  • 401: Authentication required
  • 403: Insufficient permissions
  • 404: 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 required
  • 403: Insufficient permissions
  • 404: 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",
    "productType": "PHYSICAL",
    "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,
    "discountAmount": 100.00,
    "discountPercentage": 7.69,
    "isOnSale": true,
    "groupPurchasePrice": 1099.00,
    "isInStock": true,
    "stockQuantity"isLowStock": 25,false,
    "stockInfo": {
      "stockAvailable": 23,
      "soldCount": 140
    },
    "condition": "NEW",
    "urgencyTag": "NONE",
    "shopId": "123e4567-e89b-12d3-a456-426614174000",
    "shopName": "TechStore Pro",
    "shopSlug": "techstore-pro",
    "shopLogo": {
      "fileId": "s11e4567-e89b-12d3-a456-426614174001",
      "variants": { "thumb": "https://cdn.example.com/shops/s11e4567/thumb.jpg" }
    },
    "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,
      "groupMaxSize"maxGroupSize": 50,
      "groupPrice": 1099.00,
      "groupDiscount": 100.00,
      "groupDiscountPercentage": 8.34,
      "timeLimitHours": 72
    },
    "installmentOptions": {
      "isAvailable": true,
      "downPaymentRequired": true,
      "minDownPaymentPercentage": 20.00,
      "plans": [
        {
          "planId"installmentPlanId": "..."aa1e4567-e89b-12d3-a456-426614174aaa",
          "planName"duration": 6,
          "interval": "MONTHLY",
          "interestRate": 0.00,
          "description": "6-Month Interest-Free",
          "paymentFrequency": "MONTHLY",
          "numberOfPayments": 6,
          "apr": 0.00,
          "minDownPaymentPercent": 20
        }
      ]
    },
    "previewType": "PDF",null,
    "previewUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_sample.pdf",null,
    "previewDownloadable": true,
    "isInCart": null,
    "cartQuantity": null,
    "isInWishlist": null,
    "wishlistItemId": null,
    "wishlistGroupId": null,
    "wishlistGroupName": null,
    "createdAt": "2026-05-19T10:30:00Z"00"
  }
}

Notes:

  • previewType and previewUrl are wired to legacy fields that the current preview pipeline never populates — they will always be null, even when the product has an active, ready preview. There is currently no public field exposing the resolved preview hasmedia/variants; beenpreviewDownloadable uploadedis the one real preview field here. To surface preview media publicly, the frontend must call the seller-facing GET .../preview endpoint (17b) — note that endpoint currently requires shop-owner/admin auth, so a fully public preview-viewing path doesn't exist yet
  • Preview filescolors[].images are publiclyraw accessibleFileThunder withoutfile authenticationID strings, not resolved URLs (same gap as the URLdetailed response)
  • isInCart/cartQuantity/isInWishlist/wishlistItemId/wishlistGroupId/wishlistGroupName are only populated on this single-product view (not on list responses like #10/#11), and only when the request is permanent and directauthenticated

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",
        "primaryImageVariants": {
          "thumb": "https://cdn.example.com/products/f11e4567/thumb.jpg"
        },
        "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 long
  • 404: 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 error
  • 404: 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 errors
  • 404: 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.


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 filesfile for a DIGITAL products.product. UsesThe 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 presignnew ftFileId uploadreplaces the confirmcurrent flowpending/active to upload files directly to object storage.file.

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

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. PresignRegister UploadFile

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

Request Body:

IDfromPOST , owned by the
Parameter Type Required Description
fileNameftFileId stringUUID Yes OriginalFileThunder file name
contentTypestringYesMIME typeapi/v1/files/request-upload (e.g.context application/pdfDIGITAL_PRODUCT)
fileSizelongYesFile size in bytes (must be positive)
displayOrderintegerNoSort order for multiple filescaller

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": "design-kit-v2.fig",null,
    "contentType": "application/octet-stream",null,
    "fileSize": 52428800,null,
    "displayOrder"fileVersion": 10,
    "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:

  • The top-level fileName/contentType/fileSize/status/uploadedAt describe the active (already-promoted) file; pending describes 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 digitalFileId field — same underlying registration logic

Error Responses:

  • 400: File not found in Redis (upload expired/never started), or file does not belong to the caller
  • 401: Authentication required
  • 403: Insufficient permissions, or product is not DIGITAL
  • 404: 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 required
  • 403: Insufficient permissions
  • 404: 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": "UploadDigital URLfile generated — upload directly to this URL then call /confirm"removed",
  "data": {
    "uploadUrl": "https://s3.example.com/bucket/key?X-Amz-Signature=...",
    "objectKey": "digital-files/product-456/design-kit-v2.fig",
    "expiresAt": "2026-05-19T11:00:00"
  }null
}

UploadError FlowResponses:

    • Call401: POSTAuthentication /presign-upload → receive uploadUrl and objectKeyrequired
    • PUT403: {uploadUrl}Insufficient permissions
    • 404: withShop/product binarynot found, or no digital file body (do not call the APIexists for this step)product
    • Call POST /confirm with objectKey to register the file

16b. Confirm Upload

Purpose: Registers a file after direct upload to object storage. Must be called after the actual file upload succeeds.

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

Request Body:

ParameterTypeRequiredDescription
objectKeystringYesReturned by presign-upload
fileNamestringYesOriginal file name
contentTypestringYesMIME type
fileSizelongYesFile size in bytes
displayOrderintegerNoSort order

Response JSON Sample:

{
  "success": true,
  "message": "File confirmed and linked to product",
  "data": {
    "fileId": "aaa1bbbb-e89b-12d3-a456-426614174001",
    "productId": "456e7890-e89b-12d3-a456-426614174001",
    "fileName": "design-kit-v2.fig",
    "contentType": "application/octet-stream",
    "fileSize": 52428800,
    "fileVersion": 1,
    "displayOrder": 1,
    "isActive": true,
    "uploadedAt": "2026-05-19T10:45:00"
  }
}

16c. Get Product Files

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

Returns a list of DigitalFileResponse objects for all files linked to the product.


16d. Delete File

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

Additional Path Parameter:

ParameterTypeDescription
fileIdUUIDID of the file to delete

16e. Toggle File Active Status

Endpoint: PATCH api/v1/e-commerce/shops/{shopId}/products/{productId}/digital-files/{fileId}/toggle

Query Parameters:

ParameterTypeRequiredDescription
isActivebooleanYestrue to activate, false to deactivate

Deactivated files are hidden from buyers but not deleted.


17. Product Preview Management

Purpose: Manages a single preview file per product — a publicly accessible teaser shown to buyers before purchase.purchase (works for any productType, not just DIGITAL). Distinct from the private digitalDigital content files.File. Supports VIDEO, PDF, 3D models,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)

Storage: Uploaded to nextgate-preview-content bucket (public read). The confirmed URL is permanent and requires no authentication to access.


17a. PresignRegister Preview Upload

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

Request Body (RegisterPreviewRequest):

Parameter Type Required Description
fileNameftFileId stringUUID Yes OriginalFileThunder file nameID, (usedowned to buildby the storage key)caller
contentTypepreviewDownloadable stringboolean YesNo MIMEWhether typebuyers can download the raw preview file itself. Default: false (e.g.view/stream video/mp4, application/pdf, image/jpeg, model/gltf-binary)
fileSizelongYesFile size in bytes (must be positive)only)

Request JSON Sample:

{
  "fileName"ftFileId": "product-trailer.mp4"f66e4567-e89b-12d3-a456-426614174666",
  "contentType"previewDownloadable": "video/mp4",
  "fileSize": 52428800false
}

Response JSON Sample (PreviewResponse):

{
  "success": true,
  "message": "Preview upload URL generatedregistereduploadprocessing directlyin then call /confirm"background",
  "data": {
    "uploadUrl"previewFtFileId": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_product-trailer.mp4?X-Amz-Signature=..."f66e4567-e89b-12d3-a456-426614174666",
    "objectKey"previewContext": "preview/shop-id/product-id/uuid_product-trailer.mp4"PRODUCT_PREVIEW_VIDEO",
    "expiresAt"previewStatus": "2026-05-19T11:30:00"PROCESSING",
    "previewFailureReason": null,
    "previewVariants": null,
    "previewDownloadable": false
  }
}

Upload Flow:

  1. Call POST /presign-api/v1/files/request-upload with context: "PRODUCT_PREVIEW_VIDEO" (or _IMAGE/_DOCUMENT) → receive uploadUrl and objectKeyfileId
  2. PUT {uploadUrl} with binarythe raw file bodybytes (client-to-MinIO directly,storage, not through thethis API)
  3. Call POST .../confirmpreview/register with objectKey{ "ftFileId": fileId }
  4. Poll GET .../preview and(17b) until previewTypepreviewStatus tois linkREADY — previewVariants will then hold the fileresolved, toplayable CDN URLs (image/video previews only; documents have no variants — see 17d)

Can also happen implicitly during Create Product via the productpreviewFileId/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 caller
  • 401: Authentication required
  • 403: Insufficient permissions
  • 404: Shop or product not found

17b. ConfirmGet Preview Upload

Purpose: LinksRetrieves the uploadedcurrent filepreview tostate for the product. Sets previewType and stores the permanent public URL as previewUrl. If the product already has a preview, the old file is deleted from storage.

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

Request Body:

as17a.All
ParameterTypeRequiredDescription
objectKeystringYesReturned by presign-upload
previewTypePreviewTypeYesVIDEO, PDF, THREE_D, or IMAGE
previewDownloadablebooleanNoWhether viewers can downloadReturns the file. Default:same falsePreviewResponse (view/streamshape only)
fields

Requestare JSONnull Sample:if no preview has ever been registered.

{
  "objectKey": "preview/shop-id/product-id/uuid_product-trailer.mp4",
  "previewType": "VIDEO",
  "previewDownloadable": false
}

Response JSON Sample:

{
  "success": true,
  "message": "Preview confirmed and linked to product",
  "data": null
}

After confirm, the product's public response will include:

{
  "previewType": "VIDEO",
  "previewUrl": "https://minio.example.com/nextgate-preview-content/preview/shop-id/product-id/uuid_product-trailer.mp4",
  "previewDownloadable": false
}

Error Responses:

  • 400: Missing objectKey or previewType
  • 401: Authentication required
  • 403: Insufficient permissions
  • 404: Shop or product not found

17c. Remove Preview

Purpose: DeletesFully clears the product's preview file from storage and clears previewTypepreviewFtFileId, previewContext, previewStatus, previewFailureReason, previewVariants, and previewUrlpreviewDownloadable 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 product.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 required
  • 403: Insufficient permissions
  • 404: 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 not READY yet, or previewDownloadable is false for this product
  • 401: Authentication required
  • 404: 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, IMAGElegacy enum, no longer populated by the preview pipeline (always null meansin noresponses). 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 shellshell; 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. POST(optional, /shops/{shopId}/products/{productId}/digital-files/presign-uploadif PUTnot {uploadUrl}supplied (directat create, or to storage)replace later)
   POST /shops/{shopId}/products/{productId}/digital-files/confirmfile/register  { "ftFileId": ... }uploadattach/replace the private digital filesfile (DIGITAL products only)

4. (optional, if not supplied at create, or to replace later)
   POST /shops/{shopId}/products/{productId}/preview/presign-upload
   PUTregister  {uploadUrl} (direct"ftFileId": to..., storage)"previewDownloadable": POSTfalse /shops/{shopId}/products/{productId}/preview/confirm}uploadattach/replace the preview teaser (any product type, optional)
   — preview is stored in public bucket; URL is immediately accessibletype)

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

Preview vs Digital FilesFile

Aspect Preview Digital FilesFile
Who sees it Everyone (before purchase) Buyers only (after payment)
BucketUpload context nextgate-preview-contentPRODUCT_PREVIEW_IMAGE (public)/_VIDEO/_DOCUMENT nextgate-digital-contentDIGITAL_PRODUCT (private)
Access PermanentpreviewDownloadable publicgates raw download; documents get a 15-min presigned URL via 17d PresignedBuyer-only URL,download, expires per downloadExpiryDays (see order/download endpoints)
Count One per product Multiple filesOne per product
Products PHYSICAL or DIGITAL DIGITAL only
Registration endpointPOST .../preview/registerPOST .../digital-file/register
Purpose Teaser/sample before buying Actual purchased content