Product
APIs for managing products
List ProductsGET
List all products with pagination and filtering options. per_page is capped at 100: a larger value is clamped to 100, never rejected. Default mode (pagination=page or omitted) is offset pagination and returns the paginator shown below. Keyset mode (pagination=keyset) orders by product id ascending, ignores page, sort and key_by (a sort or key_by that is not a product column is still rejected with 422), and is never cached; data then is an object with data (the products), per_page, after_id (echo of the request), next_after_id (pass it as after_id for the next page; null on the last page), has_more and total (an integer on the first request, when after_id is omitted, null afterwards). Products deleted between two keyset requests do not cause later products to be skipped, and an after_id that is unknown or belongs to another market only positions the cursor inside the caller's own products. A keyset walk is not a snapshot: a product created during the walk with an id below the cursor is not returned, so take a changes-feed position before the first page and replay the feed from it after the last page.
Create ProductPOST
Create a new product with its modifiers, prices, ingredients and recipes.
Detail ProductGET
Get details of a specific product by its ID.
Update ProductPOST
Update an existing product and its modifiers, prices, ingredients and recipes.
Delete ProductDELETE
Delete a product by its ID.
Create Product (Alias)POST
Alias path for `POST /api/v2/products` (routes/api/product.php binds both to ProductController::create). Identical single-product body, response and behaviour — it does not create multiple products.
Delete ProductsDELETE
Delete multiple products by their IDs.
Quick Create ProductPOST
Quickly create a product with a single option.
Quick Create Product (Alias)POST
Alias path for `POST /api/v2/products/quick` (routes/api/product.php binds both to ProductController::majorCreate). Identical single-product body, response and behaviour — it does not create multiple products.
Quick Update ProductPOST
Quickly update a product and its single option.
Sort ProductsPOST
Reorder products within a market and branch.
Delete Products (Alias)DELETE
Alias path for `DELETE /api/v2/products/multi` (routes/api/product.php binds both to ProductController::manyDelete). Identical `products_ids` body, response and behaviour.
List Product CategoriesGET
List product categories with pagination, search, sector/market/branch filtering and the display order: the market order, or with filter[branch_id] the branch order (a branch that never sorted follows the market order, categories the branch has no row for come after its own order). sort_level=market returns the market order even when filter[branch_id] is sent; sort_level=branch requires filter[branch_id].
Create Product CategoryPOST
Create a product category with localized names and at least one of an emoji icon or an image URL (either alone is accepted; neither is rejected).
Detail Product CategoryGET
Get a product category by its ID, including its linked sectors.
Update Product CategoryPOST
Update a product category's icon, image URL or localized names.
Delete Product CategoryDELETE
Delete a product category by its ID.
Sort Product CategoriesPOST
Replace the product category display order. Open to any order-page user (platform admin/merchant or a subscriber). A branch-bound subscriber sorts their OWN branch's order when the request carries a branch_id (any other branch id is ignored, never written) or omits it, and sorts the MARKET-wide order when the request carries an explicit branch_id null (the Product Categories page), which also requires the PRODUCT_CATEGORY_UPDATE grant (403 without it). An unbound saver (admin/merchant or an account-owner subscriber with no branch_id) chooses the scope with branch_id: send a branch_id in the caller's own market to set that branch's per-branch order, or send null/omit it to set the single market-wide global order (category_product_sorts with branch_id null). market_id is the subscriber's own market, or the request market_id for an admin/merchant (ownership-verified). The list with filter[branch_id] reads the branch order; a branch that never sorted follows the market order, and categories the branch has no row for come after its own order, in market order.
Product Category Stats — SummaryGET
Product counts across every product in the caller's scope (all categories plus uncategorized).
Product Category Stats — UncategorizedGET
Product counts for products with no category (category_id IS NULL). Reached at /v2/product-categories/none/stats.
Product Category Stats — By CategoryGET
Product counts (total / active / inactive / sellable) for a single category.
Merge Product CategoriesPOST
Merge one or more duplicate canonical categories into a single winner: re-points products, per-branch sort order and kitchen mappings from the losers onto the winner, migrates per-tenant overrides, then soft-deletes the losers. Order history is never touched. Admin only.
Promote Product CategoryPOST
Promote a private tenant category into a curated global category, discoverable and adoptable by other markets. Admin or merchant only. Optionally attach it to one or more sectors via sector_ids.
Set Product Category StatusPOST
Activate or deactivate a category for the caller's market by writing the caller's per-market is_active override (adopting/creating the config if the market has none yet for a visible global). Tenant-scoped: only the caller's own or in-sector categories, never another market's. Never mints a duplicate canonical.
Similar Product CategoriesGET
Advisory lookup that returns up to 10 existing categories whose canonical name or the caller market's override name equals or contains the given name_en/name_ar, so the client can warn about a near-duplicate before creating one. At least one of name_en/name_ar is required. Tenant-scoped: never returns another market's private categories or overrides. Exact matches are ordered first, then contains, then by name.
List Product IngredientsGET
List the ingredients attached to a product.
Create Product IngredientPOST
Attach an ingredient to a product.
Detail Product IngredientGET
Get a single ingredient attached to a product.
Update Product IngredientPOST
Update an ingredient attached to a product.
Delete Product IngredientDELETE
Detach an ingredient from a product.
List Product ModifiersGET
List the modifiers of a product.
Create Product ModifierPOST
Add a modifier to a product.
Detail Product ModifierGET
Get a single modifier of a product.
Update Product ModifierPOST
Update a modifier of a product.
Delete Product ModifierDELETE
Remove a modifier from a product.
List Product PricesGET
List the payment-method prices of a product.
Upsert Product PricePOST
Add or update a product price for a payment method or order type.
Detail Product PriceGET
Get a single price of a product.
Delete Product PriceDELETE
Remove a price from a product.
List Product RecipesGET
List the recipes attached to a product.
Create Product RecipePOST
Attach a recipe to a product.
Detail Product RecipeGET
Get a single recipe attached to a product.
Update Product RecipePOST
Update a recipe attached to a product.
Delete Product RecipeDELETE
Detach a recipe from a product.
List Addon GroupsGET
List the business add-on group library.
Create Addon GroupPOST
Add a reusable add-on group to the business library.
Detail Addon GroupGET
Get a single add-on group with its options.
Update Addon GroupPOST
Rename an add-on group or change its selection limits.
Delete Addon GroupDELETE
Delete an add-on group and its options (blocked while attached to products).
List Addon Group OptionsGET
Page through every option of an add-on group, in the same id order the product list embeds them. The product list serves at most 100 options per group (`addon_groups[].options`, with `options_count` at `X-API-Version >= 2.9.0`); this is the overflow fetch for the rest. `per_page` is capped at 100.
Create Addon Group OptionPOST
Add an option to an add-on group with its pricing mode.
Detail Addon Group OptionGET
Get a single option of an add-on group.
Update Addon Group OptionPOST
Update an option of an add-on group, including its pricing mode.
Delete Addon Group OptionDELETE
Remove an option from an add-on group.
List Product Addon GroupsGET
List the add-on groups attached to a product, in their per-product order.
Sort Product Addon GroupsPOST
Reorder the add-on groups attached to a product.
Attach Addon GroupPOST
Attach an add-on group to a product, appended after the groups it already carries.
Detach Addon GroupDELETE
Detach an add-on group from a product. The group and its options stay in the library.
Barcode VariantsGET
Paginated, searchable list of every barcode-bearing modifier for the export drawer table. Each row carries the owning product (localized name + id), the variant's own localized name and its `kind` (`OPTION` for a product option, `ADD_ON` for an add-on, `ADD_ON_GROUP` for an add-on-group option), the `barcode` value and `barcode_type`, and the `id` the frontend sends back to `/export/product-barcode`. Only rows with a non-empty barcode are returned, scoped to the caller's tenant (market). `search` matches the barcode, the variant name, and the product name. Add-on and add-on-group barcodes are included (the older product-key export dropped them).
Barcode TypesGET
List every supported barcode symbology with its validation rule set (charset, min/max length, whether a check digit is validated/auto-computed). The frontend mirrors this exact rule set for parity.
Generate BarcodePOST
Produce a valid barcode value for a symbology. Code128/Code39/Code93 derive from the SKU when supplied (or a random alphanumeric); retail types (EAN/UPC) return a check-digit-valid number. Never emits an invalid retail code.
Preview BarcodeGET
Stream a freshly rendered PNG for a modifier's stored barcode, or for a what-if value/type override, on a white (default) or transparent background. `padding` (boolean, default true) wraps the bars in a uniform quiet-zone margin (white or transparent to match the background); send `padding=0` for bars flush to the edge. Nothing is stored. Responds with image/png bytes.
List Modifier IngredientsGET
List the ingredients attached to a product modifier.
Create Modifier IngredientPOST
Attach an ingredient to a product modifier.
Detail Modifier IngredientGET
Get a single ingredient attached to a product modifier.
Update Modifier IngredientPOST
Update an ingredient attached to a product modifier.
Delete Modifier IngredientDELETE
Detach an ingredient from a product modifier.
Preview Modifier ChargePOST
Compute a per-modifier before/after price, tax, and total for a paginated list of product modifiers against a priceable (order type or payment method), reflecting saved overrides or a staged overrides payload.
List Modifier PricesGET
List the payment-method prices of a product modifier.
Upsert Modifier PricePOST
Add or update a product modifier price for a payment method or order type.
Detail Modifier PriceGET
Get a single price of a product modifier.
Delete Modifier PriceDELETE
Remove a price from a product modifier.
List Modifier RecipesGET
List the recipes attached to a product modifier.
Create Modifier RecipePOST
Attach a recipe to a product modifier.
Detail Modifier RecipeGET
Get a single recipe attached to a product modifier.
Update Modifier RecipePOST
Update a recipe attached to a product modifier.
Delete Modifier RecipeDELETE
Detach a recipe from a product modifier.
Generate SKUPOST
Generate a random SKU that is not currently used by any option/add-on in the given market (and branch, when given). The value is not reserved — it is still validated for uniqueness again when the caller submits it on create/update.