SeloraXDEVELOPERS

Products

Products API

Manage the product catalog for the authenticated store. Products include metadata, status flags, and variant information with pricing.

List Products

Retrieve a paginated list of products.

GET /api/apps/v1/products

Authentication

Required. Must include valid app credentials.

Scope

read:products

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger50Items per page (max 250)
statusstring--Filter by product status: active or inactive
sincestring (ISO 8601)--Only return products updated on or after this date
category_idinteger--Filter by category ID
price_minnumber--Only return products with at least one variant priced at or above this value
price_maxnumber--Only return products with at least one variant priced at or below this value
searchstring--Search products by name (partial match)
sortstringcreated_at:descSort results. Format: column or column:asc/column:desc. Allowed columns: created_at, updated_at, name

Price filtering

Price filters check against variant prices (from product_variant_option_combinations), not a product-level price. A product matches if any of its non-deleted variants fall within the specified range. You can use price_min and price_max independently or together.

Example Request

curl -X GET "https://api.selorax.io/api/apps/v1/products?status=active&category_id=5&search=rose" \
  -H "Authorization: Bearer <token>"

Or using client credentials with price range and sorting:

curl -X GET "https://api.selorax.io/api/apps/v1/products?price_min=100&price_max=500&sort=name:asc" \
  -H "X-Client-Id: sx_app_..." \
  -H "X-Client-Secret: sx_secret_..." \
  -H "X-Store-Id: 22"

Response

