Create and maintain menu products
This guide covers creating a product with its variants, attaching modifier categories, and the updates you'll make afterward — visibility, stock, and price.
The flow at a glance
Step 1 — categories and kitchens
A product belongs to a category and a kitchen; resolve both ids first:
GET /v1/menu/categories
GET /v1/menu/kitchens
Required scope: menu:read. A category holds at most 50 products — creating one past that
limit is rejected with catalog_limit_reached_products_per_category.
Step 2 — create the product
POST /v1/menu/products
Required scope: menu:write. Prices are decimals, not minor units, as in every
menu endpoint. Accepts application/json (no image) or multipart/form-data (field
product = the same JSON as a string, field image = a jpeg/jpg/png/webp file up to 5 MB).
curl -X POST https://public-api.olaclick.app/v1/menu/products \
-H "Authorization: Bearer olk_live_..." \
-H "Content-Type: application/json" \
-d '{
"category_id": "6b2f1a04-1c7e-4a2b-9f11-2d8e0b3c4d55",
"kitchen_id": "9c4f4203-9e2c-403d-88cc-7c0ce9e0f572",
"name": "Classic Burger",
"description": "Beef patty, cheddar, lettuce, tomato",
"variants": [
{
"name": "Regular",
"price": 8.5,
"original_price": 10,
"sku": "BURGER-REG"
}
]
}'
| Field | Required | Notes |
|---|---|---|
category_id, kitchen_id | Yes | Must belong to your company; an unknown kitchen_id is rejected with 422, an unknown category_id with 404. |
name | Yes | 1–191 characters. |
description | No | Up to 550 characters. |
visible | No | Defaults to true. |
variants | Yes | 1–30 items. Each needs name, price, original_price (list price) and sku (at most 20 characters); price must not exceed original_price. cost and packaging_price are write-only (accepted, never returned). |
modifier_category_ids | No | Up to 100 distinct UUIDs, in display order. Omit for a product with no modifiers. |
The response is the created product with its variants and modifier categories — see the REST API Reference for the full shape.
Step 3 — add or update variants
A product can get more variants (sizes, presentations) after creation:
POST /v1/menu/products/{id}/variants
{ "name": "Large", "price": 10.5, "original_price": 12, "sku": "BURGER-LRG" }
Required scope: menu:write. Same required fields as a variant in the create body:
name, original_price, price, sku.
To change an existing variant — including its
price, stock or stock_threshold — use:
PATCH /v1/menu/products/{id}/variants/{variantId}
{ "stock": 40, "stock_threshold": 5 }
All fields are optional, but at least one is required; price still cannot exceed
original_price when both are present in the request.
Step 4 — sync modifier categories
List what's available, then replace the product's association in one call:
GET /v1/menu/modifier-categories
PUT /v1/menu/products/{id}/modifier-categories
{ "modifier_category_ids": ["8e2b4d6f-9a0c-4e1b-85d7-9f2a4c6e8b10"] }
This replaces the product's modifier categories entirely, in the order given — it is not an add/remove diff. Every id must be distinct; an empty array detaches every modifier category.
Required scope: menu:write.
Step 5 — updates (visibility, stock, price)
- Visibility, stock tracking, customer-facing message:
PATCH /v1/menu/products/{id}—name,description,visible,stock_enabled,messages_for_customers. At least one field is required; the response does not includevariants(callGET /v1/menu/products/{id}for the full product). - Stock and price per variant:
PATCH /v1/menu/products/{id}/variants/{variantId}, as above.
Common validation errors
| Status | Code | When |
|---|---|---|
| 422 | validation_failed (kitchen_not_found) | kitchen_id doesn't belong to your company. |
| 404 | not found | category_id doesn't exist or belongs to another company (on create); the product (and, for variant endpoints, the variant) doesn't exist. |
| 422 | catalog_limit_reached_products_per_category | The category already has 50 products. |
| 422 | catalog_limit_reached_variants_per_product | The product already has the maximum number of variants allowed. |
| 422 | validation_failed | A variant's price exceeds its original_price; an update body has no fields; modifier_category_ids has duplicates. |
| 422 | modifier_category_not_found | A modifier_category_ids entry doesn't exist or belongs to another company. |
| 403 | insufficient_scope | The API key is missing menu:read or menu:write. |
Doing it through the MCP server
| Tool | What it does |
|---|---|
list_categories / list_kitchens | Resolve category_id / kitchen_id |
create_product | Create a product with its variants (JSON only, no image) |
create_product_variant | Add a variant to an existing product |
update_product_variant | Update a variant (price, stock, etc.) |
list_modifier_categories | Find valid modifier_category_ids |
sync_product_modifier_categories | Replace a product's modifier categories |
list_products / get_product | Browse the catalog |
Needed scopes: menu:read, menu:write.
Note: changing a product's own visibility/stock-tracking flag
(PATCH /v1/menu/products/{id}) is REST-only — there is no MCP tool for it yet.
Example prompts:
"Create a product called Classic Burger in category 6b2f1a04-1c7e-4a2b-9f11-2d8e0b3c4d55, kitchen 9c4f4203-9e2c-403d-88cc-7c0ce9e0f572, with one variant priced at 8.5"
"Set stock to 40 for variant ... on product ..."
See the MCP Server page for every tool's full parameters.