This endpoint requires a valid JWT Bearer token. Accessible via the API gateway at /v1/commerce/*.
Menu Management API
Complete API for managing restaurant menus including items, categories, modifiers, 86ing (out of stock), time-based availability, variants, nutrition, and ingredients.
All write operations require a valid Bearer token with the can_manage_menus permission. Read operations on public menus are available without authentication.
Base Path: /api/v1
Overview
The Menu Management API provides:
| Feature | Description |
|---|---|
| Menus & Items | CRUD operations for menus and menu items |
| Categories | Hierarchical categories with drag-and-drop reordering |
| Modifier Groups | Reusable modifier groups (e.g., "Burger Toppings") |
| 86ing | Mark items out of stock with audit log |
| Dayparts | Time-based availability (breakfast, lunch, dinner) |
| Variants | Size/price variants (Small, Medium, Large) |
| Nutrition | Calorie and macro information |
| Ingredients | Ingredient library with allergen tracking |
| Photos | Upload and manage item/category images |
Core Menu Operations
Create Menu
POST /api/v1/menus
Authorization: Bearer {access_token}
Content-Type: application/json
{
"name": "Main Menu",
"description": "Our regular dining menu",
"location_id": "loc-abc123",
"is_active": true
}
List Menus
GET /api/v1/menus?tenant_id={tenant_id}&location_id={location_id}
Authorization: Bearer {access_token}
Get Menu with Items
GET /api/v1/menus/{menu_id}?tenant_id={tenant_id}
Update Menu
PATCH /api/v1/menus/{menu_id}
Authorization: Bearer {access_token}
Content-Type: application/json
Delete Menu
DELETE /api/v1/menus/{menu_id}
Authorization: Bearer {access_token}
Menu Items
Create Menu Item
POST /api/v1/menus/items
Authorization: Bearer {access_token}
Content-Type: application/json
{
"menu_id": "menu-123",
"category_id": "cat-456",
"name": "Classic Burger",
"description": "Our signature 8oz beef patty",
"price": 14.99,
"sku": "BURG-001",
"is_active": true,
"allergens": ["gluten", "dairy"],
"prep_time_minutes": 15,
"display_order": 1
}
List Menu Items
GET /api/v1/menus/items?tenant_id={tenant_id}&menu_id={menu_id}&is_active=true
Get Menu Item
GET /api/v1/menus/items/{item_id}?tenant_id={tenant_id}
Update Menu Item
PATCH /api/v1/menus/items/{item_id}
Authorization: Bearer {access_token}
Content-Type: application/json
Delete Menu Item
DELETE /api/v1/menus/items/{item_id}
Authorization: Bearer {access_token}
Categories
Organize menu items into hierarchical categories.
Create Category
POST /api/v1/menus/{menu_id}/categories
Authorization: Bearer {access_token}
Content-Type: application/json
{
"name": "Appetizers",
"description": "Starters and small plates",
"parent_id": null,
"display_order": 1,
"is_active": true
}
List Categories
GET /api/v1/menus/{menu_id}/categories?tenant_id={tenant_id}&parent_id={parent_id}&include_items=true
| Parameter | Type | Description |
|---|---|---|
parent_id | UUID | Filter to subcategories of parent |
include_items | boolean | Include items in each category |
Get Category
GET /api/v1/menus/{menu_id}/categories/{category_id}?tenant_id={tenant_id}
Update Category
PATCH /api/v1/menus/{menu_id}/categories/{category_id}
Authorization: Bearer {access_token}
Delete Category
DELETE /api/v1/menus/{menu_id}/categories/{category_id}
Authorization: Bearer {access_token}
Reorder Categories
Drag-and-drop reordering of categories.
POST /api/v1/menus/{menu_id}/categories/reorder
Authorization: Bearer {access_token}
Content-Type: application/json
{
"category_ids": [
"cat-appetizers",
"cat-salads",
"cat-entrees",
"cat-desserts"
]
}
Modifier Groups
Reusable modifier groups that can be assigned to multiple menu items.
Create Modifier Group
POST /api/v1/modifier-groups
Authorization: Bearer {access_token}
Content-Type: application/json
{
"name": "Burger Toppings",
"description": "Available burger toppings",
"selection_type": "multiple",
"min_selections": 0,
"max_selections": 10,
"modifiers": [
{
"name": "Cheese",
"price": 1.50,
"is_default": true
},
{
"name": "Bacon",
"price": 2.00,
"is_default": false
},
{
"name": "Avocado",
"price": 2.50,
"is_default": false
}
]
}
List Modifier Groups
GET /api/v1/modifier-groups?tenant_id={tenant_id}&include_modifiers=true
Get Modifier Group
GET /api/v1/modifier-groups/{group_id}?tenant_id={tenant_id}
Update Modifier Group
PATCH /api/v1/modifier-groups/{group_id}
Authorization: Bearer {access_token}
Delete Modifier Group
DELETE /api/v1/modifier-groups/{group_id}
Authorization: Bearer {access_token}
Assign Modifier Group to Item
POST /api/v1/menus/items/{item_id}/modifier-groups
Authorization: Bearer {access_token}
Content-Type: application/json
{
"modifier_group_id": "group-toppings",
"is_required": true,
"display_order": 1
}
List Item's Modifier Groups
GET /api/v1/menus/items/{item_id}/modifier-groups?tenant_id={tenant_id}
Remove Modifier Group from Item
DELETE /api/v1/menus/items/{item_id}/modifier-groups/{group_id}
Authorization: Bearer {access_token}
86ing (Out of Stock)
When an item is 86'd, a menu.item.86ed event is published to all connected POS terminals, kiosks, and online ordering channels in real time. Un-86ing publishes a menu.item.restored event.
Mark items as temporarily unavailable. Industry term "86" means out of stock.
86 an Item
Mark an item as out of stock.
POST /api/v1/menus/items/{item_id}/86
Authorization: Bearer {access_token}
Content-Type: application/json
{
"reason": "Sold out for the day",
"until": "2026-01-24T06:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Reason for 86ing |
until | datetime | No | Auto-restore time (null = manual restore) |
Response: Updated menu item with is_eighty_sixed: true
Events Published
When an item is 86'd:
- Event type:
menu.item.86ed - Published to: Table events topic
Un-86 an Item
Restore an item to available status.
DELETE /api/v1/menus/items/{item_id}/86
Authorization: Bearer {access_token}
Events Published
When an item is restored:
- Event type:
menu.item.restored
Batch 86 Items
86 multiple items at once.
POST /api/v1/menus/items/86/batch
Authorization: Bearer {access_token}
Content-Type: application/json
{
"item_ids": [
"item-burger-123",
"item-steak-456",
"item-lobster-789"
],
"reason": "Supply chain issue",
"until": null
}
List 86'd Items
Get all currently 86'd items.
GET /api/v1/menus/items/86?tenant_id={tenant_id}&location_id={location_id}
Get 86 Audit Log
View the history of 86/restore actions.
GET /api/v1/menus/86/log?tenant_id={tenant_id}&entity_id={item_id}&limit=50
Response
{
"entries": [
{
"id": "log-001",
"entity_id": "item-burger-123",
"entity_type": "menu_item",
"action": "86",
"reason": "Sold out",
"performed_by": "user-manager",
"created_at": "2026-01-23T18:00:00Z"
},
{
"id": "log-002",
"entity_id": "item-burger-123",
"entity_type": "menu_item",
"action": "restore",
"reason": null,
"performed_by": "system",
"created_at": "2026-01-24T06:00:00Z"
}
]
}
Dayparts (Time-Based Availability)
Control when menu items are available based on time of day.
Create Daypart
POST /api/v1/menus/{menu_id}/dayparts
Authorization: Bearer {access_token}
Content-Type: application/json
{
"name": "Breakfast",
"start_time": "06:00",
"end_time": "11:00",
"days_of_week": ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"],
"is_active": true
}
| Field | Type | Description |
|---|---|---|
start_time | string | Start time (HH:MM format) |
end_time | string | End time (HH:MM format) |
days_of_week | array | Days this daypart is active |
List Dayparts
GET /api/v1/menus/{menu_id}/dayparts
Authorization: Bearer {access_token}
Update Daypart
PATCH /api/v1/menus/{menu_id}/dayparts/{daypart_id}
Authorization: Bearer {access_token}
Delete Daypart
DELETE /api/v1/menus/{menu_id}/dayparts/{daypart_id}
Authorization: Bearer {access_token}
Update Item Availability
Set which dayparts an item is available during.
POST /api/v1/menus/items/{item_id}/availability
Authorization: Bearer {access_token}
Content-Type: application/json
{
"daypart_ids": ["daypart-breakfast", "daypart-brunch"],
"available_from": "2026-01-01",
"available_until": "2026-03-31"
}
Variants
Menu items can have multiple variants with different sizes and prices.
Create Variant
POST /api/v1/menus/items/{item_id}/variants
Authorization: Bearer {access_token}
Content-Type: application/json
{
"name": "Large",
"sku": "PIZZA-LG",
"price": 18.99,
"price_adjustment": 4.00,
"display_order": 2,
"is_default": false,
"is_active": true
}
| Field | Type | Description |
|---|---|---|
name | string | Variant name (e.g., "Large", "12 inch") |
sku | string | Unique SKU for this variant |
price | decimal | Absolute price for this variant |
price_adjustment | decimal | Price difference from base item |
is_default | boolean | Default selection |
List Variants
GET /api/v1/menus/items/{item_id}/variants
Authorization: Bearer {access_token}
Response
{
"variants": [
{
"id": "var-sm",
"menu_item_id": "item-pizza",
"name": "Small",
"sku": "PIZZA-SM",
"price": 12.99,
"display_order": 1,
"is_default": true,
"is_active": true
},
{
"id": "var-md",
"menu_item_id": "item-pizza",
"name": "Medium",
"sku": "PIZZA-MD",
"price": 15.99,
"display_order": 2,
"is_default": false,
"is_active": true
},
{
"id": "var-lg",
"menu_item_id": "item-pizza",
"name": "Large",
"sku": "PIZZA-LG",
"price": 18.99,
"display_order": 3,
"is_default": false,
"is_active": true
}
]
}
Update Variant
PATCH /api/v1/menus/items/{item_id}/variants/{variant_id}
Authorization: Bearer {access_token}
Delete Variant
DELETE /api/v1/menus/items/{item_id}/variants/{variant_id}
Authorization: Bearer {access_token}
Nutrition Information
Track calorie and nutritional information for menu items.
Update Item Nutrition
POST /api/v1/menus/items/{item_id}/nutrition
Authorization: Bearer {access_token}
Content-Type: application/json
{
"calories": 850,
"calories_from_fat": 450,
"total_fat_g": 50,
"saturated_fat_g": 18,
"trans_fat_g": 0,
"cholesterol_mg": 125,
"sodium_mg": 1200,
"total_carbs_g": 45,
"dietary_fiber_g": 3,
"sugars_g": 8,
"protein_g": 42,
"serving_size": "1 burger (280g)"
}
Response
Returns the updated menu item with embedded nutrition info:
{
"id": "item-burger",
"name": "Classic Burger",
"nutrition": {
"calories": 850,
"protein_g": 42,
"total_fat_g": 50,
"total_carbs_g": 45,
"serving_size": "1 burger (280g)"
}
}
Ingredients
Manage an ingredient library with allergen tracking.
Create Ingredient
POST /api/v1/ingredients
Authorization: Bearer {access_token}
Content-Type: application/json
{
"name": "Beef Patty",
"description": "8oz ground beef",
"allergens": [],
"is_active": true
}
List Ingredients
GET /api/v1/ingredients
Authorization: Bearer {access_token}
Update Ingredient
PATCH /api/v1/ingredients/{ingredient_id}
Authorization: Bearer {access_token}
Delete Ingredient
DELETE /api/v1/ingredients/{ingredient_id}
Authorization: Bearer {access_token}
Assign Ingredient to Item
POST /api/v1/menus/items/{item_id}/ingredients
Authorization: Bearer {access_token}
Content-Type: application/json
{
"ingredient_id": "ing-beef-patty",
"quantity": 1,
"unit": "piece",
"is_optional": false
}
List Item Ingredients
GET /api/v1/menus/items/{item_id}/ingredients
Authorization: Bearer {access_token}
Remove Ingredient from Item
DELETE /api/v1/menus/items/{item_id}/ingredients/{ingredient_id}
Authorization: Bearer {access_token}
Photos
Upload and manage photos for items and categories.
Upload Item Photo
POST /api/v1/menus/items/{item_id}/photos
Authorization: Bearer {access_token}
Content-Type: multipart/form-data
Form data:
file: Image file (JPEG, PNG, WebP)display_order: Optional integeralt_text: Optional alt text
Upload Category Photo
POST /api/v1/menus/categories/{category_id}/photo
Authorization: Bearer {access_token}
Content-Type: multipart/form-data
Get Photo
GET /api/v1/photos/{photo_id}
Delete Photo
DELETE /api/v1/photos/{photo_id}
Authorization: Bearer {access_token}
Permissions
Most menu operations require the can_manage_menus permission:
| Role | Create | Read | Update | Delete | 86 |
|---|---|---|---|---|---|
tenant_admin | Yes | Yes | Yes | Yes | Yes |
manager | Yes | Yes | Yes | Yes | Yes |
staff | No | Yes | No | No | No |
| Public | No | Yes | No | No | No |
Error Responses
Menu Not Found (404)
{
"error": {
"code": "NOT_FOUND",
"message": "Menu not found"
}
}
Unauthorized (401)
{
"error": {
"code": "UNAUTHORIZED",
"message": "insufficient permissions to manage menus"
}
}
Related Documentation
- KDS API - Kitchen display integration
- Orders API - Order processing
- Manager Menu Guide - Staff operations