{
  "data": [
    {
      "product_id": 150,
      "name": "Premium Rose Bouquet",
      "slug": "premium-rose-bouquet",
      "category_id": 5,
      "brand_id": null,
      "is_active": 1,
      "is_visible": 1,
      "created_at": "2025-03-10T08:00:00.000Z",
      "updated_at": "2025-06-12T14:30:00.000Z"
    },
    {
      "product_id": 149,
      "name": "Seasonal Flower Basket",
      "slug": "seasonal-flower-basket",
      "category_id": 5,
      "brand_id": 2,
      "is_active": 1,
      "is_visible": 1,
      "created_at": "2025-03-08T12:00:00.000Z",
      "updated_at": "2025-05-20T09:15:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 2
  },
  "status": 200
}

Response Fields

FieldTypeDescription
product_idintegerUnique product identifier
store_idintegerStore the product belongs to
namestringProduct title
slugstringURL-friendly product slug
category_idinteger or nullAssociated category ID
brand_idinteger or nullAssociated brand ID
is_activeinteger (0 or 1)Whether the product is active
is_visibleinteger (0 or 1)Whether the product is visible on the storefront
created_atstring (ISO 8601)When the product was created
updated_atstring (ISO 8601)When the product was last updated

Node.js Example

const response = await fetch(
  "https://api.selorax.io/api/apps/v1/products?status=active&category_id=5&search=rose",
  { headers: { Authorization: "Bearer sx_at_..." } }
);
const { data, pagination } = await response.json();
console.log(`Found ${pagination.total} products`);

Python Example

import requests
 
response = requests.get(
    "https://api.selorax.io/api/apps/v1/products",
    headers={"Authorization": "Bearer sx_at_..."},
    params={"status": "active", "category_id": 5, "search": "rose"},
)
data = response.json()
for product in data["data"]:
    print(f"{product['product_id']}: {product['name']}")

Get Product

Retrieve a single product by ID, including its variants.

GET /api/apps/v1/products/:product_id

Authentication

Required. Must include valid app credentials.

Scope

read:products

Path Parameters

ParameterTypeDescription
product_idintegerThe product ID to retrieve

Example Request

curl -X GET "https://api.selorax.io/api/apps/v1/products/150" \
  -H "Authorization: Bearer <token>"

Response

{
  "data": {
    "product_id": 150,
    "sku_id": 450,
    "slug": "premium-rose-bouquet",
    "brand_id": null,
    "category_id": 5,
    "store_id": 22,
    "name": "Premium Rose Bouquet",
    "description": "<p>A hand-crafted bouquet of premium roses.</p>",
    "short_description": "Hand-crafted premium roses",
    "is_bundle": 0,
    "is_active": 1,
    "is_visible": 1,
    "is_featured": 0,
    "is_bestseller": 1,
    "is_best_deals": 0,
    "created_at": "2025-03-10T08:00:00.000Z",
    "updated_at": "2025-06-12T14:30:00.000Z",
    "variants": [
      {
        "sku_id": 450,
        "product_id": 150,
        "variant_name": "Small / Red",
        "sku_code": "PRB-SM-RED",
        "price": 500.00,
        "compare_at_price": 600.00,
        "available": 1,
        "quantity": 25,
        "is_default": 1,
        "images": null,
        "weight": 0.5,
        "width": null,
        "depth": null,
        "height": null,
        "position": 1,
        "free_shipping": 0
      },
      {
        "sku_id": 451,
        "product_id": 150,
        "variant_name": "Large / Red",
        "sku_code": "PRB-LG-RED",
        "price": 750.00,
        "compare_at_price": 900.00,
        "available": 1,
        "quantity": 12,
        "is_default": 0,
        "images": null,
        "weight": 0.8,
        "width": null,
        "depth": null,
        "height": null,
        "position": 2,
        "free_shipping": 0
      }
    ]
  },
  "status": 200
}

Product Detail Fields

The single product response includes all list fields plus:

FieldTypeDescription
sku_idintegerDefault variant SKU ID
store_idintegerStore the product belongs to
descriptionstring or nullFull product description (may contain HTML)
short_descriptionstring or nullBrief product summary
is_bundleinteger (0 or 1)Whether this is a bundle product
is_featuredinteger (0 or 1)Whether the product is featured
is_bestsellerinteger (0 or 1)Whether the product is marked as a bestseller
is_best_dealsinteger (0 or 1)Whether the product appears in best deals

Variant Fields

FieldTypeDescription
sku_idintegerVariant SKU identifier
product_idintegerParent product ID
variant_namestringCombined option labels (e.g. "Large / Red")
sku_codestringHuman-readable SKU code
pricenumberCurrent selling price
compare_at_pricenumber or nullOriginal price for comparison (strikethrough pricing)
availableinteger (0 or 1)Whether the variant is available for purchase
quantityintegerStock quantity on hand
is_defaultinteger (0 or 1)Whether this is the default variant
imagesstring or nullVariant-specific images (JSON)
weightnumber or nullWeight in kg
widthnumber or nullWidth dimension
depthnumber or nullDepth dimension
heightnumber or nullHeight dimension
positionintegerDisplay order position
free_shippinginteger (0 or 1)Whether free shipping applies to this variant

Error Responses

CodeStatusMeaning
invalid_token401Token is expired or invalid
insufficient_scope403App does not have read:products scope

If the product_id does not exist or does not belong to the authenticated store:

{
  "message": "Product not found.",
  "status": 404
}

Create Product

Create a new product with an initial variant.

POST /api/apps/v1/products

Scope

write:products

Request Body

FieldTypeRequiredDescription
namestringYesProduct name
descriptionstringNoFull product description (HTML allowed)
short_descriptionstringNoBrief summary
category_idintegerNoCategory ID
brand_idintegerNoBrand ID
is_activebooleanNoWhether active (default: true)
is_visiblebooleanNoWhether visible on storefront (default: true)
is_featuredbooleanNoWhether featured (default: false)
variantobjectYesInitial variant (see below)

Variant fields:

FieldTypeRequiredDescription
pricenumberYesSelling price
variant_namestringNoVariant label (default: "Default")
sku_codestringNoSKU code
compare_at_pricenumberNoOriginal/compare price
quantityintegerNoStock quantity (default: 0)
imagesstringNoComma-separated image keys
weightnumberNoWeight in kg
costnumberNoCost price
free_shippingbooleanNoFree shipping flag

Example Request

curl -X POST "https://api.selorax.io/api/apps/v1/products" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Premium Rose Bouquet",
    "description": "<p>Hand-crafted premium roses</p>",
    "category_id": 5,
    "variant": {
      "price": 500,
      "compare_at_price": 600,
      "sku_code": "PRB-SM-RED",
      "quantity": 25,
      "weight": 0.5
    }
  }'

Response (201)

{
  "data": {
    "product_id": 155,
    "sku_id": 460,
    "name": "Premium Rose Bouquet",
    "slug": "premium-rose-bouquet",
    "store_id": 22
  },
  "status": 201
}

Slug generation

The product slug is auto-generated from the name and guaranteed unique within the store.


Update Product

Update product metadata. Only provided fields are updated.

PUT /api/apps/v1/products/:product_id

Scope

write:products

Request Body

FieldTypeDescription
namestringProduct name (slug auto-regenerated)
descriptionstringFull description
short_descriptionstringBrief summary
category_idintegerCategory ID
brand_idintegerBrand ID
is_activebooleanActive status
is_visiblebooleanStorefront visibility
is_featuredbooleanFeatured flag
is_bestsellerbooleanBestseller flag
is_best_dealsbooleanBest deals flag

