Products API
Manage categories, brands, products, product images, product inventory, and product pricing. Public (no auth): list products, product detail, categories, brands, stock status, pricing. Protected routes require Authorization: Bearer <access_token> and permissions: manage_products (categories, brands, products, images, pricing), manage_inventory (create inventory, restock, logs; see Inventory API). Currencies and taxes: Currencies API, Taxes API. Base path: <API_BASE_URL>/products.
Categories
All category routes require manage_products. Query active_only: use 1, true, or yes to filter active only.
List categories. Response: {"categories": [{"id", "name", "slug", "description", "is_active", "created_at"}, ...]}.
Create category. Body: name (required), description (optional), is_active (optional, default true). Slug is auto-generated from name. 409 if name exists.
Get one category. Public. 404 if not found.
Update category. Body: optional name, description, is_active. Slug is updated from name. 409 if name used by another.
Delete category. Products with this category have category_id set to null. 404 if not found.
Brands
Same query and response shape as categories.
List brands. Response: {"brands": [{"id", "name", "slug", "description", "is_active", "created_at"}, ...]}.
Create brand. Body: name (required), description (optional), is_active (optional). Slug auto-generated. 409 if name exists.
Get one brand. Public. 404 if not found.
Update brand. Body: optional name, description, is_active.
Delete brand. Products with this brand have brand_id set to null. 404 if not found.
Products
Product list and detail include category, brand, images, and image_urls (main + others) using the request host as base URL. List and detail require view_products. Create, update, delete require manage_products.
List products. Permission: view_products. Query: active_only (optional). Response: {"products": [{"id", "sku", "name", "description", "category_id", "brand_id", "is_active", "created_at", "updated_at", "category", "brand", "images", "image_urls"}, ...]}.
Create product. Permission: manage_products. Body: name (required), sku (optional; auto-generated if omitted, format PRD-YYMMDD-XXXXXX), description, category_id, brand_id, is_active (optional, default true). 409 if SKU already exists. Category/brand must be active if provided.
Get one product with category, brand, images, and image_urls. Public. 404 if not found.
Update product. Body: only include fields to change: sku, name, description, category_id, brand_id, is_active. Omitted fields are left unchanged. 409 if SKU used by another product.
Delete product and all its images (DB and files). 404 if not found.
Stock status and restock
Create inventory for a product, view stock status (customers can check availability), restock, and view history. See Inventory API for low-stock, out-of-stock, and needs-reorder reports.
Create inventory for a product. Permission: manage_inventory. Body: optional stock_quantity, reserved_quantity, track_inventory, allow_backorder, low_stock_threshold, reorder_point, reorder_quantity. 409 if inventory already exists. Response: full inventory object with product (201).
Get stock status for a product. Permission: view_products. Response: {"id", "product_id", "stock_quantity", "reserved_quantity", "available_quantity", "track_inventory", "allow_backorder", "low_stock_threshold", "reorder_point", "reorder_quantity", "is_in_stock", "is_low_stock", "is_out_of_stock", "needs_reorder", "product": {"id", "sku", "name"}, ...}. 404 if no inventory.
Restock a product. Permission: manage_inventory. Body: quantity (required), optional reason (e.g. manual_restock, supplier_delivery, customer_return), reference. Creates an inventory log entry. Returns updated inventory (200). 400 if invalid quantity.
Get stock history for a product. Permission: manage_inventory. Query: limit (default 50, max 100), offset (default 0). Response: {"logs": [{"id", "action", "reason", "quantity", "previous_quantity", "new_quantity", "reference", "created_at"}, ...]}.
Create and update pricing
Product pricing uses currencies and taxes. Configure them first via Currencies API and Taxes API. GET is public. Create, update, delete sale require manage_products.
Create pricing for a product. Body: price (required), optional currency_code, compare_at_price, cost_price, tax_id, is_taxable (default true). 409 if pricing already exists.
Get pricing detail. Response: product_id, price, compare_at_price, cost_price, currency, is_on_sale, discount_percentage, savings, tax, profit, formatted_price.
Update product price. Body: price (required), optional compare_at_price.
Remove sale price. Response: full pricing detail (200).
Upload and delete images
All image routes require manage_products.
Upload an image. Use multipart/form-data. Form fields: file or image (required), image_type (optional): main for main image, or a number for position. Allowed types: png, jpg, jpeg, gif, webp. Max size 5MB. Returns {"id", "product_id", "filename", "is_main", "position"} (201). 400 if no file or invalid file.
Delete one image. Image must belong to the given product. 404 if image not found or product_id mismatch.
Common responses
401 if missing or invalid token. 403 if user lacks the required permission (view_products, manage_products, manage_inventory). 404 if resource not found. 409 if name or SKU already exists, or inventory or pricing already exists for product.