{
	"info": {
		"_postman_id": "cydekick-product-api",
		"name": "Cydekick BMP - Product API",
		"description": "Product API with multi-device API key authentication. Generate API keys via Admin Panel → Security → API Keys.",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
		"_exporter_id": "cydekick"
	},
	"variable": [
		{
			"key": "baseUrl",
			"value": "http://localhost:80",
			"type": "string"
		},
		{
			"key": "apiKey",
			"value": "",
			"type": "string",
			"description": "Your API key (ck_...)"
		}
	],
	"auth": {
		"type": "apikey",
		"apikey": [
			{
				"key": "value",
				"value": "{{apiKey}}",
				"type": "string"
			},
			{
				"key": "key",
				"value": "X-API-Key",
				"type": "string"
			}
		]
	},
	"item": [
		{
			"name": "Health Check",
			"item": [
				{
					"name": "API Health",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{baseUrl}}/api/health",
							"host": ["{{baseUrl}}"],
							"path": ["api", "health"]
						},
						"description": "Check if the API is running. No authentication required."
					},
					"response": []
				}
			],
			"description": "Public health check endpoints"
		},
		{
			"name": "Categories",
			"item": [
				{
					"name": "List Categories",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{baseUrl}}/api/categories",
							"host": ["{{baseUrl}}"],
							"path": ["api", "categories"],
							"query": [
								{
									"key": "parent_id",
									"value": "",
									"disabled": true,
									"description": "Filter by parent category ID"
								},
								{
									"key": "root_only",
									"value": "false",
									"disabled": true,
									"description": "Show only root categories (true/false)"
								}
							]
						},
						"description": "Retrieve all product categories. Requires API key authentication."
					},
					"response": []
				},
				{
					"name": "Get Category",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{baseUrl}}/api/categories/{{categoryId}}",
							"host": ["{{baseUrl}}"],
							"path": ["api", "categories", "{{categoryId}}"]
						},
						"description": "Get a single category by ID with its products and children."
					},
					"response": []
				}
			],
			"description": "Category read operations"
		},
		{
			"name": "Products - Read",
			"item": [
				{
					"name": "List Products",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{baseUrl}}/api/products",
							"host": ["{{baseUrl}}"],
							"path": ["api", "products"],
							"query": [
								{
									"key": "per_page",
									"value": "15",
									"disabled": true,
									"description": "Items per page (1-100)"
								},
								{
									"key": "search",
									"value": "",
									"disabled": true,
									"description": "Search in name, sku, reference, barcode"
								},
								{
									"key": "category_id",
									"value": "",
									"disabled": true,
									"description": "Filter by category ID"
								},
								{
									"key": "type",
									"value": "",
									"disabled": true,
									"description": "Filter by product type"
								},
								{
									"key": "enable_sales",
									"value": "",
									"disabled": true,
									"description": "Filter by sales enabled status"
								},
								{
									"key": "enable_purchase",
									"value": "",
									"disabled": true,
									"description": "Filter by purchase enabled status"
								},
								{
									"key": "company_id",
									"value": "",
									"disabled": true,
									"description": "Filter by company ID"
								},
								{
									"key": "sort_by",
									"value": "created_at",
									"disabled": true,
									"description": "Sort field (name, sku, price, cost, created_at, updated_at)"
								},
								{
									"key": "sort_dir",
									"value": "desc",
									"disabled": true,
									"description": "Sort direction (asc, desc)"
								}
							]
						},
						"description": "List all products with optional filtering and pagination. Requires 'products.read' permission."
					},
					"response": []
				},
				{
					"name": "Get Product",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{baseUrl}}/api/products/{{productId}}",
							"host": ["{{baseUrl}}"],
							"path": ["api", "products", "{{productId}}"]
						},
						"description": "Get a single product by ID with all related data (category, tags, attributes, variants, etc.). Requires 'products.read' permission."
					},
					"response": []
				}
			],
			"description": "Product read operations"
		},
		{
			"name": "Scanner",
			"item": [
				{
					"name": "Scan Barcode",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"barcode\": \"ERR3340\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{baseUrl}}/api/scanner/barcode",
							"host": ["{{baseUrl}}"],
							"path": ["api", "scanner", "barcode"]
						},
						"description": "Look up a product by barcode or SKU/reference. Drop-ship warehouses are excluded from stock rows.\n\n**Required fields:**\n- barcode: The scanned value (matched against barcode or reference/SKU fields)\n\n**Response includes:**\n- id, name, barcode, sku, company, image\n- product_cost: Standard cost price from the product record\n- stock[]: per-location rows, each with:\n  - location_id — use this in the stock-update call\n  - warehouse, location\n  - on_hand_quantity, reserved_quantity, available_quantity\n  - avg_cost: Current weighted average cost for this location\n  - bin_racks: { main_bin_rack, overflow_bin_rack }"
					},
					"response": []
				},
				{
					"name": "Scan SKU",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"sku\": \"ERR3340\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{baseUrl}}/api/scanner/sku",
							"host": ["{{baseUrl}}"],
							"path": ["api", "scanner", "sku"]
						},
						"description": "Look up a product by SKU (exact match). Returns identical data to Scan Barcode. Drop-ship warehouses are excluded from stock rows.\n\n**Required fields:**\n- sku: The product SKU\n\n**Response includes:**\n- id, name, barcode, sku, company, image\n- product_cost: Standard cost price from the product record\n- stock[]: per-location rows, each with:\n  - location_id — use this in the stock-update call\n  - warehouse, location\n  - on_hand_quantity, reserved_quantity, available_quantity\n  - avg_cost: Current weighted average cost for this location\n  - bin_racks: { main_bin_rack, overflow_bin_rack }"
					},
					"response": []
				},
				{
					"name": "Update Stock Quantity (Delta)",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"product_id\": 1,\n    \"location_id\": 48,\n    \"quantity_change\": 1,\n    \"unit_cost\": 8.50\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{baseUrl}}/api/scanner/stock-update",
							"host": ["{{baseUrl}}"],
							"path": ["api", "scanner", "stock-update"]
						},
						"description": "Adjust the on-hand quantity for a product at a specific location by a delta amount. Positive values add stock, negative values remove it.\n\n**Required fields:**\n- product_id: integer — from scan response data.id\n- location_id: integer — from scan response data.stock[n].location_id\n- quantity_change: integer (non-zero) — units to add (positive) or remove (negative)\n\n**Optional fields:**\n- unit_cost: decimal — cost per unit for incoming stock. Used to recalculate weighted average cost. Ignored on removals.\n\n**Validation:**\n- quantity_change cannot be 0\n- Result (current + change) cannot go below 0\n- Location must be internal, non-scrap, non-dropship\n- A stock record must already exist for this product/location pair\n\n**Response includes:**\n- product_id, location_id, location, warehouse\n- previous_quantity, quantity_change, new_quantity\n- unit_cost_applied: the cost used (null if not supplied)\n- new_average_cost: recalculated weighted average cost for this location\n- new_total_value: updated stock value for this location"
					},
					"response": []
				},
				{
					"name": "Scrap Stock",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"product_id\": 1,\n    \"location_id\": 48,\n    \"qty\": 2\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{baseUrl}}/api/scanner/scrap",
							"host": ["{{baseUrl}}"],
							"path": ["api", "scanner", "scrap"]
						},
						"description": "Scrap a quantity of stock from a warehouse location. Deducts from the source location, moves to the scrap location, and creates a scrap record (SP/nnn).\n\n**Required fields:**\n- product_id: integer — from scan response data.id\n- location_id: integer — from scan response data.stock[n].location_id (must be internal, non-scrap)\n- qty: integer (min 1) — number of units to scrap\n\n**Validation:**\n- qty must be > 0\n- There must be enough on-hand stock at the source location\n- Source location must be internal and non-dropship\n\n**Response includes:**\n- scrap_id: ID of the created scrap record\n- scrap_reference: Reference number e.g. SP/42\n- product_id, location_id, location, warehouse\n- qty_scrapped\n- scrap_location: name of the scrap bin\n- scrapped_at: ISO 8601 timestamp"
					},
					"response": []
				}
			],
			"description": "Barcode scanner endpoints for warehouse/handheld devices"
		},
		{
			"name": "Products - Write",
			"item": [
				{
					"name": "Create Product",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"name\": \"Sample Product\",\n    \"sku\": \"SAMPLE-001\",\n    \"type\": \"consumable\",\n    \"reference\": \"REF-001\",\n    \"barcode\": \"1234567890123\",\n    \"price\": 29.99,\n    \"cost\": 15.00,\n    \"description\": \"This is a sample product\",\n    \"description_sale\": \"Perfect for everyday use\",\n    \"enable_sales\": true,\n    \"enable_purchase\": true,\n    \"category_id\": 1,\n    \"uom_id\": 1,\n    \"tag_ids\": [1, 2]\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{baseUrl}}/api/products",
							"host": ["{{baseUrl}}"],
							"path": ["api", "products"]
						},
						"description": "Create a new product. Requires 'products.write' permission.\n\n**Required fields:**\n- name: Product name\n- sku: Unique stock keeping unit\n\n**Optional fields:**\n- type: consumable, service, or storable (default: consumable)\n- reference: Internal reference code\n- barcode: UPC/EAN barcode\n- price: Selling price\n- cost: Cost price\n- volume, weight\n- description, description_purchase, description_sale\n- enable_sales, enable_purchase, is_favorite, is_configurable\n- images: Array of image paths\n- category_id, uom_id, uom_po_id, parent_id, company_id\n- tag_ids: Array of tag IDs"
					},
					"response": []
				},
				{
					"name": "Update Product",
					"request": {
						"method": "PUT",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"name\": \"Updated Product Name\",\n    \"price\": 39.99,\n    \"description\": \"Updated description\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{baseUrl}}/api/products/{{productId}}",
							"host": ["{{baseUrl}}"],
							"path": ["api", "products", "{{productId}}"]
						},
						"description": "Update an existing product. Requires 'products.write' permission.\n\nOnly include fields you want to update. All fields except 'sku' can be modified."
					},
					"response": []
				},
				{
					"name": "Delete Product",
					"request": {
						"method": "DELETE",
						"header": [],
						"url": {
							"raw": "{{baseUrl}}/api/products/{{productId}}",
							"host": ["{{baseUrl}}"],
							"path": ["api", "products", "{{productId}}"]
						},
						"description": "Delete a product. Requires 'products.write' permission.\n\n**Note:** Products with variants cannot be deleted. Delete variants first."
					},
					"response": []
				},
				{
					"name": "Bulk Delete Products",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n    \"ids\": [1, 2, 3]\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{baseUrl}}/api/products/bulk-delete",
							"host": ["{{baseUrl}}"],
							"path": ["api", "products", "bulk-delete"]
						},
						"description": "Delete multiple products at once. Requires 'products.write' permission.\n\n**Note:** Products with variants will be excluded from deletion."
					},
					"response": []
				}
			],
			"description": "Product write operations (create, update, delete)"
		}
	],
	"event": [
		{
			"listen": "prerequest",
			"script": {
				"type": "text/javascript",
				"exec": [
					"// Log API key status",
					"const apiKey = pm.collectionVariables.get('apiKey');",
					"if (!apiKey) {",
					"    console.warn('⚠️ API Key not set! Set {{apiKey}} in collection variables.');",
					"} else if (apiKey.startsWith('ck_')) {",
					"    console.log('✓ API Key is configured');",
					"} else {",
					"    console.warn('⚠️ Invalid API Key format. Should start with ck_');",
					"}"
				]
			}
		}
	]
}