Example Request

curl -X PUT "https://api.selorax.io/api/apps/v1/products/150" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Premium Rose Bouquet - Updated",
    "is_featured": true,
    "category_id": 8
  }'

Update Variant

Update a specific product variant's price, stock, images, or dimensions.

PUT /api/apps/v1/products/:product_id/variants/:sku_id

Scope

write:products

Request Body

FieldTypeDescription
variant_namestringVariant label
sku_codestringSKU code
pricenumberSelling price
compare_at_pricenumberCompare-at price
quantityintegerStock quantity (available auto-set based on this)
imagesstringComma-separated image keys
weightnumberWeight in kg
widthnumberWidth
heightnumberHeight
depthnumberDepth
positionintegerDisplay order
free_shippingbooleanFree shipping flag
costnumberCost price

Example Request

curl -X PUT "https://api.selorax.io/api/apps/v1/products/150/variants/451" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "price": 800,
    "quantity": 50,
    "images": "products/prb-lg-red-1.jpg,products/prb-lg-red-2.jpg"
  }'

Add Variant

Add a new variant to an existing product.

POST /api/apps/v1/products/:product_id/variants

Scope

write:products

Request Body

Same fields as the variant object in Create Product, with price required.

Example Request

curl -X POST "https://api.selorax.io/api/apps/v1/products/150/variants" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "variant_name": "Extra Large / White",
    "sku_code": "PRB-XL-WHT",
    "price": 950,
    "quantity": 10
  }'

Response (201)

{
  "data": {
    "sku_id": 465,
    "product_id": 150,
    "variant_name": "Extra Large / White",
    "price": 950,
    "quantity": 10
  },
  "status": 201
}

Delete Variant

Soft-delete a product variant. Cannot delete the last remaining variant.

DELETE /api/apps/v1/products/:product_id/variants/:sku_id

Scope

write:products

Error Responses

StatusMeaning
400Cannot delete the last variant
404Product or variant not found

Delete Product

Soft-delete a product (deactivates and hides from storefront).

DELETE /api/apps/v1/products/:product_id

Scope

write:products


Upload Product Images

Upload images for a product. Images are auto-converted to WebP and thumbnails are generated. Use the returned stored_key values in variant images fields.

POST /api/apps/v1/products/:product_id/images

Scope

write:products

Content-Type

multipart/form-data

Limits

  • Max 5 files per request
  • Max 10MB per file
  • Accepted types: JPEG, PNG, GIF, WebP, SVG, BMP, TIFF

Path Parameters

ParameterTypeDescription
product_idintegerThe product ID to upload images for

Form Fields

FieldTypeRequiredDescription
filesfile(s)YesImage files (use multiple files fields for multiple images)

Example Request

curl -X POST "https://api.selorax.io/api/apps/v1/products/150/images" \
  -H "Authorization: Bearer <token>" \
  -F "[email protected]" \
  -F "[email protected]"

Response

{
  "message": "2 image(s) uploaded",
  "data": [
    {
      "id": 234,
      "file_url": "https://pub-xxxxx.r2.dev/media/22/products/a1b2c3d4.webp",
      "thumbnail_url": "https://pub-xxxxx.r2.dev/media/22/products/thumbs/a1b2c3d4.webp",
      "stored_key": "media/22/products/a1b2c3d4.webp",
      "width": 1200,
      "height": 1200,
      "file_size": 45678
    },
    {
      "id": 235,
      "file_url": "https://pub-xxxxx.r2.dev/media/22/products/e5f6g7h8.webp",
      "thumbnail_url": "https://pub-xxxxx.r2.dev/media/22/products/thumbs/e5f6g7h8.webp",
      "stored_key": "media/22/products/e5f6g7h8.webp",
      "width": 800,
      "height": 800,
      "file_size": 32100
    }
  ],
  "status": 200
}

Image Processing

  • JPEG, PNG, BMP, TIFF are auto-converted to WebP (quality 85) for optimal size
  • GIF, WebP, SVG are stored as-is
  • A 300px thumbnail is generated for each image
  • Original dimensions (width, height) are preserved in metadata

Using uploaded images

After uploading, use the stored_key values in the variant's images field as a comma-separated string:

curl -X PUT ".../products/150/variants/451" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"images": "media/22/products/a1b2c3d4.webp,media/22/products/e5f6g7h8.webp"}'

Error Responses

StatusMeaning
400No files provided or invalid file type
404Product not found
413Storage quota exceeded