Everything AI agents need to use SysPOS MCP tools
| Key format | syspos_<64 hex chars> |
| Claude connector | OAuth 2.1 (DCR + PKCE) — paste your syspos_... key on the login page when connecting https://pos-mcp.syspos.ae/mcp |
| Standard | Authorization: Bearer syspos_... HTTP header (Claude Desktop config, Cursor, SDKs, curl, n8n) |
| Escape hatch | ?api_key=syspos_... on the URL — for clients that cannot set a Bearer header |
| Legacy fallback | "api_key": "syspos_..." inside the JSON body / tool arguments |
| Scoping | Each key is tied to a customer (tenant) and optionally limited to specific locations and tools |
In Claude → Settings → Connectors → Add custom connector:
Name: SysPOS Product Tools
Remote MCP server URL: https://pos-mcp.syspos.ae/mcp
OAuth Client ID/Secret: (leave blank)
→ Add → Connect → paste your syspos_... key → Authorize
Simple HTTP POST — works with curl, fetch, n8n, Make, anything.
POST https://pos-mcp.syspos.ae/api/call
Authorization: Bearer syspos_your_key_here
Content-Type: application/json
{
"tool": "list_products"
}
Use the remote MCP URL plus a Bearer header — the standard MCP-over-HTTP shape:
# MCP endpoint URL
https://pos-mcp.syspos.ae/sse
# Claude Desktop / Cursor / generic MCP client config
{
"mcpServers": {
"syspos": {
"url": "https://pos-mcp.syspos.ae/sse",
"headers": { "Authorization": "Bearer syspos_your_key_here" }
}
}
}
# Claude Code CLI
claude mcp add syspos --transport sse https://pos-mcp.syspos.ae/sse \
--header "Authorization: Bearer syspos_your_key_here"
AI agents can fetch full tool schemas as JSON — no HTML parsing needed:
# Full docs with parameters, types, and example responses
curl https://pos-mcp.syspos.ae/api/docs
# Tool list with full parameter schemas
curl https://pos-mcp.syspos.ae/api/tools
| Status | Meaning | Example Response |
|---|---|---|
| 200 | Success | JSON result data |
| 400 | Bad request / Auth error | { "error": "Invalid API key" } |
| 404 | Tool not found | { "error": "Tool 'x' not found", "available": [...] } |
Add this to your agent's instructions:
You have access to SysPOS product tools.
API endpoint: https://pos-mcp.syspos.ae/api/call
Method: POST
Content-Type: application/json
Authorization: Bearer syspos_your_key_here
Rules:
- Use list_products to fetch the catalog. Pass limit if you need a page size other than the full catalog.
- Do not send customer_id. Tenant comes from the MCP API key.
- Create/update bodies match the dashboard product payload (name, prices, location_prices, modifiers, …).
GET /api/v2/products. Why: The menu the session branch sells — exactly what web-pos loads: live products of this branch with the branch price (actual_price, VAT incl.), option-group links (modifiers[]), sold-out flag and combo slots. Use it to show a menu or build a cart; use list_products only to manage the catalog. Paginated: loop current_page until you have total_records rows. Pair with list_selling_modifiers (option groups) and list_selling_addons (add-ons). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_products"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| page_limit | number | optional | Optional. |
| current_page | number | optional | Optional. |
| k | string | optional | Optional. |
| cat | string | optional | Optional. |
| addon | enum: 0, 1 | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_products"
}'
{
"status": true,
"message": "Data found ",
"data": {
"current_page": 1,
"records_per_page": 50,
"total_records": 1066,
"products": [
{
"id": 257509,
"category_id": 15430,
"name": "Vanilla Latte",
"description": "Authentic Flavors",
"price": 10,
"stock": 0,
"type": "Specialty & Flavored Coffees",
"image": "",
"customer_id": 437,
"location_id": 907,
"is_addon": 0,
"addons": "[]",
"bundle_items": "[]",
"is_disabled": 0,
"discount_pct": 0,
"modifiers": [
{
"priority": 3,
"max_allowed": 3,
"min_allowed": 0,
"modifier_id": 3687
},
{
"priority": 2,
"max_allowed": 3,
"min_allowed": 0,
"modifier_id": 3703
},
{
"priority": 1,
"max_allowed": 3,
"min_allowed": 0,
"modifier_id": 3710
},
{
"priority": 4,
"max_allowed": 3,
"min_allowed": 0,
"modifier_id": 3986
},
{
"priority": 5,
"max_allowed": 3,
"min_allowed": 0,
"modifier_id": 3987
},
{
"priority": 6,
"max_allowed": 3,
"min_allowed": 0,
"modifier_id": 3988
},
{
"priority": 7,
"max_allowed": 3,
"min_allowed": 0,
"modifier_id": 3989
},
{
"priority": 5,
"max_allowed": 1,
"min_allowed": 0,
"modifier_id": 5622
}
],
"is_archive": 0,
"barcode_type": null,
"barcode_value": null,
"is_open_price": 0,
"is_combo": 0,
"sync_deliverect": 0,
"sync_otter": 0,
"new_cost_date": null,
"product_cost": null,
"is_weightage_item": 0,
"unit_id": 0,
"actual_price": 10,
"old_price": null,
"vat": 0.48,
"localized": null,
"base_price": null,
"menu_sort_order": 0,
"plu": null,
"calories": null,
"is_stock_product": 0,
"is_auto_apply": null,
"combo_price": 0,
"localize_name": "Vanilla Latte",
"localize_description": "Coffee",
"label_id": 50,
"recipes": null,
"sku_no": "prod-437-257509",
"tax_id": 16,
"preparation_time": 0,
"product_recipe_total": 0,
"product_material_total": 0,
"is_overridden": 0,
"is_deleted": 0,
"category_sort_order": null,
"is_sold_out": 0,
"auto_selected_modifier_items": [],
"price_levels": [],
"tax_name": "Food Card Tax",
"tax_value": 5,
"image_path": "",
"web_image": ""
}
]
}
}
GET /api/products/webProducts. Why: Catalog management view: every product of the tenant with its master price, category, label, branch list and recipe/channel flags — this is the dashboard product list, not the POS selling menu (use list_selling_products to show or sell from a branch menu; it has branch prices, sold-out and modifier links). Use it to search/filter products and get ids for get_product → update_product / archive_product / delete_product. Filters take ids from list_categories (category_id), list_brands (brand_id), list_labels (label_id), list_channels (channel_id), list_locations (loc). `type` is the category name the POS files the product under (list_selling_categories, sync_invoice line type). Add-ons: list_addons. Per-branch prices, modifier links, BOM and recipes are NOT here — call get_product. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_products"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated branch ids (list_locations). |
| k | string | optional | Case-insensitive substring match on the master product name (products.name, not branch names). |
| category_id | string | optional | Category id or comma-separated ids (list_categories); a product matches if its category_id is any of them. |
| cat | string | optional | Comma-separated category NAMES (exact, case-insensitive, trimmed). |
| addon | enum: 0, 1 | optional | "1" → ONLY add-on products (is_addon = 1). |
| is_archive | number | optional | 0 → active products, 1 → archived products (never both). |
| brand_id | number | optional | Brand id (list_brands): products whose category is in a group linked to that brand. |
| label_id | number | optional | Label id (list_labels): exact match on label_id. |
| channel_id | number | optional | Channel id (list_channels): only products that have a price row for this channel. |
| page | number | optional | 1-based page, used only when limit < 10000. |
| limit | number | optional | Page size. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_products"
}'
{
"data": [
{
"id": 257508,
"category_id": 15231,
"name": "Mocha",
"description": "Authentic Flavors",
"price": 10,
"stock": 0,
"type": "Espresso-Based Drinks",
"image": "https://dev1.syspos.ae/api/uploads/1780385109749-apple.png",
"customer_id": 437,
"location_id": [
908,
907
],
"is_addon": 0,
"addons": "[]",
"bundle_items": "[]",
"is_disabled": 0,
"discount_pct": 0,
"modifiers": null,
"is_archive": 0,
"barcode_type": "",
"barcode_value": "",
"is_open_price": 0,
"is_combo": 0,
"sync_deliverect": 0,
"sync_otter": 0,
"new_cost_date": null,
"product_cost": null,
"is_weightage_item": 0,
"unit_id": 0,
"actual_price": 10,
"old_price": null,
"vat": 0.48,
"localized": null,
"base_price": null,
"menu_sort_order": 0,
"plu": 0,
"calories": null,
"is_stock_product": 0,
"is_auto_apply": null,
"combo_price": 0,
"localize_name": "Mocha",
"localize_description": "Coffee",
"label_id": 50,
"recipes": null,
"sku_no": "pd-437-257508",
"tax_id": 16,
"preparation_time": 0,
"product_recipe_total": 50,
"product_material_total": 0,
"is_overridden": 0,
"is_deleted": 0,
"category_sort_order": null,
"override_location_id": 908,
"override_name": "Mocha",
"override_description": "Authentic Flavors",
"override_price": 10,
"override_actual_price": 10,
"image_path": "1780385109749-apple.png",
"web_image": "1780385109749-apple.png",
"channel_price": [],
"nameChannels": {
"channels": []
},
"total_locations": 2,
"has_recipe": 1
}
],
"total": 2745
}
GET /api/products/:id. Why: Full catalog record of one product — per-branch prices (location_prices), modifier links per branch, channel prices, BOM materials and recipes — which list_products does not return. Always call it before update_product: update is not a patch, so you resend location_prices, modifiers, materials, recipes, channel_price and the columns from this record. For selling/menu display use list_selling_products instead. Ids here link to list_locations (location_prices), list_modifiers / list_selling_modifiers and list_modifier_items (modifiers), list_channels (channel_price), list_materials, list_recipes, list_categories (category_id; type = category name), list_labels (label_id). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_product"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Product id (list_products data[].id). |
| loc | string | optional | Branch id or comma-separated ids. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_product",
"id": 11
}'
[
{
"id": 257508,
"category_id": 15231,
"name": "Mocha",
"description": "Authentic Flavors",
"price": 10,
"stock": 0,
"type": "Espresso-Based Drinks",
"image": "https://dev1.syspos.ae/api/uploads/1780385109749-apple.png",
"customer_id": 437,
"location_id": [
907,
908
],
"is_addon": 0,
"addons": [],
"bundle_items": [],
"is_disabled": 0,
"discount_pct": 0,
"modifiers": [
{
"id": 136528,
"modifier_id": 3687,
"max_allowed": 3,
"min_allowed": 0,
"priority": 4,
"location_id": 907,
"customer_id": 437,
"selection_type": "multiple",
"auto_selected_modifier_item_ids": [],
"mandatory_modifier_item_ids": [],
"unavailable_modifier_item_ids": [],
"modifier_items_order": [
18005,
18052,
22145,
22146
]
}
],
"is_archive": 0,
"barcode_type": "",
"barcode_value": "",
"is_open_price": 0,
"is_combo": 0,
"sync_deliverect": 0,
"sync_otter": 0,
"new_cost_date": null,
"product_cost": null,
"is_weightage_item": 0,
"unit_id": 0,
"actual_price": 10,
"old_price": null,
"vat": 0.48,
"localized": null,
"base_price": null,
"menu_sort_order": 0,
"plu": 0,
"calories": null,
"is_stock_product": 0,
"is_auto_apply": null,
"combo_price": 0,
"localize_name": "Mocha",
"localize_description": "Coffee",
"label_id": 50,
"recipes": [
{
"id": 16779,
"name": "Test",
"yield_qty": 0.8,
"mode": "stockable",
"recipe_id": 16779,
"product_id": 257508,
"portion_qty": 0.8,
"unit": "Kilogram",
"active_from": null,
"active_to": null,
"qty_used": 1
}
],
"sku_no": "pd-437-257508",
"tax_id": 16,
"preparation_time": 0,
"product_recipe_total": 50,
"product_material_total": 0,
"is_overridden": 0,
"is_deleted": 0,
"category_sort_order": null,
"image_path": "1780385109749-apple.png",
"web_image": "1780385109749-apple.png",
"channel_price": [],
"materials": [],
"location_prices": [
{
"location_id": 907,
"location_name": "Default location",
"location_price": 10,
"location_actual_price": 10,
"is_available": true
}
]
}
]
GET /api/v2/categories. Why: Menu categories of a branch — what web-pos loads for its category rail. Use it to group list_selling_products into a menu. A product belongs to a category when product.type == category.name (web-pos's main link) or product.category_id == category.id. Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_categories"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
| k | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_categories"
}'
[
{
"id": 17557,
"name": "Footwear",
"description": "Shoes",
"customer_id": 437,
"location_id": 907,
"color": null,
"image": "https://dev1.syspos.ae/api/uploads/1767085134601-shoes.jpg",
"is_active": 1,
"sync_otter": 0,
"sync_deliverect": 0,
"sync_menu": 1,
"menu_sort_order": 12,
"sync_bitebell": 1,
"localize_name": null,
"localize_description": null,
"sku_no": "cat-437-17557",
"is_deleted": 0,
"sync_grubtech": 0,
"web_image": "api/uploads/1767085134601-shoes.jpg"
}
]
GET /api/categories. Why: Dashboard catalog view of categories (the Products → Categories list), not the POS selling rail — use list_selling_categories to see what a branch actually sells. Use it to search by name, filter by branch / group / brand / active flag, get category ids for get_category → update_category / delete_category, and the id + name a product needs (product.category_id = id, product.type = name). Shows each category's branches (categories_locations) and groups (group_categories). Includes inactive categories unless is_active=1. Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_categories"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or csv of ids (list_locations). |
| k | string | optional | Case-insensitive partial match on the category name (LIKE %k%, parameterised; % and _ act as wildcards). |
| group_id | number | optional | Group id (list_groups): only categories in that group (group_categories, inactive links included). |
| brand_id | number | optional | Brand id (list_brands / list_selling_brands): only categories in a group linked to that brand (brands_groups_relation → group_categories). |
| is_active | number | optional | 1 = only active, 0 = only inactive. |
| is_archive | number | optional | Inverse alias: 0 = only active, 1 = only inactive. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_categories"
}'
[
{
"id": 17557,
"name": "Footwear",
"description": "Shoes",
"customer_id": 437,
"sku_no": "cat-437-17557",
"location_id": [
909,
908,
922,
910,
907,
1131
],
"color": null,
"image": "1767085134601-shoes.jpg",
"web_image": "api/uploads/1767085134601-shoes.jpg",
"is_active": 1,
"sync_otter": 0,
"sync_deliverect": 0,
"sync_menu": 1,
"sync_bitebell": 1,
"sync_grubtech": 0,
"menu_sort_order": 12,
"total_categories": 39,
"groups": [
{
"id": 1465,
"group_name": "Accessories",
"categories_count": 1
}
]
}
]
GET /api/categories/:id. Why: Full dashboard record of ONE category (the Edit Category page): every column, its branches, its groups, and the active products filed under it by category_id (linked_products) — list_categories has no product list. Use it (a) as the base body for update_category (image / is_active / sync_* / menu_sort_order reset if omitted), (b) to check linked_products_count and groups before delete_category. For what a branch sells use list_selling_categories / list_selling_products instead. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_category"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Category id (list_categories [].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_category",
"id": 11
}'
[
{
"id": 17557,
"name": "Footwear",
"description": "Shoes",
"customer_id": 437,
"location_id": [
909,
908,
922,
910,
907,
1131
],
"color": null,
"image": "https://dev1.syspos.ae/api/uploads/1767085134601-shoes.jpg",
"is_active": 1,
"sync_otter": 0,
"sync_deliverect": 0,
"sync_menu": 1,
"menu_sort_order": 12,
"sync_bitebell": 1,
"localize_name": null,
"localize_description": null,
"sku_no": "cat-437-17557",
"is_deleted": 0,
"sync_grubtech": 0,
"web_image": "api/uploads/1767085134601-shoes.jpg",
"groups": [
{
"id": 1465,
"group_name": "Accessories",
"categories_count": 1
}
],
"linked_products": [
{
"id": 266406,
"name": "Flip-Flop",
"sku_no": "prod-437-266406",
"price": 10,
"is_archive": 0,
"category_sort_order": 0,
"image": "",
"web_image": ""
}
],
"linked_products_count": 2
}
]
POST /api/v2/modifiers. Why: All option groups (Size, Milk, Toppings…) available at a branch, each with its items and item prices — what web-pos loads once, then looks up by product.modifiers[].modifier_id from list_selling_products. Read-only despite POST; no body. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_modifiers"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_modifiers"
}'
{
"status": true,
"message": "Record Found",
"data": [
{
"id": 3687,
"name": "Buntie",
"description": "",
"selection_allowed": 1,
"selection_min_allowed": -1,
"priority": 2,
"items": [
{
"id": 18005,
"name": "Chocolate buntie",
"description": "",
"price": 5,
"modifier_id": 3687,
"actual_price": 5,
"vat": 0,
"priority": 1,
"image": "",
"tax_id": 16,
"tax_name": "Food Card Tax",
"tax_value": 5,
"instructions": [
{
"id": 39,
"instruction": "Modifier 123",
"priority": 0
}
],
"web_image": ""
},
{
"id": 18052,
"name": "Rainbow",
"description": "",
"price": 5,
"modifier_id": 3687,
"actual_price": 5,
"vat": 0,
"priority": 2,
"image": "",
"tax_id": null,
"tax_name": null,
"tax_value": null,
"instructions": [
{
"id": 40,
"instruction": "Modifier",
"priority": 0
},
{
"id": 140,
"instruction": "Modifier",
"priority": 0
},
{
"id": 141,
"instruction": "Modifier are extra",
"priority": 1
}
],
"web_image": ""
},
{
"id": 22145,
"name": "Rainbow Buntie",
"description": "",
"price": 5,
"modifier_id": 3687,
"actual_price": 5,
"vat": 0,
"priority": 3,
"image": "",
"tax_id": null,
"tax_name": null,
"tax_value": null,
"instructions": [
{
"id": 42,
"instruction": "Modifier",
"priority": 0
},
{
"id": 142,
"instruction": "Modifier",
"priority": 0
},
{
"id": 143,
"instruction": "Modifier are extra",
"priority": 1
}
],
"web_image": ""
},
{
"id": 22146,
"name": "Plain Buntie",
"description": "",
"price": 5,
"modifier_id": 3687,
"actual_price": 5,
"vat": 0,
"priority": 4,
"image": "",
"tax_id": null,
"tax_name": null,
"tax_value": null,
"instructions": [
{
"id": 43,
"instruction": "Modifier",
"priority": 0
},
{
"id": 144,
"instruction": "Modifier",
"priority": 0
},
{
"id": 145,
"instruction": "Modifier are extra",
"priority": 1
}
],
"web_image": ""
}
]
}
]
}
GET /api/modifiers. Why: Catalog management (dashboard Products › Modifiers list): every modifier GROUP of the tenant with its branch list and group defaults — no items, no product links. Use it to find a group id for get/update/delete_modifier, list_modifier_items modifier_id=, create/update_modifier_item modifier_ids and update_product modifiers[].modifier_id. To SELL (a group with its options and prices at a branch) use list_selling_modifiers; items of a group: list_modifier_items modifier_id=<id>; which products use a group: get_product / list_selling_products modifiers[]. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_modifiers"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or csv (e.g. |
| k | string | optional | Substring search on the group name (LIKE %k%; dashboard: search box). |
| brand_id | number | optional | Only groups linked (product_modifiers) to products in categories of this brand (list_brands id; dashboard: "Brand" select). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_modifiers"
}'
[
{
"id": 4865,
"name": "Basic Accessory",
"description": "",
"location_id": [
922,
907,
908,
909,
910,
1131
],
"customer_id": 437,
"selection_allowed": 0,
"selection_min_allowed": 0,
"priority": 0,
"is_deleted": 0,
"localize_name": null,
"localize_description": null,
"sku_no": null,
"is_mandatory": 0
}
]
GET /api/modifiers/:id. Why: Catalog management: load ONE modifier group (name, group min/max defaults, priority, translations, branch list) before update_modifier — update is a full overwrite, so read first and resend every field. No items (list_modifier_items modifier_id=<id>) and no linked products (get_product modifiers[]). For selling use list_selling_modifiers. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_modifier"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Modifier group id (list_modifiers [].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_modifier",
"id": 11
}'
[
{
"id": 3687,
"name": "Buntie",
"description": "",
"location_id": [
907,
908,
909,
910,
922,
1131
],
"customer_id": 437,
"selection_allowed": 1,
"selection_min_allowed": -1,
"priority": 2,
"is_deleted": 0,
"localize_name": null,
"localize_description": null,
"sku_no": "mod-437-3687",
"is_mandatory": 0
}
]
GET /api/modifierItems. Why: Catalog management (dashboard Products › Modifier Items list): options flat, with their groups, branches and price — to find an item id for get/update/delete_modifier_item or for update_product default / mandatory / unavailable item ids. Filter one group with modifier_id (also returns items linked to it as a second group). To SELL (groups with nested options + kitchen instructions at a branch) use list_selling_modifiers, which nests each option only under its primary modifier_id. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_modifier_items"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or csv (e.g. |
| k | string | optional | Substring search on the option name (LIKE %k%, escaped; dashboard: search box). |
| modifier_id | number | optional | Only options of this group (list_modifiers [].id; dashboard: "Modifiers" select): primary modifier_id = it OR linked via modifier_item_assignments. |
| brand_id | number | optional | Only options whose PRIMARY group is linked to a product in a category of this brand (list_brands id; dashboard: "Brand" select, which clears modifier_id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_modifier_items"
}'
[
{
"id": 18005,
"name": "Chocolate buntie",
"description": "",
"price": 5,
"customer_id": 437,
"location_id": [
1131,
922,
910,
909,
908,
907
],
"modifier_id": 3687,
"actual_price": 5,
"old_price": null,
"vat": 0,
"base_price": null,
"localize_name": "Chocolate bunties",
"localize_description": null,
"sku_no": "moditem-437-18005",
"tax_id": 16,
"priority": 1,
"is_deleted": 0,
"image": "",
"modifier_name": "Buntie",
"mapped_location_ids": "1131,922,910,909,908,907",
"tax_name": "Food Card Tax",
"tax_value": 5,
"modifier_ids": [
3687
],
"web_image": ""
}
]
GET /api/modifierItems/:id. Why: Load ONE option before update_modifier_item (dashboard Modifier Items › Edit loads exactly this) — update resets price / priority / tax and replaces groups and branches, so read first and resend actual_price, priority, modifier_ids, location_ids and tax_id. No kitchen instructions (see list_selling_modifiers items[].instructions) and no recipes. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_modifier_item"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Modifier item id (list_modifier_items [].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_modifier_item",
"id": 11
}'
[
{
"id": 18005,
"name": "Chocolate buntie",
"description": "",
"price": 5,
"customer_id": 437,
"location_id": [
907,
908,
909,
910,
922,
1131
],
"modifier_id": 3687,
"actual_price": 5,
"old_price": null,
"vat": 0,
"base_price": null,
"localize_name": "Chocolate bunties",
"localize_description": null,
"sku_no": "moditem-437-18005",
"tax_id": 16,
"priority": 1,
"is_deleted": 0,
"image": "",
"modifier_name": "Buntie",
"modifier": "3687",
"modifier_ids": [
3687
],
"web_image": ""
}
]
GET /api/v2/brands. Why: Brands of a branch WITH their groups nested (there is no separate groups route) — what web-pos loads to choose the brand, show the group rail and scope the menu. Each group lists the category ids it contains. Step 1 of building a menu: pick a brand, pick a group, then filter list_selling_categories / list_selling_products by the group's category ids. The chosen brand_id goes on every cart line and on the invoice. Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_brands"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_brands"
}'
{
"status": true,
"message": "brands found",
"data": [
{
"brand_id": 459,
"brand_name": "Zara",
"brand_thumbnail": "",
"location_id": 907,
"brand_color": "#000",
"brand_address": "Pakistan",
"brand_email": "Zara@gmail.com",
"brand_phone": "12345678",
"brand_vat_no": "",
"groups": [
{
"group_id": 1462,
"group_name": "Clothing Brand",
"group_thumbnail": "",
"group_description": "",
"brand_id": 459,
"categories": [
17539
]
},
{
"group_id": 1465,
"group_name": "Accessories",
"group_thumbnail": "",
"group_description": "",
"brand_id": 459,
"categories": [
15481,
17556,
17557,
15230
]
}
]
}
]
}
GET /api/brands. Why: Dashboard catalog view of brands (brand management), not the POS selling view — use list_selling_brands to build a branch's menu (it nests each group's category ids). Use this to find brand ids for get_brand / update_brand / delete_brand, and to see each brand's branches (brands_locations) and group names (brands_groups_relation). A brand reaches categories only through its groups: brand → groups (get_brand groups[].id) → categories (list_categories group_id=… or brand_id=…&loc=…) → products. Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_brands"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id(s), csv. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_brands"
}'
{
"status": true,
"message": "Brand found here",
"data": [
{
"id": 459,
"brand_name": "Zara",
"brand_thumbnail": "",
"description": "Clothing Brand",
"brand_groups": [
"Clothing Brand",
"Accessories"
],
"location_id": [
922,
907,
908,
909,
910,
1131
],
"brand_color": "#000",
"brand_address": "Pakistan",
"brand_email": "Zara@gmail.com",
"brand_phone": "12345678",
"brand_vat_no": ""
}
]
}
GET /api/brands/:id. Why: Full dashboard record of ONE brand: every column, branch ids and its groups with ids. Required before update_brand (which overwrites every field, group link and branch link) and to get the brand's group ids for list_categories group_id / get_group. For the POS menu use list_selling_brands. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_brand"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Brand id (list_brands data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_brand",
"id": 11
}'
[
{
"id": 459,
"brand_name": "Zara",
"brand_thumbnail": "",
"location_id": [
922,
907,
908,
909,
910,
1131
],
"customer_id": 437,
"description": "Clothing Brand",
"brand_groups": "1462,1465",
"brand_color": "#000",
"user_id": 4734,
"brand_address": "Pakistan",
"brand_email": "Zara@gmail.com",
"brand_phone": "12345678",
"brand_vat_no": "",
"sku_no": "brd-437-459",
"groups": [
{
"id": 1462,
"group_name": "Clothing Brand"
}
]
}
]
GET /api/groups. Why: List the tenant's groups with their linked categories, brands and branches — use it to see how a brand maps to categories, to find the group id to edit, or to check which categories a brand's menu will show. Produces group ids (get_group / update_group / delete_group); categories[].id feed list_categories/list_products filters; brand[].id are list_brands ids. Catalog management (dashboard) tool. For menu scoping on the POS use list_selling_brands, which already returns each brand's groups with their category ids. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_groups"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or csv. |
| brand_id | number | optional | Brand id (list_brands): keeps only groups linked to this brand in brands_groups_relation. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_groups"
}'
{
"status": true,
"message": "Groups found",
"data": [
{
"id": 1341,
"group_name": "Flavor you need",
"customer_id": 437,
"location_id": [
907,
908,
909,
910,
922,
907,
908,
909,
910,
922,
1131
],
"menu_sort_order": 0,
"categories": [
{
"id": 15481,
"name": "Alternate Options"
},
{
"id": 15230,
"name": "Brewed Coffee"
},
{
"id": 15231,
"name": "Espresso-Based Drinks"
},
{
"id": 15266,
"name": "Iced & Cold Coffees"
},
{
"id": 16686,
"name": "Sandwiches"
},
{
"id": 15430,
"name": "Specialty & Flavored Coffees"
},
{
"id": 16707,
"name": "Coffee"
}
],
"brand": [
{
"id": 412,
"brand_name": "Nexus Cafe"
}
]
}
]
}
GET /api/groups/:id. Why: Load one group with every column plus its categories, brands and branches — required before update_group (which replaces everything). Consumes a group id from list_groups or list_selling_brands. Catalog management (dashboard) tool. For menu scoping on the POS use list_selling_brands, which already returns each brand's groups with their category ids. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_group"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Group id (list_groups data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_group",
"id": 11
}'
[
{
"id": 1341,
"group_name": "Flavor you need",
"group_thumbnail": "1759910641566-coffee.jpg",
"group_description": "Flavor you need",
"categories": [
{
"id": 15481,
"name": "Alternate Options"
},
{
"id": 15230,
"name": "Brewed Coffee"
},
{
"id": 15231,
"name": "Espresso-Based Drinks"
},
{
"id": 15266,
"name": "Iced & Cold Coffees"
},
{
"id": 16686,
"name": "Sandwiches"
},
{
"id": 15430,
"name": "Specialty & Flavored Coffees"
},
{
"id": 16707,
"name": "Coffee"
}
],
"location_id": [
907,
908,
909,
910,
922,
907,
908,
909,
910,
922,
1131
],
"customer_id": 437,
"brand": [
{
"id": 412,
"brand_name": "Nexus Cafe"
}
],
"user_id": 4734,
"sku_no": "grp-437-1341",
"menu_sort_order": 0
}
]
GET /api/v2/products. Why: Add-ons of the session branch (web-pos: GET /api/v2/products?addon=1). An add-on is a small product added ON TOP of a cart line (e.g. extra sandwich with a coffee). Show an add-on for a line only when the add-on's `type` (comma-separated category names) contains the parent product's category name (`type`). Each chosen add-on is charged actual_price × qty and becomes its own invoice line with is_addon = 1 linked to the parent. Same row shape and paging as list_selling_products. web-pos offers NO add-ons when a product is added from search or favourites (no category selected). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_addons"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| page_limit | number | optional | Optional. |
| current_page | number | optional | Optional. |
| k | string | optional | Optional. |
| cat | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_addons"
}'
{
"status": true,
"message": "Data found ",
"data": {
"current_page": 1,
"records_per_page": 50,
"total_records": 7,
"products": [
{
"id": 257559,
"category_id": null,
"name": "Classic Sandwich",
"description": "",
"price": 95,
"stock": 0,
"type": "Alternate Options,Brewed Coffee,Espresso-Based Drinks,Iced & Cold Coffees,Specialty & Flavored Coffees",
"image": "",
"customer_id": 437,
"location_id": 907,
"is_addon": 1,
"addons": "[]",
"bundle_items": "[]",
"is_disabled": 0,
"discount_pct": 0,
"modifiers": [],
"is_archive": 0,
"barcode_type": "",
"barcode_value": "",
"is_open_price": 0,
"is_combo": 0,
"sync_deliverect": 0,
"sync_otter": 0,
"new_cost_date": null,
"product_cost": 0,
"is_weightage_item": 0,
"unit_id": null,
"actual_price": 100,
"old_price": null,
"vat": 5,
"localized": null,
"base_price": null,
"menu_sort_order": 0,
"plu": 0,
"calories": null,
"is_stock_product": null,
"is_auto_apply": null,
"combo_price": 0,
"localize_name": "",
"localize_description": "",
"label_id": null,
"recipes": null,
"sku_no": "prod-437-257559",
"tax_id": null,
"preparation_time": 0,
"product_recipe_total": 0,
"product_material_total": 0,
"is_overridden": 0,
"is_deleted": 0,
"category_sort_order": null,
"is_sold_out": 0,
"auto_selected_modifier_items": [],
"price_levels": [],
"tax_name": null,
"tax_value": null,
"image_path": "",
"web_image": ""
}
]
}
}
GET /api/products/webProducts. Why: Dashboard view of the tenant's add-ons (products with is_addon = 1): ids, master and branch prices, offer list (type), branch count, channel prices. Use it to find add-on ids for get_addon → update_addon / archive_product / delete_addon. Catalog management tool — what the POS offers on a cart line is list_selling_addons. Parent products: list_products / list_selling_products; `type` holds category names from list_categories / list_selling_categories; loc takes list_locations ids; channel_id takes list_channels ids. Per-branch availability and modifier/recipe links are NOT here — call get_addon. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_addons"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or csv of ids (list_locations). |
| k | string | optional | Name search, case-insensitive substring (LIKE %k%) on the MASTER name (not branch names). |
| category_id | string | optional | Category id or csv of ids (list_categories): keeps add-ons whose OWN category_id is one of them. |
| cat | string | optional | Category NAME or csv of names, exact match (case-insensitive, trimmed) on the add-on's OWN category; used only when category_id is omitted/All. |
| brand_id | number | optional | Brand id (list_brands): add-ons whose own category is in a group linked to the brand (brands_groups_relation → group_categories). |
| label_id | number | optional | Label id (list_labels): exact products.label_id match. |
| channel_id | number | optional | Channel id (list_channels): keeps add-ons that have a channel price row for it. |
| is_archive | number | optional | 0 = active add-ons (default), 1 = archived ones (never both). |
| page | number | optional | 1-based page; used only when limit < 10000. |
| limit | number | optional | Rows per page. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_addons"
}'
{
"data": [
{
"id": 257559,
"category_id": null,
"name": "Classic Sandwich",
"description": "",
"price": 100,
"stock": 0,
"type": "Alternate Options,Brewed Coffee,Espresso-Based Drinks,Iced & Cold Coffees,Specialty & Flavored Coffees",
"image": "",
"customer_id": 437,
"location_id": 907,
"is_addon": 1,
"addons": "[]",
"bundle_items": "[]",
"is_disabled": 0,
"discount_pct": 0,
"modifiers": null,
"is_archive": 0,
"barcode_type": "",
"barcode_value": "",
"is_open_price": 0,
"is_combo": 0,
"sync_deliverect": 0,
"sync_otter": 0,
"new_cost_date": null,
"product_cost": 0,
"is_weightage_item": 0,
"unit_id": null,
"actual_price": 100,
"old_price": null,
"vat": 5,
"localized": null,
"base_price": null,
"menu_sort_order": 0,
"plu": 0,
"calories": null,
"is_stock_product": null,
"is_auto_apply": null,
"combo_price": 0,
"localize_name": "",
"localize_description": "",
"label_id": null,
"recipes": null,
"sku_no": "prod-437-257559",
"tax_id": null,
"preparation_time": 0,
"product_recipe_total": 0,
"product_material_total": 0,
"is_overridden": 0,
"is_deleted": 0,
"category_sort_order": null,
"override_location_id": 907,
"override_name": "Classic Sandwich",
"override_description": "",
"override_price": 95,
"override_actual_price": 100,
"image_path": "",
"web_image": "",
"channel_price": [],
"nameChannels": {
"channels": []
},
"total_locations": 4,
"has_recipe": 0
}
],
"total": 7
}
GET /api/products/:id. Why: Load one add-on with everything update_addon needs: master prices, offer list (type), image path, per-branch price and availability (location_prices) and modifier/recipe/channel links. Call it before every update_addon (update rewrites the whole record). Takes an id from list_addons. Catalog management tool — what a branch's POS offers is list_selling_addons; branch ids/names come from list_locations, channel ids from list_channels. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_addon"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Add-on id (list_addons data[].id). |
| loc | string | optional | Branch id or csv. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_addon",
"id": 11
}'
[
{
"id": 257559,
"category_id": null,
"name": "Classic Sandwich",
"description": "",
"price": 95,
"stock": 0,
"type": "Alternate Options,Brewed Coffee,Espresso-Based Drinks,Iced & Cold Coffees,Specialty & Flavored Coffees",
"image": "",
"customer_id": 437,
"location_id": [
908,
907,
910,
1131
],
"is_addon": 1,
"addons": [],
"bundle_items": [],
"is_disabled": 0,
"discount_pct": 0,
"modifiers": [],
"is_archive": 0,
"barcode_type": "",
"barcode_value": "",
"is_open_price": 0,
"is_combo": 0,
"sync_deliverect": 0,
"sync_otter": 0,
"new_cost_date": null,
"product_cost": 0,
"is_weightage_item": 0,
"unit_id": null,
"actual_price": 100,
"old_price": null,
"vat": 5,
"localized": null,
"base_price": null,
"menu_sort_order": 0,
"plu": 0,
"calories": null,
"is_stock_product": null,
"is_auto_apply": null,
"combo_price": 0,
"localize_name": "",
"localize_description": "",
"label_id": null,
"recipes": [],
"sku_no": "prod-437-257559",
"tax_id": null,
"preparation_time": 0,
"product_recipe_total": 0,
"product_material_total": 0,
"is_overridden": 0,
"is_deleted": 0,
"category_sort_order": null,
"image_path": "",
"web_image": "",
"channel_price": [],
"materials": [],
"location_prices": [
{
"location_id": 908,
"location_name": "Johar Town",
"location_price": null,
"location_actual_price": 100,
"is_available": true
},
{
"location_id": 907,
"location_name": "Default location",
"location_price": 95,
"location_actual_price": 100,
"is_available": true
},
{
"location_id": 910,
"location_name": "Bahira Town",
"location_price": 95,
"location_actual_price": 100,
"is_available": true
},
{
"location_id": 1131,
"location_name": "Test",
"location_price": 95,
"location_actual_price": 100,
"is_available": true
}
]
}
]
GET /api/label. Why: Every product label of the tenant with its branch list — the dashboard Products → Labels page and the source of label_id for create_product / update_product, the list_products label_id filter and the labels shown on sold lines (sync_invoice / list_orders_history items[].label_id). Search by name with k, filter by branch with loc (ids from list_locations). Get ids here for get_label / update_label / delete_label. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_labels"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated branch ids (list_locations). |
| k | string | optional | Case-insensitive substring match on the label name, done in code (not SQL): % and _ are literal characters. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_labels"
}'
{
"data": [
{
"id": 50,
"name": "Coffee Seeds",
"description": "",
"customer_id": 437,
"location_id": [
907,
908,
909,
910,
922,
1131
]
},
{
"id": 51,
"name": "Coffee beans",
"description": "",
"customer_id": 437,
"location_id": [
907,
909,
908,
910,
922,
1131
]
},
{
"id": 53,
"name": "Chai seeds",
"description": "",
"customer_id": 437,
"location_id": [
907,
908,
909,
922,
910,
1131
]
},
{
"id": 60,
"name": "Garments",
"description": "",
"customer_id": 437,
"location_id": [
922,
907,
908,
909,
910,
1131
]
}
]
}
GET /api/label/:id. Why: One label with its branch list, to confirm a create_label / update_label or read the full record (name, description, location_id) that update_label must resend. Get the id from list_labels (or a product's label_id from list_products / get_product). The products using it: list_products label_id=<id>. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_label"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Label id (list_labels data[].id, or a product's label_id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_label",
"id": 11
}'
{
"label": {
"id": 50,
"name": "Coffee Seeds",
"description": "",
"customer_id": 437,
"location_id": [
907,
908,
909,
910,
922,
1131
]
},
"message": "Label retrieved successfully"
}
GET /api/deals. Why: Dashboard deal list: the tenant's live combos (is_combo 1, not archived/disabled/add-on) with price, branch list, channel prices and BOM cost — the catalog-management view, not the POS menu (list_selling_products shows what a branch sells, with is_combo / categories[] slots / combo_price). Get ids here for get_deal → update_deal / delete_deal. loc takes branch ids from list_locations. Slot ids + max_allowed are only in get_deal. Paginated with _page/_limit (default 10 per page). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_deals"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or csv of ids (list_locations): deals assigned to any of them (deals_locations, or legacy products.location_id). |
| k | string | optional | Substring match on the deal name (LIKE %k%; % and _ are wildcards). |
| is_combo | enum: 1 | optional | Any value → adds categories = the slot category NAMES (string[]) to each row. |
| _page | number | optional | 1-based page. |
| _limit | number | optional | Page size. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_deals"
}'
{
"items": [],
"total": 0,
"page": 1,
"perPage": 10
}
GET /api/deals/:id. Why: Full deal record for an edit: branch list, choice slots (categories[] with category_id + max_allowed — the same slots list_selling_products gives the POS), channel prices and image. Always call it before update_deal and resend its values (update_deal overwrites every column). Slot category ids resolve with list_categories / list_selling_categories; channel ids with list_channels; branch ids with list_locations. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_deal"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Deal id (list_deals items[].id). |
| is_archive | number | optional | 0 → only a live deal, 1 → only an archived deal (archive_product). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_deal",
"id": 11
}'
"No records found"
GET /api/channels. Why: Dashboard list of the tenant's sales channels with their branch assignments (location_id), description and sku_no. Use it to find a channel id before get/update/delete, or to see which branches a channel is sold on. For what the POS offers at one branch use list_selling_channels. Linkage: a channel id is referenced by list_selling_products data.products[].price_levels[].channel_id (per-channel price, product_channel_price), by orders (channel_id + channel_name, stored verbatim by sync_invoice — web-pos sends 0/""), and by discount codes channel_ids (list_discounts / get_user_extras data.discount_code[]). Branch availability lives in channels_locations (location_id in these tools); master channels have channels.location_id NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_channels"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_channels"
}'
[
{
"id": 463,
"name": "Coffee Bar",
"description": "",
"customer_id": 437,
"sku_no": "chn-437-463",
"location_id": [
1131,
910,
922,
907,
908,
909
]
},
{
"id": 509,
"name": "Express",
"description": "",
"customer_id": 437,
"sku_no": "chn-437-509",
"location_id": 907
}
]
GET /api/channels/:id. Why: Load one channel (name, description, branches) — call it before update_channel, which fully replaces name, description and branch list. Linkage: a channel id is referenced by list_selling_products data.products[].price_levels[].channel_id (per-channel price, product_channel_price), by orders (channel_id + channel_name, stored verbatim by sync_invoice — web-pos sends 0/""), and by discount codes channel_ids (list_discounts / get_user_extras data.discount_code[]). Branch availability lives in channels_locations (location_id in these tools); master channels have channels.location_id NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_channel"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Channel id (list_channels [].id / list_selling_channels data[].channel_id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_channel",
"id": 11
}'
{
"id": 463,
"name": "Coffee Bar",
"description": "",
"customer_id": 437,
"sku_no": "chn-437-463",
"location_id": [
909,
908,
907,
922,
910,
1131
]
}
GET /api/menus. Why: List the tenant's named menus (dashboard Products → Menus) with their branches, brand, routing label and Active status, to get ids for get_menu / update_menu / delete_menu. Menus are dashboard records only: nothing else in SysPOS reads them today. The POS (web-pos) never calls /menus, list_selling_products / list_selling_categories do not look at menus, and the aggregator syncs (Deliverect, Grubtech, Otter, LimeTray) build their menus from categories (sync_* flags, list_categories) and products, not from these menus. Adding or removing a product here does not change what a branch sells; for that use update_product (location_prices) or the category tools. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_menus"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated branch ids (list_locations). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_menus"
}'
{
"success": true,
"data": [
{
"id": 4,
"name": "classic menu",
"description": "",
"currency": "AED",
"customer_id": 437,
"location_id": [
907,
910,
922,
909,
908
],
"is_active": 1,
"brand_id": 414,
"label_id": 51,
"tax_id": 16,
"brand_name": null,
"label_name": "Coffee beans",
"tax_name": "Food Card Tax",
"tax_rate": 5
}
]
}
GET /api/menus/:id. Why: Load one menu with its categories, branches and products — the dashboard View and Edit pages. Call it before update_menu (the update overwrites every field) and to see which products/categories a menu contains. Menus are dashboard records only: nothing else in SysPOS reads them today. The POS (web-pos) never calls /menus, list_selling_products / list_selling_categories do not look at menus, and the aggregator syncs (Deliverect, Grubtech, Otter, LimeTray) build their menus from categories (sync_* flags, list_categories) and products, not from these menus. Adding or removing a product here does not change what a branch sells; for that use update_product (location_prices) or the category tools. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_menu"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Menu id (list_menus data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_menu",
"id": 11
}'
{
"success": true,
"data": {
"id": 4,
"name": "classic menu",
"description": "",
"currency": "AED",
"customer_id": 437,
"location_id": [
907,
910,
922,
909,
908
],
"is_active": 1,
"brand_id": 414,
"label_id": 51,
"tax_id": 16,
"brand_name": null,
"label_name": "Coffee beans",
"tax_name": "Food Card Tax",
"tax_rate": 5,
"categories": [
{
"id": 4,
"menu_id": 4,
"category_id": 15481,
"category_name": "Alternate Options",
"category_description": null
}
],
"products": [
{
"id": 8,
"name": "Chai",
"actual_price": 13,
"product_id": 247653,
"customer_id": 437,
"location_id": null,
"menu_id": 4,
"category_id": 15481,
"product_name": null,
"product_description": null,
"product_image": null,
"category_name": "Alternate Options"
}
],
"locations": [
{
"id": 34,
"menu_id": 4,
"location_id": 907,
"customer_id": 437,
"location_name": "Default location"
},
{
"id": 51,
"menu_id": 4,
"location_id": 910,
"customer_id": 437,
"location_name": "Bahira Town"
},
{
"id": 52,
"menu_id": 4,
"location_id": 922,
"customer_id": 437,
"location_name": "Park View City"
},
{
"id": 53,
"menu_id": 4,
"location_id": 909,
"customer_id": 437,
"location_name": "Iqbal Town"
},
{
"id": 54,
"menu_id": 4,
"location_id": 908,
"customer_id": 437,
"location_name": "Johar Town"
}
]
}
}
GET /api/v2/coupons. Why: Coupon list exactly as web-pos loads it into its local coupon cache when the Coupon dialog opens. Look the typed/scanned code up here FIRST (case-insensitive exact match on code): it tells you whether the coupon is product-scoped (product_ids non-empty → send product_ids to validate_coupon) or basket-wide (send order_total), and gives discount_type / discount_value to compute the discount — validate_coupon does not return an amount. Then ALWAYS call validate_coupon before applying. Not paginated. Product ids = list_selling_products data.products[].id; customer = list_pos_crm_users. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_coupons"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
| code | string | optional | Optional. |
| is_active | enum: 0, 1 | optional | Optional. |
| discount_type | enum: percentage, value | optional | Optional. |
| min_order_value | number | optional | Optional. |
| start_date | string | optional | Optional. |
| end_date | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_coupons"
}'
{
"status": true,
"message": "All coupons retrieved successfully.",
"data": [
{
"id": 987,
"customer_id": 437,
"location_id": null,
"code": "MASTER50",
"description": "MASTER50",
"discount_type": "value",
"discount_value": 10,
"min_order_value": 10,
"start_date": "2026-04-01T00:00:00.000Z",
"end_date": "2026-04-30T00:00:00.000Z",
"usage_limit": 10,
"per_user_limit": 2,
"is_active": 1,
"created_at": "2026-04-01T06:56:40.000Z",
"updated_at": "2026-04-01T06:56:40.000Z",
"current_usage": 0,
"location_ids": [],
"product_ids": [],
"category_ids": []
}
]
}
GET /api/coupons. Why: Dashboard coupon list with branch, product and category links — ids for get_coupon / update_coupon / delete_coupon. Same handler and same response as list_selling_coupons. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_coupons"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
| code | string | optional | Optional. |
| is_active | enum: 0, 1 | optional | Optional. |
| discount_type | enum: percentage, value | optional | Optional. |
| min_order_value | number | optional | Optional. |
| start_date | string | optional | Optional. |
| end_date | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_coupons"
}'
{
"status": true,
"message": "All coupons retrieved successfully.",
"data": [
{
"id": 987,
"customer_id": 437,
"location_id": null,
"code": "MASTER50",
"description": "MASTER50",
"discount_type": "value",
"discount_value": 10,
"min_order_value": 10,
"start_date": "2026-04-01T00:00:00.000Z",
"end_date": "2026-04-30T00:00:00.000Z",
"usage_limit": 10,
"per_user_limit": 2,
"is_active": 1,
"created_at": "2026-04-01T06:56:40.000Z",
"updated_at": "2026-04-01T06:56:40.000Z",
"current_usage": 0,
"location_ids": [],
"product_ids": [],
"category_ids": []
}
]
}
GET /api/coupons/:id. Why: Load one coupon with its branch, product and category links — read it before update_coupon (update replaces links). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_coupon"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Coupon id (list_coupons data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_coupon",
"id": 11
}'
{
"status": true,
"message": "Coupon by id retrieved successfully.",
"data": {
"id": 987,
"customer_id": 437,
"location_id": null,
"code": "MASTER50",
"description": "MASTER50",
"discount_type": "value",
"discount_value": 10,
"min_order_value": 10,
"start_date": "2026-04-01T00:00:00.000Z",
"end_date": "2026-04-30T00:00:00.000Z",
"usage_limit": 10,
"per_user_limit": 2,
"is_active": 1,
"created_at": "2026-04-01T06:56:40.000Z",
"updated_at": "2026-04-01T06:56:40.000Z",
"current_usage": 0,
"product_ids": [],
"category_ids": [],
"location_ids": []
}
}
GET /api/coupons/products-by-locations. Why: Product picker for create_coupon / update_coupon product_ids: live (not archived, not disabled) products linked to ANY of the given branches, sorted by name. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_coupon_products_by_locations"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id(s), comma-separated — the coupon's branches. |
| search | string | optional | Optional. |
| limit | number | optional | Optional. |
| page | number | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_coupon_products_by_locations"
}'
{
"success": true,
"data": [
{
"id": 270965,
"name": "(1) Cold Brew"
},
{
"id": 270946,
"name": "(A) RADAR EV PATH REPL LENS CLEAR"
},
{
"id": 270966,
"name": "*Ice Cola"
}
],
"total": 1075,
"page": 1,
"limit": 3
}
GET /api/coupons/categories-by-locations. Why: Category picker for create_coupon / update_coupon category_ids: active categories linked to ANY of the given branches, sorted by name. Note category links are not enforced at checkout. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_coupon_categories_by_locations"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id(s), comma-separated — the coupon's branches. |
| search | string | optional | Optional. |
| limit | number | optional | Optional. |
| page | number | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_coupon_categories_by_locations"
}'
{
"success": true,
"data": [
{
"id": 15481,
"name": "Alternate Options"
},
{
"id": 17556,
"name": "Bag Accessory"
},
{
"id": 15230,
"name": "Brewed Coffee"
}
],
"total": 24,
"page": 1,
"limit": 3
}
GET /api/discounts/web. Why: Every discount (discount code) of the tenant linked to the given branches — the dashboard Discounts page. These are the rows web-pos gets as get_user_extras data.discount_code[] (active and in date only) and offers in its discount dialog; they are NOT coupons (list_selling_coupons / validate_coupon). Use it to find a discount id for update_discount / delete_discount — also instead of get_discount, which is broken. Scope ids: products list_selling_products, categories list_selling_categories, channels list_selling_channels, payment methods list_selling_payment_methods, order types list_order_types, branches list_locations. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_discounts"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated ids (list_locations); keeps discounts linked to any of them. |
| category_ids | number | optional | ONE category id: only discounts whose category_ids contain it (a csv of several ids matches nothing). |
| is_available_on | string | optional | ONE lowercase weekday name (e.g. |
| start_time | string | optional | "HH:MM:SS". |
| end_time | string | optional | "HH:MM:SS". |
| complimentary | string | optional | Any non-empty value (even "0") → only discounts with discount_percent = 100. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_discounts"
}'
{
"status": true,
"message": "All discounts retrieved successfully.",
"data": [
{
"id": 2376,
"name": "Method Test",
"discount_percent": 50,
"max_cap_amount": 100,
"min_basket_value": 20,
"location_id": [
907,
908,
909,
922
],
"is_active": 1,
"start_date": "2026-03-01T00:00:00.000Z",
"end_date": "2026-03-31T00:00:00.000Z",
"discount_type": 0,
"category_ids": "15481,15230",
"apply_discount_to": "category",
"product_ids": null,
"start_time": "00:00:00",
"end_time": "23:59:00",
"is_available_on": "monday,tuesday,wednesday,thursday,friday,saturday,sunday",
"is_auto_apply": 1,
"is_promotion": 0,
"payment_methods": null,
"channel_ids": "463,509,482,515",
"customer_id": 437,
"order_type_ids": "1139,1174,1129,1127",
"discount_method": "percentage",
"discount_value": 50,
"linked_location_id": 907,
"location_name": "Default location"
}
]
}
GET /api/discounts/:id. Why: Meant to load one discount by id, but the server handler is broken: it reads the id from a route param that does not exist and ALWAYS answers 400 "Please enter valid discount id". Use list_discounts (loc="All") and pick the row by id instead — that is also what the dashboard does before an edit. BROKEN on the server: always returns 400 "Please enter valid discount id" — use list_discounts (loc="All") and pick the row by id. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_discount"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Discount id (list_discounts data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_discount",
"id": 11
}'
{
"status": false,
"message": "Please enter valid discount id",
"data": null
}
GET /api/v2/payment-methods. Why: Tenders the branch can take (Cash, Card, Wallet…) — what web-pos loads for checkout, change-payment and refund. The method NAME is the identity: it is the key in the order's payment_method list, e.g. [{ "CASH": 30 }]. Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_payment_methods"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_payment_methods"
}'
[
{
"id": 1632,
"name": "card",
"is_enable": 1,
"priority": 0,
"type": "cash",
"customer_id": 437,
"location_id": 907,
"sync_locations": 1
},
{
"id": 2135,
"name": "Wallet",
"is_enable": 1,
"priority": 0,
"type": "wallet",
"customer_id": 437,
"location_id": 907,
"sync_locations": 1
}
]
GET /api/payment-methods. Why: Dashboard management list of tender types (Cash, Card, Wallet …) with the branches each is linked to — use it for ids (get/update/delete_payment_method), to audit which branches accept which methods, and to find methods linked to no branch. Not the POS selling view: web-pos reads GET /api/v2/payment-methods (list_selling_payment_methods), which defaults to the cashier's branch. Linkage: the payment method NAME is the key in an invoice's payment_method[] (e.g. [{"cash": 30}]) and is matched case-insensitively against the methods linked to the invoice's branch — so renaming or deleting a method changes what the POS can settle with. Branch availability lives in payment_methods_locations (location_id[] in these tools); on master tenants payment_methods.location_id is NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_payment_methods"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id(s), csv ("907,908"). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_payment_methods"
}'
[
{
"id": 1632,
"name": "card",
"is_enable": 1,
"priority": 0,
"type": "cash",
"customer_id": 437,
"location_id": 907,
"sync_locations": 1
},
{
"id": 2135,
"name": "Wallet",
"is_enable": 1,
"priority": 0,
"type": "wallet",
"customer_id": 437,
"location_id": 907,
"sync_locations": 1
}
]
GET /api/payment-methods/:id. Why: Load one payment method with its full branch list before update_payment_method (update is a full replace — resend is_enable, priority, type, sync_locations and the complete location_id list). Linkage: the payment method NAME is the key in an invoice's payment_method[] (e.g. [{"cash": 30}]) and is matched case-insensitively against the methods linked to the invoice's branch — so renaming or deleting a method changes what the POS can settle with. Branch availability lives in payment_methods_locations (location_id[] in these tools); on master tenants payment_methods.location_id is NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_payment_method"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Payment method id (list_payment_methods). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_payment_method",
"id": 11
}'
[
{
"id": 2135,
"name": "Wallet",
"customer_id": 437,
"location_id": [
907,
908,
909,
910,
922,
1100,
1101,
1102,
1117,
1131
],
"is_enable": 1,
"priority": 0,
"type": "wallet",
"sync_locations": 1
}
]
GET /api/v2/order-types. Why: Order types (Dine-in, Takeaway, Delivery, Drive-thru…) the POS offers — what web-pos loads for the order-type chips and checkout step 1. The chosen type's TITLE (a free-text string) is what goes on the order as order_type; nothing uses the id or code. Tenant-wide list (not per branch). Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_order_types"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| icon_pixel_size | number | optional | Optional. |
| icon_format | enum: png, jpg, jpeg, webp, gif | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_order_types"
}'
{
"status": true,
"message": "data found",
"data": [
{
"id": 1139,
"title": "delivery",
"icon_key": "carbon:delivery",
"icon_url": "https://images.weserv.nl/?url=https%3A%2F%2Fapi.iconify.design%2Fcarbon%2Fdelivery.svg%3Fwidth%3D128%26height%3D128&output=png",
"order_type_code": "DELIVERY",
"is_system_default": 1
},
{
"id": 1174,
"title": "dine-in",
"icon_key": "boxicons:fork-knife",
"icon_url": "https://images.weserv.nl/?url=https%3A%2F%2Fapi.iconify.design%2Fboxicons%2Ffork-knife.svg%3Fwidth%3D128%26height%3D128&output=png",
"order_type_code": "DINE_IN",
"is_system_default": 1
},
{
"id": 1129,
"title": "drive-thru",
"icon_key": "ic:baseline-drive-eta",
"icon_url": "https://images.weserv.nl/?url=https%3A%2F%2Fapi.iconify.design%2Fic%2Fbaseline-drive-eta.svg%3Fwidth%3D128%26height%3D128&output=png",
"order_type_code": "DRIVE_THRU",
"is_system_default": 1
},
{
"id": 1127,
"title": "takeaway",
"icon_key": "boxicons:takeaway",
"icon_url": "https://images.weserv.nl/?url=https%3A%2F%2Fapi.iconify.design%2Fboxicons%2Ftakeaway.svg%3Fwidth%3D128%26height%3D128&output=png",
"order_type_code": "TAKEAWAY",
"is_system_default": 1
}
]
}
GET /api/order-type. Why: Dashboard management list of the tenant's order types (dine-in, takeaway, delivery, drive-thru and custom ones) — use it to get ids for update_order_type / delete_order_type, check whether a name exists before create_order_type, and see which rows are protected system defaults. Not the POS selling view: web-pos reads GET /api/v2/order-types (list_selling_order_types), which returns the same rows as `title` plus a rendered icon_url. Order types are tenant-wide (order_type.customer_id) — NOT per branch; every branch sees the same list and there is no loc filter. The order type NAME (order_type here, `title` in GET /api/v2/order-types) is the string stored in an invoice's order_type; order_type_code is the fixed behaviour (DINE_IN / TAKEAWAY / DELIVERY / DRIVE_THRU) the POS uses to decide table/driver/address flow. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_order_types"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_order_types"
}'
{
"success": true,
"data": [
{
"id": 1139,
"order_type": "delivery",
"order_type_code": "DELIVERY",
"is_system_default": 1,
"customer_id": 437,
"icon_key": "carbon:delivery"
},
{
"id": 1174,
"order_type": "dine-in",
"order_type_code": "DINE_IN",
"is_system_default": 1,
"customer_id": 437,
"icon_key": "boxicons:fork-knife"
}
]
}
GET /api/order-type/codes. Why: The fixed set of behaviour codes accepted by create_order_type.order_type_code (DINE_IN, DRIVE_THRU, TAKEAWAY, DELIVERY). Call before creating a custom order type (e.g. "Talabat" with code DELIVERY) to pick its behaviour. Static list — same for every tenant. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_order_type_codes"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_order_type_codes"
}'
{
"success": true,
"data": [
{
"code": "DINE_IN",
"default_label": "Dine-in"
},
{
"code": "DRIVE_THRU",
"default_label": "Drive-thru"
},
{
"code": "TAKEAWAY",
"default_label": "Takeaway"
},
{
"code": "DELIVERY",
"default_label": "Delivery"
}
]
}
GET /api/v2/kds-orders. Why: The kitchen board — what web-pos's KDS screen polls every 4 s: today's orders of the session branch that are not bumped and not cancelled, each with its lines (is_prepared = ticked) and accept state (is_accepted). Use it to see what the kitchen still has to make, per station (c = category name), and to get uuid / order_items[].id for the three KDS write tools. is_order_rebump=1 returns the bumped orders instead (recall list). Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_kds_orders"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| c | string | optional | Optional. |
| is_order_rebump | number | optional | Optional. |
| brand_id | number | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_kds_orders"
}'
[
{
"id": "a0b1c2d3e4f5",
"oid": 57,
"order_no": 57,
"customer_invoice_id": 10433,
"token_number": null,
"is_accepted": 1,
"external_order_ready": 0,
"exchange_rate": null,
"is_wastage": 0,
"paid_in_usd": null,
"is_discount_on_total": 0,
"items_number": 2,
"aggregator_order_id": null,
"subtotal": 48,
"vat": 0,
"channel_id": 0,
"payable": 48,
"discountamount": 0,
"items_discount_amount": 0,
"is_ticket_available": 0,
"is_wallet_used": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "Guest allergic to sesame",
"uuid": "c1d2e3f4-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
"created_at": 1790227800,
"updated_at": 1790227890,
"customer_id": 437,
"location_id": 907,
"row_discount": 0,
"order_notes": null,
"info_name": null,
"info_address": null,
"info_phone": null,
"info_email": null,
"status": "open",
"by": "web-pos",
"external_order_id": null,
"burn_points": 0,
"earn_points": 0,
"burn_points_amount": 0,
"available_points": 0,
"available_points_amount": 0,
"charges_fee": 0,
"delivery_charges": 0,
"phone_number": "",
"loyalty_mobile_number": "",
"loyalty_mobile_code": "",
"crm_delivery_area": null,
"user": "cashier@example.com",
"table_id": 1204,
"plate_number": null,
"is_vat_on_discount_exclude": 0,
"created_by_user_id": 3101,
"close_by_user_id": null,
"modified_by_user_ids": [],
"crm_user_id": 0,
"crm_user_name": "",
"order_type": "Dine In",
"tip_amount": 0,
"brand_id": null,
"dining_guests": 2,
"channel_name": "",
"discount_reason": null,
"paid_at": null,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": null,
"printed_invoice_receipts": 0,
"printed_kds_receipts": 0,
"app_version": null,
"app_os_version": null,
"build_number": null,
"os_type": null,
"round": 1,
"rider_pickup_at": null,
"rider_arrived_at": null,
"order_type_icon_key": "mdi:silverware-fork-knife",
"notes": [],
"payment_method": null,
"payment_methods": null,
"payments": null,
"order_items": [
{
"id": 1583204,
"identifier": "8f7c2a10-3b1e-4c55-9a7e-0d4b6f1e2a31",
"name": "Chicken Shawarma",
"name_localize": null,
"actual_price": 18,
"base_price": 18,
"price": 18,
"is_weightage_item": 0,
"unit_id": null,
"qty": 2,
"subtotal": 36,
"line_total": null,
"line_vat": null,
"line_total_without_vat": null,
"invoice_uuid": "c1d2e3f4-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
"product_id": 52110,
"extras": null,
"vat": 0,
"is_updated": 0,
"updated_qty": 0,
"is_addon": 0,
"is_refunded": 0,
"refund_quantity": 0,
"refund_reason": null,
"note": "no pickles",
"addon_linked_with": null,
"discount_pct": 0,
"discount_amount": 0,
"modifier": [
{
"id": 881,
"name": "Spicy",
"price": 0,
"qty": 1
}
],
"transferred_to": 0,
"transferred_to_uuid": null,
"transferred_to_uuid_list": [],
"transferred_quantities": [],
"transfer_recipients": [],
"created_at": 1790227800,
"updated_at": 1790227800,
"is_prepared": null,
"preparation_time": 0,
"type": "Hot Kitchen",
"label_id": null,
"is_combo": 0,
"is_deleted": 0,
"deleted_at": null,
"deleted_qty": null,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": null,
"updated_by_user_ids": [],
"re": null,
"ri": null,
"is_wastage": null,
"discount_id": null,
"discount_name": null,
"round": 1,
"combo_items": null
},
{
"id": 1583205,
"identifier": "2b9e5d71-6c0a-4f18-b3d2-7e1a9c4f8d02",
"name": "Mint Lemonade",
"name_localize": null,
"actual_price": 12,
"base_price": 12,
"price": 12,
"is_weightage_item": 0,
"unit_id": null,
"qty": 1,
"subtotal": 12,
"line_total": null,
"line_vat": null,
"line_total_without_vat": null,
"invoice_uuid": "c1d2e3f4-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
"product_id": 52187,
"extras": null,
"vat": 0,
"is_updated": 0,
"updated_qty": 0,
"is_addon": 0,
"is_refunded": 0,
"refund_quantity": 0,
"refund_reason": null,
"note": null,
"addon_linked_with": null,
"discount_pct": 0,
"discount_amount": 0,
"modifier": [],
"transferred_to": 0,
"transferred_to_uuid": null,
"transferred_to_uuid_list": [],
"transferred_quantities": [],
"transfer_recipients": [],
"created_at": 1790227800,
"updated_at": 1790227890,
"is_prepared": 1,
"preparation_time": 0,
"type": "Beverages",
"label_id": null,
"is_combo": 0,
"is_deleted": 0,
"deleted_at": null,
"deleted_qty": null,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": null,
"updated_by_user_ids": [],
"re": null,
"ri": null,
"is_wastage": null,
"discount_id": null,
"discount_name": null,
"round": 1,
"combo_items": null
}
],
"order_type_icon": null
}
]
GET /api/v2/products/sold-out. Why: Ids of the products switched to sold out at the branch (products_locations.is_sold_out = 1, set by update_product_availability). web-pos polls it every 60 s (and listens on Firestore) to grey out tiles. It is the same flag as is_sold_out on list_selling_products, but cheaper to refresh. It does NOT include products whose quota is used up (see list_product_quota). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_sold_out_products"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_sold_out_products"
}'
{
"status": true,
"message": "Sold out products fetched",
"data": []
}
GET /api/v2/user-product-quota. Why: Product quotas of the branch: for each product with a quota, how many units may be sold (total_quota), how many were sold (sold_count) and what is left (remaining_stock). web-pos loads it to show 'N left' on tiles, to block adding more than remaining_stock to a cart, and on the Product Sold Counter page. A quota is per product per branch (not per user, despite the route name). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_product_quota"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | string | optional | Optional. |
| product_id | string | optional | Optional. |
| assignment_id | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_product_quota"
}'
{
"status": true,
"message": "All product quotas retrieved successfully",
"data": [
{
"counter_id": null,
"assignment_id": 4,
"product_id": 257497,
"product_name": "Espresso",
"total_quota": 10,
"sold_count": 0,
"remaining_stock": 10,
"history": []
},
{
"counter_id": null,
"assignment_id": 5,
"product_id": 257499,
"product_name": "Caramel Latte",
"total_quota": 10,
"sold_count": 0,
"remaining_stock": 10,
"history": []
},
{
"counter_id": 15848,
"assignment_id": 25,
"product_id": 257508,
"product_name": "Mocha",
"total_quota": 20,
"sold_count": 7,
"remaining_stock": 13,
"history": [
{
"id": 27,
"quantity": -1,
"invoice_id": 0,
"counter_id": 15848,
"identifier": "58556941-8651-4fbe-a683-8bf32143dfe7",
"order_uuid": "b7b056e0-3c5f-437d-9258-968b6cc34a6e",
"product_id": 257508,
"created_at": 1787738590
}
]
}
]
}
GET /api/v2/shifts/active. Why: Get the active shift for this device/user. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_active_shift"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_active_shift"
}'
{
"id": 1,
"status": "open"
}
GET /api/v2/shifts. Why: List shifts. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_shifts"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| page | string | optional | Page number (1-based). |
| limit | string | optional | Rows per page. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_shifts"
}'
[
{
"id": 1
}
]
GET /api/v2/shifts/drawer-categories. Why: Cash in/out category list. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_drawer_categories"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_drawer_categories"
}'
[
{
"id": 1,
"name": "Petty cash"
}
]
GET /api/v2/shifts/drawer-transactions. Why: List cash drawer transactions for the shift. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_drawer_transactions"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| shift_id | string | optional | Shift id (list_shifts / get_active_shift). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_drawer_transactions"
}'
[
{
"id": 1,
"amount": 10
}
]
GET /api/v2/shifts/tills-summary. Why: Per-user tills summary for a shift. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_tills_summary"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| shift_id | string | optional | Shift id (list_shifts / get_active_shift). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_tills_summary"
}'
[
{
"user_id": 1,
"total": 100
}
]
GET /api/v2/ol-info. Why: Last order of the branch's CURRENT business day (its daily order_no and running receipt number customer_invoice_id) plus that day's UTC window. web-pos caches it at boot but reads nothing from it. Use it to see the last numbers before/after sync_invoice (the server assigns both) or the UTC bounds of 'today'. Hours/time zone come from get_pos_configs. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_ol_info"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_ol_info"
}'
{
"id": 0,
"invoice_id": 0,
"order_id": 0,
"username": "cashier@example.com",
"customer_id": 437,
"location_id": 907,
"fromDate": "2026-09-24 05:00:00",
"toDate": "2026-09-25 05:00:00"
}
GET /api/v2/user-extras-v3. Why: Branch + user configuration web-pos loads at boot: receipt header (name, address, VAT no.), flat VAT %, currency, money decimals, business-day hours, the user's max manual discount %, active discount codes, the void PIN and the ios_modules feature flags (is_loyalty_enable, is_branding_enable, is_activities_enabled). Read it before pricing an order for sync_invoice (VAT fallback, decimal_points) or offering discounts. The per-user POS switches (is_channels_enable, is_soldout, discount_codes, is_change_payment_method_enabled, tip_amount, dining_guest_count, combo_options) are NOT here: they are list_pos_users data[].setting. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_user_extras"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_user_extras"
}'
{
"status": true,
"message": "Data found",
"data": {
"today_guests": 0,
"can_preview_invoice": 0,
"max_discount_permit": 50,
"discount_code": [
{
"id": 101,
"name": "Happy Hour 10",
"discount_percent": null,
"discount_method": "percentage",
"discount_value": 10,
"max_cap_amount": 20,
"min_basket_value": 30,
"location_id": null,
"is_active": 1,
"start_date": "2026-09-01T00:00:00.000Z",
"end_date": "2026-12-31T00:00:00.000Z",
"discount_type": 0,
"category_ids": "",
"apply_discount_to": "entire_basket",
"product_ids": "",
"start_time": "15:00:00",
"end_time": "18:00:00",
"is_available_on": "monday,tuesday,wednesday,thursday,friday",
"is_auto_apply": 0,
"is_promotion": 0,
"payment_methods": "",
"channel_ids": "",
"customer_id": 437,
"order_type_ids": ""
}
],
"opening_hour": "10:00 AM",
"closing_hour": "08:00 AM",
"business_logo": "https://dev1.syspos.ae/api/uploads/example-logo.png",
"business_config": {
"vat": "5",
"info_name": "Example Cafe",
"info_address": "Shop 1, Example Street",
"info_email": "info@example-cafe.test",
"vat_no": "100000000000003",
"info_phone": "+971500000000",
"legal_name": null,
"exchange_rate": 12,
"pincode": 1234,
"currency": {
"id": 10103,
"name": "currency",
"value": "",
"customer_id": 437,
"location_id": 907
}
},
"decimal_points": 0,
"ios_modules": {
"is_kds_enable": 1,
"is_daily_sale_report_enable": 1,
"is_order_hisotry_screen_enable": 1,
"is_sms_enable": 1,
"is_cloud_print_enable": 1,
"is_user_blocked": 0,
"is_branding_enable": 1,
"is_actual_price": 1,
"is_usd_base": 0,
"is_dine_in_enable": 1,
"is_wastage_enable": 1,
"is_material_stock_enable": 1,
"is_balance_enable": 1,
"is_product_scanner_enable": 1,
"is_bar_scanner_enable": 1,
"is_devices_enable": 1,
"is_peer_enable": 1,
"is_setting_enable": 1,
"is_loyalty_enable": 1,
"is_ticket_enabled": 0,
"is_wallet_enabled": 1,
"is_location_service_enabled": 1,
"is_crm_mobile_mandatory": 0,
"reload_dine_order": 1,
"order_dispatch_feature": 0,
"is_activities_enabled": 0
},
"tos": "<ul><li>Optional note</li></ul>",
"tos_links": {},
"receipt_header": "",
"receipt_header_links": {},
"is_shift_enabled": 1,
"business_day_start": "10:00"
}
}
GET /api/v2/user-location. Why: The session user's home branch row (locations table): id, name and integration ids. web-pos caches it at boot for the N-Genius card terminal ids. Use it to learn which branch the session sells in (the branch sync_invoice and every list_selling_* tool use). For all branches in the key's scope use list_locations. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_user_location"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_user_location"
}'
[
{
"id": 907,
"name": "Default location",
"customer_id": 437,
"is_ware_house": "1",
"is_primary": null,
"otter_location_id": null,
"deliverect_location_id": null,
"enable_menu_checkout": null,
"invoice_service_version": "v1",
"payment_provider": null,
"ngenius_terminal_id": null,
"ngenius_merchant_id": null,
"enable_grubtech": 0,
"allow_grubtech_status_updates": 0
}
]
GET /api/v2/users-list. Why: POS users of the session branch with their POS switches (data[].setting). Use it to pick the cashier ids for sync_invoice (created_by_user_id / close_by_user_id / user email), for get_today_sales close_by_user_id and log_invoice_activities, and to read the per-user feature switches web-pos obeys: is_channels_enable + channel_id (list_selling_channels), is_soldout (update_product_availability), discount_codes (discount codes + list_selling_coupons / validate_coupon), is_change_payment_method_enabled (update_invoice_payment_methods), tip_amount, dining_guest_count, combo_options, pay_zero_amount. The session user's own row = the switches that apply to MCP orders. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_pos_users"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_pos_users"
}'
{
"status": true,
"message": "users list",
"data": [
{
"id": 4734,
"username": "Cashier One",
"email": "cashier@example.com",
"pin": "1234",
"role": "admin",
"role_id": 31,
"location_id": 907,
"thumbnail": "",
"is_multi_location_allowed": 0,
"is_master_locations_enabled": 1,
"shift_z_report": 0,
"view_estimated_cash": 0,
"external_order_ready": 0,
"ios_modules": {
"is_kds_enable": 1,
"is_daily_sale_report_enable": 1,
"is_order_hisotry_screen_enable": 1,
"is_sms_enable": 1,
"is_cloud_print_enable": 1,
"is_user_blocked": 0,
"is_branding_enable": 1,
"is_actual_price": 1,
"is_usd_base": 0,
"is_dine_in_enable": 1,
"is_wastage_enable": 1,
"is_material_stock_enable": 1,
"is_balance_enable": 1,
"is_product_scanner_enable": 1,
"is_bar_scanner_enable": 1,
"is_devices_enable": 1,
"is_peer_enable": 1,
"is_setting_enable": 1,
"is_loyalty_enable": 1,
"is_ticket_enabled": 0,
"is_wallet_enabled": 1,
"is_location_service_enabled": 1,
"is_crm_mobile_mandatory": 0,
"reload_dine_order": 1,
"order_dispatch_feature": 0
},
"device_info": [
{
"id": 123,
"device_id": "device-0001",
"device_name": "Front iPad",
"app_os_version": null,
"app_version": null,
"build_number": null,
"user_id": 4734,
"username": "Cashier One",
"customer_id": 437,
"location_id": 907,
"last_online": "2026-09-18T13:19:53.000Z"
}
],
"token": "<jwt>",
"setting": {
"is_multi_location_allowed": 0,
"is_master_locations_enabled": 1,
"print_receipt": 1,
"print_no_of_times": 1,
"is_ingredient_disabled": 0,
"kds_auto_receipt": 1,
"invoice_auto_receipt": 1,
"cart_options": 1,
"print_and_save": 1,
"cloud_print": 1,
"quit_printers_with_cloud_enabled": 1,
"invoice_printer_act_as_kds": 1,
"cart_note": 1,
"cart_car_plate": 1,
"token_number": 1,
"discount_codes": 1,
"transfer_order_pincode": 1,
"daily_sale_input_check": 1,
"daily_sale_media_check": 1,
"active_pincode": 1,
"label_printing": 1,
"qrcode_on_label_print": 1,
"is_kds_device": 1,
"open_drawer": 1,
"user_card_digits": 1,
"invoice_qrcode": 1,
"force_adons": 1,
"combo_options": 1,
"ensure_combo_minimum_item_requirement": 1,
"row_discount": 0,
"sms_invoice": 0,
"language": "",
"zebra_ip": "",
"zebra_port": 0,
"offline_order_sync": 0,
"orderhistory_print_receipt": 0,
"fraction_digits": 2,
"brand_id": 0,
"dining_guest_count": 1,
"is_brand_groups": 0,
"is_extras_enble": 0,
"display_modifier_titles": 0,
"modifier_sort_min_required_desc": 1,
"print_only_save_kds_orders": 0,
"print_note_on_receipt": 0,
"show_receipt_screen": 0,
"order_type": 0,
"open_dine_in": 0,
"hide_cart_pay_button": 0,
"delivery_detail_enable": 0,
"is_attributed_receipt": 0,
"is_cds_enable": 0,
"external_order_print_receipt": 0,
"text_receipt": 0,
"each_category_kds_print": 0,
"exchange_format": 0,
"has_void_order_permission": 0,
"hide_clear_checkout": 1,
"kds_instruction": 1,
"pay_default_usd": 0,
"rpt_business_logo": 0,
"rpt_order_type": 0,
"rpt_is_modified": 0,
"rpt_biller_info": 0,
"rpt_total_before_vat": 0,
"rpt_vat_info": 0,
"rpt_net_total": 0,
"rpt_exchange_rate": 0,
"rpt_dine_guest_count": 0,
"rpt_paid_at": 0,
"esc_command": 0,
"tip_amount": 0,
"print_void_orders": 0,
"qr_code_on_print_receipt": 0,
"is_channels_enable": 0,
"channel_id": 463,
"unify_brands": 0,
"ingredient_stay_standalone": 0,
"allegy_item": 0,
"dinein_serving_role": 0,
"draft_order": 0,
"is_kds_expeditor": 0,
"is_qr_payment": 0,
"pay_zero_amount": 0,
"print_deleted_item_on_receipt": 0,
"receipt_format": 0,
"receipt_translation": 0,
"product_translation": 0,
"category_translation": 0,
"print_invoice_translation": 0,
"print_kds_translation": 0,
"reservation_with_dine_in": 0,
"splitefire": 0,
"station_id": 0,
"cloud_target_user_id": 0,
"cloud_target_device_id": null,
"one_table_one_order": 0,
"is_socket_server": 0,
"is_client": 0,
"exchange_rate": 12,
"currency": {
"id": 10103,
"name": "currency",
"value": "",
"customer_id": 437,
"location_id": 907
},
"web_local_network_printer": 0,
"open_price_pincode": 0,
"is_transfer_enabled": 0,
"block_print_until_order_open": 0,
"user_product_sales_counter": 0,
"is_change_payment_method_enabled": 0,
"skip_zero_quantity_items": 0,
"skip_zero_amount_items": 0,
"settle_wallet_topup": 1,
"is_shift_management": 0,
"acknowledge_dine_table_edit": 0,
"lock_screen": 0,
"is_soldout": 0,
"force_crm_user": 0,
"shift_transactions": 0,
"eod_shift_report": 0,
"override_shift": 0
}
}
]
}
GET /api/v2/get-channels. Why: Sales channels offered at the cashier's branch — what web-pos loads at boot and shows in Settings → Channel. The channel_id is the key for per-channel product prices (list_selling_products data.products[].price_levels[].channel_id), for discount-code channel scope (channel_ids) and for an order's channel_id/channel_name. Not paginated. Management (description, branches, create/update/delete): list_channels / get_channel in the channels catalog. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_channels"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_channels"
}'
{
"status": true,
"message": "Channels data found",
"data": [
{
"channel_id": 463,
"channel_name": "Coffee Bar"
},
{
"channel_id": 509,
"channel_name": "Express"
},
{
"channel_id": 482,
"channel_name": "Food Panda"
}
]
}
GET /api/v2/master-data. Why: Raw table dump used by the POS apps for offline sync. Useful rows: table=locations (id + name + integration ids of the branches the session USER may open — web-pos branch picker) and table=user (the session user's discount limit). table=product_materials / materials are for NON-master (inventory-per-branch) tenants and out of scope; table=products is a multi-MB raw dump (use list_selling_products); table=categories is broken. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_master_data"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| table | enum: locations, user, products, product_materials, materials, categories | required | Required. |
| location-only | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_master_data",
"table": "example"
}'
[
{
"id": 907,
"name": "Default location",
"customer_id": 437,
"is_ware_house": "1",
"is_primary": null,
"otter_location_id": null,
"deliverect_location_id": null,
"enable_menu_checkout": null,
"invoice_service_version": "v1",
"payment_provider": null,
"ngenius_terminal_id": null,
"ngenius_merchant_id": null,
"enable_grubtech": 0,
"allow_grubtech_status_updates": 0
},
{
"id": 908,
"name": "Johar Town",
"customer_id": 437,
"is_ware_house": "1",
"is_primary": null,
"otter_location_id": null,
"deliverect_location_id": null,
"enable_menu_checkout": null,
"invoice_service_version": "v1",
"payment_provider": null,
"ngenius_terminal_id": null,
"ngenius_merchant_id": null,
"enable_grubtech": 0,
"allow_grubtech_status_updates": 0
}
]
GET /api/v2/today-sales-v3. Why: The web-pos Daily Sales (Z-style) report of one branch and business day: net sales, orders, VAT, tenders, order types, items, discounts, voids, open orders and per-user totals. Business day = opening_hour → next opening_hour in the branch time_zone (get_pos_configs). Filter by cashier with close_by_user_id (list_pos_users). Order-level drill-down: list_orders_history; audit trail: get_invoice_activities. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_today_sales"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| date | string | optional | Optional. |
| from_date | string | optional | Optional. |
| to_date | string | optional | Optional. |
| location_id | string | optional | Optional. |
| close_by_user_id | string | optional | Optional. |
| paid_at | string | optional | Optional. |
| brand_id | string | optional | Optional. |
| brands | string | optional | Optional. |
| shift_id | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_today_sales"
}'
{
"order_type": {
"Delivery": 2,
"Takeaway": 1,
"void_orders": 5,
"refunded_items": 0
},
"dining_guests": 2,
"item_transferred": 0,
"items": [
{
"type": "Espresso-Based Drinks",
"name": "Flat White",
"unit_id": 0,
"extras": null,
"is_addon": 0,
"is_refunded": 0,
"total": 33,
"qty": 3,
"period": "2026-09-24T13:37:06.484Z",
"list": [],
"extra": []
}
],
"addons": [],
"refund_items": [],
"modifiers": [],
"itemsCat": [
{
"type": "Espresso-Based Drinks",
"total": 33
}
],
"itemsPayment": [
{
"payment_method": "cash",
"total": 22,
"transactions": 2,
"tip_amount": 0
}
],
"totalAmount": 33,
"total_sales": 33,
"total_orders": 3,
"hourly": [
{
"hour": 15,
"value": 11,
"order_count": 1
}
],
"time_distribution": [
{
"hour": 15,
"value": 11,
"order_count": 1
}
],
"opening_hour": "10:00",
"time_zone": "+05:00",
"location_id": 907,
"wallet_orders": {
"total_amount": 0,
"total_count": 0
},
"wallet_topups": {
"transaction_type": "debit",
"amount": 0,
"count": 0
},
"avg_invoice_bill": 11,
"avg_per_order": 11,
"avg_per_guest": 11,
"total_vat": 1.04,
"void_orders": {
"title": "Void orders",
"no_of_cancelled_order": 5,
"no_of_canceled_order": 5,
"no_of_items_refund": 0,
"total": 424,
"transaction": [
{
"payment_method": "cash",
"total": 424,
"transactions": 4
}
]
},
"ts": 1790257026487,
"time": "06:37:06 PM",
"withdraw_amount": 0,
"credit_amount": 0,
"active_shift_id": null,
"active_shift": null,
"shift_filter_applied": false,
"discount_summary": [],
"invoice_begin": 16,
"invoice_end": 24,
"invoices_count": 9,
"order_types": [
{
"name": "Delivery",
"count": 2,
"amount": 22
}
],
"user_statistics": {
"open_orders": {
"users": [
{
"user": "cashier@example.com",
"total_amount": 31,
"order_count": 1,
"orders": [
{
"id": 8085857,
"uuid": "b7b056e0-3c5f-437d-9258-968b6cc34a6e",
"payable": 31,
"created_at": "2026-08-26T10:03:08.000Z",
"order_type": "Dine In",
"dining_guests": 2
}
]
}
],
"total_amount": 31,
"total_orders": 1
},
"completed_orders": {
"users": [
{
"user": "cashier@example.com",
"total_amount": 33,
"order_count": 3,
"orders": [
{
"id": 8085852,
"uuid": "f5bd46f3-377a-477d-b748-876e9aab0063",
"payable": 11,
"created_at": "2026-08-20T11:07:42.000Z",
"order_type": "Delivery",
"dining_guests": 1
}
]
}
],
"total_amount": 33,
"total_orders": 3
},
"created_by_users": {
"users": [
{
"user": "cashier@example.com",
"user_id": "4734",
"total_amount": 499,
"order_count": 9,
"orders": [
{
"id": 8085848,
"uuid": "fdf751c3-f3f8-4cbe-9af0-76fd0d8482c8",
"payable": 106,
"created_at": "2026-08-17T06:25:20.000Z",
"order_type": "takeaway",
"dining_guests": null
}
]
}
],
"total_amount": 499,
"total_orders": 9
},
"closed_by_users": {
"users": [
{
"user": "cashier@example.com",
"user_id": "4734",
"total_amount": 33,
"order_count": 3,
"orders": [
{
"id": 8085852,
"uuid": "f5bd46f3-377a-477d-b748-876e9aab0063",
"payable": 11,
"created_at": "2026-08-20T11:07:42.000Z",
"order_type": "Delivery",
"dining_guests": 1
}
]
}
],
"total_amount": 33,
"total_orders": 3
}
},
"tip_amount": {
"title": "Tip Amount",
"total": 0,
"transactions": 0
},
"points": {
"title": "Points",
"total": 0,
"transactions": 0
},
"discount_amount": {
"title": "Discounts",
"total": 0,
"transactions": 0
},
"unpaid_order": {
"title": "Unpaid Order",
"total": 31,
"transactions": 1
},
"pending_orders": [
{
"invoice_number": 23,
"order_type": "Dine In",
"amount": 31,
"currency": "AED",
"created_date": "Aug 26",
"user": "cashier@example.com"
}
],
"late_closure": [],
"total_without_vat": 31.96,
"total_payable": 33,
"gross_sales": 33,
"order_charges": {
"charges_fee": 0,
"delivery_charges": 0
},
"date_range": {
"from": "2026-08-01",
"to": "2026-09-24"
},
"total_customers": {
"count": 1
},
"total_discounts": {
"total_amount": 0,
"total_applications": 0,
"references": []
},
"complete_order_summary": {
"rows": [],
"total_discount_amount": 0
},
"order_type_summary": {
"rows": [
{
"type_name": "Delivery",
"orders": 2,
"share_percent": 66.67,
"amount": 22
}
],
"totals": {
"orders": 3,
"share_percent": 100,
"amount": 33
}
},
"order_discount_summary": {
"rows": [],
"totals": {
"applied_times": 0,
"avg_discount": 0,
"total_amount": 0
},
"footer": {
"discount_percent_of_gross_sales": 0,
"gross_sales_amount": 31.96,
"gross_sales_currency": "AED",
"message": "Discounts represent 0% of gross sales (31.96 AED) · Deducted from net collected"
}
},
"item_discount_summary": {
"rows": [],
"totals": {
"qty": 0,
"original": 0,
"discount": 0,
"net": 0
}
}
}
GET /api/v2/activities/:uuid. Why: Audit trail of one order by uuid — what web-pos shows in the order's "Activity" section: who did what and when (created, items added/removed, paid, voided, payment method changed…). uuid comes from list_orders_history or list_dine_orders. Rows are written by the POS apps; cancel_invoice / refund_invoice done by an agent do NOT add rows. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_invoice_activities"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_invoice_activities",
"uuid": "example"
}'
{
"status": true,
"message": "No activities found against the order.",
"data": []
}
GET /api/v2/maintenance/status. Why: Global (all tenants) maintenance switch. web-pos polls it every 2 minutes and blocks the whole POS with a maintenance screen while is_maintenance = 1 or the server answers 500/502/503. Check it before a batch of writes (sync_invoice etc.) or when calls start failing. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_maintenance_status"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_maintenance_status"
}'
{
"status": true,
"data": {
"is_maintenance": 0,
"message": "All systems are up and running smoothly."
}
}
GET /api/v2/loyalty-program/card/:crmUserId. Why: A customer's loyalty card — what web-pos loads for the cart balance banner and before any redemption at payment. The tenant has ONE loyalty program (type point, stamp or membership) shared by every branch; a customer joins it only by enrolling (the card is NOT created by the POS, by create_pos_crm_user or by a sale). point: current_points and their money value (available_points_amount). stamp: current_stamps / stamps_required and the reward. membership: expiry and active flag. Read-only. Use it only when get_user_extras data.ios_modules.is_loyalty_enable = 1 and a customer is on the order (crm_user_id > 0 from list_pos_crm_users / create_pos_crm_user). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_loyalty_card"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| crmUserId | string | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_loyalty_card",
"crmUserId": "example"
}'
{
"status": true,
"message": "Loyalty card retrieved successfully.",
"data": {
"card_id": 57,
"crm_user_id": 63821,
"loyalty_program_id": 16,
"loyalty_type": "point",
"customer_name": "Sara Ahmed",
"email": "sara.ahmed@example.com",
"phone_number": "+971500000001",
"is_active": 1,
"total_redeemed": 0,
"last_redeemed_date": null,
"current_points": 3,
"available_points": 3,
"available_points_amount": 0.3,
"program": {
"id": 16,
"name": "Loyalty 1",
"description": null,
"reward_description": null,
"is_active": 1
},
"pass_info": {
"serial_number": "00000000-0000-4000-8000-000000000057",
"type_identifier": "pass.com.syspos.pos"
},
"card_created_at": "2026-06-29T10:27:28.000Z",
"card_updated_at": "2026-08-20T11:07:43.000Z"
}
}
POST /api/v2/loyalty-program/burn-validation. Why: Server check of a loyalty redemption before you apply it at payment — what web-pos calls from the Redeem points dialog (point program) and the Redeem stamps button (stamp program). Uses the tenant's program and the customer's card (from get_loyalty_card). point: checks burn_points <= current_points and returns burn_amount = burn_points × redeem_amount / redeem_points. stamp / membership: checks the card is full / not expired, the minimum order and returns discount_amount. Does NOT reserve or deduct anything: it only reads (no OTP is sent, nothing is stored); the points are burned when the PAID order is saved (sync_invoice). No side effects. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"validate_loyalty_burn"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| crm_user_id | string | required | Required. |
| order_total | number | required | Required, > 0. |
| burn_points | number | optional | Required for a point program: points to burn, > 0 and <= get_loyalty_card data.available_points (missing / 0 → 400 "Burn points must be a positive number"). |
| loyalty_card_id | string | optional | Optional. |
| product_ids | array<number> | optional | Required only for a free_product reward (stamp or membership): must contain the reward product (get_loyalty_card data.product_id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "validate_loyalty_burn",
"crm_user_id": 11,
"order_total": 1
}'
{
"status": true,
"message": "Burn validation successful",
"data": {
"status": "Success",
"available_points": 30,
"burn_points": 20,
"burn_amount": 2,
"points_after_burn": 10,
"conversion_rate": 0.1,
"loyalty_program_id": 16,
"loyalty_card_id": 57,
"crm_user_id": 63821,
"order_total": 45.5,
"discount_reason": "Loyalty discount"
}
}
GET /api/locations. Why: Every branch (location) of the tenant this key may use, with its id and name — the id is the loc / location_id that every other tool takes (get_pos_configs, get_location_config, get_business_info, get_business_hours, get_location_days_hours, list_* catalog tools, create_* location_id lists). Call it first whenever the user names a branch, to turn the name into an id. Also shows the warehouse flag and the per-branch payment settings. This is the unpaginated list every dashboard picker uses; the dashboard Locations page uses the paginated /api/locations/v2 (same rows, fewer columns, newest first) — not exposed because this tool returns the same data in one call and MCP filters it to the key's branches. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_locations"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_locations"
}'
[
{
"id": 907,
"name": "Default location",
"is_ware_house": "1",
"enable_menu_checkout": null,
"payment_provider": null,
"ngenius_terminal_id": null,
"ngenius_merchant_id": null,
"invoice_service_version": "v1"
},
{
"id": 908,
"name": "Johar Town",
"is_ware_house": "1",
"enable_menu_checkout": null,
"payment_provider": null,
"ngenius_terminal_id": null,
"ngenius_merchant_id": null,
"invoice_service_version": "v1"
},
{
"id": 909,
"name": "Iqbal Town",
"is_ware_house": "1",
"enable_menu_checkout": null,
"payment_provider": null,
"ngenius_terminal_id": null,
"ngenius_merchant_id": null,
"invoice_service_version": "v1"
},
{
"id": 910,
"name": "Bahira Town",
"is_ware_house": "0",
"enable_menu_checkout": null,
"payment_provider": null,
"ngenius_terminal_id": null,
"ngenius_merchant_id": null,
"invoice_service_version": "v1"
}
]
GET /api/locations/:id. Why: The full row of ONE branch — every column list_locations shows plus the integration ids (otter, deliverect, grubtech flags). Read it before update_location: that tool overwrites name, isWareHouse, enable_menu_checkout, payment_provider and both N-Genius ids, so you must send the current values of the ones you are not changing. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_location"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Location id (list_locations[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_location",
"id": 11
}'
[
{
"id": 907,
"name": "Default location",
"customer_id": 437,
"is_ware_house": "1",
"is_primary": null,
"otter_location_id": null,
"deliverect_location_id": null,
"enable_menu_checkout": null,
"invoice_service_version": "v1",
"payment_provider": null,
"ngenius_terminal_id": null,
"ngenius_merchant_id": null,
"enable_grubtech": 0,
"allow_grubtech_status_updates": 0
}
]
GET /api/config/business-info. Why: The branch's business profile — what the receipt header prints (name, address, VAT No, logo) plus contact, legal name, delivery area and T&C. Read it before create_business_info / update_business_info and to check what a receipt will show. Pass strict="1": without it an EMPTY branch silently returns ANOTHER branch's profile. Returns rows { id, name, value }; name is one of: info_name, legal_name, info_address, info_phone, info_email, info_vat_no, logo_image, license_image, location_latitude, location_longitude, polygon_coordinates, area_coordinates, receipt_footer_text, reciept_header_text (sic), currency, terms_and_conditions. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_business_info"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | ONE branch id (list_locations). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_business_info"
}'
[
{
"id": 9993,
"value": "Example Cafe",
"name": "info_name"
},
{
"id": 9994,
"value": "Shop 1, Example Street",
"name": "info_address"
},
{
"id": 9995,
"value": "info@example-cafe.test",
"name": "info_email"
},
{
"id": 9996,
"value": "100000000000003",
"name": "info_vat_no"
},
{
"id": 9997,
"value": "1762167059218-example-logo.png",
"name": "logo_image"
},
{
"id": 10103,
"value": "",
"name": "currency"
},
{
"id": 10237,
"value": "+971500000000",
"name": "info_phone"
},
{
"id": 11174,
"value": "<ul><li>Optional note</li></ul>",
"name": "receipt_footer_text"
}
]
GET /api/config/all. Why: Read a branch's settings exactly as the dashboard Settings page shows them: VAT %, currency, decimals, time zone, business hours, exchange rate, receipt texts, POS flags, plus the main business-info keys. Use it to check a branch before selling (sync_invoice needs vat, decimal_points, time_zone) and before save_config. Branch rows only — no fallback. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_location_config"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | ONE branch id (list_locations). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_location_config"
}'
[
{
"id": 9905,
"value": "+05:00",
"name": "time_zone"
},
{
"id": 9943,
"value": "5",
"name": "vat"
},
{
"id": 9993,
"value": "Example Cafe",
"name": "info_name"
},
{
"id": 9994,
"value": "Shop 1, Example Street",
"name": "info_address"
},
{
"id": 9995,
"value": "info@example-cafe.test",
"name": "info_email"
},
{
"id": 9996,
"value": "100000000000003",
"name": "info_vat_no"
},
{
"id": 9997,
"value": "1762167059218-example-logo.png",
"name": "logo_image"
},
{
"id": 10101,
"value": "10:00",
"name": "opening_hour"
},
{
"id": 10102,
"value": "08:00",
"name": "closing_hour"
},
{
"id": 10103,
"value": "",
"name": "currency"
},
{
"id": 10237,
"value": "+971500000000",
"name": "info_phone"
},
{
"id": 10977,
"value": "Asia/Karachi",
"name": "time_zone_code"
},
{
"id": 11174,
"value": "<ul><li>Optional note</li></ul>",
"name": "receipt_footer_text"
},
{
"id": 11578,
"value": "12",
"name": "exchange_rate"
},
{
"id": 11583,
"value": "20",
"name": "max_order_time"
}
]
GET /api/config/currency. Why: Currency code of one branch (display only). Same route as get_config_value with name=currency. Pass loc — without a branch the server returns the first currency row of ANY branch. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_currency"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | ONE branch id (list_locations). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_currency"
}'
{
"name": "currency",
"value": ""
}
GET /api/config/:name. Why: Read ONE config key of one branch (any key, including ones get_location_config does not list). Use before save_config to see the current value. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_config_value"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Config key, exact spelling and case (e.g. |
| loc | string | optional | ONE branch id (list_locations). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_config_value",
"name": "Iced Latte"
}'
{
"name": "vat",
"value": "5"
}
GET /api/config/business-hours. Why: The branch's BUSINESS DAY as a UTC window — the same opening_hour + time_zone config rule the POS uses for 'today' (get_today_sales, get_ol_info, list_kds_orders, daily reports). Use start_utc / end_utc to bound order or invoice queries for a date range in the branch's local business days. It says nothing about per-weekday open/closed times — that is get_location_days_hours. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_business_hours"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | string | optional | Optional. |
| from | string | optional | Optional. |
| to | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_business_hours"
}'
{
"success": true,
"data": {
"start_utc": "2026-09-25 05:00:00",
"end_utc": "2026-09-26 05:00:00",
"time_zone": "+00:00",
"opening_hour": "10:00",
"closing_hour": "08:00"
}
}
GET /api/location-days-hours-config. Why: The branch's weekly OPERATING HOURS as set on the dashboard Business Info > Operating Hours tab: one row per weekday with open / close time or 'closed all day'. Use it to answer 'when is branch X open on Sunday' or before upsert_location_days_hours. It is independent from the business-day hours (opening_hour config → get_business_hours), which drive POS 'today' and reports. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_location_days_hours"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | string | optional | ONE branch id — pass it. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_location_days_hours"
}'
[
{
"id": 29,
"customer_id": 437,
"location_id": 909,
"day": "monday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 30,
"customer_id": 437,
"location_id": 909,
"day": "tuesday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 31,
"customer_id": 437,
"location_id": 909,
"day": "wednesday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 32,
"customer_id": 437,
"location_id": 909,
"day": "thursday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 33,
"customer_id": 437,
"location_id": 909,
"day": "friday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 34,
"customer_id": 437,
"location_id": 909,
"day": "saturday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 35,
"customer_id": 437,
"location_id": 909,
"day": "sunday",
"open": null,
"close": null,
"is_off": 1,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
}
]
GET /api/v2/config/get-all-configs. Why: Every config row (name/value) of ONE branch — the source web-pos uses at boot for the flat VAT % (key vat), currency (currency) and time zone (time_zone, else time_zone_code). Read it before building a sync_invoice order: vat → line/order vat and vat_pct, time_zone → seconds_from_gmt. Also holds business hours (opening_hour / closing_hour → get_today_sales, get_ol_info), receipt header keys and exchange_rate. Tax groups are out of scope. For a named subset use get_location_config / get_business_info. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_pos_configs"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_pos_configs"
}'
{
"status": true,
"message": "Configs retrieved successfully",
"data": [
{
"id": 9905,
"name": "time_zone",
"value": "+05:00",
"customer_id": 437,
"location_id": 907
},
{
"id": 9943,
"name": "vat",
"value": "5",
"customer_id": 437,
"location_id": 907
},
{
"id": 9993,
"name": "info_name",
"value": "Example Cafe",
"customer_id": 437,
"location_id": 907
},
{
"id": 9994,
"name": "info_address",
"value": "Shop 1, Example Street",
"customer_id": 437,
"location_id": 907
},
{
"id": 9995,
"name": "info_email",
"value": "info@example-cafe.test",
"customer_id": 437,
"location_id": 907
},
{
"id": 9996,
"name": "info_vat_no",
"value": "100000000000003",
"customer_id": 437,
"location_id": 907
},
{
"id": 9997,
"name": "logo_image",
"value": "example-logo.png",
"customer_id": 437,
"location_id": 907
},
{
"id": 10101,
"name": "opening_hour",
"value": "10:00",
"customer_id": 437,
"location_id": 907
},
{
"id": 10102,
"name": "closing_hour",
"value": "08:00",
"customer_id": 437,
"location_id": 907
},
{
"id": 10103,
"name": "currency",
"value": "",
"customer_id": 437,
"location_id": 907
},
{
"id": 10237,
"name": "info_phone",
"value": "+971500000000",
"customer_id": 437,
"location_id": 907
},
{
"id": 10977,
"name": "time_zone_code",
"value": "Asia/Karachi",
"customer_id": 437,
"location_id": 907
},
{
"id": 11174,
"name": "receipt_footer_text",
"value": "<ul><li>Optional note</li></ul>",
"customer_id": 437,
"location_id": 907
},
{
"id": 11578,
"name": "exchange_rate",
"value": "12",
"customer_id": 437,
"location_id": 907
},
{
"id": 11583,
"name": "max_order_time",
"value": "20",
"customer_id": 437,
"location_id": 907
}
]
}
GET /api/v2/floors. Why: Floors (dining areas) of the cashier's session branch — what web-pos loads at boot for the floor tabs on the Dine screen and in the "Select table" dialog of a dine-in order. A floor only groups tables: tables point to it with floor_id (list_selling_tables), and the live occupancy per table comes from list_dine_orders. Session branch only (no params); for another branch use list_floors_by_location. Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_floors"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_floors"
}'
{
"status": true,
"message": "Record Found",
"data": [
{
"id": 606,
"name": "Ground Floor",
"customer_id": 437,
"location_id": 907,
"image": "",
"width": 0,
"length": 0,
"floor_design": "",
"web_image": ""
},
{
"id": 615,
"name": "1st Floor",
"customer_id": 437,
"location_id": 907,
"image": "",
"width": 0,
"length": 0,
"floor_design": "false",
"web_image": ""
},
{
"id": 619,
"name": "Second Floor",
"customer_id": 437,
"location_id": 907,
"image": "",
"width": 0,
"length": 0,
"floor_design": "",
"web_image": ""
}
]
}
GET /api/v2/tables. Why: Tables of the cashier's session branch — what web-pos loads at boot to resolve table names (tickets, order list, bill header) and each table's floor. The table id is what a dine-in order carries as table_id. Occupancy is NOT here: list_dine_orders returns the same tables with their open tickets (dinerOrderDetail) and is what the Dine screen actually draws; get_table_order gives the latest ticket of one table. Tables have no brand_id. Session branch only (no params); for another branch use list_tables_by_location. Not paginated. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_selling_tables"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_selling_tables"
}'
[
{
"id": 5935,
"name": "Table A5",
"customer_id": 437,
"location_id": 907,
"x_pos": null,
"y_pos": null,
"people_size": 0,
"attended_by": null,
"unavailable": 1,
"plate_number": null,
"is_plate": 1,
"guest_name": null,
"floor_id": 606,
"width": 30,
"height": 30,
"rotation": 0,
"is_rounded": 0,
"x": 72,
"y": 49,
"pgo_restaurant_short_name": null,
"pgo_table_short_id": null,
"is_active": 1,
"capacity": 4,
"current_status": "available",
"qr_code_link": null,
"today_activity_percentage": 0,
"last_activity_at": null,
"table_number": null,
"created_at": "2026-02-24T11:13:55.000Z",
"updated_at": "2026-02-24T11:13:55.000Z"
},
{
"id": 6194,
"name": "Table 1",
"customer_id": 437,
"location_id": 907,
"x_pos": null,
"y_pos": null,
"people_size": 0,
"attended_by": null,
"unavailable": 0,
"plate_number": null,
"is_plate": 0,
"guest_name": null,
"floor_id": 606,
"width": 30,
"height": 30,
"rotation": 0,
"is_rounded": 0,
"x": 42,
"y": 29,
"pgo_restaurant_short_name": null,
"pgo_table_short_id": null,
"is_active": 1,
"capacity": 4,
"current_status": "available",
"qr_code_link": null,
"today_activity_percentage": 0,
"last_activity_at": null,
"table_number": null,
"created_at": "2026-06-24T08:03:09.000Z",
"updated_at": "2026-06-24T08:03:09.000Z"
}
]
GET /api/floors-location/:location_id. Why: Floors of one branch (dashboard route; any branch of the tenant, not only the session branch). Tables link to a floor with floor_id (list_tables_by_floor / list_tables_by_location); live occupancy per table is in list_dine_orders. POS view of the session branch: list_selling_floors. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_floors_by_location"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | number | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_floors_by_location",
"location_id": 11
}'
[
{
"id": 606,
"name": "Ground Floor",
"customer_id": 437,
"location_id": 907,
"image": "",
"width": 0,
"length": 0,
"floor_design": "",
"web_image": ""
},
{
"id": 615,
"name": "1st Floor",
"customer_id": 437,
"location_id": 907,
"image": "",
"width": 0,
"length": 0,
"floor_design": "false",
"web_image": ""
},
{
"id": 619,
"name": "Second Floor",
"customer_id": 437,
"location_id": 907,
"image": "",
"width": 0,
"length": 0,
"floor_design": "",
"web_image": ""
}
]
GET /api/floors/:id. Why: One floor by id (dashboard route). Scoped to your tenant; any branch. Its tables: list_tables_by_floor with floor_id = id. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_floor"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_floor",
"id": 11
}'
{
"status": true,
"message": "Record Found",
"data": [
{
"id": 606,
"name": "Ground Floor",
"customer_id": 437,
"location_id": 907,
"image": "",
"width": 0,
"length": 0,
"floor_design": "",
"web_image": ""
}
]
}
GET /api/tables-location/:location_id. Why: Tables of one branch (dashboard route; any branch of the tenant). Same rows as list_selling_tables but for a chosen branch. Each table's floor is floor_id (get_floor); its open tickets are in list_dine_orders (session branch only) / get_table_order. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_tables_by_location"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | number | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_tables_by_location",
"location_id": 11
}'
[
{
"id": 5935,
"name": "Table A5",
"customer_id": 437,
"location_id": 907,
"x_pos": null,
"y_pos": null,
"people_size": 0,
"attended_by": null,
"unavailable": 1,
"plate_number": null,
"is_plate": 1,
"guest_name": null,
"floor_id": 606,
"width": 30,
"height": 30,
"rotation": 0,
"is_rounded": 0,
"x": 72,
"y": 49,
"pgo_restaurant_short_name": null,
"pgo_table_short_id": null,
"is_active": 1,
"capacity": 4,
"current_status": "available",
"qr_code_link": null,
"today_activity_percentage": 0,
"last_activity_at": null,
"table_number": null,
"created_at": "2026-02-24T11:13:55.000Z",
"updated_at": "2026-02-24T11:13:55.000Z"
},
{
"id": 6194,
"name": "Table 1",
"customer_id": 437,
"location_id": 907,
"x_pos": null,
"y_pos": null,
"people_size": 0,
"attended_by": null,
"unavailable": 0,
"plate_number": null,
"is_plate": 0,
"guest_name": null,
"floor_id": 606,
"width": 30,
"height": 30,
"rotation": 0,
"is_rounded": 0,
"x": 42,
"y": 29,
"pgo_restaurant_short_name": null,
"pgo_table_short_id": null,
"is_active": 1,
"capacity": 4,
"current_status": "available",
"qr_code_link": null,
"today_activity_percentage": 0,
"last_activity_at": null,
"table_number": null,
"created_at": "2026-06-24T08:03:09.000Z",
"updated_at": "2026-06-24T08:03:09.000Z"
}
]
GET /api/tables-floor/:floor_id. Why: Tables on one floor (dashboard route). Scoped to your tenant but NOT to a branch: floor_id 0 returns every table of the tenant across all branches, and the API key allow_locations is not applied to this tool — filter on location_id yourself. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_tables_by_floor"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| floor_id | number | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_tables_by_floor",
"floor_id": 11
}'
[
{
"id": 5935,
"name": "Table A5",
"customer_id": 437,
"location_id": 907,
"x_pos": null,
"y_pos": null,
"people_size": 0,
"attended_by": null,
"unavailable": 1,
"plate_number": null,
"is_plate": 1,
"guest_name": null,
"floor_id": 606,
"width": 30,
"height": 30,
"rotation": 0,
"is_rounded": 0,
"x": 72,
"y": 49,
"pgo_restaurant_short_name": null,
"pgo_table_short_id": null,
"is_active": 1,
"capacity": 4,
"current_status": "available",
"qr_code_link": null,
"today_activity_percentage": 0,
"last_activity_at": null,
"table_number": null,
"created_at": "2026-02-24T11:13:55.000Z",
"updated_at": "2026-02-24T11:13:55.000Z"
}
]
GET /api/tables/:id. Why: BROKEN on the server — do not rely on it: the handler never binds the id, so the SQL fails and the response is HTTP 200 with an EMPTY body for every id. To read one table use list_selling_tables (session branch) or list_tables_by_location and match id. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_table"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_table",
"id": 11
}'
"<empty body: HTTP 200, no JSON>"
GET /api/v2/dine-orders-v3. Why: The live dine board of the JWT session branch — exactly what web-pos draws on the Dine screen: EVERY table of the branch (id = table id, floor_id, name, people_size, x, y …) with its open/done tickets in dinerOrderDetail. A table is OCCUPIED when dinerOrderDetail has at least one ticket, FREE when it is null or []. Group rows by floor_id (list_selling_floors). Optional table_id filter (list_selling_tables). The location cannot be changed via query. For one table's latest ticket use get_table_order; for a ticket's lines use get_invoice_items with its uuid. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_dine_orders"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| table_id | number | optional | Optional. |
| brand_id | number | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_dine_orders"
}'
[
{
"floor_id": 606,
"id": 6194,
"name": "Table 1",
"customer_id": 437,
"location_id": 907,
"x": 42,
"y": 29,
"is_rounded": 0,
"rotation": 0,
"height": 30,
"width": 30,
"people_size": 0,
"attended_by": null,
"unavailable": 0,
"is_plate": 0,
"plate_number": null,
"uuid": [
"b7b056e0-3c5f-437d-9258-968b6cc34a6e"
],
"dinerOrderDetail": [
{
"id": 23,
"customer_invoice_id": 23,
"uuid": "b7b056e0-3c5f-437d-9258-968b6cc34a6e",
"status": "open",
"order_no": 1,
"table_id": 6194,
"floor_id": 606,
"order_type": "Dine In",
"is_paid": null,
"payable": 31,
"payment_method": null,
"created_at": 1787738588,
"order_items": [
{
"id": 25504110,
"identifier": "5f1c0c66-2efb-4335-ab40-99ce6ceaa433",
"name": "Flat White",
"name_localize": null,
"actual_price": 11,
"price": 11,
"qty": 1,
"subtotal": 11,
"line_total": null,
"line_vat": null,
"line_total_without_vat": null,
"invoice_uuid": "b7b056e0-3c5f-437d-9258-968b6cc34a6e",
"product_id": 257506,
"extras": null,
"vat": 0,
"is_updated": 0,
"updated_qty": 0,
"is_addon": 0,
"is_refunded": 0,
"refund_quantity": 0,
"refund_reason": null,
"note": null,
"addon_linked_with": "5f1c0c66-2efb-4335-ab40-99ce6ceaa433",
"discount_pct": 0,
"discount_amount": 0,
"modifier": [],
"transferred_to": 0,
"transferred_to_uuid": null,
"transferred_to_uuid_list": [
"3ccd598a-f022-4939-931f-eedd132e79b6",
"f2ab957f-2cba-4b48-a534-8681b8934a1a",
"0ea2c766-fd6d-418d-9e04-70aa6a60fa1a"
],
"transferred_quantities": [
2,
4,
1.5
],
"transfer_recipients": [
12,
43,
60
],
"created_at": 1787738588,
"updated_at": 1787738588,
"is_prepared": 0,
"preparation_time": null,
"is_weightage_item": 0,
"unit_id": 0,
"type": "Espresso-Based Drinks",
"label_id": 50,
"item_status": "active",
"linked_uuid": null,
"original_linked_uuid": null,
"disposition_type": null,
"image": null,
"is_combo": 0,
"is_deleted": 0,
"deleted_at": null,
"deleted_qty": 0,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": "",
"updated_by_user_ids": [],
"re": null,
"ri": 0,
"is_wastage": 0,
"discount_id": null,
"discount_name": null,
"round": null,
"combo_items": null
}
]
}
]
},
{
"floor_id": 606,
"id": 6195,
"name": "Table 2",
"customer_id": 437,
"location_id": 907,
"x": 29,
"y": 46,
"is_rounded": 0,
"rotation": 0,
"height": 30,
"width": 30,
"people_size": 0,
"attended_by": null,
"unavailable": 0,
"is_plate": 0,
"plate_number": null,
"uuid": [],
"dinerOrderDetail": null
}
]
GET /api/dine-table-order/:table. Why: The latest open (or done) ticket of ONE table at the JWT session branch, with its lines — what to reopen when adding items to an occupied table. Table id comes from list_selling_tables / list_dine_orders. Only the newest ticket is returned; when a table has several open tickets use list_dine_orders (dinerOrderDetail lists them all). Session branch only (a table of another branch returns []). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_table_order"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| table | number | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_table_order",
"table": 1
}'
[
{
"token_number": null,
"id": 23,
"items_number": 3,
"aggregator_order_id": null,
"is_accepted": 0,
"exchange_rate": 0,
"subtotal": 31,
"vat": 0,
"payable": 31,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"channel_id": 0,
"change": 0,
"note": "dev1 transfer-fields e2e dine",
"uuid": "b7b056e0-3c5f-437d-9258-968b6cc34a6e",
"created_at": "2026-08-26T10:03:08.000Z",
"customer_id": 437,
"location_id": 907,
"info_name": "",
"info_address": "",
"info_phone": "",
"info_email": "",
"status": "open",
"user": "admin@devnexus.co",
"user_id": 4734,
"table_id": 6194,
"payment_method": null,
"order_id": null,
"invoice_id": null,
"done_at": null,
"period": null,
"is_kds_printed": 0,
"is_invoice_printed": 0,
"plate_number": null,
"order_no": 1,
"is_paid": "0",
"is_refunded": 0,
"guest_name": "Transfer E2E Dine",
"is_updated": 0,
"card_digits": "",
"mac_addresses": "",
"crm_user_id": 0,
"updated_at": "2026-08-26T10:03:10.000Z",
"crm_user_name": "",
"created_by_user_id": 4734,
"close_by_user_id": null,
"modified_by_user_ids": [],
"discount_id": null,
"order_type": "Dine In",
"is_vat_on_discount_exclude": 0,
"dining_guests": 2,
"channel_name": "",
"order_items": [
{
"id": 25504110,
"identifier": "5f1c0c66-2efb-4335-ab40-99ce6ceaa433",
"name": "Flat White",
"name_localize": null,
"actual_price": 11,
"base_price": null,
"price": 11,
"is_weightage_item": 0,
"unit_id": 0,
"qty": 1,
"subtotal": 11,
"line_total": null,
"line_vat": null,
"line_total_without_vat": null,
"invoice_uuid": "b7b056e0-3c5f-437d-9258-968b6cc34a6e",
"product_id": 257506,
"extras": null,
"vat": 0,
"is_updated": 0,
"updated_qty": 0,
"is_addon": 0,
"is_refunded": 0,
"refund_quantity": 0,
"refund_reason": null,
"note": null,
"addon_linked_with": "5f1c0c66-2efb-4335-ab40-99ce6ceaa433",
"discount_pct": 0,
"discount_amount": 0,
"modifier": [],
"transferred_to": 0,
"transferred_to_uuid": null,
"transferred_to_uuid_list": [
"3ccd598a-f022-4939-931f-eedd132e79b6",
"f2ab957f-2cba-4b48-a534-8681b8934a1a",
"0ea2c766-fd6d-418d-9e04-70aa6a60fa1a"
],
"transferred_quantities": [
2,
4,
1.5
],
"transfer_recipients": [
12,
43,
60
],
"created_at": 1787738588,
"updated_at": 1787738588,
"is_prepared": 0,
"preparation_time": null,
"type": "Espresso-Based Drinks",
"label_id": 50,
"is_combo": 0,
"is_deleted": 0,
"deleted_at": null,
"deleted_qty": 0,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": "",
"updated_by_user_ids": [],
"re": null,
"ri": 0,
"is_wastage": 0,
"discount_id": null,
"discount_name": null,
"round": null,
"combo_items": null
}
],
"items_discount_amount": 0
}
]
GET /api/v2/orders-history. Why: The order list of the session branch — exactly what the web-pos Invoices screen loads: every order (open, completed, cancelled) newest first, 30 per page, with lines, payments, customer and a summary. Use it to find an order (by status, invoice number, customer, phone, plate, shift, brand or business day) and read it in full; then act on its uuid with cancel_invoice, refund_invoice, update_invoice_payment_methods or get_invoice_activities. Paginated with current_page / page_limit. order_type cannot be filtered on the server — filter the rows yourself by list_selling_order_types title. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_orders_history"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | string | optional | Optional. |
| status | string | optional | Optional. |
| date | string | optional | Optional. |
| current_page | number | optional | Optional. |
| page_limit | number | optional | Optional. |
| invoice_no | string | optional | Optional. |
| crm_user_id | number | optional | Optional. |
| crm_user_name | string | optional | Optional. |
| mobile_number | string | optional | Optional. |
| phone_number | string | optional | Optional. |
| guest_name | string | optional | Optional. |
| plate_number | string | optional | Optional. |
| brand_id | string | optional | Optional. |
| shift_id | number | optional | Optional. |
| user_id | number | optional | Optional. |
| is_order_rebump | enum: 1 | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_orders_history"
}'
{
"status": true,
"message": "Orders history",
"data": {
"current_page": "1",
"records_per_page": 10,
"total_records": 5,
"force_location_ids": [
907
],
"summary": {
"total_orders": 5,
"total_revenue": 461,
"completed_orders": 0,
"completed_revenue": 0,
"open_orders": 0,
"open_revenue": 0,
"void_orders": 5,
"void_revenue": 461
},
"orders": [
{
"id": "3609c849e2e4",
"oid": 19,
"customer_invoice_id": 19,
"brand_id": null,
"exchange_rate": null,
"token_number": null,
"is_wastage": 0,
"paid_in_usd": null,
"is_accepted": null,
"external_order_ready": 0,
"is_discount_on_total": null,
"items_number": 2,
"aggregator_order_id": null,
"subtotal": 106,
"vat": 0,
"payable": 106,
"discountamount": 0,
"items_discount_amount": 1.1,
"is_ticket_available": 1,
"is_wallet_used": 0,
"discountpct": 0,
"paid": 106,
"change": 0,
"note": "qa cancel **canceled by: Nexus Cafe",
"uuid": "3a00fb3a-0c78-444a-abef-3609c849e2e4",
"deliverect_id": null,
"created_at": 1786948030,
"updated_at": 1786948031,
"customer_id": 437,
"location_id": 907,
"guest_name": "QA rd 0",
"info_name": "QA",
"info_address": "",
"info_phone": "",
"info_email": "cashier@example.com",
"status": "cancelled",
"by": null,
"external_order_id": null,
"user": "cashier@example.com",
"table_id": 0,
"order_id": null,
"payment_method": [
{
"cash": 106
}
],
"burn_points": 0,
"earn_points": 0,
"burn_points_amount": 0,
"available_points": 0,
"available_points_amount": 0,
"charges_fee": 0,
"delivery_charges": 0,
"phone_number": "",
"loyalty_mobile_number": "",
"loyalty_mobile_code": "",
"crm_delivery_area": null,
"invoice_id": null,
"done_at": null,
"period": null,
"is_kds_printed": 0,
"is_invoice_printed": 0,
"plate_number": null,
"order_no": 4,
"created_by_user_id": 4734,
"close_by_user_id": 4734,
"modified_by_user_ids": [],
"is_vat_on_discount_exclude": 0,
"order_type": "takeaway",
"channel_id": 0,
"tip_amount": 0,
"dining_guests": null,
"channel_name": "",
"is_paid": 1,
"card_digits": "",
"crm_user_id": 0,
"crm_user_name": "",
"discount_reason": null,
"row_discount": 0,
"paid_at": null,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": null,
"printed_invoice_receipts": 0,
"printed_kds_receipts": 0,
"app_version": "qa-rd",
"app_os_version": "qa",
"build_number": "1",
"os_type": "qa",
"round": null,
"rider_pickup_at": null,
"rider_arrived_at": null,
"shift_id": null,
"cashier_user_id": null,
"cashier_name": null,
"shift_clock_in_at": null,
"is_order_rebump": 1,
"order_type_icon_key": "boxicons:takeaway",
"notes": [],
"grand_total": 106,
"payment_methods": [
{
"cash": {
"amount": 106,
"tip_amount": null
}
}
],
"payments": [
{
"payment_method": "cash",
"amount": 106,
"tip_amount": 0
}
],
"items": [
{
"id": 25504102,
"identifier": "7c53c3c5-569d-47ae-84b4-7f2e28cbcee6",
"name": "QA rd 0 Flat White",
"name_localize": null,
"base_price": null,
"actual_price": 11,
"preparation_time": null,
"price": 11,
"qty": 1,
"is_weightage_item": 0,
"unit_id": 0,
"subtotal": 11,
"line_total": null,
"line_vat": null,
"line_total_without_vat": null,
"invoice_uuid": "3a00fb3a-0c78-444a-abef-3609c849e2e4",
"product_id": 257506,
"extras": [
{
"name": "QA Extra",
"actual_price": 5,
"price": 5,
"quantity": 1,
"discount_pct": 0
}
],
"vat": 0,
"is_updated": 0,
"updated_qty": 0,
"is_addon": 0,
"is_refunded": 0,
"refund_quantity": 0,
"refund_reason": null,
"note": null,
"addon_linked_with": "7c53c3c5-569d-47ae-84b4-7f2e28cbcee6",
"discount_pct": 10,
"discount_amount": 1.1,
"modifier": [
{
"name": "QA Modifier",
"actual_price": 3,
"price": 3,
"quantity": 1,
"discount_pct": 0
}
],
"transferred_to": 0,
"transferred_to_uuid": null,
"transferred_quantity": 0,
"transferred_to_uuid_list": [],
"transferred_quantities": [],
"transfer_recipients": [],
"created_at": 1786948030,
"updated_at": 1786948030,
"is_prepared": 0,
"type": "Espresso-Based Drinks",
"label_id": 50,
"item_status": "active",
"linked_uuid": null,
"original_linked_uuid": null,
"disposition_type": null,
"image": null,
"is_combo": 0,
"is_deleted": null,
"deleted_at": null,
"deleted_qty": null,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": "",
"updated_by_user_ids": [],
"re": null,
"ri": 0,
"is_wastage": 0,
"discount_id": null,
"discount_name": null,
"round": null,
"combo_items": null
},
{
"id": 25504103,
"identifier": "cba24a8b-6e50-4798-a047-05834e9d9d6a",
"name": "QA addon Classic Sandwich",
"name_localize": null,
"base_price": null,
"actual_price": 100,
"preparation_time": null,
"price": 95,
"qty": 1,
"is_weightage_item": 0,
"unit_id": null,
"subtotal": 95,
"line_total": null,
"line_vat": null,
"line_total_without_vat": null,
"invoice_uuid": "3a00fb3a-0c78-444a-abef-3609c849e2e4",
"product_id": 257559,
"extras": null,
"vat": 0,
"is_updated": 0,
"updated_qty": 0,
"is_addon": 1,
"is_refunded": 0,
"refund_quantity": 0,
"refund_reason": null,
"note": null,
"addon_linked_with": "7c53c3c5-569d-47ae-84b4-7f2e28cbcee6",
"discount_pct": 0,
"discount_amount": 0,
"modifier": [],
"transferred_to": 0,
"transferred_to_uuid": null,
"transferred_quantity": 0,
"transferred_to_uuid_list": [],
"transferred_quantities": [],
"transfer_recipients": [],
"created_at": 1786948030,
"updated_at": 1786948030,
"is_prepared": 0,
"type": "Alternate Options,Brewed Coffee,Espresso-Based Drinks,Iced & Cold Coffees,Specialty & Flavored Coffees",
"label_id": null,
"item_status": "active",
"linked_uuid": null,
"original_linked_uuid": null,
"disposition_type": null,
"image": "https://dev1.syspos.ae/api/uploads/",
"is_combo": 0,
"is_deleted": null,
"deleted_at": null,
"deleted_qty": null,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": "",
"updated_by_user_ids": [],
"re": null,
"ri": 0,
"is_wastage": 0,
"discount_id": null,
"discount_name": null,
"round": null,
"combo_items": null
}
],
"order_type_icon": "https://images.weserv.nl/?url=https%3A%2F%2Fapi.iconify.design%2Fboxicons%2Ftakeaway.svg%3Fwidth%3D128%26height%3D128&output=png"
}
]
}
}
GET /api/invoice-v2/:uuid. Why: One order header by uuid: status, totals, payments, customer_invoice_id and the branch's receipt header (business_info). web-pos never calls it (it backs the public receipt page). Use it when you only hold a uuid; for the full order (lines, customer, is_paid, order_type) call list_orders_history with invoice_no = customer_invoice_id. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_invoice"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_invoice",
"uuid": "example"
}'
[
{
"logo_image": "1762167059218-Nexus_logo_2015.svg.png",
"id": 8085851,
"items_number": 2,
"subtotal": 106,
"vat": 0,
"payable": 106,
"discountamount": 0,
"discountpct": 0,
"paid": 106,
"change": 0,
"note": "qa cancel **canceled by: Nexus Cafe",
"uuid": "3a00fb3a-0c78-444a-abef-3609c849e2e4",
"created_at": "2026-08-17T06:27:10.000Z",
"customer_id": 437,
"location_id": 907,
"info_name": "QA",
"info_address": "",
"info_phone": "",
"info_email": "cashier@example.com",
"status": "cancelled",
"user": "cashier@example.com",
"table_id": 0,
"plate_number": null,
"customer_invoice_id": 19,
"business_info": {
"time_zone": "+05:00",
"vat": "5",
"info_name": "Nexus",
"info_address": "Nexus located at Dev",
"info_email": "info@example.com",
"info_vat_no": "1423",
"logo_image": "1762167059218-Nexus_logo_2015.svg.png",
"opening_hour": "10:00",
"closing_hour": "08:00",
"currency": "",
"info_phone": "500000000",
"receipt_footer_text": "<ul><li>Optional note</li></ul>",
"exchange_rate": "12",
"max_order_time": "20"
},
"payments": [
{
"payment_method": "cash",
"amount": 106
}
]
}
]
GET /api/invoice-v2/:uuid/items. Why: Raw database lines of one order by uuid (every invoice_items column). web-pos never calls it. Prefer list_orders_history items[] (parsed, correct extras, nested combos); use this only for database-only columns such as refund_pm_type, refund_amount, item_price_after_discount or cost snapshots. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_invoice_items"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_invoice_items",
"uuid": "example"
}'
[
{
"id": 25504102,
"name": "QA rd 0 Flat White",
"price": 11,
"qty": 1,
"subtotal": 11,
"invoice_uuid": "3a00fb3a-0c78-444a-abef-3609c849e2e4",
"customer_id": 437,
"location_id": 907,
"product_id": 257506,
"extras": [],
"vat": 0,
"vat_pct": null,
"vat_name": null,
"is_addon": 0,
"note": null,
"is_refunded": 0,
"refund_quantity": 0,
"refund_reason": null,
"is_updated": 0,
"updated_qty": 0,
"addon_linked_with": [
{
"id": 25504103,
"name": "QA addon Classic Sandwich",
"actual_price": 100,
"vat": 0,
"price": 95,
"subtotal": 95,
"qty": 1
}
],
"discount_pct": 10,
"refund_pm_type": "cash",
"modifier": "[{\"name\":\"QA Modifier\",\"actual_price\":3,\"price\":3,\"quantity\":1,\"discount_pct\":0}]",
"transferred_to": 0,
"transferred_to_uuid": null,
"created_at": "2026-08-17T06:27:10.000Z",
"updated_at": "2026-08-17T06:27:10.000Z",
"combo_item_id": null,
"is_combo": 0,
"product_type": null,
"is_internal": null,
"sort_order": null,
"plu": null,
"otter_account_id": null,
"delivery_cost": null,
"tip": null,
"by": null,
"otter_id": null,
"transferred_quantity": 0,
"actual_price": 11,
"is_prepared": 0,
"discount_amount": 1.1,
"base_price": null,
"brand_id": 0,
"is_deleted": null,
"deleted_qty": null,
"deleted_at": null,
"label_id": 50,
"preparation_time": null,
"linked_uuid": null,
"item_type": null,
"item_price_after_discount": 9.9,
"line_vat": null,
"line_total": null,
"line_total_without_vat": null,
"identifier": "7c53c3c5-569d-47ae-84b4-7f2e28cbcee6",
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": "",
"product_recipe_cost": 0,
"total_cost": 0,
"updated_by_user_ids": "",
"re": null,
"ri": 0,
"is_wastage": 0,
"discount_id": null,
"discount_name": null,
"round": null,
"name_localize": null,
"original_linked_uuid": null,
"item_status": "active",
"disposition_type": null,
"disposition_reason": null,
"refund_amount": null,
"modifiers_cost_amount": 0,
"cost_snapshot_at": "2026-08-17T06:27:10.000Z",
"transferred_to_uuid_list": null,
"transferred_quantities": null,
"transfer_recipients": null
}
]
GET /api/materials. Why: Material (inventory SKU) catalog of the master inventory: raw materials (material_type mat) and recipe-output materials (rm) with branch stock, branch unit cost (avg_price), storage/ingredient units, category, suppliers and brands — the dashboard Inventory → Materials list. Use it to find material ids for get_material / update_material / delete_material, recipe ingredients (list_recipes / get_recipe), supplier catalogs and draft purchase orders (list_supplier_materials, create_draft_purchase_order), wastage, counts, transfers and work orders. Stock here is the branch on-hand qty; the movements behind it are in get_material_movements / list_inventory_movements, the ledger sum in get_material_stock. Filter by category with `cat` (id from list_material_categories), branch with `loc` (list_locations). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_materials"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated ids (list_locations). |
| k | string | optional | Case-insensitive substring of the material name (LIKE %k%; % and _ act as wildcards). |
| cat | string | optional | Category filter. |
| material_type | enum: mat, rm | optional | "mat" = raw/purchased materials, "rm" = recipe-output materials. |
| mode | enum: stockable, phantom | optional | "stockable" or "phantom" (exact materials.mode). |
| is_prepared | enum: 0, 1 | optional | "1" = produced in-house (recipe/preparation outputs), "0" = purchased (exact match). |
| is_active | enum: 0, 1 | optional | "1" → active materials (default when omitted), "0" → deactivated ones (delete_material). |
| brand_id | number | optional | Brand id (list_brands): only materials explicitly limited to that brand (material_brands). |
| _page | number | optional | 1-based page. |
| _limit | number | optional | Page size. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_materials"
}'
{
"data": [
{
"id": 119158,
"name": "Cherry Blosom",
"description": "Cherry",
"price": 0,
"stock": -12762188.785166,
"unit": "Kilogram",
"customer_id": 437,
"location_id": [
907,
908,
909,
910,
922
],
"low_stock": 0,
"physical_stock": null,
"is_active": 1,
"barcode": "",
"ingredientunit": "Gram",
"storageunit": "Kilogram",
"is_prepared": 0,
"conversionrate": "1000",
"minimum_level": "0",
"maximum_level": "0",
"par_level": "0",
"costingmethod": "average",
"actual_stock": 0,
"type": "raw",
"avg_price": 1.6474,
"materials": null,
"sale_price": 0,
"selling_price": 0,
"created_at": "2026-02-09T07:05:18.000Z",
"updated_at": "2026-08-05T07:03:56.000Z",
"wholesale_price": 0,
"tax_id": null,
"material_type": "mat",
"linked_recipe": null,
"linked_sku": null,
"mode": "stockable",
"unit_id": 2,
"ingredient_unit_id": 1,
"custom_unit_id": null,
"category_id": 2,
"yield_percentage": 100,
"sku_no": null,
"tax_name": null,
"tax_value": null,
"suppliers": [
970
],
"suppliers_prices": [
{
"supplier_id": 970,
"purchase_price": 1.1092
}
],
"brand_ids": [
412,
473
]
}
],
"pagination": {
"total": 1,
"page": 1,
"limit": 20,
"totalPages": 1
}
}
GET /api/materials/:id. Why: One material with its stock, reservations and unit cost at one branch (`loc`), units, costing method, suppliers with purchase prices, brands and branch list — the dashboard material edit/detail page. Load it before update_material (the update overwrites every field, so you pass this record back) and to read reserved/available stock before a transfer, wastage or count. Per-branch stock/cost: call once per branch (loc). Recipe outputs (material_type rm) link to their recipe via linked_recipe (get_recipe). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_material"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Material id (list_materials). |
| loc | number | optional | ONE branch id (list_locations). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_material",
"id": 11
}'
[
{
"id": 119158,
"name": "Cherry Blosom",
"description": "Cherry",
"price": 0,
"selling_price": 0,
"wholesale_price": 0,
"stock": -12762188.785166,
"unit": "Kilogram",
"unit_id": 2,
"custom_unit_id": null,
"ingredient_unit_id": 1,
"avg_price": 1.6474,
"customer_id": 437,
"location_id": [
907,
908,
909,
910,
922
],
"low_stock": 0,
"physical_stock": null,
"actual_stock": 0,
"is_active": 1,
"barcode": "",
"sku_no": "",
"conversionrate": "1000",
"ingredientunit": "Gram",
"storageunit": "Kilogram",
"costingmethod": "average",
"minimum_level": "0",
"maximum_level": "0",
"par_level": "0",
"type": "raw",
"category_id": 2,
"material_type": "mat",
"mode": "stockable",
"linked_recipe": null,
"tax_id": null,
"tax_name": null,
"tax_value": null,
"suppliers": [
970
],
"suppliers_prices": [
{
"supplier_id": 970,
"purchase_price": 1.1092
}
],
"reserved_stock": 0,
"committed_stock": 0,
"available_for_sale": -12762188.785166,
"brands": [
{
"id": 412,
"name": "Nexus Cafe"
},
{
"id": 473,
"name": "Test "
}
]
}
]
GET /api/inventory/:material_id/stock. Why: Ledger check: the sum of every inventory movement qty of one material at one branch (storage unit). Use it to cross-check the branch stock shown by get_material / list_materials (materials_locations) — they can drift — or when only the movement ledger counts. For history use get_material_movements; for all materials list_inventory / list_materials. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_material_stock"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| material_id | number | required | Material id (list_materials). |
| location | number | required | ONE branch id (list_locations). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_material_stock",
"material_id": 11,
"location": 1
}'
[
{
"stock": 67.1985
}
]
GET /api/inventory. Why: Legacy stock board of one branch: active materials linked to the branch with ledger stock, tenant average cost, unit, low-stock level and stock value. CURRENTLY RETURNS 500 FOR MASTER TENANTS (server count query bug) — for branch stock use list_inventory_movements (current_stock, avg_cost, total_value per material × branch), get_material_stock (one material) or list_materials. No page of the dashboard menu uses it any more. Master tenants: always HTTP 500 "Unknown column 'm.is_active'" (backend bug) — use list_inventory_movements or get_material_stock instead. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_inventory"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | required | ONE branch id (list_locations). |
| k | string | optional | Case-insensitive substring match on the material name (LIKE %k%; `%` and `_` act as wildcards). |
| cat | string | optional | PRODUCT category name (exact, trimmed): keeps materials used in the recipe/BOM (product_materials) of a non-archived product whose type equals it. |
| brand_id | string | optional | Brand id (list_brands): keeps materials used by non-archived products whose category is in a group linked to that brand. |
| _page | number | optional | 1-based page, used only with _limit. |
| _limit | number | optional | Page size. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_inventory",
"loc": "example"
}'
{
"data": [
{
"id": 120802,
"name": "Gotcha Recipe",
"description": null,
"stock": 9689,
"physical_stock": 0,
"price": 2.4616,
"unit": "Kilogram",
"low_stock": 0,
"diff": 9689,
"total": 23850.44
}
],
"total": 1
}
GET /api/materials/stats. Why: The four header numbers of the dashboard Materials page for a branch scope: total materials, active, recipe materials (rm) and low-stock count (branch stock below low_stock). Quick context before list_materials; for the items themselves use list_materials (and get_material for one). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_materials_stats"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated ids (list_locations). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_materials_stats"
}'
{
"total": 2250,
"active": 2247,
"recipes": 902,
"low_stock": 0
}
GET /api/inventory/categories. Why: Material category tree of the tenant (top-level categories and their subcategories, max 2 levels; separate from product categories). Use the ids as category_id on create_material / update_material and as `cat` to filter list_materials; pick parents for create_material_category. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_material_categories"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| format | enum: flat, tree | optional | "flat" → flat array (each row has parent_id). |
| k | string | optional | Case-insensitive substring of the category name (LIKE %k%). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_material_categories"
}'
{
"success": true,
"data": [
{
"id": 2,
"name": "Farm",
"description": "",
"parent_id": null,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
},
{
"id": 4,
"name": "Frozen",
"description": "",
"parent_id": null,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
},
{
"id": 6,
"name": "Meat",
"description": "",
"parent_id": null,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
},
{
"id": 3,
"name": "Dairy",
"description": "",
"parent_id": 2,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
},
{
"id": 1,
"name": "Meat",
"description": "",
"parent_id": 2,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
},
{
"id": 5,
"name": "Meat",
"description": "",
"parent_id": 4,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
}
]
}
GET /api/materials/:id/used-in-recipes. Why: Where-used for one material: every recipe that has it as an ingredient, with the quantity and unit per recipe batch, and the recipe's own output material. Run it before changing units/conversion/yield/cost, deactivating (delete_material) or advising substitutions; then get_recipe for the full recipe and list_products / get_product to see which products and modifier items sell through those recipes (sync_invoice deducts along them). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_material_used_in_recipes"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Material id (list_materials). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_material_used_in_recipes",
"id": 11
}'
{
"message": "Used-in recipes fetched successfully",
"data": [
{
"recipe_id": 17224,
"recipe_name": "Gotcha Recipe",
"yield_qty": 0.782,
"recipe_unit": "Kilogram",
"recipe_mode": "stockable",
"qty": 1000,
"qty_unit": "Gram",
"wastage_pct": 0,
"item_type": "mat",
"material_id": 120802,
"material_name": "Gotcha Recipe",
"material_sku": "mat-437-120802",
"material_mode": "stockable",
"material_type": "rm"
},
{
"recipe_id": 16779,
"recipe_name": "Test",
"yield_qty": 0.8,
"recipe_unit": "Kilogram",
"recipe_mode": "stockable",
"qty": 500,
"qty_unit": "Gram",
"wastage_pct": 0,
"item_type": "mat",
"material_id": 119197,
"material_name": "Test",
"material_sku": "mat-437-119197",
"material_mode": "stockable",
"material_type": "rm"
}
]
}
GET /api/inventory/movements-v3. Why: Stock movement summary (dashboard Inventory → Movement page): one row per material × branch that moved in the date range, with opening stock, totals per movement type (purchased in, transferred in/out, produced, sold, consumed, wasted, adjusted …), closing stock, live current stock, branch average cost and stock value, plus variance between ledger closing and live stock. Use it to answer "how much X came in / went out / is on hand at branch Y" and for stock valuation. Drill into single transactions with get_material_movements (one material) or get_inventory_movements_ledger (all materials). Read only — it never changes stock. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_inventory_movements"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated ids (list_locations). |
| material_name | string | optional | Case-insensitive substring match on the material name (LIKE %…%; `%`/`_` are wildcards). |
| start_date | string | optional | From date YYYY-MM-DD, inclusive (movements are stored per calendar day). |
| end_date | string | optional | To date YYYY-MM-DD, inclusive. |
| _page | number | optional | 1-based page. |
| _limit | number | optional | Rows (material × branch pairs) per page. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_inventory_movements"
}'
{
"success": true,
"data": [
{
"material_id": 120802,
"material_name": "Gotcha Recipe",
"material_unit": "Kilogram",
"location_id": 907,
"location_name": "Default location",
"opening_stock": -111,
"stock_in": 0,
"stock_out": 0,
"transferred_in": 0,
"transferred_out": 200,
"transfer_return": 0,
"consumed": 0,
"produced": 10000,
"waste": 0,
"sold": 0,
"purchase_return": 0,
"adjustment_type_in": 0,
"adjustment_type_out": 0,
"closing_stock": 9689,
"current_stock": 9700,
"avg_cost": 2.4616,
"total_value": 23877.52,
"expected_closing_stock": "9689.000",
"variance": "-11.000",
"variance_percentage": -0.11
}
],
"pagination": {
"page": 1,
"limit": 3,
"total": 3,
"totalPages": 1
}
}
GET /api/inventory/movements-ledger. Why: Transaction ledger across materials (dashboard Reports → Movement Report): every stock movement row — sale deductions, purchase receipts, transfers, wastage, count adjustments, work-order consumption/production, returns — with signed quantity, unit cost, value, reference document, user and a running balance per material × branch. Use it to audit what changed stock, by whom and why (filter by branch, materials, movement types, dates). For per-period totals per material use list_inventory_movements; for one material's history get_material_movements. Read only. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_inventory_movements_ledger"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated ids (list_locations). |
| material_ids | string | optional | Material id or comma-separated ids (list_materials). |
| mov_types | string | optional | Comma-separated movement types (exact): in, out, sold, refund, wastage, waste, transferred_in, transferred_out, transfer_return, consumed, produced, purchase_r… |
| k | string | optional | Case-insensitive substring match on the material name (LIKE %k%; `%`/`_` are wildcards). |
| start_date | string | optional | From date YYYY-MM-DD, inclusive (created_at ≥ start_date 00:00). |
| end_date | string | optional | To date YYYY-MM-DD, inclusive (created_at < end_date + 1 day). |
| group_by | enum: none, location, material, movementType, day, week | optional | Only changes the sort: location → by branch name, material → by material name, movementType → by type, then newest first. |
| _page | number | optional | 1-based page. |
| _limit | number | optional | Rows per page, clamped to 1–1000. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_inventory_movements_ledger"
}'
{
"success": true,
"data": [
{
"id": 12472803,
"timestamp": "2026-08-12T10:48:20.000Z",
"material_id": 120802,
"material_name": "Gotcha Recipe",
"material_unit": "Kilogram",
"location_id": 907,
"location_name": "Default location",
"movement_type": "transferred_out",
"quantity_delta": -200,
"unit_cost": "18.1956",
"total_value": -3639.12,
"reference_id": "Stock transfer fe1ecd13-f615-4326-b6a1-482b5353a3c6 - Approved quantity",
"expiry_date": null,
"user_id": 4734,
"recorded_by": "Nexus Cafe",
"running_balance": 9689
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 3290,
"totalPages": 165
}
}
GET /api/inventory/:id/movs. Why: Stock history of ONE material at a branch (dashboard material page History tab and the Movement page drawer): every ledger movement newest first with signed qty, unit cost, reference document, user and brand, plus the material's current stock at that branch on the first row. Use it to explain why a material's stock is what it is. Material id from list_materials; for all materials use get_inventory_movements_ledger, for period totals list_inventory_movements. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_material_movements"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Material id (list_materials / get_material). |
| location | string | required | Branch id (list_locations). |
| start_date | string | optional | From date YYYY-MM-DD, inclusive. |
| end_date | string | optional | To date YYYY-MM-DD, inclusive (created_at < end_date + 1 day). |
| _page | number | optional | 1-based page. |
| _limit | number | optional | Rows per page. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_material_movements",
"id": 11,
"location": "example"
}'
{
"data": [
{
"id": 12472802,
"mov_type": "transferred_out",
"material_id": 119196,
"qty": -200,
"price": "11.0136",
"reference": "Stock transfer fe1ecd13-f615-4326-b6a1-482b5353a3c6 - Approved quantity",
"created_at": "2026-08-12 10:48:20",
"expiry_date": null,
"user_id": 4734,
"brand_id": null,
"location_id": 907,
"location_name": "Default location",
"brand_name": null,
"username": "Nexus Cafe",
"stock": -21387897.14439
},
{
"id": 12472793,
"mov_type": "in",
"material_id": 119196,
"qty": 1000.921387,
"price": "0.1256",
"reference": "Supplier Order d39ca7ba-1468-4f0b-855e-94d042731c3f",
"created_at": "2026-08-04 16:49:29",
"expiry_date": null,
"user_id": 4734,
"brand_id": null,
"location_id": 907,
"location_name": "Default location",
"brand_name": null,
"username": "Nexus Cafe"
}
],
"total": 41
}
GET /api/suppliers. Why: Supplier (vendor) directory of the tenant with per-supplier counters: how many active materials are linked to it (items_in_catalog), how many purchase orders it has (total_orders) and how many are still pending delivery. Use it to find a supplier id before get_supplier / update_supplier / delete_supplier, before reading its price list (list_supplier_materials sup=<id>) or its purchase orders (list_supplier_invoices sid=<id>). Supplier ↔ material links and purchase prices live in list_supplier_materials; material master data in list_materials / get_material. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_suppliers"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated ids (list_locations). |
| k | string | optional | Case-insensitive substring match on supplier name (LIKE %k%; `%` and `_` act as wildcards). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_suppliers"
}'
[
{
"id": 825,
"name": "Grocery Supplier",
"description": "Grocery items",
"customer_id": 437,
"location_id": 907,
"address": "Warehouse 4, Al Quoz, Dubai",
"logo": "",
"tax_pct": 5,
"payments": "{\"is_cash\":true,\"is_credit\":false,\"is_cheque\":false}",
"trade_license": "",
"contact_name": "Sample Contact",
"contact_email": "orders@supplier.example",
"contact_phone": "+971500000001",
"trn_certificate": "",
"can_recieve_email": 1,
"items_in_catalog": 5,
"total_orders": 7,
"pending_deliveries": 1
}
]
GET /api/supplier-materials. Why: A supplier's price list: the materials linked to a supplier at a branch with the supplier's last purchase price (purchase_price) and the material's average cost. Use it to pick material ids and unit prices for draft PO lines (create_draft_purchase_order / update_draft_purchase_order_lines) — the dashboard PO pickers show exactly this list. Supplier ids from list_suppliers, branch ids from list_locations; material details and units via get_material, stock via list_inventory. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_supplier_materials"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| sup | number | required | Supplier id (list_suppliers). |
| loc | string | optional | Branch id or csv (list_locations). |
| k | string | optional | Case-insensitive substring match on material name (LIKE %k%; `%`/`_` are wildcards). |
| filter | enum: linked, unlinked, all | optional | "linked" → only materials linked to the supplier; "unlinked" → only materials NOT linked; "all" (or any other value) → both. |
| mode | enum: stockable, phantom, all | optional | Material mode filter: "stockable" (default — what POs use), "phantom", or "all" for no filter. |
| exclude_prepared | enum: 0, false, 1, true | optional | Prepared / stockable-recipe materials are excluded by default (they are made in-house, not purchased). |
| include_recipes | enum: 0, 1, true, false | optional | "1"/"true" → include prepared / stockable-recipe materials (same as exclude_prepared=0). |
| _page | number | optional | 1-based page. |
| _limit | number | optional | Page size. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_supplier_materials",
"sup": 1
}'
[
{
"id": 112157,
"name": "\t SRC-BT-86CBPR-SG3 BB86-BB92 INTERLOCK SHIMANO CERAMIC COATED BLACK",
"unit": "Pieces",
"type": "CEMA BEARINGS",
"material_type": "mat",
"mode": "stockable",
"avg_price": 1.2343,
"sku": "mat-437-112157",
"supplier_id": 825,
"material_id": 112157,
"purchase_price": 2,
"brand_ids": []
},
{
"id": 112295,
"name": " Atx 27.5 In Vibrant Blue",
"unit": "Pieces",
"type": "SUN AND SAND SPORTS LLC",
"material_type": "mat",
"mode": "stockable",
"avg_price": 3,
"sku": "mat-437-112295",
"supplier_id": 825,
"material_id": 112295,
"purchase_price": 3,
"brand_ids": []
}
]
GET /api/supplier-invoices. Why: Purchase-order board of the tenant (the dashboard "Purchase Orders" list): confirmed POs, drafts, partially received and received ones, with supplier snapshot, totals, PO status and supplier response. Use it to find a PO/draft uuid for get_purchase_order and the draft tools, to see a supplier's orders (sid from list_suppliers) or what is still pending delivery. Read-only: approving, sending, receiving and cancelling POs happen only in the dashboard. Stock arrives only when the dashboard receives a PO (then visible in list_inventory / list_inventory_movements). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_supplier_invoices"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | ONE branch id (list_locations): exact match on location_id. |
| st | enum: all, draft, awaiting, pending_approval, pending, partially_received, delivered, canceled, cancelled, returned | optional | Status filter: all | draft | awaiting | pending_approval | pending | partially_received | delivered | canceled | cancelled | returned (POs with at least one su… |
| sid | number | optional | Supplier id (list_suppliers): only POs of that supplier (table_id). |
| from | string | optional | Start date YYYY-MM-DD on created_at in the tenant time zone (inclusive). |
| to | string | optional | End date YYYY-MM-DD (inclusive). |
| k | string | optional | Partial match on PO number (oid), supplier invoice number, system supplier invoice id or your_order_ref. |
| is_archive | enum: 0, 1, all | optional | "0" (default) → active POs only; "1" → archived only; "all" → both. |
| type | string | optional | Exact payment_method filter; "All" → none. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_supplier_invoices"
}'
[
{
"id": "aa9412cc3eb9",
"oid": 6187,
"items_number": 1,
"subtotal": 12,
"vat": 0.6,
"payable": 12.6,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "",
"published_date": null,
"uuid": "52e14591-f11f-430c-8672-aa9412cc3eb9",
"created_at": "2026-05-19T16:16:14.000Z",
"customer_id": 437,
"location_id": 907,
"info_name": "Faisal Suppliers",
"info_address": "Industrial Area 2, Sharjah",
"info_phone": "+971500000003",
"info_email": "sales@faisal.example",
"status": "draft",
"user": "Nexus Cafe",
"table_id": 881,
"payment_method": null,
"your_order_ref": "",
"supplier_order_ref": "",
"is_draft": 1,
"parent_purchase_uuid": "6f6db90f-2a7d-4910-82e9-3a0dae7e6e70",
"is_archive": 0
},
{
"id": "3a0dae7e6e70",
"oid": 6180,
"items_number": 2,
"subtotal": 10,
"vat": 0.5,
"payable": 10.5,
"discountamount": 0,
"discountpct": null,
"paid": null,
"change": 2,
"note": "",
"published_date": "2026-05-19T00:00:00.000Z",
"uuid": "6f6db90f-2a7d-4910-82e9-3a0dae7e6e70",
"created_at": "2026-05-19T14:41:09.000Z",
"customer_id": 437,
"location_id": 907,
"info_name": "Grocery Supplier",
"info_address": "Warehouse 4, Al Quoz, Dubai",
"info_phone": "+971500000001",
"info_email": "orders@supplier.example",
"status": "partially_received",
"user": "Nexus Cafe",
"table_id": 825,
"payment_method": null,
"your_order_ref": "",
"supplier_order_ref": null,
"is_draft": 0,
"parent_purchase_uuid": null,
"is_archive": 0
}
]
GET /api/stock-transfer-invoices. Why: Inter-branch stock transfer board (Inventory → Transfer Request): every transfer between the tenant's branches with status, source/destination branch, transfer_no, value and who requested/approved/dispatched/received it. Use it to find a transfer id for get_stock_transfer (lines, on-hand, history) → update_stock_transfer / update_stock_transfer_items (pending only), to find the transfer you just created with create_stock_transfer (its response has no id), and to answer 'what is in transit / waiting for approval'. Branch ids come from list_locations. Server-side scope: only transfers whose source or destination is in the calling user's allowed branches. Read only — approve/dispatch/receive/complete/reject are dashboard actions. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_stock_transfers"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| status | enum: draft, pending, approved, dispatched, received, completed, rejected, cancelled, delivered | optional | Exact status filter. |
| from_location_id | number | optional | Source branch id (list_locations): only transfers leaving this branch. |
| to_location_id | number | optional | Destination branch id (list_locations): only transfers arriving at this branch. |
| from | string | optional | Start date YYYY-MM-DD, compared to DATE(created_at) in UTC (inclusive). |
| to | string | optional | End date YYYY-MM-DD, compared to DATE(created_at) in UTC (inclusive). |
| _page | number | optional | 1-based page. |
| _limit | number | optional | Rows per page. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_stock_transfers"
}'
{
"success": true,
"message": "Stock transfers fetched successfully",
"data": {
"data": [
{
"id": 7777,
"items_number": 2,
"subtotal": 5841.84,
"vat": 292.092,
"payable": 6133.932,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "",
"uuid": "fe1ecd13-f615-4326-b6a1-482b5353a3c6",
"created_at": "2026-08-12T10:48:07.000Z",
"customer_id": 437,
"from_location_id": 907,
"info_name": "Nexus Cafe",
"info_address": null,
"info_email": "admin@devnexus.co",
"info_phone": null,
"status": "dispatched",
"user": "Nexus Cafe",
"table_id": null,
"payment_method": null,
"order_id": null,
"invoice_id": 44,
"to_location_id": 909,
"customer_location_id": 907,
"email": null,
"delivery_date": null,
"user_id": 4734,
"is_edit": null,
"username": "Nexus Cafe",
"transfer_no": "STR-2026-01628",
"requested_by": 4734,
"approved_by": 4734,
"dispatched_by": 4734,
"received_by": null,
"requested_at": "2026-08-12T10:48:07.000Z",
"approved_at": "2026-08-12T10:48:16.000Z",
"dispatched_at": "2026-08-12T10:48:21.000Z",
"received_at": null,
"notes": null,
"priority": null,
"expected_delivery_date": null,
"updated_at": "2026-08-12T10:48:20.000Z",
"rejected_by": null,
"rejected_at": null,
"parent_transfer_id": null,
"from_location": "Default location",
"to_location": "Iqbal Town",
"requested_by_username": "Nexus Cafe",
"approved_by_username": "Nexus Cafe",
"dispatched_by_username": "Nexus Cafe",
"received_by_username": null
}
],
"total": 47,
"page": 1,
"limit": "5"
}
}
GET /api/stock-order-request. Why: Branch material order requests (Inventory → Order Requests): what a branch asked to buy, per request number REQ-<oid>, with status (Pending / Draft PO Created / Declined), total, item count and the draft PO it became (po_number). Use it to find a request uuid for get_stock_order_request → update_stock_order_request (Pending only), to check a request you created, or to see which requests already have POs (then get_purchase_order). Branch ids from list_locations. Read only — decline / create draft PO are dashboard actions. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_stock_order_requests"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | ONE requesting-branch id (list_locations); only requests whose from_location_id equals it. |
| st | string | optional | Exact status filter (case-insensitive): Pending | Draft PO Created | Declined (dashboard dropdown), or a legacy status. |
| k | string | optional | Substring search on request number (invoice_id/id), user, username or info_name. |
| from | string | optional | Start business day YYYY-MM-DD (from the branch opening hour, tenant timezone). |
| to | string | optional | End business day YYYY-MM-DD (inclusive, up to the next day's opening hour). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_stock_order_requests"
}'
[
{
"id": "4338bd97b70a",
"oid": 13,
"subtotal": 6000,
"vat": 300,
"payable": 6300,
"change": 0,
"note": "",
"uuid": "59c4fb6e-fb85-4ab1-ac0b-4338bd97b70a",
"delivery_date": "2026-08-06T12:52:39.000Z",
"created_at": "2026-08-06T12:52:49.000Z",
"customer_id": 437,
"to_location_id": 907,
"to_location": "Default location",
"customer_location_id": 907,
"from_location_id": 907,
"from_location": "Default location",
"location_name": "Default location",
"info_name": "Nexus Cafe",
"info_email": "admin@devnexus.co",
"status": "Pending",
"user": "Nexus Cafe",
"username": "Nexus Cafe",
"table_id": 0,
"payment_method": "1",
"items_count": 1,
"purchase_uuid": null,
"po_number": null
},
{
"id": "8cbb626a9d44",
"oid": 12,
"subtotal": 125.72,
"vat": 6.29,
"payable": 132,
"change": 0,
"note": "",
"uuid": "d5bc4cdc-f6b6-4d04-bdcf-8cbb626a9d44",
"delivery_date": "2026-08-04T21:48:06.000Z",
"created_at": "2026-08-04T21:48:44.000Z",
"customer_id": 437,
"to_location_id": 907,
"to_location": "Default location",
"customer_location_id": 907,
"from_location_id": 907,
"from_location": "Default location",
"location_name": "Default location",
"info_name": "Nexus Cafe",
"info_email": "admin@devnexus.co",
"status": "Draft PO Created",
"user": "Nexus Cafe",
"username": "Nexus Cafe",
"table_id": 0,
"payment_method": "1",
"items_count": 1,
"purchase_uuid": "d39ca7ba-1468-4f0b-855e-94d042731c3f",
"po_number": "PO-55"
}
]
GET /api/wastage. Why: Wastage batches (spoiled / expired / damaged stock write-offs) of the tenant with status, reason, cost and creator — the dashboard Wastage page. Use it to find batch ids for get_wastage / update_wastage_draft / delete_wastage_draft and to report waste cost. Read only; the stock-out happens when a batch is approved in the dashboard. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_wastage"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or csv of branch ids (list_locations). |
| from | string | optional | Start date YYYY-MM-DD (created_at ≥ that day 00:00 in server time). |
| to | string | optional | End date YYYY-MM-DD inclusive (created_at ≤ 23:59:59). |
| approval_status | enum: draft, pending, approved, rejected | optional | Exact status. |
| user | number | optional | Creator user id (list_users). |
| brand_filter | number | optional | Brand id (list_brands): batches with at least one line of that brand. |
| page | number | optional | 1-based page. |
| limit | number | optional | Rows per page. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_wastage"
}'
{
"success": true,
"message": "Wastage batches fetched successfully",
"data": {
"data": [
{
"id": 29,
"batch_number": "WB-2026-00029",
"wastage_reason": "invoice_cancellation",
"items_count": 1,
"total_cost": 1.1624,
"approval_status": "pending",
"requires_approval": 1,
"created_by": 4734,
"approved_by": null,
"batch_notes": "Auto-created from invoice cancellation — 6CAC39F5-C142-4FF2-8970-DF090FEA68EE",
"approval_notes": null,
"location_id": 907,
"customer_id": 437,
"submitted_at": "2026-07-01T12:12:18.000Z",
"approved_at": null,
"created_at": "2026-07-01T12:12:18.000Z",
"updated_at": "2026-07-01T12:12:18.000Z",
"reference_type": "invoice_cancellation",
"reference_id": "6CAC39F5-C142-4FF2-8970-DF090FEA68EE",
"brand_id": null,
"location_name": "Default location",
"created_by_name": "Nexus Cafe",
"approved_by_name": null,
"item_brand_names_csv": null
},
{
"id": 14,
"batch_number": "WB-2026-00014",
"wastage_reason": "expired",
"items_count": 1,
"total_cost": 5.4144,
"approval_status": "approved",
"requires_approval": 1,
"created_by": 4734,
"approved_by": 4734,
"batch_notes": null,
"approval_notes": null,
"location_id": 907,
"customer_id": 437,
"submitted_at": "2026-04-30T09:16:16.000Z",
"approved_at": "2026-04-30T14:16:46.000Z",
"created_at": "2026-04-30T09:16:15.000Z",
"updated_at": "2026-04-30T09:16:45.000Z",
"reference_type": "manual",
"reference_id": null,
"brand_id": 473,
"location_name": "Default location",
"created_by_name": "Nexus Cafe",
"approved_by_name": "Nexus Cafe",
"item_brand_names_csv": "Test "
}
],
"pagination": {
"total": 4,
"limit": "5"
}
}
}
GET /api/inventory-counts/list. Why: Physical inventory counts (stock-take sessions) of a branch with every line (expected vs counted). Use it to find a count id and its status before get_inventory_count / update_inventory_count_materials / add_inventory_count_item, or to report variances. Read only — approval (which adjusts stock) happens in the dashboard. Count-sheet templates are list_inventory_count_sheets. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_inventory_counts"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or csv of branch ids (list_locations); counts whose location_id is any of them. |
| status | enum: draft, submitted, pending, approved, rejected | optional | Exact status filter. |
| type | number | optional | Exact type filter: 0 partial, 1 full, 2 count sheet. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_inventory_counts"
}'
{
"status": true,
"message": "Inventory count list found",
"data": [
{
"id": 477,
"start_date": "Apr 25, 2026",
"start_time": "03:41 PM",
"location_id": 907,
"name": "Default location - Apr 25, 2026 03:41 PM",
"type": 0,
"is_partial": 1,
"status": "pending",
"created_at": 1777113069,
"updated_at": 1777113069,
"created_by_user_id": 4734,
"materials": [
{
"material_id": 119197,
"counted_quantity": 10.45,
"expected_quantity": 0,
"price": 0.2873,
"name": "Test"
}
]
},
{
"id": 476,
"start_date": "Apr 25, 2026",
"start_time": "03:57 PM",
"location_id": 907,
"name": "Default location - Apr 25, 2026 03:57 PM",
"type": 0,
"is_partial": 1,
"status": "approved",
"created_at": 1777112893,
"updated_at": 1777112893,
"created_by_user_id": 4734,
"materials": [
{
"material_id": 119197,
"counted_quantity": 10.9985,
"expected_quantity": 0,
"price": 0.2873,
"name": "Test"
}
]
}
]
}
GET /api/inventory-counts/by-id. Why: One inventory count with every line: line id, material, expected vs counted quantity, price and units. Needed before update_inventory_count_item (line ids), update_inventory_count_materials (full line list) or add_inventory_count_item (check the material is not already on the count), and to report variance. Read only. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_inventory_count"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Inventory count id (list_inventory_counts → data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_inventory_count",
"id": 11
}'
{
"status": true,
"message": "Inventory count found",
"data": [
{
"id": 156,
"start_date": "Mar 03, 2026",
"start_time": "01:16 PM",
"location_id": 907,
"name": "Default location - Mar 03, 2026 01:16 PM",
"type": 2,
"is_partial": 0,
"status": "approved",
"created_at": 1772524002,
"updated_at": 1772524002,
"created_by_user_id": 4734,
"created_by_user_name": "Nexus Cafe",
"materials": [
{
"id": 1400,
"material_id": 119196,
"counted_quantity": 50,
"expected_quantity": 100,
"price": 100,
"name": "Test Material",
"unit": "Kilogram",
"ingredientunit": "Gram",
"conversionrate": "1000"
}
]
}
]
}
GET /api/recipe. Why: Recipe (BOM) catalog of the tenant: name, output yield_qty + unit, mode (phantom | stockable), last computed cost and brand tags. Recipes are tenant-wide (no branch filter). Use it to find recipe ids for get_recipe → update_recipe / delete_recipe, to pick STOCKABLE recipes for create_work_order (mode=stockable), or phantom recipes to link to products (create_product / update_product recipes[]). Ingredient lines, product links and branches are only in get_recipe. A recipe's prepared material (its stock item) is in list_materials (linked_recipe = recipe id). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_recipes"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| page | number | optional | 1-based page. |
| limit | number | optional | Page size. |
| search | string | optional | Case-insensitive substring of the recipe name (`%`/`_` act as wildcards). |
| mode | enum: phantom, stockable | optional | Exact mode filter. |
| brand_id | number | optional | Brand id (list_brands): only recipes tagged with this brand (brand_ids). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_recipes"
}'
{
"message": "Recipes Fetched Successfully",
"data": [
{
"id": 16779,
"customer_id": 437,
"name": "Test",
"yield_qty": 0.8,
"unit": "Kilogram",
"version": 1,
"created_by": 4734,
"mode": "stockable",
"sku_no": "rcp-437-16779",
"unit_id": 2,
"unit_cost": 50,
"total_cost": 50,
"calculation_date": "2026-02-12T13:26:26.000Z",
"material_count": 2,
"waste_pct": 0,
"notes": null,
"brands": [
{
"id": 412,
"name": "Nexus Cafe"
},
{
"id": 473,
"name": "Test "
}
]
}
],
"pagination": {
"total": 902,
"page": 1,
"limit": 1,
"totalPages": 902
}
}
GET /api/recipe/:id. Why: One recipe with its ingredient lines (material, qty in ingredient unit, yield override, material cost data), linked products (qty_used), brand tags and the branches of its prepared material. Always call it before update_recipe (update is a full replace) and before create_work_order (the work-order material lines are computed from these lines). Material ids → get_material / list_materials; product ids → get_product; branch ids → list_locations; brand ids → list_brands. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_recipe"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Recipe id (list_recipes data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_recipe",
"id": 11
}'
{
"message": "Recipe Fetched Successfully",
"data": [
{
"id": 16779,
"customer_id": 437,
"name": "Test",
"yield_qty": 0.8,
"unit": "Kilogram",
"version": 1,
"created_by": 4734,
"mode": "stockable",
"sku_no": "rcp-437-16779",
"unit_id": 2,
"recipe_items": [
{
"recipe_id": 16779,
"material_id": 119196,
"qty": 500,
"unit": "Gram",
"wastage_pct": 0,
"id": 988201,
"type": "mat",
"unit_id": 1,
"yield_pct": null,
"name": "Test Material",
"ingredientunit": "Gram",
"conversionrate": "1000",
"avg_price": 22.0271,
"price": 0
},
{
"recipe_id": 16779,
"material_id": 119158,
"qty": 500,
"unit": "Gram",
"wastage_pct": 0,
"id": 988202,
"type": "mat",
"unit_id": 1,
"yield_pct": null,
"name": "Cherry Blosom",
"ingredientunit": "Gram",
"conversionrate": "1000",
"avg_price": 0.3295,
"price": 0
}
],
"product_ids": [
257508
],
"linked_products": [
{
"product_id": 257508,
"id": 257508,
"name": "Mocha",
"qty_used": 1,
"portion_qty": 0.8,
"unit": "Kilogram"
}
],
"brand_ids": [
412,
473
],
"location_id": [
907,
909,
908,
922,
910,
1101,
1102,
1100,
1117
]
}
]
}
GET /api/inventory/categories/:id. Why: One material category (name, parent, description, image) — load it before update_material_category or to resolve a material's category_id to a name. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_material_category"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Material category id (list_material_categories). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_material_category",
"id": 11
}'
{
"success": true,
"data": {
"id": 3,
"name": "Dairy",
"description": "",
"parent_id": 2,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
}
}
GET /api/suppliers/:id. Why: Load one supplier record (all its columns) before update_supplier — the update overwrites every column, so you need the current values to send back — or to show its contact / payment details. Ids come from list_suppliers. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_supplier"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Supplier id from list_suppliers. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_supplier",
"id": 11
}'
[
{
"id": 825,
"name": "Grocery Supplier",
"description": "Grocery items",
"customer_id": 437,
"location_id": 907,
"address": "Warehouse 4, Al Quoz, Dubai",
"logo": "",
"tax_pct": 5,
"payments": "{\"is_cash\":true,\"is_credit\":false,\"is_cheque\":false}",
"trade_license": "",
"contact_name": "Sample Contact",
"contact_email": "orders@supplier.example",
"contact_phone": "+971500000001",
"trn_certificate": "",
"can_recieve_email": 1
}
]
GET /api/supplier-invoices/:uuid. Why: Full purchase order or draft: header (supplier snapshot, dates, refs, status, totals, who received/approved), every line with material, unit, qty, requested qty, unit price and line total, plus the branch's business info. Load it before update_draft_purchase_order_lines (you must resend all lines and keep the VAT rate) or delete_draft_purchase_order (check is_draft = 1), and to answer "what's on PO-123". uuid from list_supplier_invoices. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_purchase_order"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | PO/draft uuid (list_supplier_invoices → uuid; not id/oid). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_purchase_order",
"uuid": "example"
}'
[
{
"id": 6187,
"items_number": 1,
"subtotal": 12,
"vat": 0.6,
"payable": 12.6,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "",
"published_date": null,
"delivery_date": null,
"delivery_details": null,
"uuid": "52e14591-f11f-430c-8672-aa9412cc3eb9",
"created_at": "2026-05-19T00:00:00.000Z",
"customer_id": 437,
"location_id": 907,
"info_name": "Faisal Suppliers",
"info_address": "Industrial Area 2, Sharjah",
"info_phone": "+971500000003",
"info_email": "sales@faisal.example",
"status": "draft",
"user": "Nexus Cafe",
"table_id": 881,
"your_order_ref": "",
"supplier_order_ref": "",
"supplier_invoice_number": null,
"supplier_invoice_id": 3,
"received_by": null,
"received_at": null,
"received_by_name": null,
"approved_by": null,
"approved_at": null,
"approved_by_name": null,
"rejected_by": null,
"rejected_at": null,
"rejected_by_name": null,
"is_draft": 1,
"parent_purchase_uuid": "6f6db90f-2a7d-4910-82e9-3a0dae7e6e70",
"order_id": 18,
"is_archive": 0,
"parent_order_id": 6180,
"location_name": "Default location",
"items": [
{
"id": 14292,
"name": "\t SRC-BT-86CBPR-SG3 BB86-BB92 INTERLOCK SHIMANO CERAMIC COATED BLACK",
"price": 2,
"qty": 6,
"subtotal": 12,
"purchase_uuid": "52e14591-f11f-430c-8672-aa9412cc3eb9",
"customer_id": 437,
"location_id": 907,
"material_id": 112157,
"extras": null,
"custom_price": 2,
"product_id": null,
"unit": "Pieces",
"wastage": null,
"brand_id": null,
"requested_qty": 6,
"remark": null,
"ingredientunit": "Pieces",
"brand_name": null
}
],
"business_info": [
{
"id": 9993,
"value": "Nexus",
"name": "info_name"
},
{
"id": 9994,
"value": "Nexus located at Dev",
"name": "info_address"
},
{
"id": 9995,
"value": "info@buyer.example",
"name": "info_email"
},
{
"id": 10237,
"value": "+971500000009",
"name": "info_phone"
},
{
"id": 9996,
"value": "100000000000003",
"name": "info_vat_no"
},
{
"id": 9997,
"value": "1762167059218-logo.png",
"name": "logo_image"
}
]
}
]
GET /api/wastage/:id. Why: One wastage batch with all its lines (material, qty, unit, cost). Needed before update_wastage_draft (it replaces the whole batch, so resend everything) or delete_wastage_draft (confirm it is still a draft). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_wastage"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Wastage batch id (list_wastage → data.data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_wastage",
"id": 11
}'
{
"success": true,
"message": "Wastage batch fetched successfully",
"data": [
{
"header": {
"id": 33,
"batch_number": "WB-2026-00030",
"wastage_reason": "Customer complaint",
"items_count": 7,
"total_cost": 13.2238,
"approval_status": "pending",
"requires_approval": 1,
"created_by": 4734,
"approved_by": null,
"batch_notes": "Refund wastage — invoice 79EEB56A-1E47-4DB1-9F24-9234A4FB017E",
"approval_notes": null,
"location_id": 907,
"customer_id": 437,
"submitted_at": "2026-07-01T13:17:21.000Z",
"approved_at": null,
"created_at": "2026-07-01T13:17:20.000Z",
"updated_at": "2026-07-01T13:17:20.000Z",
"reference_type": "invoice_refund",
"reference_id": "79EEB56A-1E47-4DB1-9F24-9234A4FB017E",
"brand_id": null,
"location_name": "Default location",
"created_by_name": "Nexus Cafe",
"approved_by_name": null
},
"items": [
{
"id": 121,
"wastage_batch_id": 33,
"material_id": 111644,
"qty": 3,
"unit": "Pieces",
"unit_price": 0.4512,
"total_cost": 1.3536,
"item_notes": null,
"customer_id": 437,
"created_at": "2026-07-01T13:17:20.000Z",
"brand_id": null,
"material_name": "[CBSH114I] Caliper Brake Road SHIMANO Ultegra BR-R8000 F/R ",
"brand_name": null
},
{
"id": 122,
"wastage_batch_id": 33,
"material_id": 111649,
"qty": 3,
"unit": "Pieces",
"unit_price": 1.001,
"total_cost": 3.003,
"item_notes": null,
"customer_id": 437,
"created_at": "2026-07-01T13:17:20.000Z",
"brand_id": null,
"material_name": "[CBSH001FI] Caliper Brake Road SHIMANO 105 BR-R7000 IBRR7000AF82XL",
"brand_name": null
}
]
}
]
}
GET /api/stock-transfer-invoices/:id. Why: One transfer with its lines (material, requested/approved/dispatched/received qty, variance, cost, source on-hand and shortfall), status history, linked draft POs and parent/child re-supply transfers — the dashboard transfer page. Call it before update_stock_transfer / update_stock_transfer_items (status must be pending or draft, and you need items[].id), and to explain where a transfer stands. Ids come from list_stock_transfers; material ids link to get_material / list_materials; on_hand is the source branch stock (same as list_inventory for that branch). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_stock_transfer"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Numeric transfer id (list_stock_transfers data.data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_stock_transfer",
"id": 11
}'
{
"success": true,
"message": "Stock transfer fetched successfully",
"data": [
{
"id": 7772,
"items_number": 1,
"subtotal": 1101.36,
"vat": 55.068,
"payable": 1156.428,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "",
"uuid": "799537a3-c13f-400b-a494-78aeb1a5bd79",
"created_at": "2026-07-31T06:17:04.000Z",
"customer_id": 437,
"from_location_id": 907,
"info_name": "Nexus Cafe",
"info_address": null,
"info_email": "admin@devnexus.co",
"info_phone": null,
"status": "completed",
"user": "Nexus Cafe",
"table_id": null,
"payment_method": null,
"order_id": null,
"invoice_id": 41,
"to_location_id": 909,
"customer_location_id": 907,
"email": null,
"delivery_date": null,
"user_id": 4734,
"is_edit": null,
"username": "Nexus Cafe",
"transfer_no": "STR-2026-01623",
"requested_by": 4734,
"approved_by": 4734,
"dispatched_by": 4734,
"received_by": 4734,
"requested_at": "2026-07-31T06:17:05.000Z",
"approved_at": "2026-07-31T06:17:21.000Z",
"dispatched_at": "2026-07-31T06:17:25.000Z",
"received_at": "2026-07-31T06:17:38.000Z",
"notes": null,
"priority": null,
"expected_delivery_date": null,
"updated_at": "2026-07-31T06:17:45.000Z",
"rejected_by": null,
"rejected_at": null,
"parent_transfer_id": null,
"from_location": "Default location",
"to_location": "Iqbal Town",
"requested_by_username": "Nexus Cafe",
"approved_by_username": "Nexus Cafe",
"dispatched_by_username": "Nexus Cafe",
"received_by_username": "Nexus Cafe",
"items": [
{
"id": 67284,
"name": "Test Material",
"price": 11.0136,
"qty": 100,
"subtotal": 1101.36,
"stock_transfer_uuid": "799537a3-c13f-400b-a494-78aeb1a5bd79",
"location_id": 907,
"customer_id": 437,
"material_id": 119196,
"extras": null,
"is_edit": "0",
"approved_qty": 100,
"dispatched_qty": 100,
"received_qty": 50,
"variance_qty": -50,
"variance_reason": "lost",
"unit": "Kilogram",
"unit_id": null,
"item_status": null,
"notes": null,
"created_at": "2026-07-31T06:17:04.000Z",
"updated_at": "2026-07-31T06:17:37.000Z",
"stock_transfer_id": null,
"brand_id": null,
"disposition_type": null,
"disposition_status": null,
"resolved_by": null,
"resolved_at": null,
"disposition_notes": null,
"po_meta": {},
"purchase_uuid": null,
"supplier_id": null,
"supplier_name": null,
"po_number": null,
"moved_to_transfer_id": null,
"moved_to_transfer_no": null,
"on_hand": -21311072.05389,
"shortfall": 21311172.05389,
"resupply_draft_created": true,
"resupply_draft_transfer_id": 7773,
"resupply_draft_transfer_uuid": "808adb34-cc06-4c22-93bc-0589f3101788",
"resupply_draft_transfer_no": "STR-2026-01624",
"resupply_draft_status": "draft"
}
],
"history": [
{
"id": 9460,
"stock_transfer_id": 0,
"changed_by": 4734,
"changed_at": "2026-07-31T06:17:45.000Z",
"notes": "Transfer completed",
"metadata": {
"user": "Nexus Cafe",
"action": "complete",
"status": "completed",
"old_status": "received"
},
"customer_id": 437,
"stock_transfer_uuid": "799537a3-c13f-400b-a494-78aeb1a5bd79",
"created_at": "2026-07-31T06:17:45.000Z",
"changed_by_username": "Nexus Cafe"
},
{
"id": 9458,
"stock_transfer_id": 0,
"changed_by": 4734,
"changed_at": "2026-07-31T06:17:38.000Z",
"notes": "Variance shortfall draft STR-2026-01624 created and linked to this transfer.",
"metadata": {
"user": "Nexus Cafe",
"action": "variance_draft_created",
"shortfall_items": 1,
"child_transfer_id": 7773,
"child_transfer_no": "STR-2026-01624",
"child_transfer_uuid": "808adb34-cc06-4c22-93bc-0589f3101788"
},
"customer_id": 437,
"stock_transfer_uuid": "799537a3-c13f-400b-a494-78aeb1a5bd79",
"created_at": "2026-07-31T06:17:38.000Z",
"changed_by_username": "Nexus Cafe"
},
{
"id": 9456,
"stock_transfer_id": 0,
"changed_by": 4734,
"changed_at": "2026-07-31T06:17:37.000Z",
"notes": "Transfer moved from DISPATCHED to RECEIVED. All items received with variance adjustments.",
"metadata": {
"user": "Nexus Cafe",
"action": "move_to_received",
"new_status": "received",
"old_status": "dispatched",
"items_count": 1
},
"customer_id": 437,
"stock_transfer_uuid": "799537a3-c13f-400b-a494-78aeb1a5bd79",
"created_at": "2026-07-31T06:17:37.000Z",
"changed_by_username": "Nexus Cafe"
},
{
"id": 9455,
"stock_transfer_id": 0,
"changed_by": 4734,
"changed_at": "2026-07-31T06:17:25.000Z",
"notes": "Transfer moved from APPROVED to DISPATCHED. Approved quantities copied to dispatched quantities.",
"metadata": {
"user": "Nexus Cafe",
"action": "move_to_dispatched",
"new_status": "dispatched",
"old_status": "approved",
"items_count": 1
},
"customer_id": 437,
"stock_transfer_uuid": "799537a3-c13f-400b-a494-78aeb1a5bd79",
"created_at": "2026-07-31T06:17:25.000Z",
"changed_by_username": "Nexus Cafe"
},
{
"id": 9454,
"stock_transfer_id": 0,
"changed_by": 4734,
"changed_at": "2026-07-31T06:17:21.000Z",
"notes": "Transfer moved from PENDING to APPROVED. All items approved.",
"metadata": {
"user": "Nexus Cafe",
"action": "move_to_approved",
"new_status": "approved",
"old_status": "pending",
"items_count": 1
},
"customer_id": 437,
"stock_transfer_uuid": "799537a3-c13f-400b-a494-78aeb1a5bd79",
"created_at": "2026-07-31T06:17:21.000Z",
"changed_by_username": "Nexus Cafe"
},
{
"id": 9453,
"stock_transfer_id": 0,
"changed_by": 4734,
"changed_at": "2026-07-31T06:17:04.000Z",
"notes": "Stock transfer created",
"metadata": {
"user": "Nexus Cafe",
"action": "create",
"status": "pending",
"items_number": 1
},
"customer_id": 437,
"stock_transfer_uuid": "799537a3-c13f-400b-a494-78aeb1a5bd79",
"created_at": "2026-07-31T06:17:04.000Z",
"changed_by_username": "Nexus Cafe"
}
],
"purchase_orders": [],
"is_auto_created": false,
"is_variance_draft": false,
"source_transfer_id": null,
"source_transfer_no": null,
"source_transfer_uuid": null,
"parent_transfer": null,
"child_transfers": [
{
"id": 7773,
"uuid": "808adb34-cc06-4c22-93bc-0589f3101788",
"transfer_no": "STR-2026-01624",
"status": "draft",
"created_at": "2026-07-31T06:17:38.000Z",
"parent_transfer_id": 7772
}
],
"originating_po_uuids": [],
"originating_po_numbers": [],
"transfer_meta": {},
"linked_po_count": 0
}
]
}
GET /api/stock-order-request/:uuid. Why: One order request with its lines (material, qty in storage unit, unit cost, supplier) and the draft PO it produced — the dashboard request detail page. Call it before update_stock_order_request (status must be Pending; you must resend every line). uuids come from list_stock_order_requests; material ids link to get_material, supplier ids to list_suppliers, purchase_uuid to get_purchase_order. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_stock_order_request"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Order request uuid (list_stock_order_requests [].uuid). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_stock_order_request",
"uuid": "example"
}'
[
{
"id": 13,
"username": "Nexus Cafe",
"subtotal": 6000,
"vat": 300,
"email": null,
"delivery_date": "2026-08-06T12:52:39.000Z",
"payable": 6300,
"change": 0,
"note": "",
"uuid": "59c4fb6e-fb85-4ab1-ac0b-4338bd97b70a",
"reject_reason": null,
"created_at": "2026-08-06T00:00:00.000Z",
"customer_id": 437,
"to_location_id": 907,
"from_location_id": 907,
"customer_location_id": 907,
"info_name": "Nexus Cafe",
"info_email": "admin@devnexus.co",
"status": "Pending",
"user": "Nexus Cafe",
"table_id": 0,
"purchase_uuid": null,
"po_number": null,
"from_location": "Default location",
"to_location": "Default location",
"location_name": "Default location",
"items": [
{
"item_id": 35046,
"order_uuid": "59c4fb6e-fb85-4ab1-ac0b-4338bd97b70a",
"material_id": 112157,
"name": "\t SRC-BT-86CBPR-SG3 BB86-BB92 INTERLOCK-W:86.5/89.5/92XOD:41 SHIMANO-CERAMIC COATED BLACK 4712910901471",
"price": 3,
"qty": 2000,
"subtotal": 6000,
"unit_name": "Pieces",
"supplier_id": 881,
"is_edit": "0",
"reason": null,
"old_qty": null,
"new_qty": null,
"material_table_id": 112157,
"material_name": "\t SRC-BT-86CBPR-SG3 BB86-BB92 INTERLOCK-W:86.5/89.5/92XOD:41 SHIMANO-CERAMIC COATED BLACK 4712910901471",
"material_unit": "Pieces",
"ingredientunit": "Pieces",
"storageunit": "Pieces",
"id": 35046,
"material": "\t SRC-BT-86CBPR-SG3 BB86-BB92 INTERLOCK-W:86.5/89.5/92XOD:41 SHIMANO-CERAMIC COATED BLACK 4712910901471"
}
],
"current_location_id": 907,
"tax": "5.00"
}
]
GET /api/work-orders. Why: Production work orders (batches of stockable recipes to produce at a branch) with status, dates, branch, cost totals and a packed summary of their recipe lines. Filter by status, production date range, branch, recipe or text; get ids for get_work_order (lines) → update_work_order / delete_work_order (drafts only). Completed orders are the ones that moved stock (list_inventory_movements, reference WORK_ORDER_<uuid>). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_work_orders"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| _page | number | optional | 1-based page. |
| _limit | number | optional | Page size. |
| status | enum: draft, in_progress, completed, cancelled | optional | Exact status. |
| from | string | optional | production_date ≥ this day (YYYY-MM-DD). |
| to | string | optional | production_date ≤ this day (YYYY-MM-DD, inclusive). |
| loc_id | number | optional | ONE branch id (list_locations): orders of that branch. |
| recipe_id | number | optional | Only orders that contain this recipe (list_recipes). |
| k | string | optional | Substring match on work_order_no, id, notes or a line's recipe_name (`%`/`_` are wildcards). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_work_orders"
}'
{
"success": true,
"message": "Work orders fetched successfully",
"data": {
"data": [
{
"id": 1737,
"uuid": "66663B01-F720-4574-A3CF-65F6F1F02BB3",
"public_slug": "8d090ad8-258f-41cd-91ae-ebab74853540",
"work_order_no": "PRD-2026-01659",
"status": "completed",
"production_date": "2026-08-05T00:00:00.000Z",
"expected_completion": null,
"created_by": 4734,
"notes": null,
"net_total": 23017.9029,
"location_id": 907,
"customer_id": 437,
"created_at": "2026-08-05T07:03:22.000Z",
"updated_at": "2026-08-05T07:03:58.000Z",
"total_materials_cost": 23017.9029,
"total_recipes_cost": 23017.9029,
"recipes_count": 1,
"materials_count": 2,
"recipe_data": "Gotcha Recipe|10000.000000|2.3018|23017.9029",
"location_name": "Default location",
"item_brand_names_csv": null
}
],
"pagination": {
"total": 29,
"page": 1,
"limit": 1
}
}
}
GET /api/work-orders/:id. Why: One work order: header (status, dates, branch, notes, creator, QR image), its recipe lines (what is produced, qty in the recipe unit, cost) and material lines (what completion will consume, yield-adjusted qty, price). Call it before update_work_order (full replace) and to explain what completing it will do to stock. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_work_order"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Work order id (list_work_orders data.data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_work_order",
"id": 11
}'
{
"success": true,
"message": "Work order fetched successfully",
"data": [
{
"header": {
"id": 1737,
"uuid": "66663B01-F720-4574-A3CF-65F6F1F02BB3",
"public_slug": "8d090ad8-258f-41cd-91ae-ebab74853540",
"work_order_no": "PRD-2026-01659",
"status": "completed",
"production_date": "2026-08-05T00:00:00.000Z",
"expected_completion": null,
"created_by": 4734,
"notes": null,
"net_total": 23017.9029,
"location_id": 907,
"customer_id": 437,
"qr_png": {
"type": "Buffer",
"data": [
137,
80,
78,
71,
"…3870 byte values…"
]
},
"created_at": "2026-08-05T07:03:22.000Z",
"updated_at": "2026-08-05T07:03:58.000Z",
"location_name": "Default location",
"created_by_name": "Nexus Cafe"
},
"materials": [
{
"id": 23605,
"work_order_id": 1737,
"recipe_id": 17224,
"material_id": 119196,
"unit": "Gram",
"qty": 21312872.975277,
"unit_price": 0.0001,
"total_price": 2557.5448,
"customer_id": 437,
"created_at": "2026-08-05T07:03:22.000Z",
"material_name": "Test Material",
"brand_id": null,
"brand_name": null
},
{
"id": 23606,
"work_order_id": 1737,
"recipe_id": 17224,
"material_id": 119158,
"unit": "Gram",
"qty": 12787723.785166,
"unit_price": 0.0016,
"total_price": 20460.3581,
"customer_id": 437,
"created_at": "2026-08-05T07:03:22.000Z",
"material_name": "Cherry Blosom",
"brand_id": null,
"brand_name": null
}
],
"recipes": [
{
"id": 4470,
"work_order_id": 1737,
"recipe_id": 17224,
"qty": 10000,
"unit_cost": 2.3018,
"total_cost": 23017.9029,
"customer_id": 437,
"created_at": "2026-08-05T07:03:22.000Z",
"recipe_name": "Gotcha Recipe"
}
]
}
]
}
GET /api/users. Why: Staff users (the people who log into the dashboard and the POS) with their role, branches and every per-user POS switch — the admin view of the users table. Admin-only: the key's user must have is_admin 1 or role admin/super, else 401. Use it to get user ids for get_user / update_user / assign_user_role, for sync_invoice created_by_user_id / close_by_user_id and for list_orders_history user_id; role_id links to list_roles, location_id / allow_locations to list_locations, brand_id to list_selling_brands, channel_id to list_selling_channels. For the POS view of the session branch (setting object, devices) use list_pos_users; to change the session user's own POS switches use update_user_permissions; decoded ios_modules / max discount of the session user: get_user_extras. Customers are the CRM tools, not this. Rows carry the clear-text PIN — never show it. Without loc the server lists only the session user's branch (plus multi-branch users); for every user pass loc = comma-separated ids from list_locations. Bare array, no paging. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_users"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | Branch id or comma-separated branch ids (list_locations). |
| _limit | number | optional | Max rows (1-50000); anything else is ignored (no limit). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_users"
}'
[
{
"id": 4789,
"username": "Sara Cashier",
"email": "sara.cashier@example.com",
"pin": "0000",
"role": "staff",
"role_id": 32,
"is_admin": 0,
"location_id": 907,
"customer_id": 437,
"allow_locations": "907,909,908",
"organization_customer_ids": null,
"organization_id": null,
"is_daily_report": 0,
"is_weekly_report": 0,
"is_monthly_report": 1,
"modules": "1;1;1;1;1;1;1;1;1;1;1",
"ios_modules": "1;1;1;1;1;1;1;1;1",
"can_preview_invoice": 1,
"max_discount_permit": 26,
"image": "",
"permissions": "1;0;1;1;0;0;0;0;1;1;0;0;0;0;0;1;0;0;0;1;0;0;0",
"print_no_of_times": 1,
"is_ingredient_disabled": 0,
"brand_id": 0,
"channel_id": 0,
"fraction_digits": 2,
"dining_guest_count": 0,
"is_brand_groups": 0,
"is_extras_enble": 1,
"display_modifier_titles": 1,
"modifier_sort_min_required_desc": 1,
"print_only_save_kds_orders": 1,
"print_note_on_receipt": 1,
"orderhistory_print_receipt": 1,
"show_receipt_screen": 1,
"order_type": 1,
"open_dine_in": 0,
"hide_cart_pay_button": 0,
"qr_code_on_print_receipt": 0,
"is_channels_enable": 0,
"tip_amount": 1,
"print_void_orders": 0,
"has_void_order_permission": 1,
"delivery_detail_enable": 0,
"is_attributed_receipt": 1,
"exchange_format": 0,
"is_cds_enable": null,
"external_order_print_receipt": 0,
"is_multi_location_allowed": 1,
"is_master_locations_enabled": 1,
"is_qr_payment": 0,
"is_kds_expeditor": 0,
"unify_brands": 0,
"text_receipt": 0,
"each_category_kds_print": 0,
"dinein_serving_role": 0,
"receipt_format": 0,
"ingredient_stay_standalone": 0,
"reservation_with_dine_in": 0,
"pay_zero_amount": 0,
"receipt_translation": 0,
"product_translation": 0,
"category_translation": 0,
"print_invoice_translation": 0,
"print_kds_translation": 0,
"print_deleted_item_on_receipt": 0,
"draft_order": 0,
"splitefire": 0,
"allegy_item": 0,
"hide_clear_checkout": 1,
"kds_instruction": 0,
"pay_default_usd": 1,
"rpt_business_logo": 1,
"rpt_order_type": 1,
"rpt_is_modified": 1,
"rpt_biller_info": 1,
"rpt_total_before_vat": 1,
"rpt_vat_info": 1,
"rpt_net_total": 1,
"rpt_exchange_rate": 0,
"rpt_dine_guest_count": 0,
"rpt_paid_at": 0,
"esc_command": 0,
"one_table_one_order": 0,
"is_socket_server": 0,
"is_client": 0,
"web_local_network_printer": 0,
"open_price_pincode": 1,
"is_transfer_enabled": 0,
"block_print_until_order_open": 0,
"user_product_sales_counter": 0,
"is_change_payment_method_enabled": 0,
"skip_zero_quantity_items": 0,
"skip_zero_amount_items": 0,
"settle_wallet_topup": 0,
"sms_invoice": 0,
"offline_order_sync": 1,
"is_shift_management": 0,
"acknowledge_dine_table_edit": 0,
"lock_screen": 0,
"force_crm_user": 0,
"shift_transactions": 0,
"eod_shift_report": 0,
"override_shift": 0,
"web_image": "",
"is_crm_mobile_mandatory": 0,
"reload_dine_order": 0
}
]
GET /api/users/:id. Why: One staff user's full record — what the dashboard user edit form loads before update_user: the list_users columns plus language, Zebra printer, cloud-print target, KDS station, is_soldout and the branch currency. Admin-only. Currently fails through MCP (HTTP 500): this route has no Bearer-token auth, it only works with a dashboard login cookie. Use list_users (loc = all branch ids) and pick the row by id instead. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_user"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | string | required | User id (list_users [].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_user",
"id": 11
}'
[
{
"id": 4789,
"username": "Sara Cashier",
"email": "sara.cashier@example.com",
"pin": "0000",
"role": "staff",
"role_id": 32,
"is_admin": 0,
"location_id": 907,
"customer_id": 437,
"allow_locations": "907,909,908",
"organization_customer_ids": null,
"organization_id": null,
"is_daily_report": 0,
"is_weekly_report": 0,
"is_monthly_report": 1,
"modules": "1;1;1;1;1;1;1;1;1;1;1",
"ios_modules": "1;1;1;1;1;1;1;1;1",
"can_preview_invoice": 1,
"max_discount_permit": 26,
"image": null,
"permissions": "1;0;1;1;0;0;0;0;1;1;0;0;0;0;0;1;0;0;0;1;0;0;0",
"print_no_of_times": 1,
"is_ingredient_disabled": 0,
"brand_id": 0,
"channel_id": 0,
"fraction_digits": 2,
"dining_guest_count": 0,
"is_brand_groups": 0,
"is_extras_enble": 1,
"display_modifier_titles": 1,
"modifier_sort_min_required_desc": 1,
"print_only_save_kds_orders": 1,
"print_note_on_receipt": 1,
"orderhistory_print_receipt": 1,
"show_receipt_screen": 1,
"order_type": 1,
"open_dine_in": 0,
"hide_cart_pay_button": 0,
"qr_code_on_print_receipt": 0,
"is_channels_enable": 0,
"tip_amount": 1,
"print_void_orders": 0,
"has_void_order_permission": 1,
"delivery_detail_enable": 0,
"is_attributed_receipt": 1,
"exchange_format": 0,
"is_cds_enable": null,
"external_order_print_receipt": 0,
"is_multi_location_allowed": 1,
"is_master_locations_enabled": 1,
"is_qr_payment": 0,
"is_kds_expeditor": 0,
"unify_brands": 0,
"text_receipt": 0,
"each_category_kds_print": 0,
"dinein_serving_role": 0,
"receipt_format": 0,
"ingredient_stay_standalone": 0,
"reservation_with_dine_in": 0,
"pay_zero_amount": 0,
"receipt_translation": 0,
"product_translation": 0,
"category_translation": 0,
"print_invoice_translation": 0,
"print_kds_translation": 0,
"print_deleted_item_on_receipt": 0,
"draft_order": 0,
"splitefire": 0,
"allegy_item": 0,
"hide_clear_checkout": 1,
"kds_instruction": 0,
"pay_default_usd": 1,
"rpt_business_logo": 1,
"rpt_order_type": 1,
"rpt_is_modified": 1,
"rpt_biller_info": 1,
"rpt_total_before_vat": 1,
"rpt_vat_info": 1,
"rpt_net_total": 1,
"rpt_exchange_rate": 0,
"rpt_dine_guest_count": 0,
"rpt_paid_at": 0,
"esc_command": 0,
"one_table_one_order": 0,
"is_socket_server": 0,
"is_client": 0,
"web_local_network_printer": 0,
"open_price_pincode": 1,
"is_transfer_enabled": 0,
"block_print_until_order_open": 0,
"user_product_sales_counter": 0,
"is_change_payment_method_enabled": 0,
"skip_zero_quantity_items": 0,
"skip_zero_amount_items": 0,
"settle_wallet_topup": 0,
"sms_invoice": 0,
"offline_order_sync": 1,
"is_shift_management": 0,
"acknowledge_dine_table_edit": 0,
"lock_screen": 0,
"force_crm_user": 0,
"ios_language": "en",
"zebra_ip": "",
"zebra_port": 0,
"cloud_target_user_id": null,
"cloud_target_device_id": null,
"station_id": 0,
"is_soldout": 0,
"shift_transactions": 0,
"eod_shift_report": 0,
"row_discount": 0,
"shift_z_report": 0,
"view_estimated_cash": 0,
"external_order_ready": 0,
"currency_code": "AED"
}
]
GET /api/v2/crm/users. Why: The customer list the POS cashier picks from to attach a customer to an order — exactly what web-pos loads (page_limit 500, order_by ASC) and caches. Each row carries what checkout needs: id (→ crm_user_id on the order), name, phone, delivery area, the wallet balance and the loyalty program. Customers are tenant-wide (every branch sees the same list). Use it to find/attach a customer; use list_crm_users_details / get_crm_user_details for analytics (visits, segment, wallet history). Paginated: loop current_page until you have total_records rows. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_pos_crm_users"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| page_limit | number | optional | Optional. |
| current_page | number | optional | Optional. |
| order_by | enum: ASC, DESC | optional | Optional. |
| q | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_pos_crm_users"
}'
{
"status": true,
"message": "Users found",
"data": {
"total_records": 2,
"records_per_page": 500,
"current_page": 1,
"users": [
{
"id": 63821,
"first_name": "Test",
"last_name": "Customer",
"mobile_number": "+971500000000",
"email": "test@example.com",
"dob": "1990-01-01",
"customer_id": 437,
"delivery_area": "Test Area",
"city": "Test City",
"country": "Pakistan",
"is_push_notification_enable": 0,
"preferred_currency": "USD",
"latitude": 0,
"longitude": 0,
"company_name": "Test Company",
"thumbnail": "",
"is_mobile_number_verified": 0,
"gender": "male",
"created_at_tz": 1782320919,
"number_of_purchases": 2,
"average_basket_size": 11,
"earn_points": 3,
"total_spend": 22,
"trn_number": "100000000000003",
"loyalty_program_name": "Loyalty 1",
"redeem_points": 10,
"redeem_amount": 1,
"wallet_id": 35,
"wallet_current_balance": 80,
"wallet_currency_code": null,
"wallet_status": "active"
},
{
"id": 64887,
"first_name": "Second",
"last_name": "Customer",
"mobile_number": "+971500000001",
"email": "second@example.com",
"dob": null,
"customer_id": 437,
"delivery_area": null,
"city": null,
"country": null,
"is_push_notification_enable": 0,
"preferred_currency": null,
"latitude": 0,
"longitude": 0,
"company_name": null,
"thumbnail": "",
"is_mobile_number_verified": 0,
"gender": null,
"created_at_tz": 1782747497,
"number_of_purchases": 0,
"average_basket_size": 0,
"earn_points": 0,
"total_spend": 0,
"trn_number": null,
"loyalty_program_name": "Loyalty 1",
"redeem_points": 10,
"redeem_amount": 1,
"wallet_id": null,
"wallet_current_balance": 0,
"wallet_currency_code": null,
"wallet_status": null
}
]
}
}
GET /api/crm-users. Why: Dashboard customer (CRM) list — one slim row per customer of the tenant (name, phone, email, company, TRN). Same ids as list_pos_crm_users and the crm_user_id on orders. To attach a customer at the POS use list_pos_crm_users (it has wallet + loyalty); for visits/segments use list_crm_users_details. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_crm_users"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| current_page | number | optional | Optional. |
| page_limit | number | optional | Optional. |
| k | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_crm_users"
}'
{
"data": [
{
"id": 63821,
"first_name": "Test",
"last_name": "Customer",
"company_name": "Test Company",
"mobile_number": "+971500000000",
"earn_points": 0,
"email": "test@example.com",
"gender": "male",
"trn_number": "100000000000003",
"thumbnail": " "
},
{
"id": 64887,
"first_name": "Second",
"last_name": "Customer",
"company_name": null,
"mobile_number": "+971500000001",
"earn_points": 0,
"email": "second@example.com",
"gender": null,
"trn_number": null,
"thumbnail": null
}
],
"total_records": 2
}
GET /api/crm-users/details. Why: Dashboard CRM analytics list: every customer column plus an all-time order summary, RFM segment and churn risk per customer, with tenant-wide KPIs. Filters by order date window, branch, channel, spend, visit frequency, inactivity, segment. Sorted by last visit (most recent first, never-ordered last). Use it for marketing/retention questions; use list_pos_crm_users to attach a customer to an order. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_crm_users_details"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| k | string | optional | Optional. |
| current_page | number | optional | Optional. |
| page_limit | number | optional | Optional. |
| from_date | string | optional | Optional. |
| to_date | string | optional | Optional. |
| location_id | string | optional | Branch id. |
| channel_id | number | optional | Optional. |
| spend_range | string | optional | Optional. |
| visit_frequency | enum: Low, Medium, High | optional | Optional. |
| inactive_days | number | optional | Optional. |
| segment | enum: VIP, Loyal, Regular, At Risk, Needs Attention, Lost, New, One-timer | optional | Optional. |
| churn_risk | enum: Low, Medium, High | optional | Optional. |
| preferred_time | enum: breakfast, brunch, lunch, evening, dinner | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_crm_users_details"
}'
{
"status": true,
"message": "CRM users with detail fetched successfully",
"data": [
{
"id": 63821,
"first_name": "Test",
"last_name": "Customer",
"mobile_number": "+971500000000",
"email": "test@example.com",
"dob": "1990-01-01",
"customer_id": 437,
"delivery_area": "Test Area",
"city": "Test City",
"country": "Pakistan",
"created_at": "2026-06-24T12:08:39.000Z",
"gender": "male",
"preferred_currency": "USD",
"latitude": 0,
"longitude": 0,
"thumbnail": " ",
"is_mobile_number_verified": 0,
"is_push_notification_enable": 0,
"number_of_purchases": 2,
"average_basket_size": 11,
"total_spend": 22,
"earn_points": 0,
"trn_number": "100000000000003",
"company_name": "Test Company",
"summary": {
"total_spend": 22,
"visit_count": 2,
"last_visit": "2026-08-20T11:07:42.000Z",
"avg_order_value": 11
},
"segment": "Needs Attention",
"churn_risk": "Low"
},
{
"id": 64887,
"first_name": "Second",
"last_name": "Customer",
"mobile_number": "+971500000001",
"email": "second@example.com",
"dob": null,
"customer_id": 437,
"delivery_area": null,
"city": null,
"country": null,
"created_at": "2026-06-29T10:38:17.000Z",
"gender": null,
"preferred_currency": null,
"latitude": 0,
"longitude": 0,
"thumbnail": null,
"is_mobile_number_verified": 0,
"is_push_notification_enable": 0,
"number_of_purchases": 0,
"average_basket_size": 0,
"total_spend": 0,
"earn_points": 0,
"trn_number": null,
"company_name": null,
"summary": {
"total_spend": 0,
"visit_count": 0,
"last_visit": null,
"avg_order_value": 0
},
"segment": "New",
"churn_risk": null
}
],
"total_records": 2,
"kpis": {
"total_customers": 2,
"total_revenue": 893,
"avg_order_value": 49.61,
"vip_customers": 0,
"high_churn_risk_customers": 0,
"avg_visits_per_month": 1.7
}
}
GET /api/crm-users/:id. Why: One customer's stored profile (every contact/address field + server-maintained purchase stats). Tenant-checked. For visits, segment and wallet history use get_crm_user_details; for wallet balance and loyalty in POS terms use list_pos_crm_users. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_crm_user"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | string | required | Customer id (crm_user_id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_crm_user",
"id": 11
}'
[
{
"id": 63821,
"first_name": "Test",
"last_name": "Customer",
"company_name": "Test Company",
"email": "test@example.com",
"mobile_number": "+971500000000",
"dob": "1990-01-01",
"earn_points": 0,
"customer_id": 437,
"delivery_area": "Test Area",
"city": "Test City",
"is_push_notification_enable": 0,
"preferred_currency": "USD",
"trn_number": "100000000000003",
"latitude": 0,
"longitude": 0,
"thumbnail": " ",
"is_mobile_number_verified": 0,
"gender": "male",
"country": "Pakistan",
"created_at": "2026-06-24T12:08:39.000Z",
"number_of_purchases": 2,
"average_basket_size": 11,
"total_spend": 22
}
]
GET /api/crm-users/:id/details. Why: Customer intelligence for one customer: profile, all-time order summary, RFM segment, churn risk, value rank, visit patterns (time, days, order types), menu preferences (top items), spending trend, discount usage and the in-house wallet with its last 50 transactions. Tenant-checked. Use it to answer "who is this customer / how are they doing". Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_crm_user_details"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | string | required | Customer id (crm_user_id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_crm_user_details",
"id": 11
}'
{
"status": true,
"message": "CRM user detail fetched successfully",
"data": {
"user": {
"id": 63821,
"first_name": "Test",
"last_name": "Customer",
"mobile_number": "+971500000000",
"email": "test@example.com",
"dob": "1990-01-01",
"customer_id": 437,
"delivery_area": "Test Area",
"city": "Test City",
"country": "Pakistan",
"created_at": "2026-06-24T12:08:39.000Z",
"gender": "male",
"preferred_currency": "USD",
"latitude": 0,
"longitude": 0,
"thumbnail": " ",
"is_mobile_number_verified": 0,
"is_push_notification_enable": 0,
"number_of_purchases": 2,
"average_basket_size": 11,
"total_spend": 22,
"earn_points": 0,
"trn_number": "100000000000003",
"company_name": "Test Company"
},
"summary": {
"total_spend": 22,
"visit_count": 2,
"last_visit": "2026-08-20T11:07:42.000Z",
"avg_order_value": 11,
"visit_frequency": 2
},
"segment": "Needs Attention",
"loyalty_tier": null,
"churn_risk": "Low",
"tags": null,
"notes": null,
"rfm_score": {
"recency": 4,
"frequency": 1,
"monetary": 1,
"rfm_segment": "Needs Attention"
},
"value_rank": "Mid Value",
"value_rank_percentile": 50,
"lifetime_value": 264,
"visit_patterns": {
"visit_frequency": 2,
"preferred_time": "Lunch",
"first_visit": "2026-08-20T11:07:42.000Z",
"days_since_last_visit": 35,
"avg_party_size": 1,
"table_preference": "0",
"preferred_days": [
"Thursday"
],
"order_type_split": {
"Delivery": 100
}
},
"menu_preferences": {
"favorite_category": "espresso-based drinks",
"avg_items_per_order": 1,
"beverage_food_ratio": "100% beverages",
"top_ordered_items": [
"QA min Flat White"
],
"dietary_preferences": null
},
"spending": {
"total_spend": 22,
"average_order_value": 11,
"lowest_order": 11,
"highest_order": 11,
"lifetime_value": 264,
"avg_items_per_order": 1,
"spending_trend": "Decreasing",
"trend_percentage": -100,
"trend_label": "-100%"
},
"promotions_discounts": {
"discount_usage_rate": 0,
"discount_revenue_ratio": 0,
"promo_responsiveness": 0
},
"wallet": {
"current_balance": 80,
"transactions": [
{
"id": 408,
"wallet_id": 35,
"user_id": 63821,
"transaction_type": "credit",
"amount": 60,
"balance_before": 20,
"balance_after": 80,
"reference_type": "invoice",
"reference_id": "8D27D146-767C-4E22-9FB3-0611991DAB63",
"performed_by_user_id": 4734,
"source": "invoice-sync-v3",
"description": "Wallet debit for invoice 8D27D146-767C-4E22-9FB3-0611991DAB63",
"customer_id": 437,
"location_id": 907,
"created_at": "2026-06-29T10:14:55.000Z",
"payment_id": null,
"payment_method_name": null
}
]
}
}
}
GET /api/roles. Why: Roles of the tenant with the permissions each grants — the left list of the dashboard Roles & Permissions page and the Roles dropdown of the user edit form. Use it to pick role_id for update_user / assign_user_role, to read which dashboard pages/actions a user's role (list_users role_id) allows, and to get permission ids for create_role / update_role. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"list_roles"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
No additional parameters — only the Authorization: Bearer header is required.
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "list_roles"
}'
{
"success": true,
"data": [
{
"id": 32,
"name": "Staff",
"description": "Default staff role with module group permissions",
"customer_id": 437,
"is_deleted": 0,
"created_at": "2025-09-29T08:05:50.000Z",
"updated_at": "2025-09-29T08:05:50.000Z",
"permissions": [
{
"id": 1,
"name": "Manage Inventory Count",
"value": "Inventory_Count",
"group": "inventory",
"sub_group": null,
"action": null
},
{
"id": 139,
"name": "Attendance",
"value": "module_attendance",
"group": "module_access",
"sub_group": null,
"action": null
},
{
"id": 629,
"name": "Roles",
"value": "users.roles",
"group": "Users",
"sub_group": "Roles",
"action": null
},
{
"id": 630,
"name": "Add",
"value": "users.roles.add",
"group": "Users",
"sub_group": "Roles",
"action": "add"
}
]
},
{
"id": 182,
"name": "Manager",
"description": "",
"customer_id": 437,
"is_deleted": 0,
"created_at": "2025-12-13T13:24:49.000Z",
"updated_at": "2025-12-13T13:24:49.000Z",
"permissions": [
{
"id": 139,
"name": "Attendance",
"value": "module_attendance",
"group": "module_access",
"sub_group": null,
"action": null
},
{
"id": 140,
"name": "Configuration",
"value": "module_config",
"group": "module_access",
"sub_group": null,
"action": null
}
]
}
]
}
GET /api/roles/:id. Why: One role with its permissions — read it before update_role (which replaces the permission list) or to explain what a user's role (list_users role_id) lets them do in the dashboard. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"get_role"} when you need field meanings. Safety: read. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | string | required | Role id (list_roles data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "get_role",
"id": 11
}'
{
"success": true,
"data": {
"id": 182,
"name": "Manager",
"description": "",
"customer_id": 437,
"is_deleted": 0,
"created_at": "2025-12-13T13:24:49.000Z",
"updated_at": "2025-12-13T13:24:49.000Z",
"permissions": [
{
"id": 139,
"name": "Attendance",
"value": "module_attendance",
"group": "module_access",
"sub_group": null,
"action": null
},
{
"id": 140,
"name": "Configuration",
"value": "module_config",
"group": "module_access",
"sub_group": null,
"action": null
},
{
"id": 141,
"name": "Dashboard",
"value": "module_dashboard",
"group": "module_access",
"sub_group": null,
"action": null
}
]
}
}
Full documentation of another tool: every param, every response field with its meaning, the rules (how the POS/dashboard uses it and how it links to other tools), caveats and real examples. Read-only, no SysPOS call. Use it before any write tool and whenever you need to know what a response field means.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Exact tool name, e.g. "sync_invoice" or "list_selling_products". |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "describe_tool",
"name": "Iced Latte"
}'
{
"message": "JSON body returned by the matching SysPOS API"
}
POST /api/products. Why: Add a new catalog product (master tenant). Check list_products (k=name) first — duplicate active names are rejected. Get category_id and its name (type) from list_categories, label_id from list_labels, branch ids for location_prices / location_id from list_locations, modifier_id from list_modifiers (item ids from list_modifier_items), channel ids from list_channels, recipe ids from list_recipes (phantom only). For add-ons use create_addon. The new product shows on list_selling_products only at branches listed in location_prices. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_product"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Required. |
| description | string | optional | Free-text description; also copied onto every branch row. |
| actual_price | number | optional | Master price, VAT INCLUDED — the "Base Price" the user types (required by the dashboard form). |
| price | number | optional | Price EXCLUDING VAT. |
| category_id | number | optional | Category id (list_categories). |
| label_id | number | optional | Label id (list_labels). |
| location_id | array<number> | optional | Branch id or array of branch ids (list_locations). |
| location_prices | array<object> | optional | Branches that sell the product, with their price. |
| modifiers | array<object> | optional | Modifier groups attached to the product, written for each branch in location_id (or tenant-wide when none). |
| materials | array<object> | optional | Direct raw-material (BOM) lines; drive product_material_total / product_cost. |
| recipes | array<object> | optional | Recipe links (phantom recipes only); drive product_recipe_total / product_cost. |
| sku_no | string | optional | SKU; trimmed. |
| plu | number | optional | PLU / short code for the POS keypad (INT column). |
| image | string | optional | Image path: an uploaded relative path (list_products image_path) or a URL on the SysPOS media host (normalised to a relative path). |
| is_addon | string | optional | Truthy → 1 (add-on product), else 0. |
| is_open_price | string | optional | Truthy → 1 (cashier types the price at sale time). |
| is_weightage_item | string | optional | Truthy → 1 (sold by weight; set unit_id). |
| unit_id | number | optional | Weight unit id for weighted items. |
| is_archive | string | optional | Truthy → 1 (archived). |
| discount_pct | number | optional | Product discount percent; missing, < 0 or > 100 → 0. |
| combo_price | number | optional | Price when sold inside a combo. |
| localize_name | string | optional | Second-language (e.g. |
| localize_description | string | optional | Second-language description. |
| menu_sort_order | number | optional | Sort position inside its category (ascending). |
| calories | number | optional | Calories shown on menus. |
| preparation_time | number | optional | Preparation time (kitchen display hint). |
| barcode_type | string | optional | Barcode symbology label: the dashboard sends "EAN-13" or "QR Code". |
| barcode_value | string | optional | Barcode value scanned at the POS. |
| is_stock_product | number | optional | 1 = tracked as a stock item, 0 = not. |
| tax_id | number | optional | Tax group id — tax groups are out of scope; omit on create. |
| vat | number | optional | VAT AMOUNT inside actual_price (not a %). |
| price_level | array<object> | optional | Sales-channel prices (delivery apps etc.). |
| stock | number | optional | Legacy opening-stock column (DECIMAL 10,2), not live inventory. |
| type | string | optional | Category NAME(s), comma-separated. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_product",
"name": "Iced Latte"
}'
{
"success": true,
"message": "Product created successfully",
"productId": 257508
}
PUT /api/products/:id. Why: Edit one catalog product (name, price, category, branches + branch prices, modifier links, channel prices, BOM, recipes). NOT a patch: first call get_product and resend the whole record with your changes — omitted fields reset prices/image/tax to 0/empty, un-archive the product, wipe materials/recipes and rebuild or reprice branch rows. Same id sources as create_product (list_categories, list_labels, list_locations, list_modifiers, list_channels, list_recipes). Use archive_product to archive/restore, update_product_availability for the POS sold-out switch and delete_product to remove. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_product"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Product id (list_products / get_product). |
| name | string | optional | Product name. |
| description | string | optional | Free-text description; also copied onto every branch row. |
| actual_price | number | optional | Master price, VAT INCLUDED (dashboard "Base Price", required by its form). |
| price | number | optional | Price EXCLUDING VAT. |
| category_id | number | optional | Category id (list_categories). |
| label_id | number | optional | Label id (list_labels). |
| location_id | array<number> | optional | Branch id or array of branch ids (list_locations). |
| location_prices | array<object> | optional | FULL REPLACE of the product's branches: a non-empty array deletes all existing branch rows and inserts these (branch-specific names are reset to `name`). |
| modifiers | array<object> | optional | Modifier groups attached to the product, written for each branch in location_id (or tenant-wide when none). |
| materials | array<object> | optional | Direct raw-material (BOM) lines; drive product_material_total / product_cost. |
| recipes | array<object> | optional | Recipe links (phantom recipes only); drive product_recipe_total / product_cost. |
| sku_no | string | optional | SKU; trimmed. |
| plu | number | optional | PLU / short code for the POS keypad (INT column). |
| image | string | optional | Image path: an uploaded relative path (list_products image_path) or a URL on the SysPOS media host (normalised to a relative path). |
| is_addon | string | optional | Truthy → 1 (add-on product), else 0. |
| is_open_price | string | optional | Truthy → 1 (cashier types the price at sale time). |
| is_weightage_item | string | optional | Truthy → 1 (sold by weight; set unit_id). |
| unit_id | number | optional | Weight unit id for weighted items. |
| is_archive | string | optional | Truthy → 1 (archived). |
| discount_pct | number | optional | Product discount percent; missing, < 0 or > 100 → 0. |
| combo_price | number | optional | Price when sold inside a combo. |
| localize_name | string | optional | Second-language (e.g. |
| localize_description | string | optional | Second-language description. |
| menu_sort_order | number | optional | Sort position inside its category (ascending). |
| calories | number | optional | Calories shown on menus. |
| preparation_time | number | optional | Preparation time (kitchen display hint). |
| barcode_type | string | optional | Barcode symbology label: the dashboard sends "EAN-13" or "QR Code". |
| barcode_value | string | optional | Barcode value scanned at the POS. |
| is_stock_product | number | optional | 1 = tracked as a stock item, 0 = not. |
| tax_id | number | optional | Tax group id — tax groups are out of scope: never choose one. |
| vat | number | optional | VAT AMOUNT inside actual_price (not a %). |
| channel_price | array<object> | optional | Sales-channel prices. |
| modifiers_by_location | string | optional | Per-branch modifier links: { "<location_id>": [modifier rows] }. |
| stock | number | optional | Legacy opening-stock column (DECIMAL 10,2), not live inventory. |
| type | string | optional | Category NAME(s), comma-separated. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_product",
"id": 11
}'
{
"success": true,
"message": "Product updated successfully",
"product": {
"id": "257508",
"name": "Mocha",
"actual_price": 12,
"category_id": 15231,
"location_prices": [
{
"location_id": 907,
"location_price": 12,
"is_available": 1
}
],
"customer_id": 437
}
}
PUT /api/products/archive_product/:id. Why: Hide a product from the active catalog and every branch menu without deleting it (is_archive = 1), or restore it with is_archive="0". Archived products are listed with list_products is_archive=1 and dropped from list_selling_products. Use instead of delete_product when the item may come back; to hide it at one branch use update_product location_prices[].is_available=false, and for a temporary sell-out use update_product_availability. Note: update_product un-archives unless you send is_archive = 1. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"archive_product"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Product id (list_products; archived ones via is_archive=1). |
| is_archive | enum: 0, 1 | optional | Exactly "0" → restore (is_archive = 0). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "archive_product",
"id": 11
}'
{
"message": "Product archived successfully",
"product": {
"id": 113573,
"category_id": 15481,
"name": "Violet Whispers ",
"description": null,
"price": 0,
"stock": 0,
"type": "Alternate Options",
"image": "https://dev1.syspos.ae/api/uploads/KOZ_3276 copy.jpg",
"customer_id": 437,
"location_id": [
null
],
"is_addon": 0,
"addons": [],
"bundle_items": [],
"is_disabled": 0,
"discount_pct": 0,
"modifiers": [],
"is_archive": 1,
"barcode_type": null,
"barcode_value": null,
"is_open_price": 0,
"is_combo": 0,
"sync_deliverect": 0,
"sync_otter": 0,
"new_cost_date": null,
"product_cost": null,
"is_weightage_item": 0,
"unit_id": 0,
"actual_price": 0,
"old_price": null,
"vat": null,
"localized": null,
"base_price": null,
"menu_sort_order": 0,
"plu": 1236,
"calories": null,
"is_stock_product": 0,
"is_auto_apply": null,
"combo_price": 0,
"localize_name": null,
"localize_description": null,
"label_id": null,
"recipes": [],
"sku_no": "prod-437-113573",
"tax_id": null,
"preparation_time": 0,
"product_recipe_total": 0,
"product_material_total": 0,
"is_overridden": 1,
"is_deleted": 0,
"category_sort_order": null,
"image_path": "KOZ_3276 copy.jpg",
"web_image": "KOZ_3276 copy.jpg",
"channel_price": [],
"materials": [],
"selectionLength": 0
}
}
POST /api/categories. Why: Create a new catalog category (dashboard Add Category) linked to branches. Returns the new id, which you then use as product.category_id (and its name as product.type) in create_product / update_product, and add to a group with update_group so brands with groups show it on the POS. It sells only at the branches in location_id. Check list_categories k=<name> first — duplicate names are not rejected. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_category"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Category name (varchar 300), trimmed. |
| location_id | array<number> | required | Branches that sell this category — one categories_locations row per id. |
| image | string | optional | Image file name relative to /api/uploads (e.g. |
| description | string | optional | Free-text description (categories.description, varchar 300). |
| color | string | optional | Tile colour, hex like "#FF8800" (varchar 45). |
| menu_sort_order | number | optional | Menu sort key, ascending (lower first). |
| localize_name | string | optional | Name in the secondary language (dashboard "Local Name (Arabic)", right-to-left), varchar 255. |
| localize_description | string | optional | Description in the secondary language (dashboard "Local Description (Arabic)"), varchar 255. |
| sync_menu | number | optional | 0 | 1. |
| sync_grubtech | number | optional | 0 | 1. |
| sync_otter | number | optional | 0 | 1. |
| sync_deliverect | number | optional | 0 | 1. |
| sync_bitebell | number | optional | 0 | 1. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_category",
"name": "Iced Latte",
"location_id": 11
}'
{
"success": true,
"message": "Category created successfully",
"category_id": 19250
}
PUT /api/categories/:id. Why: Edit one catalog category (dashboard Edit Category / row Activate-Deactivate / Grubtech sync screen): rename (product.type follows for products filed under this id), change branches, activate/deactivate, image, colour, sort order, channel sync flags. Deactivating (is_active 0) is the safe alternative to delete_category when products still use it. Call get_category first and send the whole record back — image, is_active, sync_* and menu_sort_order RESET when omitted. Group membership is edited with update_group, not here. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_category"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Category id (list_categories / get_category). |
| name | string | optional | New name, trimmed; 400 if it contains a comma. |
| location_id | array<number> | required | Full list of branches that sell this category. |
| image | string | optional | Image. |
| is_active | number | optional | 1 = active, 0 = inactive/archived (dropped from list_selling_categories, the POS rail). |
| description | string | optional | Free-text description (categories.description, varchar 300). |
| color | string | optional | Tile colour, hex like "#FF8800" (varchar 45). |
| menu_sort_order | number | optional | Menu sort key, ascending (lower first). |
| localize_name | string | optional | Name in the secondary language (dashboard "Local Name (Arabic)", right-to-left), varchar 255. |
| localize_description | string | optional | Description in the secondary language (dashboard "Local Description (Arabic)"), varchar 255. |
| sync_menu | number | optional | 0 | 1. |
| sync_grubtech | number | optional | 0 | 1. |
| sync_otter | number | optional | 0 | 1. |
| sync_deliverect | number | optional | 0 | 1. |
| sync_bitebell | number | optional | 0 | 1. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_category",
"id": 11,
"location_id": 11
}'
{
"name": "Footwear",
"location_id": [
909,
908,
922,
910,
907,
1131
],
"image": "1767085134601-shoes.jpg",
"is_active": 1,
"sync_menu": 1,
"sync_bitebell": 1,
"sync_grubtech": 0,
"sync_otter": 0,
"sync_deliverect": 0,
"menu_sort_order": 12,
"id": "17557"
}
POST /api/modifiers. Why: Create a modifier GROUP (e.g. "Milk") at one or more branches (dashboard Modifiers › Add). Next: add its options with create_modifier_item (modifier_ids [new id], location_ids = the same branches), then link it to products with update_product modifiers[] ({modifier_id, min_allowed, max_allowed, priority, selection_type, default/mandatory item ids}). It reaches the POS (list_selling_modifiers / list_selling_products modifiers[]) only once a product links it. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_modifier"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Group name, e.g. |
| description | string | optional | Free-text description (max 100 chars; dashboard: "Description"). |
| location_id | array<number> | required | Branch ids that carry the group, as an ARRAY, e.g. |
| selection_allowed | number | optional | Group default max picks (dashboard: "Selection Allowed", default 0; 0 = unlimited). |
| selection_min_allowed | number | optional | Group default min picks (dashboard: "Minimum Selection Allowed", default 0; 0 = optional). |
| priority | number | optional | Group order, ascending (dashboard: "Priority", default 0; list drag/sort writes 1..n). |
| localize_name | string | optional | Translated (e.g. |
| localize_description | string | optional | Translated description (dashboard: "Local Description"). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_modifier",
"name": "Iced Latte",
"location_id": 11
}'
{
"name": "Milk",
"description": "",
"location_id": null,
"customer_id": 437,
"selection_allowed": 1,
"selection_min_allowed": 0,
"priority": 0,
"localize_name": null,
"localize_description": null,
"id": 5701
}
PUT /api/modifiers/:id. Why: Rename a modifier group, change its group defaults / order / translations, or change which branches carry it (dashboard Modifiers › Edit). FULL overwrite: get_modifier first and resend every field; location_id replaces the branch list. Does not touch the group's items (update_modifier_item) or its product links and per-product min/max (update_product modifiers[]). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_modifier"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Modifier group id to update (list_modifiers [].id). |
| name | string | required | Group name, e.g. |
| description | string | optional | Free-text description (max 100 chars; dashboard: "Description"). |
| location_id | array<number> | required | Branch ids that carry the group, as an ARRAY, e.g. |
| selection_allowed | number | optional | Group default max picks (dashboard: "Selection Allowed", default 0; 0 = unlimited). |
| selection_min_allowed | number | optional | Group default min picks (dashboard: "Minimum Selection Allowed", default 0; 0 = optional). |
| priority | number | optional | Group order, ascending (dashboard: "Priority", default 0; list drag/sort writes 1..n). |
| localize_name | string | optional | Translated (e.g. |
| localize_description | string | optional | Translated description (dashboard: "Local Description"). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_modifier",
"id": 11,
"name": "Iced Latte",
"location_id": 11
}'
{
"success": true,
"id": "4417",
"name": "Milk",
"description": "",
"location_id": [
907,
908
],
"selection_allowed": 1,
"selection_min_allowed": 0,
"priority": 0,
"localize_name": "حليب",
"localize_description": ""
}
POST /api/modifierItems. Why: Add an option (e.g. "Oat milk" +2.00) to one or more modifier groups at given branches (dashboard Modifier Items › Add). Needs group ids from list_modifiers / create_modifier. Then it shows at the POS under its primary group (list_selling_modifiers) on products that link that group, and its id can be used in update_product modifiers[] default / mandatory / unavailable item lists. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_modifier_item"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Option name, e.g. |
| description | string | optional | Free-text description (max 100 chars; dashboard: "Description"). |
| actual_price | number | optional | THE option price per unit, VAT INCLUDED (dashboard: "Actual Price", required, default 0, sent as typed — no VAT maths on either side). |
| modifier_ids | array<number> | required | Group(s) the option belongs to (dashboard: "Modifiers" multi-select, at least one; options from list_modifiers loc=<branch>). |
| location_ids | array<number> | required | Branches that carry the option (dashboard: "Locations" checkboxes, at least one; sent there as a csv location_id). |
| priority | number | optional | Order inside the group, ascending (dashboard: "Priority", required, ≥ 0, default 0; the group screen's drag/sort writes 1..n). |
| image | string | optional | Relative upload path (e.g. |
| tax_id | number | optional | Tax id — tax is OUT OF SCOPE: never choose one. |
| localize_name | string | optional | Translated (e.g. |
| localize_description | string | optional | Translated description (dashboard: "Local Description"). |
| sku_no | string | optional | Not needed: create overwrites it with "moditem-<customer_id>-<id>" for master tenants. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_modifier_item",
"name": "Iced Latte",
"modifier_ids": [
11
],
"location_ids": [
11
]
}'
{
"name": "Oat milk",
"actual_price": 2,
"modifier_ids": [
4417
],
"location_ids": [
907,
908
],
"priority": 3,
"id": 30211,
"modifier_id": 4417
}
PUT /api/modifierItems/:id. Why: Change an option's name, price, order, groups or branches (dashboard Modifier Items › Edit). Read it with get_modifier_item first and resend actual_price, priority, modifier_ids, location_ids and tax_id — omitted ones are reset or replaced. Per-product defaults / availability of the option are set with update_product, not here; the POS sold-out switch for options (update_product_availability itemType MODIFIER_ITEM) only reaches Grubtech. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_modifier_item"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Modifier item id to update (list_modifier_items [].id). |
| name | string | optional | Option name (max 100 chars). |
| description | string | optional | Free-text description (max 100 chars; dashboard: "Description"). |
| actual_price | number | required | THE option price per unit, VAT INCLUDED (dashboard: "Actual Price", required, default 0, sent as typed — no VAT maths on either side). |
| modifier_ids | array<number> | required | Group(s) the option belongs to (dashboard: "Modifiers" multi-select, at least one; options from list_modifiers loc=<branch>). |
| location_ids | array<number> | required | Branches that carry the option (dashboard: "Locations" checkboxes, at least one; sent there as a csv location_id). |
| priority | number | required | Order inside the group, ascending (dashboard: "Priority", required, ≥ 0, default 0; the group screen's drag/sort writes 1..n). |
| image | string | optional | Relative upload path (e.g. |
| tax_id | number | optional | Tax id — tax is OUT OF SCOPE: never choose one. |
| localize_name | string | optional | Translated (e.g. |
| localize_description | string | optional | Translated description (dashboard: "Local Description"). |
| sku_no | string | optional | SKU (read-only on the dashboard). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_modifier_item",
"id": 11,
"actual_price": 1,
"modifier_ids": [
11
],
"location_ids": [
11
],
"priority": 1
}'
{
"name": "Oat milk",
"actual_price": 2.5,
"modifier_ids": [
4417
],
"location_ids": [
907,
908
],
"priority": 3,
"tax_id": null,
"id": "30211",
"modifier_id": 4417
}
POST /api/brands. Why: Create a new brand (dashboard catalog management) on one or more branches, optionally linking existing groups. The new id is what list_selling_brands returns as brand_id and what cart lines/invoices carry. A brand shows categories only through its groups — create groups (create_group) and link them here via brand_groups or later with update_brand. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_brand"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| brand_name | string | required | Brand name (brands.brand_name, max 100). |
| location_id | string | required | Branches of the brand as a CSV STRING, e.g. |
| brand_groups | string | optional | Group ids as a CSV STRING ("1462,1465") — one brands_groups_relation row per id (brand ↔ group; a group's categories then define the brand's menu). |
| description | string | optional | Free-text description (brands.description). |
| brand_thumbnail | string | optional | Logo path relative to /api/uploads (e.g. |
| brand_color | string | optional | Hex colour, e.g. |
| brand_address | string | optional | Address printed in the receipt header for this brand (invoice info_address). |
| brand_email | string | optional | Email printed in the receipt header (invoice info_email). |
| brand_phone | string | optional | Phone printed in the receipt header (invoice info_phone). |
| brand_vat_no | string | optional | Brand VAT registration number (text only; VAT itself comes from branch config). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_brand",
"brand_name": "example",
"location_id": 11
}'
{
"success": true,
"brand_ids": [
480
]
}
PUT /api/brands/:id. Why: Edit one brand: name, logo, receipt-header contact fields, branches and linked groups. It is a FULL REPLACE — call get_brand first and resend every field, brand_groups (else all groups are unlinked) and location_id (else branches are reset). Use it to attach/detach groups (and therefore categories) to a brand. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_brand"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Brand id (list_brands). |
| brand_name | string | required | Brand name (brands.brand_name, max 100). |
| location_id | string | required | Branches of the brand as a CSV STRING, e.g. |
| brand_groups | string | optional | Group ids as a CSV STRING ("1462,1465") — REPLACES all brands_groups_relation rows of this brand. |
| description | string | optional | Free-text description (brands.description). |
| brand_thumbnail | string | optional | Logo path relative to /api/uploads. |
| brand_color | string | optional | Hex colour, e.g. |
| brand_address | string | optional | Address printed in the receipt header for this brand (invoice info_address). |
| brand_email | string | optional | Email printed in the receipt header (invoice info_email). |
| brand_phone | string | optional | Phone printed in the receipt header (invoice info_phone). |
| brand_vat_no | string | optional | Brand VAT registration number (text only; VAT itself comes from branch config). |
| image | string | optional | Alias for brand_thumbnail (used only when brand_thumbnail is absent); a full uploads URL is cut to the relative path. |
| name_update | string | optional | Truthy = check that no OTHER brand of this tenant already has brand_name → 400 { success:false, message:"Brand name already exists" }. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_brand",
"id": 11,
"brand_name": "example",
"location_id": 11
}'
{
"success": true,
"brand_id": 459
}
POST /api/groups. Why: Create a group to attach categories to a brand's menu (e.g. "Hot drinks" under brand Nexus Cafe). Pass categories and brand as csv strings and location_id as an array. Produces group_id for get_group / update_group. Catalog management (dashboard) tool. For menu scoping on the POS use list_selling_brands, which already returns each brand's groups with their category ids. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_group"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| group_name | string | required | Required. |
| group_description | string | optional | Free-text description (max 200 chars). |
| group_thumbnail | string | optional | Image path or URL for the group tile (max 100 chars). |
| image | string | optional | update_group only: alias of group_thumbnail, used when group_thumbnail is absent. |
| location_id | array<number> | required | Branch ids the group belongs to (groups_locations rows). |
| categories | string | optional | CSV string of category ids (list_categories), e.g. |
| brand | string | optional | CSV string of brand ids (list_brands), e.g. |
| menu_sort_order | number | optional | Sort position (ascending) in lists and menus. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_group",
"group_name": "example",
"location_id": 11
}'
{
"success": true,
"group_id": 1486
}
PUT /api/groups/:id. Why: Rename a group, change its categories, brands or branches. FULL REPLACE — read get_group first and send every field back (categories and brand as csv of ids, location_id as array), or links are wiped. Consumes a group id from list_groups/get_group. Catalog management (dashboard) tool. For menu scoping on the POS use list_selling_brands, which already returns each brand's groups with their category ids. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_group"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Group id (list_groups / get_group). |
| group_name | string | required | Required. |
| group_description | string | optional | Free-text description (max 200 chars). |
| group_thumbnail | string | optional | Image path or URL for the group tile (max 100 chars). |
| image | string | optional | update_group only: alias of group_thumbnail, used when group_thumbnail is absent. |
| location_id | array<number> | required | Branch ids the group belongs to (groups_locations rows). |
| categories | string | optional | CSV string of category ids (list_categories), e.g. |
| brand | string | optional | CSV string of brand ids (list_brands), e.g. |
| menu_sort_order | number | optional | Sort position (ascending) in lists and menus. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_group",
"id": 11,
"group_name": "example",
"location_id": 11
}'
{
"success": true,
"message": "Group updated successfully",
"group_id": "1341"
}
POST /api/products. Why: Create a new add-on (products row with is_addon = 1). Set type to the parent category NAMES it is offered with (list_categories) and location_prices to the branches/prices it sells at (list_locations) — without location_prices a master add-on is in no branch and the POS never offers it. Channel prices: price_level (list_channels ids). Produces productId for get_addon / update_addon. Afterwards it shows in list_addons and, at those branches, in list_selling_addons. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_addon"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Required. |
| description | string | optional | Optional description. |
| actual_price | number | optional | VAT-inclusive selling price — the price the POS charges. |
| price | number | optional | Ex-VAT price = actual_price − vat. |
| vat | number | optional | VAT AMOUNT inside actual_price (not a percent), e.g. |
| type | string | optional | Comma-separated CATEGORY NAMES of the parent products this add-on is offered with, e.g. |
| category_id | number | optional | The add-on's OWN category id (list_categories). |
| image | string | optional | Image relative upload path or URL; normalised to the relative path. |
| web_image | string | optional | Alternative image field; wins over image when both are sent (not a column). |
| is_addon | string | optional | Add-on flag. |
| location_id | array<number> | optional | Branch ids (list_locations). |
| location_prices | array<object> | optional | Branch rows (products_locations) — the ONLY way to put a new add-on in branches on master tenants; without it the add-on is in no branch and the POS never show… |
| is_archive | string | optional | 1 = archived (hidden from list_addons and the POS). |
| is_open_price | string | optional | 1 = cashier enters the price at sale time. |
| is_weightage_item | string | optional | 1 = sold by weight. |
| discount_pct | number | optional | Per-product discount percent 0–100; anything else or omitted → 0 (also on update). |
| sku_no | string | optional | SKU, trimmed. |
| label_id | number | optional | Label id (list_labels). |
| menu_sort_order | number | optional | Menu sort position (ascending). |
| localize_name | string | optional | Name in the second (localised) language. |
| localize_description | string | optional | Description in the second language. |
| calories | number | optional | Calories for digital menus. |
| preparation_time | number | optional | Preparation minutes for KDS. |
| addons | array<number> | optional | Legacy JSON list stored as text. |
| bundle_items | array<number> | optional | Legacy bundle list stored as text. |
| modifiers | array<object> | optional | Modifier groups to link (rare for add-ons). |
| modifiers_by_location | string | optional | update_addon only. |
| recipes | array<object> | optional | Recipe links (rare for add-ons). |
| price_level | array<object> | optional | create_addon only: per-channel prices. |
| channel_price | array<object> | optional | update_addon only: per-channel prices. |
| stock | number | optional | Legacy opening-stock column (not live inventory). |
| barcode_type | enum: , EAN-13, QR Code | optional | Product-code symbology. |
| barcode_value | string | optional | Barcode value. |
| unit_id | number | optional | Unit of measure id for weighted items. |
| plu | number | optional | PLU code (INT column) for scales/integrations. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_addon",
"name": "Iced Latte"
}'
{
"success": true,
"message": "Product created successfully",
"productId": 271199
}
PUT /api/products/:id. Why: Edit an add-on: prices, name, offer list (type = category names from list_categories), branches and per-branch price/availability (location_prices, list_locations ids), channel prices (channel_price, list_channels ids), image. FULL REWRITE — read get_addon first and send every field back. Archive/unarchive only: archive_product is lighter. Sold-out at a branch: update_product_availability, not this tool. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_addon"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Add-on id (list_addons / get_addon). |
| name | string | required | Required. |
| description | string | optional | Optional description. |
| actual_price | number | optional | VAT-inclusive selling price — the price the POS charges. |
| price | number | optional | Ex-VAT price = actual_price − vat. |
| vat | number | optional | VAT AMOUNT inside actual_price (not a percent), e.g. |
| type | string | optional | Comma-separated CATEGORY NAMES of the parent products this add-on is offered with, e.g. |
| category_id | number | optional | The add-on's OWN category id (list_categories). |
| image | string | optional | Image relative upload path or URL; normalised to the relative path. |
| web_image | string | optional | Alternative image field; wins over image when both are sent (not a column). |
| is_addon | string | optional | Add-on flag. |
| location_id | array<number> | optional | Branch ids (list_locations). |
| location_prices | array<object> | optional | Branch rows (products_locations) — the ONLY way to put a new add-on in branches on master tenants; without it the add-on is in no branch and the POS never show… |
| is_archive | string | optional | 1 = archived (hidden from list_addons and the POS). |
| is_open_price | string | optional | 1 = cashier enters the price at sale time. |
| is_weightage_item | string | optional | 1 = sold by weight. |
| discount_pct | number | optional | Per-product discount percent 0–100; anything else or omitted → 0 (also on update). |
| sku_no | string | optional | SKU, trimmed. |
| label_id | number | optional | Label id (list_labels). |
| menu_sort_order | number | optional | Menu sort position (ascending). |
| localize_name | string | optional | Name in the second (localised) language. |
| localize_description | string | optional | Description in the second language. |
| calories | number | optional | Calories for digital menus. |
| preparation_time | number | optional | Preparation minutes for KDS. |
| addons | array<number> | optional | Legacy JSON list stored as text. |
| bundle_items | array<number> | optional | Legacy bundle list stored as text. |
| modifiers | array<object> | optional | Modifier groups to link (rare for add-ons). |
| modifiers_by_location | string | optional | update_addon only. |
| recipes | array<object> | optional | Recipe links (rare for add-ons). |
| price_level | array<object> | optional | create_addon only: per-channel prices. |
| channel_price | array<object> | optional | update_addon only: per-channel prices. |
| stock | number | optional | Legacy opening-stock column (not live inventory). |
| barcode_type | enum: , EAN-13, QR Code | optional | Product-code symbology. |
| barcode_value | string | optional | Barcode value. |
| unit_id | number | optional | Unit of measure id for weighted items. |
| plu | number | optional | PLU code (INT column) for scales/integrations. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_addon",
"id": 11,
"name": "Iced Latte"
}'
{
"success": true,
"message": "Product updated successfully",
"product": {
"id": "257559",
"name": "Classic Sandwich",
"description": "",
"actual_price": 100,
"price": 95,
"vat": 5,
"type": "Alternate Options,Brewed Coffee",
"image": "",
"is_addon": 1,
"location_id": [
907
],
"location_prices": [
{
"location_id": 907,
"location_price": 100,
"is_available": 1
}
],
"customer_id": 437
}
}
POST /api/label. Why: Add a product label (dashboard Products → Labels → Add). Check list_labels k=<name> first — duplicates are not rejected on master tenants. Branch ids from list_locations. Then put it on products with create_product / update_product label_id (full product from get_product); when product_unique_formula is "name,label_id" the same product name may exist once per label. Confirm with get_label. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_label"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Label name (varchar 255), stored as sent (not trimmed). |
| description | string | optional | Free-text description (TEXT). |
| location_id | array<number> | required | Branches the label is offered at — one labels_locations row per id (the label row's own location_id stays NULL on master tenants). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_label",
"name": "Iced Latte",
"location_id": 11
}'
[
{
"label": {
"name": "Coffee beans",
"description": "",
"customer_id": 437,
"location_id": [
907,
908
],
"id": 61
}
},
{
"labels_locations": [
{
"id": 5120,
"label_id": 61,
"location_id": 907
},
{
"id": 5121,
"label_id": 61,
"location_id": 908
}
]
}
]
PUT /api/label/:id. Why: Rename a label, change its description or its branch list (dashboard Edit Label). NOT a patch — resend name, description and every branch from list_labels / get_label. Products keep label_id, so a rename shows on list_products, create_product / update_product label pickers and on sold lines (sync_invoice / list_orders_history items[].label_id). Branch ids from list_locations. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_label"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Label id (list_labels data[].id). |
| name | string | required | Label name (varchar 255), stored as sent. |
| description | string | optional | Free-text description (TEXT). |
| location_id | array<number> | required | Branches the label is offered at — one labels_locations row per id (the label row's own location_id stays NULL on master tenants). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_label",
"id": 11,
"name": "Iced Latte",
"location_id": 11
}'
{
"label": {
"id": "50",
"name": "Coffee Seeds",
"description": "Whole-bean products",
"customer_id": 437,
"location_id": [
907,
908,
909,
910,
922,
1131
]
},
"message": "Label updated successfully"
}
POST /api/deals. Why: Add a fixed-price combo (is_combo 1, type "combo"): a name, a VAT-incl. actual_price, the branches (list_locations) and the choice slots — categories[] { category_id from list_categories, max_allowed }. At the POS the customer picks max_allowed products from each slot category (products whose type equals the category name, list_selling_products); the chosen children are sold as combo_items under one combo parent line in sync_invoice (BUILD 9); a child's combo_price (update_product) adds to its line. Channel prices: price_level with list_channels ids. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_deal"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Deal name (dashboard: required, trimmed, non-empty). |
| description | string | optional | Free-text description. |
| actual_price | number | optional | Deal price VAT included — what the POS charges for the combo (dashboard: required number). |
| price | number | optional | Price without VAT. |
| vat | number | optional | Tax AMOUNT (not a percent). |
| discount_pct | number | optional | Deal discount percent. |
| barcode_type | enum: EAN-13, QR Code, | optional | Barcode symbology (dashboard "Code Type" select). |
| barcode_value | string | optional | Barcode / "Deal Code Number". |
| image | string | optional | Relative upload file name (e.g. |
| location_id | string | optional | Branch ids (list_locations) as a csv string, e.g. |
| categories | array<object> | optional | Choice slots: one row per category the customer picks from, with max_allowed. |
| price_level | array<object> | optional | Channel prices (product_channel_price), CREATE key — update uses channel_price. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_deal",
"name": "Iced Latte"
}'
{
"name": "Lunch Combo",
"description": "Burger + side + drink",
"actual_price": 35,
"price": 35,
"discount_pct": 0,
"location_id": "907",
"categories": [
{
"category_id": 15231,
"max_allowed": 1
},
{
"category_id": 15240,
"max_allowed": 2
}
],
"type": "combo",
"customer_id": 437,
"is_addon": false,
"addons": [],
"modifier": null,
"barcode_type": null,
"barcode_value": null,
"image": "",
"is_open_price": null,
"vat": 0,
"id": 260001,
"plu": null
}
PUT /api/deals/:id. Why: Edit one deal (price, name, branches, choice slots, channel prices, image). It is a FULL overwrite, not a patch: start from get_deal and resend every value; categories (slots → list_categories ids), location_id (branches → list_locations) and channel_price (list_channels) are fully replaced. Slot changes reach the POS through list_selling_products categories[]; to hide a deal use archive_product, to remove it delete_deal. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_deal"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Deal id (list_deals items[].id). |
| name | string | required | Deal name (dashboard: required, trimmed, non-empty). |
| description | string | optional | Free-text description. |
| actual_price | number | required | Deal price VAT included — what the POS charges for the combo (dashboard: required number). |
| price | number | required | Price without VAT. |
| vat | number | optional | Tax AMOUNT (not a percent). |
| discount_pct | number | optional | Deal discount percent. |
| barcode_type | enum: EAN-13, QR Code, | optional | Barcode symbology. |
| barcode_value | string | optional | Barcode / "Deal Code Number". |
| image | string | required | REQUIRED STRING — the handler calls image.split() before its try block, so a missing/non-string image crashes the API process. |
| location_id | string | required | REQUIRED csv STRING of ALL branch ids the deal should stay on, e.g. |
| selected_location_ids | array<number> | optional | Alternative branch list as an array of ids; when present it wins over location_id. |
| categories | array<object> | optional | Choice slots — FULL replace: every deal_categories row is deleted and these are inserted. |
| channel_price | array<object> | optional | Channel prices, UPDATE key (create uses price_level) — FULL replace: omitted/[] deletes every channel price. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_deal",
"id": 11,
"name": "Iced Latte",
"actual_price": 1,
"price": 1,
"image": "example",
"location_id": 11
}'
{
"id": 260001,
"name": "Lunch Combo",
"description": "Burger + side + drink",
"actual_price": 39,
"price": 39,
"vat": 0,
"discount_pct": 0,
"barcode_type": "",
"barcode_value": "",
"image": "1780385109749-combo.png",
"location_id": "907,908",
"categories": [
{
"category_id": 15231,
"max_allowed": 1
},
{
"category_id": 15240,
"max_allowed": 2
}
],
"channel_price": [
{
"channel_id": 12,
"channel_name": "Talabat",
"channel_price": 42,
"channel_vat": 0,
"channel_actual_price": 42
}
],
"type": "combo",
"customer_id": 437
}
POST /api/channels. Why: Add a sales channel and assign it to branches. The POS (list_selling_channels) offers it at those branches; per-channel product prices are then set on products (update_product channel_price) and discounts can be limited to it (channel_ids). Check list_channels first — names must be unique. Linkage: a channel id is referenced by list_selling_products data.products[].price_levels[].channel_id (per-channel price, product_channel_price), by orders (channel_id + channel_name, stored verbatim by sync_invoice — web-pos sends 0/""), and by discount codes channel_ids (list_discounts / get_user_extras data.discount_code[]). Branch availability lives in channels_locations (location_id in these tools); master channels have channels.location_id NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_channel"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Channel name, max 100 chars. |
| description | string | optional | Free text, max 100 chars. |
| selected_location_ids | array<number> | required | Branch ids the channel is sold on (one channels_locations row each). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_channel",
"name": "Iced Latte",
"selected_location_ids": [
11
]
}'
{
"message": "Channel created successfully",
"data": {
"name": "Talabat",
"description": "Delivery app",
"selected_location_ids": [
907,
908
],
"customer_id": 437,
"id": 516
}
}
PUT /api/channels/:id. Why: Rename a channel, change its description or the branches it is sold on. FULL REPLACE: name and description are both overwritten (omitted → NULL) and the branch list is replaced — call get_channel first and send everything back. Renaming also renames the channel on every product price row (price_levels[].channel_name); old orders keep the old channel_name. Linkage: a channel id is referenced by list_selling_products data.products[].price_levels[].channel_id (per-channel price, product_channel_price), by orders (channel_id + channel_name, stored verbatim by sync_invoice — web-pos sends 0/""), and by discount codes channel_ids (list_discounts / get_user_extras data.discount_code[]). Branch availability lives in channels_locations (location_id in these tools); master channels have channels.location_id NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_channel"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Channel id (list_channels / get_channel). |
| name | string | required | Channel name. |
| description | string | optional | Always overwritten: omitted → NULL. |
| selected_location_ids | array<number> | required | The COMPLETE branch list after the update: all channels_locations rows are deleted and re-inserted from it. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_channel",
"id": 11,
"name": "Iced Latte",
"selected_location_ids": [
11
]
}'
{
"message": "Channel updated successfully",
"success": true
}
POST /api/menus. Why: Create a named menu: a name plus branches (list_locations), categories (list_categories) and products (list_products) with an optional stored price per product. Menus are dashboard records only: nothing else in SysPOS reads them today. The POS (web-pos) never calls /menus, list_selling_products / list_selling_categories do not look at menus, and the aggregator syncs (Deliverect, Grubtech, Otter, LimeTray) build their menus from categories (sync_* flags, list_categories) and products, not from these menus. Adding or removing a product here does not change what a branch sells; for that use update_product (location_prices) or the category tools. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_menu"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Menu name. |
| description | string | optional | Free-text description. |
| currency | string | optional | Currency code label. |
| is_active | number | optional | 1 = Active, 0 = Inactive. |
| brand_id | number | optional | Brand id (list_brands). |
| label_id | number | optional | Routing label id (list_labels). |
| tax_id | number | optional | Tax id. |
| location_id | array<number> | required | Branches the menu is linked to (menu_locations), as an ARRAY of ids. |
| category_id | array<number> | optional | Categories (menu sections) on the menu (menu_categories). |
| product_id | array<number> | optional | Products on the menu, used ONLY when products_with_categories is omitted or []. |
| products_with_categories | array<object> | optional | Explicit product rows; when non-empty it wins over product_id. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_menu",
"name": "Iced Latte",
"location_id": 11
}'
{
"success": true,
"message": "Menu created successfully",
"menu_id": 5,
"data": {
"id": 5,
"name": "Breakfast Menu",
"description": "Served until noon",
"currency": "AED",
"customer_id": 437,
"location_id": [
907,
910
],
"is_active": 1,
"brand_id": null,
"label_id": 51,
"tax_id": null,
"brand_name": null,
"label_name": "Coffee beans",
"tax_name": null,
"tax_rate": null,
"categories": [
{
"category_id": 15481,
"category_name": "Alternate Options"
}
],
"products": [
{
"product_id": 247653,
"category_id": 15481,
"actual_price": 13,
"product_name": "Chai",
"product_type": "Alternate Options"
}
]
}
}
PUT /api/menus/:id. Why: Edit a menu: rename, (de)activate, change branches, categories or products. It is a full replace, not a patch — load get_menu first and send every field back. Menus are dashboard records only: nothing else in SysPOS reads them today. The POS (web-pos) never calls /menus, list_selling_products / list_selling_categories do not look at menus, and the aggregator syncs (Deliverect, Grubtech, Otter, LimeTray) build their menus from categories (sync_* flags, list_categories) and products, not from these menus. Adding or removing a product here does not change what a branch sells; for that use update_product (location_prices) or the category tools. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_menu"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Menu id (list_menus data[].id). |
| name | string | required | Menu name. |
| description | string | optional | Free-text description. |
| currency | string | optional | Currency code label. |
| is_active | number | optional | 1 = Active, 0 = Inactive. |
| brand_id | number | optional | Brand id (list_brands). |
| label_id | number | optional | Routing label id (list_labels). |
| tax_id | number | optional | Tax id. |
| location_id | array<number> | required | Branches the menu is linked to (menu_locations), as an ARRAY of ids. |
| category_id | array<number> | optional | Categories (menu sections) on the menu (menu_categories). |
| product_id | array<number> | optional | Products on the menu, used ONLY when products_with_categories is omitted or []. |
| products_with_categories | array<object> | optional | Explicit product rows; when non-empty it wins over product_id. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_menu",
"id": 11,
"name": "Iced Latte",
"location_id": 11
}'
{
"success": true,
"message": "Menu updated successfully",
"menu_id": "4",
"data": {
"id": 4,
"name": "classic menu",
"description": "",
"currency": "AED",
"customer_id": 437,
"location_id": [
907,
910,
922,
909,
908
],
"is_active": 1,
"brand_id": 414,
"label_id": 51,
"tax_id": 16,
"brand_name": null,
"label_name": "Coffee beans",
"tax_name": "Food Card Tax",
"tax_rate": 5,
"categories": [
{
"category_id": 15481,
"category_name": "Alternate Options"
}
],
"products": [
{
"product_id": 247653,
"category_id": 15481,
"actual_price": 13,
"product_name": null,
"product_type": null
}
]
}
}
POST /api/v2/coupons/validate. Why: Server check of a coupon code before you apply it — what web-pos calls after finding the code in list_selling_coupons. Checks: code exists for the tenant, linked to location_id, is_active = 1, now within start_date/end_date, total usage_limit, per-customer per_user_limit (only if crm_user_id is sent), fixed/percentage discount not larger than order_total (basket mode), and for product-scoped coupons that the basket holds a linked product. Returns the coupon row and, for product-scoped coupons, target_product_id. Does NOT return a discount amount and does NOT consume the coupon (usage is counted when sync_invoice saves the order). No side effects. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"validate_coupon"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| coupon_code | string | required | Required. |
| location_id | number | required | Required. |
| order_total | number | optional | Required when product_ids is not sent (basket coupon) — missing, non-numeric or negative → 400 "Order total is required and cannot be negative.". |
| crm_user_id | number | optional | Optional. |
| product_ids | array<number> | optional | Send ONLY for a product-scoped coupon (list_selling_coupons product_ids non-empty): the product_id of every active cart line. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "validate_coupon",
"coupon_code": "example",
"location_id": 11
}'
{
"status": true,
"message": "Coupon is valid.",
"target_product_id": 270965,
"data": {
"id": 1001,
"customer_id": 437,
"location_id": null,
"code": "COLDBREW5",
"description": "5 off Cold Brew",
"discount_type": "value",
"discount_value": 5,
"min_order_value": null,
"start_date": "2026-09-01T00:00:00.000Z",
"end_date": "2026-12-31T23:59:59.000Z",
"usage_limit": 100,
"per_user_limit": 1,
"is_active": 1,
"created_at": "2026-09-01T08:00:00.000Z",
"updated_at": "2026-09-01T08:00:00.000Z",
"current_usage": 3,
"linked_products": [
{
"id": 270965,
"name": "(1) Cold Brew"
}
],
"user_usage": 0
}
}
POST /api/coupons. Why: Create a coupon linked to one or more branches, optionally limited to products (list_coupon_products_by_locations) or categories (list_coupon_categories_by_locations) — not both. Send is_active: 1, or the coupon is stored inactive and never validates. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_coupon"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| code | string | required | Required. |
| discount_type | enum: percentage, value | required | Required (NOT NULL column; missing → 500). |
| location_id | array<number> | required | Required. |
| discount_value | number | optional | Optional (null = no discount). |
| is_active | number | optional | 1 = active, 0 = inactive. |
| description | string | optional | Optional. |
| min_order_value | number | optional | Optional. |
| start_date | string | optional | Optional. |
| end_date | string | optional | Optional. |
| usage_limit | number | optional | Optional. |
| per_user_limit | number | optional | Optional. |
| product_ids | array<number> | optional | Optional. |
| category_ids | array<number> | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_coupon",
"code": "example",
"discount_type": "example",
"location_id": 11
}'
{
"status": true,
"message": "Your coupon has been successfully created with multiple locations.",
"data": {
"id": 1001,
"customer_id": 437,
"location_id": 907,
"code": "COLDBREW5",
"description": "5 off Cold Brew",
"discount_type": "value",
"discount_value": 5,
"min_order_value": null,
"start_date": "2026-09-01T00:00:00.000Z",
"end_date": "2026-12-31T23:59:59.000Z",
"usage_limit": 100,
"per_user_limit": 1,
"is_active": 1,
"created_at": "2026-09-01T08:00:00.000Z",
"updated_at": "2026-09-01T08:00:00.000Z",
"current_usage": 0
}
}
PUT /api/coupons/:id. Why: Change a coupon. Coupon columns are PARTIAL (only the ones you send change), but branch links and product/category links are FULLY REPLACED: always send location_id, and send product_ids or category_ids again (from get_coupon) or they are removed. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_coupon"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Coupon id (list_coupons data[].id). |
| location_id | array<number> | required | Required. |
| code | string | optional | Optional. |
| description | string | optional | Optional. |
| discount_type | enum: percentage, value | optional | Optional. |
| discount_value | number | optional | Optional. |
| min_order_value | number | optional | Optional. |
| start_date | string | optional | Optional. |
| end_date | string | optional | Optional. |
| usage_limit | number | optional | Optional. |
| per_user_limit | number | optional | Optional. |
| is_active | number | optional | Optional. |
| product_ids | array<number> | optional | Product links. |
| category_ids | array<number> | optional | Category links (not enforced at checkout). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_coupon",
"id": 11,
"location_id": 11
}'
{
"status": true,
"message": "Your coupon has been successfully updated.",
"data": {
"id": 1001,
"customer_id": 437,
"location_id": 907,
"code": "COLDBREW5",
"description": "5 off Cold Brew",
"discount_type": "value",
"discount_value": 5,
"min_order_value": null,
"start_date": "2026-09-01T00:00:00.000Z",
"end_date": "2026-12-31T23:59:59.000Z",
"usage_limit": 200,
"per_user_limit": 1,
"is_active": 1,
"created_at": "2026-09-01T08:00:00.000Z",
"updated_at": "2026-09-10T08:00:00.000Z",
"current_usage": 3
}
}
POST /api/discounts. Why: Add a discount (discount code) that web-pos offers in its discount dialog (get_user_extras data.discount_code[]) at the listed branches while active and in date. Not a coupon (create_coupon). Branch ids from list_locations; scope ids: products list_selling_products, categories list_selling_categories, channels list_selling_channels (channel_id), order types list_order_types, payment methods list_selling_payment_methods. Check list_discounts first so the name (= the code cashiers type) is not a duplicate. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_discount"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | array<number> | required | Branches the discount is valid at (list_locations ids). |
| name | string | required | Display name, also the code the cashier types at the POS. |
| discount_type | number | optional | Kind of value: 1 (or 2) = percentage, 0 = fixed amount. |
| discount_percent | number | optional | The value: percent (0–100) when discount_type = 1, money amount (≥ 0) when 0. |
| is_changeable | string | optional | 1/true = changeable (stored as discount_type = 1), 0/false/omitted = fixed (discount_type = 0). |
| max_cap_amount | number | optional | Maximum money off (percentage discounts). |
| min_basket_value | number | optional | Minimum order total for the code to be eligible at the POS. |
| is_active | string | optional | 1 = active, 0 = inactive (Number(value); "true" as a string → 0). |
| start_date | string | required | First valid day, "YYYY-MM-DD". |
| end_date | string | required | Last valid day (inclusive), "YYYY-MM-DD". |
| start_time | string | optional | Daily window start "HH:MM" or "HH:MM:SS" (HH:MM → HH:MM:00). |
| end_time | string | optional | Daily window end, same format; earlier than start_time = wraps midnight. |
| is_available_on | string | optional | CSV of lowercase full weekday names ("monday,tuesday,…,sunday"); "" → null = every day. |
| apply_discount_to | enum: entire_basket, basket, category, product | optional | Scope: "entire_basket" (whole order; "basket" / "entire basket" are stored as "entire_basket"), "category" (send category_ids), "product" (send product_ids; "i… |
| category_ids | array<number> | optional | Categories for apply_discount_to = "category" (csv string also accepted). |
| product_ids | array<number> | optional | Products for apply_discount_to = "product" (csv string also accepted). |
| channel_ids | array<number> | optional | Sales channels the code is valid on (csv also accepted). |
| order_type_ids | array<number> | optional | Order types (csv also accepted). |
| payment_methods | array<number> | optional | Payment methods (csv also accepted). |
| is_auto_apply | string | optional | 1/true = auto apply, 0/false = cashier picks it. |
| is_promotion | string | optional | 1/true = promotion. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_discount",
"location_id": 11,
"name": "Iced Latte",
"start_date": "example",
"end_date": "example"
}'
{
"status": true,
"message": "Your discount has been successfully created.",
"data": {
"id": 2400,
"name": "Weekend 10%",
"discount_percent": 10,
"max_cap_amount": 50,
"min_basket_value": 30,
"location_id": null,
"is_active": 1,
"start_date": "2026-10-01T00:00:00.000Z",
"end_date": "2026-10-31T00:00:00.000Z",
"discount_type": 0,
"category_ids": null,
"apply_discount_to": "entire_basket",
"product_ids": null,
"start_time": "00:00:00",
"end_time": "23:59:00",
"is_available_on": "friday,saturday,sunday",
"is_auto_apply": 0,
"is_promotion": 0,
"payment_methods": null,
"channel_ids": null,
"customer_id": 437,
"order_type_ids": null,
"discount_method": "percentage",
"discount_value": 10
}
}
PUT /api/discounts/:id. Why: Change a discount (discount code): value, scope, branches, dates, active flag. NOT a patch — read the row from list_discounts (loc="All"; get_discount is broken) and send every field again, or the omitted scope/value/time fields are cleared. The change reaches web-pos with its next get_user_extras. Scope ids: list_selling_products, list_selling_categories, list_selling_channels, list_selling_payment_methods, list_order_types; branches list_locations. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_discount"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Discount id (list_discounts data[].id). |
| location_id | array<number> | required | Branches the discount is valid at (list_locations ids). |
| name | string | optional | Display name, also the code the cashier types at the POS. |
| discount_type | number | optional | Kind of value: 1 (or 2) = percentage, 0 = fixed amount. |
| discount_percent | number | optional | The value: percent (0–100) when discount_type = 1, money amount (≥ 0) when 0. |
| is_changeable | string | optional | 1/true = changeable (stored as discount_type = 1), 0/false/omitted = fixed (discount_type = 0). |
| max_cap_amount | number | optional | Maximum money off (percentage discounts). |
| min_basket_value | number | optional | Minimum order total for the code to be eligible at the POS. |
| is_active | string | optional | 1 = active, 0 = inactive (Number(value); "true" as a string → 0). |
| start_date | string | optional | First valid day, "YYYY-MM-DD". |
| end_date | string | optional | Last valid day (inclusive), "YYYY-MM-DD". |
| start_time | string | optional | Daily window start "HH:MM" or "HH:MM:SS" (HH:MM → HH:MM:00). |
| end_time | string | optional | Daily window end, same format; earlier than start_time = wraps midnight. |
| is_available_on | string | optional | CSV of lowercase full weekday names ("monday,tuesday,…,sunday"); "" → null = every day. |
| apply_discount_to | enum: entire_basket, basket, category, product | optional | Scope: "entire_basket" (whole order; "basket" / "entire basket" are stored as "entire_basket"), "category" (send category_ids), "product" (send product_ids; "i… |
| category_ids | array<number> | optional | Categories for apply_discount_to = "category" (csv string also accepted). |
| product_ids | array<number> | optional | Products for apply_discount_to = "product" (csv string also accepted). |
| channel_ids | array<number> | optional | Sales channels the code is valid on (csv also accepted). |
| order_type_ids | array<number> | optional | Order types (csv also accepted). |
| payment_methods | array<number> | optional | Payment methods (csv also accepted). |
| is_auto_apply | string | optional | 1/true = auto apply, 0/false = cashier picks it. |
| is_promotion | string | optional | 1/true = promotion. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_discount",
"id": 11,
"location_id": 11
}'
{
"status": true,
"message": "Your discount has been successfully updated.",
"data": {
"id": 2400,
"name": "Weekend 15%",
"discount_percent": 15,
"max_cap_amount": 50,
"min_basket_value": 30,
"location_id": null,
"is_active": 1,
"start_date": "2026-10-01T00:00:00.000Z",
"end_date": "2026-10-31T00:00:00.000Z",
"discount_type": 0,
"category_ids": null,
"apply_discount_to": "entire_basket",
"product_ids": null,
"start_time": "00:00:00",
"end_time": "23:59:00",
"is_available_on": "friday,saturday,sunday",
"is_auto_apply": 0,
"is_promotion": 0,
"payment_methods": null,
"channel_ids": null,
"customer_id": 437,
"order_type_ids": null,
"discount_method": "percentage",
"discount_value": 15
}
}
POST /api/payment-methods. Why: Add a tender type and enable it at chosen branches. The name becomes the invoice payment_method[] key, so match what cashiers/integrations will send (case-insensitive). Check list_payment_methods (no loc) first — names must be unique across the tenant. Linkage: the payment method NAME is the key in an invoice's payment_method[] (e.g. [{"cash": 30}]) and is matched case-insensitively against the methods linked to the invoice's branch — so renaming or deleting a method changes what the POS can settle with. Branch availability lives in payment_methods_locations (location_id[] in these tools); on master tenants payment_methods.location_id is NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_payment_method"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Payment method name (trimmed; blank/omitted → 400 "Payment method name is required"). |
| location_id | array<number> | required | Branch ids to enable this method at, as an array, e.g. |
| is_enable | number | optional | 1 = enabled, 0 = disabled. |
| priority | number | optional | Sort priority (lower first in the unfiltered list). |
| type | string | optional | Free-form tender type, e.g. |
| sync_locations | number | optional | Master flag: 1 (or true / "1") = shared master method; anything else → 0. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_payment_method",
"name": "Iced Latte",
"location_id": 11
}'
{
"success": true,
"message": "Payment method created successfully",
"data": {
"name": "Wallet",
"is_enable": 1,
"priority": 3,
"type": "wallet",
"sync_locations": 1,
"id": 2140,
"customer_id": 437,
"location_id": [
907,
908
]
}
}
PUT /api/payment-methods/:id. Why: Rename, enable/disable, re-prioritise or change the branches of a payment method. FULL REPLACE: name, is_enable, priority, type and sync_locations are all overwritten (omitted → NULL/0), so call get_payment_method first and resend everything. Renaming changes the invoice payment_method[] key — invoices/integrations still sending the old name will no longer match. Linkage: the payment method NAME is the key in an invoice's payment_method[] (e.g. [{"cash": 30}]) and is matched case-insensitively against the methods linked to the invoice's branch — so renaming or deleting a method changes what the POS can settle with. Branch availability lives in payment_methods_locations (location_id[] in these tools); on master tenants payment_methods.location_id is NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_payment_method"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Payment method id (list_payment_methods). |
| name | string | required | Payment method name (trimmed; blank/omitted → 400 "Payment method name is required"). |
| location_id | array<number> | optional | FULL REPLACEMENT of the branch list, as an array of branch ids. |
| is_enable | number | optional | 1 = enabled, 0 = disabled. |
| priority | number | optional | Sort priority (lower first in the unfiltered list). |
| type | string | optional | Free-form tender type, e.g. |
| sync_locations | number | optional | Master flag: 1 (or true / "1") = shared master method; anything else → 0. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_payment_method",
"id": 11,
"name": "Iced Latte"
}'
{
"success": true,
"message": "Payment method updated successfully",
"data": {
"name": "Wallet",
"is_enable": 1,
"priority": 0,
"type": "wallet",
"sync_locations": 1,
"id": "2135",
"customer_id": 437,
"location_id": [
907,
908,
909
]
}
}
POST /api/order-type. Why: Add a custom order type for the whole tenant (all branches get it), e.g. "Talabat" behaving as DELIVERY. Its name becomes the order_type string invoices carry, so choose it deliberately. Check list_order_types first — names must be unique per tenant. Order types are tenant-wide (order_type.customer_id) — NOT per branch; every branch sees the same list and there is no loc filter. The order type NAME (order_type here, `title` in GET /api/v2/order-types) is the string stored in an invoice's order_type; order_type_code is the fixed behaviour (DINE_IN / TAKEAWAY / DELIVERY / DRIVE_THRU) the POS uses to decide table/driver/address flow. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_order_type"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| order_type | string | required | Order type name shown on the POS and stored as the invoice order_type string, e.g. |
| order_type_code | enum: DINE_IN, TAKEAWAY, DELIVERY, DRIVE_THRU | required | Behaviour code (list_order_type_codes): DINE_IN | TAKEAWAY | DELIVERY | DRIVE_THRU. |
| icon_key | string | required | Iconify icon key "prefix:name" (e.g. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_order_type",
"order_type": "example",
"order_type_code": "example",
"icon_key": "example"
}'
{
"success": true,
"message": "Order type created successfully",
"data": {
"id": 1201,
"order_type": "Talabat",
"icon_key": "carbon:delivery",
"order_type_code": "DELIVERY",
"is_system_default": 0
}
}
PUT /api/order-type/:id. Why: Rename a custom order type or change its icon. The 4 system defaults (is_system_default = 1) cannot be edited (403); the behaviour code can never change (delete and recreate instead). Renaming changes the order_type string that NEW invoices carry — old invoices keep the old name, so reports will show both. Send BOTH order_type and icon_key every time (full replace). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_order_type"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Order type id (list_order_types). |
| order_type | string | required | New name (trimmed). |
| icon_key | string | required | Iconify key "prefix:name" (same rules as create). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_order_type",
"id": 11,
"order_type": "example",
"icon_key": "example"
}'
{
"success": true,
"message": "Order type updated successfully",
"data": {
"id": 1201,
"order_type": "Talabat Delivery",
"icon_key": "carbon:delivery",
"order_type_code": "DELIVERY",
"is_system_default": 0
}
}
POST /api/v2/invoice-sync-v3. Why: Create or update a POS order — web-pos 'Pay' / 'Save order' (finalizeSyncPayload). One call = the whole order: header, every line, and (when paid) the tender. New uuid → new order on the session branch with order_no / customer_invoice_id; the same uuid → update of that OPEN order (full resend). status 'open' saves a pay-later / dine order (stock reserved), status 'completed' + is_paid 1 closes a paid sale (payments, stock commit, quota, loyalty, wallet, coupon usage, CRM totals, SMS, KDS push). The server stores the totals as sent — it computes no prices, VAT or totals and validates none of them. Feed it from list_selling_products / list_selling_modifiers / list_selling_addons / list_selling_brands / list_selling_order_types / list_selling_tables / list_pos_crm_users / list_selling_payment_methods, validate_coupon, validate_loyalty_burn and list_product_quota. Read it back with list_orders_history or list_dine_orders; afterwards cancel_invoice, refund_invoice or update_invoice_payment_methods. MCP wraps this one invoice into the array the backend expects. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"sync_invoice"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Required. |
| request_id | string | optional | Optional but recommended. |
| status | enum: open, completed | required | Required. |
| is_paid | number | optional | Optional. |
| items | array<object> | required | Required. |
| items_number | number | optional | Optional. |
| subtotal | number | required | Required. |
| vat | number | required | Required. |
| payable | number | required | Required. |
| discountamount | number | optional | Optional. |
| discountpct | number | optional | Optional. |
| discount_id | number | optional | Optional. |
| discount_reason | string | optional | Optional. |
| is_vat_on_discount_exclude | number | optional | Optional. |
| is_discount_on_total | number | optional | Optional. |
| row_discount | number | optional | Optional. |
| charges_fee | number | optional | Optional. |
| delivery_charges | number | optional | Optional. |
| paid | number | optional | Optional (send when is_paid = 1). |
| change | number | optional | Optional. |
| tip_amount | number | optional | Optional. |
| payment_method | array<string> | optional | Tender rows (send with is_paid = 1). |
| card_digits | string | optional | Optional. |
| is_wallet_used | number | optional | Optional. |
| wallet_current_balance | number | optional | Optional. |
| created_at | number | optional | Optional. |
| paid_at | number | optional | Optional (send when is_paid = 1). |
| order_type | string | optional | Optional. |
| table_id | number | optional | Optional. |
| dining_guests | number | optional | Optional. |
| token_number | number | optional | Optional. |
| round | number | optional | Optional. |
| brand_id | number | optional | Optional. |
| channel_id | number | optional | Optional. |
| channel_name | string | optional | Optional. |
| shift_id | number | optional | Optional. |
| guest_name | string | optional | Optional. |
| note | string | optional | Optional. |
| notes | array<string> | optional | Optional. |
| info_name | string | optional | Optional. |
| info_address | string | optional | Optional. |
| info_phone | string | optional | Optional. |
| info_email | string | optional | Optional. |
| phone_number | string | optional | Optional. |
| crm_user_id | number | optional | Optional. |
| crm_delivery_area | string | optional | Optional. |
| plate_number | string | optional | Optional. |
| coupon_id | number | optional | Optional. |
| coupon_code | string | optional | Optional. |
| coupon_amount | number | optional | Optional. |
| burn_points | number | optional | Optional. |
| created_by_user_id | number | optional | Optional. |
| close_by_user_id | number | optional | Optional. |
| modified_by_user_ids | array<number> | optional | Optional. |
| is_kds_printed | number | optional | Optional, read on CREATE only. |
| app_version | string | optional | Optional. |
| app_os_version | string | optional | Optional. |
| build_number | string | optional | Optional. |
| os_type | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "sync_invoice",
"uuid": "example",
"status": "example",
"items": [
11
],
"subtotal": 1,
"vat": 1,
"payable": 1
}'
{
"status": true,
"message": "order saved successfully",
"data": [
{
"id": "5d0b7a6c4e19",
"oid": 25,
"customer_invoice_id": 25,
"items_number": 1,
"aggregator_order_id": null,
"exchange_rate": null,
"paid_in_usd": null,
"is_wastage": null,
"is_accepted": null,
"external_order_ready": 0,
"is_discount_on_total": null,
"subtotal": 10.48,
"vat": 0.52,
"payable": 11,
"discountamount": 0,
"items_discount_amount": 0,
"is_ticket_available": 1,
"is_wallet_used": 0,
"discountpct": 0,
"paid": 11,
"change": 0,
"note": null,
"uuid": "3f6c1d2a-8b4e-4c7a-9e21-5d0b7a6c4e19",
"created_at": 1790242200,
"updated_at": 1790242201,
"customer_id": 437,
"location_id": 907,
"guest_name": "Walk-in",
"info_name": null,
"info_address": null,
"info_phone": null,
"info_email": null,
"status": "completed",
"user": "owner@example.com",
"table_id": 0,
"order_id": null,
"payment_method": [
{
"CASH": 11
}
],
"earn_points": 0,
"burn_points": 0,
"burn_points_amount": 0,
"available_points": 0,
"available_points_amount": 0,
"charges_fee": 0,
"delivery_charges": 0,
"phone_number": "",
"loyalty_mobile_number": "",
"loyalty_mobile_code": "",
"crm_delivery_area": null,
"row_discount": 0,
"invoice_id": null,
"done_at": null,
"paid_at": 1790242200,
"period": null,
"is_kds_printed": 0,
"is_invoice_printed": 0,
"plate_number": null,
"crm_user_id": 0,
"crm_user_name": "",
"order_no": 3,
"token_number": null,
"brand_id": null,
"order_type": "Takeaway",
"tip_amount": 0,
"created_by_user_id": null,
"close_by_user_id": null,
"modified_by_user_ids": [],
"channel_id": null,
"is_vat_on_discount_exclude": 0,
"discount_id": null,
"dining_guests": null,
"channel_name": null,
"is_paid": 1,
"discount_reason": null,
"coupon_id": 0,
"coupon_amount": 0,
"coupon_code": null,
"printed_invoice_receipts": 0,
"printed_kds_receipts": 0,
"round": null,
"rider_pickup_at": null,
"rider_arrived_at": null,
"order_type_icon_key": "boxicons:takeaway",
"notes": [],
"order_type_icon": "https://images.weserv.nl/?url=https%3A%2F%2Fapi.iconify.design%2Fboxicons%2Ftakeaway.svg%3Fwidth%3D128%26height%3D128&output=png",
"payment_methods": [
{
"CASH": {
"amount": 11,
"tip_amount": null
}
}
],
"payments": [
{
"payment_method": "CASH",
"amount": 11,
"tip_amount": 0
}
],
"items": [
{
"id": 25504190,
"identifier": "9a4f0c3e-6d21-4b8a-8e5f-2c7d1b0a9e44",
"name": "Flat White",
"name_localize": null,
"base_price": null,
"actual_price": 11,
"preparation_time": null,
"price": 11,
"qty": 1,
"is_weightage_item": 0,
"unit_id": 0,
"subtotal": 11,
"line_total": null,
"line_vat": null,
"line_total_without_vat": null,
"invoice_uuid": "3f6c1d2a-8b4e-4c7a-9e21-5d0b7a6c4e19",
"product_id": 257506,
"extras": null,
"vat": 0.52,
"is_updated": 0,
"updated_qty": 0,
"is_addon": 0,
"is_refunded": 0,
"refund_quantity": 0,
"refund_reason": null,
"note": null,
"addon_linked_with": "9a4f0c3e-6d21-4b8a-8e5f-2c7d1b0a9e44",
"discount_pct": 0,
"discount_amount": 0,
"modifier": [],
"transferred_to": null,
"transferred_to_uuid": null,
"transferred_quantity": null,
"transferred_to_uuid_list": [],
"transferred_quantities": [],
"transfer_recipients": [],
"created_at": 1790242200,
"updated_at": 1790242200,
"is_prepared": null,
"type": "Espresso-Based Drinks",
"label_id": 50,
"item_status": "active",
"linked_uuid": null,
"original_linked_uuid": null,
"disposition_type": null,
"image": null,
"is_combo": null,
"is_deleted": null,
"deleted_at": null,
"deleted_qty": null,
"coupon_amount": 0,
"coupon_id": 0,
"coupon_code": "",
"updated_by_user_ids": [],
"re": null,
"ri": 0,
"is_wastage": 0,
"discount_id": null,
"discount_name": null,
"round": null,
"combo_items": null
}
]
}
]
}
PUT /api/v2/invoice-cancel/:uuid. Why: Void / cancel an order — the web-pos 'Cancel Order' button (order history: status open, opened, completed or partially_refunded) and 'Void Order' on an open dine table. Works on OPEN (unpaid) and COMPLETED (paid) orders. Sets invoices.status = 'cancelled' (the order drops out of sales, list_dine_orders and list_kds_orders), releases or returns the order's stock (master-inventory tenants), books every payment of the order as a cash-out (withdrawal), reverses a wallet payment, gives product-quota units back (list_product_quota), refreshes the CRM customer's totals and tells Grubtech / Limetray / Kiply. It CANNOT be undone and a cancelled order can no longer be refunded or have its payment changed. Use refund_pos_invoice (pending — route not deployed) instead when the sale stands but money (all or some lines) goes back to the guest. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"cancel_invoice"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Required. |
| note | string | optional | Optional. |
| re | string | optional | Optional. |
| is_wastage | number | optional | Stock disposition. |
| ri | number | optional | Stock disposition. |
| close_by_user_id | number | optional | Optional. |
| brand_id | number | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "cancel_invoice",
"uuid": "example"
}'
{
"status": true,
"message": "Order canceled successfully"
}
POST /api/v2/orders-refund. Why: LEGACY money-only full refund (the iOS route; web-pos does not call it). is_refund=1 marks the whole invoice refunded (invoices.is_refunded = 1 — status stays 'completed') and adds ONE negative payment of the full payable; undo_refund=1 reverses exactly that. No stock, lines, quota, wallet, loyalty or KDS change. Use it only when the user wants the money recorded as refunded without touching stock, or to undo a refund made by this tool. For web-pos parity (status 'refunded'/'partially_refunded', per-line refunds, stock returned or wasted) use refund_pos_invoice (pending — route not deployed). Never use both tools on the same order. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"refund_invoice"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| order_uuid | string | required | Required. |
| is_refund | number | optional | Send 1 to refund the whole invoice. |
| undo_refund | number | optional | Send 1 to undo a full refund previously made with is_refund=1 (only when is_refund is not sent). |
| refund_payment_method | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "refund_invoice",
"order_uuid": "example"
}'
{
"status": true,
"message": "Invoice refunded successfully"
}
PUT /api/v2/invoice/:uuid/payment-methods. Why: Re-book the tender of a PAID order (web-pos 'Change Payment Method' in order history, e.g. the guest paid by card, not cash). REPLACES all payments rows of the order with payment_methods and rewrites invoices.payment_method / paid / change / tip_amount / card_digits (and shift_id). The order total, lines and status do not change. Method names come from list_selling_payment_methods. Logged as an order activity 'Payment Method Updated' (get_invoice_activities). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_invoice_payment_methods"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Required. |
| payment_methods | array<string> | required | Required. |
| paid | number | optional | Optional. |
| change | number | optional | Optional. |
| tip_amount | number | optional | Optional. |
| card_digits | string | optional | Optional. |
| shift_id | number | optional | Optional. |
| reason | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_invoice_payment_methods",
"uuid": "example",
"payment_methods": [
11
]
}'
{
"status": true,
"http_status": 200,
"message": "Payment method updated successfully",
"data": {
"invoice_id": 9412231,
"invoice_uuid": "0b7c2f4e-3a91-4d2e-9c55-7e1f0a6b8d21",
"customer_invoice_id": 1187,
"invoice_status": "completed",
"shift_id": null,
"payment_methods": [
{
"card": {
"amount": 11,
"tip_amount": 0
}
}
],
"previous_payment_methods": [
{
"payment_method": "Cash",
"payment_method_id": 1631,
"amount": 11,
"tip_amount": 0
}
],
"change_rows": [
{
"change": "Cash → card",
"from_payment_method": "Cash",
"to_payment_method": "card",
"from_is_cash": true,
"to_is_cash": false,
"amount": 11,
"cash_impact": -11
}
],
"cash_before": 11,
"cash_after": 0,
"cash_impact": -11,
"reason": null,
"paid": "11.00",
"change": "0.00",
"tip_amount": "0.00",
"total_amount": "11.00",
"invoice_payable": "11.00",
"card_digits": null
}
}
POST /api/v2/activities. Why: Write ONE audit row to an order's Activity trail — the batch activity writer web-pos uses (the server never logs cashier actions itself, except update_invoice_payment_methods). Log what you did through other tools so the trail matches a web-pos till: e.g. after cancel_invoice title "Void" message "Cancelled order reason: X"; after sync_invoice paid title "Paid" message "paid [30] using [CASH]"; title "Order Created" for a new order. Read back with get_invoice_activities (same uuid). Skip it when get_user_extras ios_modules.is_activities_enabled = 0. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"log_invoice_activities"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| identifier | string | required | Required (by this tool). |
| uuid | string | required | Required. |
| title | string | required | Required. |
| message | string | optional | Optional. |
| user_id | number | optional | Optional. |
| username | string | optional | Optional. |
| role | string | optional | Optional. |
| timestamp | number | optional | Optional but send it. |
| platform | string | optional | Optional. |
| device_model | string | optional | Optional. |
| device_id | string | optional | Optional. |
| app_version | string | optional | Optional. |
| location_id | number | optional | Optional. |
| session_id | string | optional | Optional. |
| order_detail | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "log_invoice_activities",
"identifier": "example",
"uuid": "example",
"title": "example"
}'
{
"request": {
"identifier": "5b0f1c2e-8a4d-4b7e-9d3a-2f6c1e0a9b71",
"uuid": "f5bd46f3-377a-477d-b748-876e9aab0063",
"title": "Void",
"message": "Cancelled order reason: wrong item",
"user_id": 4734,
"username": "Cashier One",
"role": "admin",
"timestamp": 1790257026,
"platform": "mcp",
"device_model": "MCP agent",
"location_id": 907,
"session_id": ""
},
"response": {
"status": true,
"message": "Activities have been successfully saved.",
"data": [
{
"identifier": "5b0f1c2e-8a4d-4b7e-9d3a-2f6c1e0a9b71",
"uuid": "f5bd46f3-377a-477d-b748-876e9aab0063",
"title": "Void",
"message": "Cancelled order reason: wrong item",
"user_id": 4734,
"username": "Cashier One",
"role": "admin",
"timestamp": 1790257026,
"platform": "mcp",
"device_model": "MCP agent",
"location_id": 907,
"session_id": "",
"id": 1790257026487
}
]
},
"note": "Code-derived (write route not called live)."
}
PUT /api/v2/kds-item-status. Why: Tick (or un-tick) one line on the kitchen board — the web-pos KDS line tap. Writes invoice_items.is_prepared for that line only; the order stays on the board until it is bumped with change_kds_order_status. Single line per call (no bulk): call once per line. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_kds_item_status"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| item_id | number | required | Required. |
| is_prepared | number | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_kds_item_status",
"item_id": 11,
"is_prepared": 1
}'
{
"status": true,
"message": "KDS item Chicken Shawarma is ready to served."
}
PUT /api/v2/kds-order-status. Why: Accept or reject a ticket on the kitchen board — the web-pos KDS ACCEPT / REJECT buttons shown on PENDING tickets (is_accepted 0/null: online/aggregator orders and, in web-pos, new POS orders too). Writes invoices.is_accepted. Accepting an order whose status is 'pending_acceptance' also completes it (status 'completed'). Reject only flags the ticket (greyed CANCELLED on the KDS) — it does NOT cancel the sale; use cancel_invoice for that. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_kds_order_status"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Required. |
| status | number | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_kds_order_status",
"uuid": "example",
"status": 1
}'
{
"status": true,
"message": "Kds Order 10433 is Accepted"
}
PUT /api/v2/kds-change-status/:uuid. Why: BUMP a ticket off the kitchen board (order ready) or RECALL it back — the web-pos KDS DONE/bump button, dismiss of a rejected ticket, UNDO and the recall list. Bump sets invoices.done_at = now: the order leaves list_kds_orders and shows in the is_order_rebump=1 list; recall (is_order_rebump=1) clears done_at and it is back on the board. Bump also notifies: 'order ready' push to the branch's POS devices (with the table name for dine-in), Ably 'ready', a one-time WhatsApp 'order ready' to an opted-in guest, and — when the user's external_order_ready setting is on — 'prepared' to Grubtech / Limetray. It does NOT change the invoice status or payment. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"change_kds_order_status"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Required. |
| is_order_rebump | number | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "change_kds_order_status",
"uuid": "example"
}'
{
"status": true,
"message": "Order status updated"
}
POST /api/v2/products/availability. Why: The POS sold-out switch (long-press on a tile in web-pos): marks one product sold out (UNAVAILABLE) or back in stock (AVAILABLE) at one branch. Writes products_locations.is_sold_out (what list_sold_out_products and list_selling_products[].is_sold_out return), updates the Firestore list other tills listen to, and also pushes the change to Grubtech (delivery aggregator). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_product_availability"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| itemId | string | required | Required. |
| availability | enum: AVAILABLE, UNAVAILABLE, HIDDEN | required | Required (case-insensitive). |
| itemType | enum: PRODUCT, MODIFIER_ITEM | optional | Optional. |
| location_id | string | optional | Optional. |
| brandId | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_product_availability",
"itemId": "example",
"availability": "example"
}'
{
"status": true,
"message": "Product have been updated.",
"data": {
"message": "Grubtech update availability failed",
"menuId": "location-store-907-menu",
"itemId": "PRO-257508",
"storeId": "store-907",
"availability": "UNAVAILABLE",
"itemType": "PRODUCT",
"is_sold_out": 1,
"soldOutPersist": {
"updated": true,
"affectedRows": 1,
"is_sold_out": 1
},
"firestoreSync": {
"ok": true,
"action": "set",
"docId": "257508_907"
},
"grubtechUrl": "https://api.grubtech.io/menu/v1/menus/location-store-907-menu/items/PRO-257508/availability",
"hint": "menuId/storeId must match a menu synced via sync/products (e.g. storeId: store-59, menuId: location-store-59-menu). Send location_id to derive them automatically.",
"grubtechStatus": 404,
"grubtechError": {}
}
}
POST /api/v2/user-product-quota. Why: Create a quota for one product at the session branch: it may sell total_quota units, the sold counter starts at 0. CREATE ONLY — if the product already has an active quota the server answers 409 (it cannot change or delete one through MCP). Not used by web-pos (quotas are set on iOS / the dashboard). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_product_quota"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| product_id | string | required | Required. |
| total_quota | string | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_product_quota",
"product_id": 11,
"total_quota": "example"
}'
{
"status": true,
"message": "Quota created successfully",
"data": {
"counter_id": 15848,
"assignment_id": 26,
"product_id": 257508,
"product_name": "Mocha",
"total_quota": 20,
"sold_count": 0,
"remaining_stock": 20,
"history": []
}
}
POST /api/v2/user-product-quota/mark-sold-out. Why: Use up a product's quota at the session branch: sets sold_count = total_quota so remaining_stock becomes 0 (sold out on the menu). Only works for products that HAVE a quota (404 otherwise). It does NOT set is_sold_out — for a product without a quota use update_product_availability. Not used by web-pos. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"mark_product_sold_out"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| product_id | string | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "mark_product_sold_out",
"product_id": 11
}'
{
"status": true,
"message": "Product marked as sold out",
"data": {
"product_id": 257508,
"total_quota": 20,
"sold_count": 20,
"remaining_stock": 0
}
}
POST /api/v2/user-product-quota/:counterId/reset. Why: Start a quota over: sets the counter's sold_count to 0 (remaining_stock = total_quota again) and deletes its sale history. The quota itself (total_quota) is kept. This is the only 'daily reset' — nothing resets counters automatically. web-pos has this button on the Product Sold Counter page but it is currently hidden. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"reset_product_quota"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| counterId | string | required | Required. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "reset_product_quota",
"counterId": "example"
}'
{
"status": true,
"message": "Counter reset successfully",
"data": {
"counter_id": 15848,
"product_id": 257508,
"sold_count": 0
}
}
POST /api/v2/shifts/clock-in. Why: Clock in / open shift. Body typically opening cash + device_id. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"shift_clock_in"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| opening_cash | string | optional | Cash in the drawer at clock-in (float). |
| device_id | string | optional | POS device identifier the shift runs on (optional). |
| location_id | string | optional | Location id, csv, or array — the branch(es) this row belongs to. |
| notes | string | optional | Opening note for the shift. |
| denominations | string | optional | Cash-count per note: { "500": n, "200": n, "100": n, "50": n, "20": n, "10": n, "5": n, "1": n, "coins": amount }. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "shift_clock_in"
}'
{
"status": true
}
POST /api/v2/shifts/clock-out. Why: Clock out / close shift. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"shift_clock_out"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| closing_cash | string | optional | Cash counted at clock-out. |
| notes | string | optional | Closing note for the shift. |
| denominations | string | optional | Cash-count per note: { "500": n, "200": n, "100": n, "50": n, "20": n, "10": n, "5": n, "1": n, "coins": amount }. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "shift_clock_out"
}'
{
"status": true
}
POST /api/v2/shifts/drawer-transactions. Why: Record cash in/out. Include uuid for idempotency when POS does. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_drawer_transaction"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Client-generated UUID for this cash movement (idempotency key). |
| shift_id | string | required | Open shift id (get_active_shift). |
| direction | enum: in, out | required | 'in' = cash added to the drawer (paid-in), 'out' = cash removed (paid-out / expense). |
| category_id | string | required | Drawer category id (list_drawer_categories), e.g. |
| amount | string | required | Cash amount, > 0. |
| description | string | required | What the movement was for — printed on the shift report. |
| receipt_ref | string | optional | External receipt / voucher reference (optional). |
| brand_id | string | optional | Brand id when the location runs multiple brands (optional). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_drawer_transaction",
"uuid": "example",
"shift_id": 11,
"direction": "example",
"category_id": 11,
"amount": "example",
"description": "example"
}'
{
"status": true
}
POST /api/v2/update-user-permissions. Why: Save POS switches of the SESSION user (the API key user) — what web-pos Settings does: one key per call (e.g. { discount_codes: 1 }, { tip_amount: 0 }, { channel_id: 463 } from Settings → Channel). PARTIAL merge: only the keys you send change. Turns features on/off for this user's till: is_channels_enable + channel_id (list_selling_channels), is_soldout (update_product_availability), discount_codes (discount codes + list_selling_coupons / validate_coupon), is_change_payment_method_enabled (update_invoice_payment_methods), tip_amount, dining_guest_count, combo_options. Read the current values first with list_pos_users (own row). Only role admin may call it (else 401). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_user_permissions"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| print_receipt | number | optional | Optional. |
| kds_auto_receipt | number | optional | Optional. |
| invoice_auto_receipt | number | optional | Optional. |
| cart_options | number | optional | Optional. |
| print_and_save | number | optional | Optional. |
| cloud_print | number | optional | Optional. |
| quit_printers_with_cloud_enabled | number | optional | Optional. |
| invoice_printer_act_as_kds | number | optional | Optional. |
| cart_note | number | optional | Optional. |
| cart_car_plate | number | optional | Optional. |
| token_number | number | optional | Optional. |
| discount_codes | number | optional | Optional. |
| transfer_order_pincode | number | optional | Optional. |
| daily_sale_input_check | number | optional | Optional. |
| daily_sale_media_check | number | optional | Optional. |
| active_pincode | number | optional | Optional. |
| label_printing | number | optional | Optional. |
| qrcode_on_label_print | number | optional | Optional. |
| is_kds_device | number | optional | Optional. |
| open_drawer | number | optional | Optional. |
| user_card_digits | number | optional | Optional. |
| invoice_qrcode | number | optional | Optional. |
| force_adons | number | optional | Optional. |
| combo_options | number | optional | Optional. |
| ensure_combo_minimum_item_requirement | number | optional | Optional. |
| sms_invoice | number | optional | Optional. |
| offline_order_sync | number | optional | Optional. |
| orderhistory_print_receipt | number | optional | Optional. |
| dining_guest_count | number | optional | Optional. |
| is_brand_groups | number | optional | Optional. |
| is_extras_enble | number | optional | Optional. |
| display_modifier_titles | number | optional | Optional. |
| modifier_sort_min_required_desc | number | optional | Optional. |
| print_only_save_kds_orders | number | optional | Optional. |
| print_note_on_receipt | number | optional | Optional. |
| show_receipt_screen | number | optional | Optional. |
| open_dine_in | number | optional | Optional. |
| hide_cart_pay_button | number | optional | Optional. |
| tip_amount | number | optional | Optional. |
| print_void_orders | number | optional | Optional. |
| has_void_order_permission | number | optional | Optional. |
| hide_clear_checkout | number | optional | Optional. |
| kds_instruction | number | optional | Optional. |
| pay_default_usd | number | optional | Optional. |
| rpt_business_logo | number | optional | Optional. |
| rpt_order_type | number | optional | Optional. |
| rpt_is_modified | number | optional | Optional. |
| rpt_biller_info | number | optional | Optional. |
| rpt_total_before_vat | number | optional | Optional. |
| rpt_vat_info | number | optional | Optional. |
| rpt_net_total | number | optional | Optional. |
| rpt_exchange_rate | number | optional | Optional. |
| rpt_dine_guest_count | number | optional | Optional. |
| rpt_paid_at | number | optional | Optional. |
| esc_command | number | optional | Optional. |
| one_table_one_order | number | optional | Optional. |
| qr_code_on_print_receipt | number | optional | Optional. |
| is_channels_enable | number | optional | Optional. |
| delivery_detail_enable | number | optional | Optional. |
| is_attributed_receipt | number | optional | Optional. |
| is_cds_enable | number | optional | Optional. |
| external_order_print_receipt | number | optional | Optional. |
| text_receipt | number | optional | Optional. |
| each_category_kds_print | number | optional | Optional. |
| unify_brands | number | optional | Optional. |
| ingredient_stay_standalone | number | optional | Optional. |
| allegy_item | number | optional | Optional. |
| dinein_serving_role | number | optional | Optional. |
| draft_order | number | optional | Optional. |
| is_kds_expeditor | number | optional | Optional. |
| is_qr_payment | number | optional | Optional. |
| pay_zero_amount | number | optional | Optional. |
| print_deleted_item_on_receipt | number | optional | Optional. |
| receipt_format | number | optional | Optional. |
| receipt_translation | number | optional | Optional. |
| product_translation | number | optional | Optional. |
| category_translation | number | optional | Optional. |
| print_invoice_translation | number | optional | Optional. |
| print_kds_translation | number | optional | Optional. |
| reservation_with_dine_in | number | optional | Optional. |
| splitefire | number | optional | Optional. |
| is_ingredient_disabled | number | optional | Optional. |
| is_socket_server | number | optional | Optional. |
| is_client | number | optional | Optional. |
| open_price_pincode | number | optional | Optional. |
| is_transfer_enabled | number | optional | Optional. |
| block_print_until_order_open | number | optional | Optional. |
| web_local_network_printer | number | optional | Optional. |
| user_product_sales_counter | number | optional | Optional. |
| is_change_payment_method_enabled | number | optional | Optional. |
| skip_zero_quantity_items | number | optional | Optional. |
| skip_zero_amount_items | number | optional | Optional. |
| settle_wallet_topup | number | optional | Optional. |
| is_shift_management | number | optional | Optional. |
| acknowledge_dine_table_edit | number | optional | Optional. |
| is_soldout | number | optional | Optional. |
| force_crm_user | number | optional | Optional. |
| shift_transactions | number | optional | Optional. |
| eod_shift_report | number | optional | Optional. |
| shift_z_report | number | optional | Optional. |
| view_estimated_cash | number | optional | Optional. |
| external_order_ready | number | optional | Optional. |
| row_discount | number | optional | Optional. |
| channel_id | number | optional | Optional. |
| brand_id | number | optional | Optional. |
| print_no_of_times | number | optional | Optional. |
| language | string | optional | Optional. |
| zebra_ip | string | optional | Optional. |
| zebra_port | number | optional | Optional. |
| fraction_digits | number | optional | Optional. |
| order_type | number | optional | Optional. |
| exchange_format | number | optional | Optional. |
| station_id | number | optional | Optional. |
| lock_screen | number | optional | Optional. |
| cloud_target_user_id | number | optional | Optional. |
| cloud_target_device_id | string | optional | Optional. |
| exchange_rate | number | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_user_permissions"
}'
{
"request": {
"discount_codes": 1
},
"response": {
"status": true,
"message": "setting saved successfully.",
"data": {
"setting": {
"print_receipt": 1,
"discount_codes": 1,
"tip_amount": 0,
"dining_guest_count": 1,
"is_channels_enable": 0,
"channel_id": 463,
"is_soldout": 0,
"is_change_payment_method_enabled": 0,
"combo_options": 1,
"exchange_rate": 0
}
}
},
"note": "Code-derived (write route not called live); data.setting is abbreviated."
}
POST /api/config. Why: Set ONE setting of ONE branch — exactly what each card of the dashboard Settings page saves (Tax, Currency, Decimal Points, Time Zone, Opening Hours, Base Price, receipt texts, POS flags…). Upsert by (tenant, branch, name); other keys are untouched. Call once per key and per branch (the dashboard saves time_zone + time_zone_code, and opening_hour + closing_hour, as two calls). Read the current value first (get_config_value / get_location_config) and confirm with the user. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"save_config"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | ONE branch id (list_locations). |
| name | string | required | Config key, exact spelling (see config_keys): e.g. |
| value | string | required | New value AS A STRING in the key's format (vat "5", decimal_points "2", time_zone "+04:00", opening_hour "06:00", flags "0"/"1"). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "save_config",
"name": "Iced Latte",
"value": "example"
}'
{
"name": "decimal_points",
"value": "2",
"customer_id": 437,
"location_id": "907"
}
POST /api/config/business-info. Why: Save a branch's business profile (receipt name, address, VAT No, logo, contact, delivery area). POST and PUT run the SAME handler (update_business_info is identical): an upsert of each field you send. Send only the fields to change; fields you omit are untouched. Read get_business_info (strict="1") first and confirm with the user. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_business_info"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | ONE branch id (list_locations). |
| info_name | string | optional | Optional. |
| legal_name | string | optional | Optional. |
| info_address | string | optional | Optional. |
| info_phone | string | optional | Optional. |
| info_email | string | optional | Optional. |
| info_vat_no | string | optional | Optional. |
| logo_image | string | optional | Optional. |
| license_image | string | optional | Optional. |
| location_latitude | string | optional | Optional. |
| location_longitude | string | optional | Optional. |
| polygon_coordinates | string | optional | Optional. |
| area_coordinates | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_business_info"
}'
{
"msg": "Business info saved successfully"
}
PUT /api/config/business-info. Why: Save a branch's business profile (receipt name, address, VAT No, logo, contact, delivery area). PUT and POST run the SAME handler (create_business_info is identical): an upsert of each field you send. Send only the fields to change; fields you omit are untouched. Read get_business_info (strict="1") first and confirm with the user. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_business_info"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| loc | string | optional | ONE branch id (list_locations). |
| info_name | string | optional | Optional. |
| legal_name | string | optional | Optional. |
| info_address | string | optional | Optional. |
| info_phone | string | optional | Optional. |
| info_email | string | optional | Optional. |
| info_vat_no | string | optional | Optional. |
| logo_image | string | optional | Optional. |
| license_image | string | optional | Optional. |
| location_latitude | string | optional | Optional. |
| location_longitude | string | optional | Optional. |
| polygon_coordinates | string | optional | Optional. |
| area_coordinates | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_business_info"
}'
{
"msg": "Business info saved successfully"
}
POST /api/locations. Why: Open a new branch (location) for the tenant — the dashboard Locations > Add form. It only inserts the locations row (name + warehouse flag), links every synced payment method to it, optionally links all master products / categories, and appends the new id to the CALLING USER's allow_locations. It does NOT create business info, VAT, currency, time zone, opening hours, weekday hours, menus, brands, tables or users for the branch — configure those afterwards (business-info / config tools, upsert_location_days_hours, update_location with sync_* flags, create_* tools with the new location_id). Counts against the tenant's max_locations subscription limit. Confirm the name with the user first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_location"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Branch name. |
| isWareHouse | string | optional | Optional. |
| sync_products | string | optional | Optional. |
| sync_categories | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_location",
"name": "Iced Latte"
}'
{
"message": "Location created successfully",
"data": {
"name": "Marina Walk",
"isWareHouse": "0",
"sync_products": "1",
"sync_categories": "1",
"customer_id": 437,
"id": 1132
},
"syncedProductsCount": 152,
"syncMessage": "",
"syncedCategoriesCount": 18,
"categoriesSyncMessage": "",
"syncedMaterialsCount": 0,
"materialsSyncMessage": ""
}
PUT /api/locations/:id. Why: Rename a branch, toggle its warehouse flag, set its payment provider / N-Genius terminal / menu-checkout / invoice-service version, and optionally link ALL master items of a catalog entity to it (sync_* flags — the way to fill a new or incomplete branch on a MASTER tenant). It is a full overwrite of name, isWareHouse, enable_menu_checkout, payment_provider, ngenius_terminal_id and ngenius_merchant_id: call get_location first and send every one of those with its current value unless you mean to change or clear it. Business info, hours, VAT, currency are config rows, not this tool. Confirm with the user before any sync_* flag — it can reset per-branch overrides. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_location"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Location id (list_locations[].id). |
| name | string | required | Branch name — ALWAYS send it (current name if unchanged): the UPDATE writes it, so omitting it blanks the name. |
| isWareHouse | string | optional | "1" = warehouse, "0" = not. |
| enable_menu_checkout | number | optional | 1 = QR/online menu checkout on, 0 = off. |
| payment_provider | string | optional | Online payment gateway id (e.g. |
| ngenius_terminal_id | string | optional | N-Genius terminal id (TID). |
| ngenius_merchant_id | string | optional | N-Genius merchant id (MID). |
| invoice_service_version | enum: v1, v2 | optional | Optional. |
| sync_products | string | optional | Optional. |
| sync_categories | string | optional | Optional. |
| sync_brands | string | optional | Optional. |
| sync_groups | string | optional | Optional. |
| sync_menus | string | optional | Optional. |
| sync_labels | string | optional | Optional. |
| sync_deals | string | optional | Optional. |
| sync_modifiers | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_location",
"id": 11,
"name": "Iced Latte"
}'
{
"message": "Location updated successfully",
"data": {
"name": "Marina Walk",
"isWareHouse": "0",
"enable_menu_checkout": 0,
"invoice_service_version": "v1",
"sync_menus": 1,
"customer_id": 437,
"id": "1132"
},
"syncedMenusCount": 3,
"menusSyncMessage": "Successfully synced 3 Menus"
}
POST /api/location-days-hours-config/bulk-upsert. Why: Set a branch's weekly operating hours — exactly what the dashboard Operating Hours 'Save' does (it always sends all 7 days). Each config updates that weekday's row if it exists, else creates it. Days you leave out are untouched. This does NOT change the business day / opening_hour config used by the POS and reports (that is a config row). Confirm the schedule with the user first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"upsert_location_days_hours"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| location_id | number | required | ONE branch id (list_locations[].id). |
| configs | array<object> | required | 1–7 day entries; send each weekday at most once (a new day listed twice creates two rows). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "upsert_location_days_hours",
"location_id": 11,
"configs": [
11
]
}'
{
"message": "Bulk upsert completed. Created: 0, Updated: 2, Failed: 0",
"data": [
{
"id": 29,
"customer_id": 437,
"location_id": 909,
"day": "monday",
"open": "09:00",
"close": "23:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 30,
"customer_id": 437,
"location_id": 909,
"day": "tuesday",
"open": "09:00",
"close": "23:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 31,
"customer_id": 437,
"location_id": 909,
"day": "wednesday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 32,
"customer_id": 437,
"location_id": 909,
"day": "thursday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 33,
"customer_id": 437,
"location_id": 909,
"day": "friday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 34,
"customer_id": 437,
"location_id": 909,
"day": "saturday",
"open": "12:00",
"close": "00:00",
"is_off": 0,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
},
{
"id": 35,
"customer_id": 437,
"location_id": 909,
"day": "sunday",
"open": null,
"close": null,
"is_off": 1,
"created_at": "2025-10-03T05:50:48.000Z",
"updated_at": "2025-10-03T05:50:48.000Z"
}
],
"results": {
"created": 0,
"updated": 2,
"failed": 0,
"errors": []
}
}
POST /api/materials. Why: Add a raw material (metadata only): name, storage unit (unit_id) and ingredient unit + conversionrate for recipes, costing method and cost, branches (location_id; one stock row per branch at 0), suppliers with purchase prices, category and brands. Stock starts at 0 everywhere — receive it with create_draft_purchase_order / create_stock_transfer; then use the id in recipes (create_recipe), supplier catalogs and counts. Recipe-output materials are created by the recipe tools, not here. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_material"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| is_prepared | enum: 1 | optional | Any non-empty value (even "0") → materials.is_prepared = 1 (in-house prepared item). |
| name | string | required | Material name. |
| description | string | optional | Free text. |
| unit_id | number | optional | Standard storage unit id (units_of_measure; on dev1 1 Gram, 2 Kilogram, 3 Pound, 4 Ounce, 5 Milliliter, 6 Liter, 7 Gallon, 8 Fluid Ounce, 9 Pieces, 10 Dozen, 1… |
| custom_unit_id | number | optional | Customer-defined storage unit id (customer_uom_conversions; copy from a material that uses it). |
| unit | string | optional | Storage unit display label stored in materials.unit, e.g. |
| ingredientunit | string | optional | Ingredient (recipe) unit label, e.g. |
| conversionrate | string | optional | Ingredient units per 1 storage unit, e.g. |
| costingmethod | enum: average, fixed | optional | "average" (default; cost follows purchases/receipts) or "fixed" (cost = price, per-branch override via location_prices). |
| price | number | optional | Cost price per storage unit. |
| selling_price | number | optional | Retail price when the material is sold directly (0 = none). |
| wholesale_price | number | optional | Wholesale price (0 = none). |
| low_stock | number | optional | Low-stock alert threshold in the storage unit (0 = no alert; drives get_materials_stats.low_stock). |
| minimum_level | number | optional | Minimum stock level (storage unit). |
| maximum_level | number | optional | Maximum stock level. |
| par_level | number | optional | Par (target) stock level. |
| barcode | string | optional | Barcode. |
| category_id | number | optional | Material category id (list_material_categories; must exist and not be deleted, else 400 INVALID_MATERIAL_CATEGORY). |
| type | string | optional | Legacy free-text kind: "raw" for normal materials (dashboard always sends raw on create and keeps the current value on edit). |
| material_type | enum: mat, rm | optional | "mat" = raw/purchased material (default). |
| yield_percentage | number | optional | Usable yield % after prep, 0.01–100+ (100 = no loss). |
| tax_id | number | optional | Tax id — tax groups are out of scope: omit on create; on update resend get_material.tax_id unchanged (omitting it sets the tax to NULL). |
| location_id | array<number> | optional | Branch ids to stock the material at (list_locations), as an ARRAY of ints (or csv string). |
| location_prices | array<object> | optional | Per-branch opening unit cost. |
| suppliers | array<number> | optional | Supplier ids (list_suppliers) to link. |
| suppliers_prices | array<object> | optional | Per-supplier purchase prices; also links those suppliers. |
| brand_ids | array<number> | optional | Brand ids (list_brands) the material is limited to; unknown ids are dropped. |
| ingredient_unit_id | number | optional | Standard unit id of the ingredient unit (units_of_measure). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_material",
"name": "Iced Latte"
}'
[
{
"material": {
"name": "Whole Milk",
"unit_id": 6,
"unit": "Liter",
"ingredientunit": "Milliliter",
"conversionrate": 1000,
"costingmethod": "fixed",
"price": 3.2,
"location_id": [
907,
908
],
"location_prices": [
{
"location_id": 907,
"avg_cost": 3.2
},
{
"location_id": 908,
"avg_cost": 3.4
}
],
"suppliers_prices": [
{
"supplier_id": 970,
"purchase_price": 3.1
}
],
"category_id": 3,
"yield_percentage": 100,
"stock": 0,
"is_master_user": 1,
"id": 121001,
"sku_no": "mat-437-121001",
"mode": "stockable",
"customer_id": 437,
"brand_ids": []
}
},
{
"materials_locations": [
{
"id": 0,
"material_id": 121001,
"location_id": 907,
"customer_id": 437,
"material_type": "mat",
"avg_cost": 3.2,
"stock": 0
},
{
"id": 1,
"material_id": 121001,
"location_id": 908,
"customer_id": 437,
"material_type": "mat",
"avg_cost": 3.4,
"stock": 0
}
]
}
]
PUT /api/materials/:id. Why: Edit a material's metadata — name, units/conversion, costing method and cost (fixed: per-branch costs via location_prices), selling/wholesale price, stock levels, barcode, category, yield, branches, suppliers + purchase prices, brands. It is a FULL overwrite: load get_material {id, loc} first and send that record back with your changes (including stock, location_id, unit_id, tax_id, suppliers_prices, brand_ids = brands[].id). Never use it to change on-hand stock — use counts, wastage, transfers or purchase orders. FULL OVERWRITE: every field you omit (except category_id) is set to NULL/default, omitted suppliers/brands are unlinked, and branches missing from location_id lose their stock rows. Always start from get_material. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_material"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Material id (list_materials). |
| name | string | required | Material name. |
| description | string | optional | Free text. |
| unit_id | number | optional | Standard storage unit id (units_of_measure; on dev1 1 Gram, 2 Kilogram, 3 Pound, 4 Ounce, 5 Milliliter, 6 Liter, 7 Gallon, 8 Fluid Ounce, 9 Pieces, 10 Dozen, 1… |
| custom_unit_id | number | optional | Customer-defined storage unit id (customer_uom_conversions; copy from a material that uses it). |
| unit | string | optional | Storage unit display label stored in materials.unit, e.g. |
| ingredientunit | string | optional | Ingredient (recipe) unit label, e.g. |
| conversionrate | string | optional | Ingredient units per 1 storage unit, e.g. |
| costingmethod | enum: average, fixed | optional | "average" (default; cost follows purchases/receipts) or "fixed" (cost = price, per-branch override via location_prices). |
| price | number | optional | Cost price per storage unit. |
| selling_price | number | optional | Retail price when the material is sold directly (0 = none). |
| wholesale_price | number | optional | Wholesale price (0 = none). |
| low_stock | number | optional | Low-stock alert threshold in the storage unit (0 = no alert; drives get_materials_stats.low_stock). |
| minimum_level | number | optional | Minimum stock level (storage unit). |
| maximum_level | number | optional | Maximum stock level. |
| par_level | number | optional | Par (target) stock level. |
| barcode | string | optional | Barcode. |
| category_id | number | optional | Material category id (list_material_categories; must exist and not be deleted, else 400 INVALID_MATERIAL_CATEGORY). |
| type | string | optional | Legacy free-text kind: "raw" for normal materials (dashboard always sends raw on create and keeps the current value on edit). |
| material_type | enum: mat, rm | optional | "mat" = raw/purchased material (default). |
| yield_percentage | number | optional | Usable yield % after prep, 0.01–100+ (100 = no loss). |
| tax_id | number | optional | Tax id — tax groups are out of scope: omit on create; on update resend get_material.tax_id unchanged (omitting it sets the tax to NULL). |
| location_id | array<number> | required | REQUIRED on update: the COMPLETE list of branch ids the material must stay on (get_material.location_id, plus/minus the branches you change). |
| location_prices | array<object> | optional | Fixed costing only (ignored for average): per-branch unit cost. |
| suppliers | array<number> | optional | Supplier ids (list_suppliers). |
| suppliers_prices | array<object> | optional | Per-supplier purchase prices. |
| brand_ids | array<number> | optional | Brand ids (list_brands) the material is limited to; unknown ids are dropped. |
| stock | number | required | Pass get_material.stock back unchanged. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_material",
"id": 11,
"name": "Iced Latte",
"location_id": 11,
"stock": 1
}'
{
"id": 119158,
"name": "Cherry Blossom",
"description": "Cherry",
"price": 0,
"selling_price": 0,
"wholesale_price": 0,
"stock": -12762188.785166,
"unit": "Kilogram",
"unit_id": 2,
"custom_unit_id": null,
"ingredientunit": "Gram",
"conversionrate": "1000",
"costingmethod": "average",
"low_stock": 0,
"minimum_level": "0",
"maximum_level": "0",
"par_level": "0",
"barcode": "",
"type": "raw",
"material_type": "mat",
"category_id": 2,
"yield_percentage": 100,
"tax_id": null,
"location_id": null,
"suppliers": [
970
],
"suppliers_prices": [
{
"supplier_id": 970,
"purchase_price": 1.1092
}
],
"brand_ids": [
412,
473
],
"customer_id": 437
}
POST /api/inventory/categories. Why: Add a material category or subcategory (taxonomy only; no stock impact) so materials can be grouped via create_material / update_material category_id and filtered in list_materials (cat). Two levels max: a subcategory's parent must be a top-level category. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_material_category"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Category name, trimmed. |
| description | string | optional | Free-text description. |
| parent_id | number | optional | Parent category id for a subcategory (must be an existing non-deleted TOP-LEVEL category, else 400 CATEGORY_PARENT_INVALID / CATEGORY_MAX_DEPTH_EXCEEDED). |
| image | string | optional | Image path or URL stored verbatim (upload URL from the dashboard uploader). |
| color | string | optional | Colour string, stored verbatim (not shown by the dashboard). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_material_category",
"name": "Iced Latte"
}'
{
"success": true,
"message": "Material category created successfully",
"data": {
"id": 7,
"name": "Cheese",
"description": "",
"parent_id": 2,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
}
}
PUT /api/inventory/categories/:id. Why: Rename, re-describe, change the image of, or re-parent a material category (taxonomy only; materials keep their category_id). Partial update: only the keys you send change. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_material_category"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Material category id (list_material_categories). |
| name | string | optional | New name, trimmed (blank → 400). |
| description | string | optional | Free-text description. |
| parent_id | number | optional | Move under this top-level category (null / "" / 0 → top level). |
| image | string | optional | Image path or URL stored verbatim (upload URL from the dashboard uploader). |
| color | string | optional | Colour string, stored verbatim (not shown by the dashboard). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_material_category",
"id": 11
}'
{
"success": true,
"message": "Material category updated successfully",
"data": {
"id": 3,
"name": "Dairy & Eggs",
"description": "",
"parent_id": 2,
"customer_id": 437,
"color": "",
"image": "",
"is_deleted": 0
}
}
POST /api/suppliers. Why: Add a vendor to the supplier directory (no PO, no stock). Check list_suppliers k=<name> first — the API does not reject duplicates. After creating, link the materials it sells in the dashboard (Suppliers → Link materials; no MCP tool) and read them back with list_supplier_materials. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_supplier"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Supplier display name (max 300). |
| description | string | optional | Free-text description (max 300). |
| address | string | optional | Postal address (max 200). |
| tax_pct | number | optional | Supplier tax % (≥ 0, 2 decimals). |
| contact_name | string | optional | Contact person (max 200). |
| contact_email | string | optional | Contact email (max 200); must be a valid email if given. |
| contact_phone | string | optional | Contact phone (max 45). |
| payments | string | optional | Accepted payment methods as a JSON STRING (not an object): "{\"is_cash\":true,\"is_credit\":false,\"is_cheque\":false}". |
| can_recieve_email | string | optional | 1/true = email POs to contact_email, 0/false = don't. |
| logo | string | optional | Relative upload path of a logo image (from the dashboard file upload). |
| trade_license | string | optional | Relative upload path of the trade-licence document (dashboard file upload, max 200). |
| trn_certificate | string | optional | Relative upload path of the TRN certificate document (dashboard file upload, max 45). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_supplier",
"name": "Iced Latte"
}'
{
"name": "Fresh Farms Trading",
"description": "Vegetables and dairy",
"address": "Warehouse 12, Al Quoz, Dubai",
"tax_pct": 5,
"contact_name": "Sample Contact",
"contact_email": "orders@freshfarms.example",
"contact_phone": "+971500000002",
"payments": "{\"is_cash\":true,\"is_credit\":true,\"is_cheque\":false}",
"can_recieve_email": 1,
"logo": "",
"trade_license": "",
"trn_certificate": "",
"location_id": 907,
"customer_id": 437
}
PUT /api/suppliers/:id. Why: Change a supplier's directory fields (name, contacts, address, payment methods, PO-email flag). The API overwrites ALL columns, so load the row with get_supplier first and send every field back with your changes. Does not touch POs already issued (they keep their info_* snapshot). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_supplier"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Supplier id from list_suppliers. |
| name | string | required | Supplier display name. |
| description | string | optional | Free-text description (max 300). |
| address | string | optional | Postal address (max 200). |
| tax_pct | number | optional | Supplier tax % (≥ 0, 2 decimals). |
| contact_name | string | optional | Contact person (max 200). |
| contact_email | string | optional | Contact email (max 200); must be a valid email if given. |
| contact_phone | string | optional | Contact phone (max 45). |
| payments | string | optional | Accepted payment methods as a JSON STRING (not an object): "{\"is_cash\":true,\"is_credit\":false,\"is_cheque\":false}". |
| can_recieve_email | string | optional | 1/true = email POs to contact_email, 0/false = don't. |
| logo | string | optional | Relative upload path of a logo image (from the dashboard file upload). |
| trade_license | string | optional | Relative upload path of the trade-licence document (dashboard file upload, max 200). |
| trn_certificate | string | optional | Relative upload path of the TRN certificate document (dashboard file upload, max 45). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_supplier",
"id": 11,
"name": "Iced Latte"
}'
{
"name": "Grocery Supplier LLC",
"description": "Grocery items",
"address": "Warehouse 4, Al Quoz, Dubai",
"tax_pct": 5,
"contact_name": "Sample Contact",
"contact_email": "orders@supplier.example",
"contact_phone": "+971500000001",
"payments": "{\"is_cash\":true,\"is_credit\":false,\"is_cheque\":false}",
"can_recieve_email": 1,
"logo": "",
"trade_license": "",
"trn_certificate": "",
"location_id": 907,
"customer_id": 437
}
POST /api/recipe. Why: Create a recipe (BOM): output yield_qty + unit, mode, ingredient lines (materials from list_materials, qty in their ingredient unit, optional yield %), branches, brand tags and — phantom only — product links. The server also creates the recipe's prepared material (list_materials, material_type "rm", linked_recipe = new id) at the chosen branches with the computed cost, and saves the cost snapshot. No stock moves: a phantom recipe deducts ingredients when a linked product is sold (sync_invoice); a stockable one is produced via create_work_order (completed in the dashboard). Send name, yield_qty, unit, unit_id, mode, recipe_items [{material_id, qty (ingredient unit), unit, yield_pct}] and location_id; products only for phantom recipes. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_recipe"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Recipe name. |
| yield_qty | number | required | Output of one batch in `unit` (> 0). |
| unit | string | required | Output unit label; the dashboard sends the UOM name (e.g. |
| unit_id | number | optional | UOM id matching `unit` (no MCP units tool: copy from another recipe/material with the same unit, else null). |
| mode | enum: phantom, stockable | optional | phantom (default) = no own stock, sales of linked products deduct the ingredients; stockable = produced by work orders into the prepared material's stock, cann… |
| recipe_items | array<object> | required | Ingredient lines, at least one (the dashboard requires ≥ 1). |
| location_id | array<number> | required | Branches (list_locations) where the prepared material (its stock/cost row) is created, as an array like the dashboard (the server also accepts a number or csv). |
| brand_ids | array<number> | optional | Brand ids (list_brands) to tag the recipe with; [] / omitted = none. |
| products | array<object> | optional | Product links, PHANTOM recipes only: [{ id, qty_used }]. |
| product_ids | array<number> | optional | Product ids to link (each gets qty_used 1). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_recipe",
"name": "Iced Latte",
"yield_qty": 1,
"unit": "example",
"recipe_items": [
11
],
"location_id": 11
}'
{
"message": "Recipe Created Successfully",
"data": {
"id": 17225,
"customer_id": 437,
"name": "Tomato sauce",
"yield_qty": 2,
"unit": "Kilogram",
"version": 1,
"created_by": 4734,
"mode": "stockable",
"sku_no": "rcp-437-17225",
"unit_id": 2,
"recipe_items": [
{
"recipe_id": 17225,
"material_id": 119196,
"qty": 1500,
"unit": "Gram",
"wastage_pct": 0,
"id": 1044600,
"type": "mat",
"unit_id": null,
"yield_pct": null,
"name": "Test Material",
"ingredientunit": "Gram",
"conversionrate": "1000",
"avg_price": 22.0271,
"price": 0,
"material_yield": null,
"effective_yield": 100
},
{
"recipe_id": 17225,
"material_id": 119158,
"qty": 700,
"unit": "Gram",
"wastage_pct": 0,
"id": 1044601,
"type": "mat",
"unit_id": null,
"yield_pct": 90,
"name": "Cherry Blosom",
"ingredientunit": "Gram",
"conversionrate": "1000",
"avg_price": 0.3295,
"price": 0,
"material_yield": null,
"effective_yield": 90
}
],
"product_ids": [],
"linked_products": [],
"brand_ids": [
412
]
}
}
PUT /api/recipe/:id. Why: Edit a recipe: name, yield, unit, ingredient lines, product links (phantom), brand tags and branches of its prepared material; the cost snapshot and the prepared material (name, unit, branch avg cost) are recomputed. NOT a patch — omitted lines, links, brands and header fields are wiped — so always start from get_recipe. The mode cannot be changed. No stock moves. Full replace, not a patch: resend every field from get_recipe (lines, products, brand_ids, location_id, unit_id, current mode). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_recipe"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Recipe id (list_recipes / get_recipe). |
| name | string | required | Recipe name (NOT NULL; omitted → 500, rolled back). |
| yield_qty | number | required | Output of one batch in `unit` (> 0). |
| unit | string | required | Output unit label; the dashboard sends the UOM name (e.g. |
| unit_id | number | optional | UOM id matching `unit` (no MCP units tool: copy from another recipe/material with the same unit, else null). |
| mode | enum: phantom, stockable | required | Resend the recipe's CURRENT mode (get_recipe). |
| recipe_items | array<object> | required | FULL list of ingredient lines — replaces all lines. |
| products | array<object> | optional | Product links for a PHANTOM recipe: [{ id, qty_used }]. |
| product_ids | array<number> | optional | Product ids to link (each gets qty_used 1). |
| brand_ids | array<number> | optional | Brand ids (list_brands). |
| location_id | array<number> | optional | Branches (list_locations) of the prepared material, as an array (the server also accepts a number or csv). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_recipe",
"id": 11,
"name": "Iced Latte",
"yield_qty": 1,
"unit": "example",
"mode": "example",
"recipe_items": [
11
]
}'
{
"message": "Recipe Updated Successfully",
"data": {
"recipe": {
"id": 16779,
"customer_id": 437,
"name": "Test",
"yield_qty": 0.8,
"unit": "Kilogram",
"version": 1,
"created_by": 4734,
"mode": "stockable",
"sku_no": "rcp-437-16779",
"unit_id": 2,
"recipe_items": [
{
"recipe_id": 16779,
"material_id": 119196,
"qty": 450,
"unit": "Gram",
"wastage_pct": 0,
"id": 988201,
"type": "mat",
"unit_id": 1,
"yield_pct": null,
"name": "Test Material",
"ingredientunit": "Gram",
"conversionrate": "1000",
"avg_price": 22.0271,
"price": 0
},
{
"recipe_id": 16779,
"material_id": 119158,
"qty": 500,
"unit": "Gram",
"wastage_pct": 0,
"id": 988202,
"type": "mat",
"unit_id": 1,
"yield_pct": null,
"name": "Cherry Blosom",
"ingredientunit": "Gram",
"conversionrate": "1000",
"avg_price": 0.3295,
"price": 0
}
],
"product_ids": [
257508
],
"linked_products": [
{
"product_id": 257508,
"id": 257508,
"name": "Mocha",
"qty_used": 1,
"portion_qty": 0.8,
"unit": "Kilogram"
}
],
"brand_ids": [
412,
473
]
}
},
"materialId": 119300
}
POST /api/inventory-count-sheets/create-sheet. Why: Save a reusable count-sheet template: a named list of materials (list_materials ids) assigned to branches (list_locations ids). A sheet only pre-selects materials for a later sheet-based count (create_inventory_count type 2); creating it does not create a count or touch stock. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_inventory_count_sheet"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Sheet name. |
| reference | string | optional | Free-text reference / sheet code. |
| material_ids | array<number> | required | Every material on the sheet (the dashboard requires ≥ 1 and picks from stockable materials of the selected branches). |
| location_id | array<number> | required | Branches the sheet is assigned to — ALWAYS an array of numbers (the dashboard sends e.g. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_inventory_count_sheet",
"name": "Iced Latte",
"material_ids": [
11
],
"location_id": 11
}'
{
"status": true,
"message": "Inventory count sheet created successfully"
}
PUT /api/inventory-count-sheets/update-sheet/:id. Why: Rename a count-sheet template or change its materials / branches. NOT a patch: name, reference and material_ids are overwritten and branches are synced to exactly location_id — resend the full sheet with your change. Template only; no count or stock change. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_inventory_count_sheet"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Count sheet id (list_inventory_count_sheets → data[].id). |
| name | string | required | Sheet name. |
| reference | string | optional | Free-text reference / sheet code. |
| material_ids | array<number> | required | Every material on the sheet (the dashboard requires ≥ 1 and picks from stockable materials of the selected branches). |
| location_id | array<number> | required | Branches the sheet is assigned to — ALWAYS an array of numbers (the dashboard sends e.g. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_inventory_count_sheet",
"id": 11,
"name": "Iced Latte",
"material_ids": [
11
],
"location_id": 11
}'
{
"status": true,
"message": "Inventory count sheet updated successfully"
}
POST /api/supplier-invoices/draft. Why: Create a DRAFT purchase order (is_draft = 1, status draft) for a supplier, split from an existing PO — the dashboard does this when a delivery is short: the missing quantities become drafts (possibly for another supplier) that a user later confirms in the dashboard. Nothing is sent to the supplier and no stock moves. Needs the parent PO uuid (get_purchase_order / list_supplier_invoices), the supplier (list_suppliers), material ids and prices (list_supplier_materials, list_materials). The draft only becomes a real PO when a user presses Confirm in the dashboard; stock arrives only when that PO is received there (list_inventory / list_inventory_movements). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_draft_purchase_order"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| parent_purchase_uuid | string | required | uuid of the existing (non-archived) purchase order this draft is split from (list_supplier_invoices / get_purchase_order). |
| location_id | number | optional | Branch of the draft — send the parent PO's location_id (one id). |
| table_id | number | optional | Supplier id of the draft (list_suppliers). |
| info_name | string | optional | Supplier name snapshot = supplier.name. |
| info_phone | string | optional | Supplier phone snapshot = supplier.contact_phone. |
| info_email | string | optional | Supplier email snapshot = supplier.contact_email (where the PO email goes on confirm). |
| info_address | string | optional | Supplier address snapshot = supplier.address. |
| items | array<object> | required | Draft lines (at least one; 400 "items array is required" otherwise). |
| subtotal | number | required | Header subtotal = Σ line subtotals (4 dp). |
| vat | number | required | VAT AMOUNT (not a percent) = round4(subtotal × VAT% / 100), stored with 2 decimals. |
| payable | number | required | Grand total = subtotal + vat (4 dp). |
| discountamount | number | optional | Header discount amount. |
| note | string | optional | Header note. |
| your_order_ref | string | optional | Your reference. |
| supplier_order_ref | string | optional | Supplier's reference. |
| delivery_details | string | optional | Multi-line delivery block printed on the PO. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_draft_purchase_order",
"parent_purchase_uuid": "example",
"items": [
11
],
"subtotal": 1,
"vat": 1,
"payable": 1
}'
{
"success": true,
"draft": {
"id": 6187,
"items_number": 1,
"subtotal": 12,
"vat": 0.6,
"payable": 12.6,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "",
"uuid": "52e14591-f11f-430c-8672-aa9412cc3eb9",
"created_at": "2026-05-19T16:16:14.000Z",
"customer_id": 437,
"location_id": 907,
"info_name": "Faisal Suppliers",
"info_address": "Industrial Area 2, Sharjah",
"info_phone": "+971500000003",
"info_email": "sales@faisal.example",
"status": "draft",
"user": "Nexus Cafe",
"user_id": 1201,
"table_id": 881,
"payment_method": null,
"order_id": 18,
"invoice_id": null,
"custom_price": null,
"your_order_ref": "",
"supplier_order_ref": "",
"published_date": null,
"is_draft": 1,
"parent_purchase_uuid": "6f6db90f-2a7d-4910-82e9-3a0dae7e6e70",
"delivery_date": null,
"order_request_uuid": null,
"supplier_invoice_number": null,
"received_by": null,
"received_at": null,
"approved_by": null,
"approved_at": null,
"rejected_by": null,
"rejected_at": null,
"supplier_invoice_id": null,
"delivery_details": "Nexus\nNexus located at Dev\nEmail: info@buyer.example\nVAT: 100000000000003\nPhone: +971500000009",
"is_archive": 0,
"items": [
{
"id": 14292,
"name": "SRC-BT-86CBPR-SG3 BB86-BB92",
"price": 2,
"qty": 6,
"subtotal": 12,
"purchase_uuid": "52e14591-f11f-430c-8672-aa9412cc3eb9",
"customer_id": 437,
"location_id": 907,
"material_id": 112157,
"extras": null,
"custom_price": 2,
"product_id": null,
"unit": null,
"wastage": null,
"brand_id": null,
"requested_qty": 6,
"remark": null,
"material_name": "SRC-BT-86CBPR-SG3 BB86-BB92",
"unit_id": null,
"unit_name": "Pieces"
}
]
}
}
PUT /api/supplier-invoices/:uuid/price-quantity. Why: Replace the lines (materials, quantities, unit prices) and totals of a DRAFT purchase order — the dashboard draft editor. Load the draft with get_purchase_order first (you must resend every line you want to keep and reuse its VAT rate). Materials and prices from list_supplier_materials (sup = the draft's table_id, loc = its location_id) or list_materials. No stock moves; the supplier is not notified. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_draft_purchase_order_lines"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | uuid of a draft (is_draft = 1) from list_supplier_invoices / create_draft_purchase_order. |
| edit_mode | enum: processing, receiving | optional | "processing" = edit the ORDER (use this for drafts, as the dashboard does): lines are replaced by the list you send, qty = requested qty, new materials allowed. |
| items | array<object> | required | The COMPLETE line list of the draft (at least one; 400 "Please send at least one line item to update."). |
| subtotal | number | required | Header subtotal = Σ line subtotals. |
| vat | number | required | VAT AMOUNT (not percent) = round4(subtotal × VAT% / 100), VAT% = the draft's stored vat / subtotal × 100 (0 if its vat is 0). |
| payable | number | required | subtotal + vat. |
| discountamount | number | optional | Header discount amount; the dashboard resends the draft's current value (normally 0). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_draft_purchase_order_lines",
"uuid": "example",
"items": [
11
],
"subtotal": 1,
"vat": 1,
"payable": 1
}'
{
"success": true,
"message": "Your updates were saved.",
"invoice": {
"id": 6187,
"items_number": 2,
"subtotal": 32,
"vat": 1.6,
"payable": 33.6,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "",
"uuid": "52e14591-f11f-430c-8672-aa9412cc3eb9",
"created_at": "2026-05-19T16:16:14.000Z",
"customer_id": 437,
"location_id": 907,
"info_name": "Faisal Suppliers",
"info_address": "Industrial Area 2, Sharjah",
"info_phone": "+971500000003",
"info_email": "sales@faisal.example",
"status": "draft",
"user": "Nexus Cafe",
"user_id": 1201,
"table_id": 881,
"payment_method": null,
"order_id": 18,
"invoice_id": null,
"custom_price": null,
"your_order_ref": "",
"supplier_order_ref": "",
"published_date": null,
"is_draft": 1,
"parent_purchase_uuid": "6f6db90f-2a7d-4910-82e9-3a0dae7e6e70",
"delivery_date": null,
"order_request_uuid": null,
"supplier_invoice_number": null,
"received_by": null,
"received_at": null,
"approved_by": null,
"approved_at": null,
"rejected_by": null,
"rejected_at": null,
"supplier_invoice_id": 3,
"delivery_details": null,
"is_archive": 0
},
"items": [
{
"id": 14292,
"name": "SRC-BT-86CBPR-SG3 BB86-BB92",
"price": 2,
"qty": 10,
"subtotal": 20,
"purchase_uuid": "52e14591-f11f-430c-8672-aa9412cc3eb9",
"customer_id": 437,
"location_id": 907,
"material_id": 112157,
"extras": null,
"custom_price": 2,
"product_id": null,
"unit": null,
"wastage": null,
"brand_id": null,
"requested_qty": 10,
"remark": null
},
{
"id": 14310,
"name": "Atx 27.5 In Vibrant Blue",
"price": 3,
"qty": 4,
"subtotal": 12,
"purchase_uuid": "52e14591-f11f-430c-8672-aa9412cc3eb9",
"customer_id": 437,
"location_id": 907,
"material_id": 112295,
"extras": null,
"custom_price": 3,
"product_id": null,
"unit": null,
"wastage": null,
"brand_id": null,
"requested_qty": 4,
"remark": null
}
]
}
POST /api/inventory-counts/create. Why: Record a physical stock count for one branch as a DRAFT (MCP forces status draft): each line stores counted vs expected quantity and unit cost. Stock is NOT changed — a user must submit and approve the count in the dashboard, which then posts counted − expected as adjustments. Ids/quantities come from list_materials / get_material (material, units, avg_price), get_material_stock or list_materials with loc (expected stock), list_locations (branch). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_inventory_count"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Count name. |
| type | number | required | 0 = partial (only the materials you list), 1 = full (every stockable material of the branch), 2 = from a count sheet. |
| start_date | string | required | Count date as display text (stored verbatim). |
| start_time | string | required | Count time as display text (stored verbatim). |
| is_partial | number | required | 1 when type = 0, else 0 (what the dashboard sends). |
| location_id | number | required | ONE branch id (list_locations) — where expected stock is taken and where approval will adjust stock. |
| materials | array<object> | required | One row per material, each with EXACTLY these 5 keys (any other key → validation error "must enter materials value as an array of objects with material_id, cou… |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_inventory_count",
"name": "Iced Latte",
"type": 1,
"start_date": "example",
"start_time": "example",
"is_partial": 1,
"location_id": 11,
"materials": [
11
]
}'
{
"status": true,
"message": "Inventory count created",
"data": {
"id": 478,
"start_date": "Sep 26, 2026",
"start_time": "03:15 PM",
"location_id": 907,
"name": "Default location - Sep 26, 2026 03:15 PM",
"type": 0,
"is_partial": 1,
"status": "draft",
"created_at": 1790414100,
"updated_at": 1790414100,
"created_by_user_id": 4734,
"materials": [
{
"material_id": 119196,
"counted_quantity": 48.5,
"expected_quantity": 50,
"price": 100,
"name": "Test Material"
}
]
},
"_note": "Code-derived (no write calls made)."
}
PUT /api/inventory-counts/update-materials. Why: Correct lines already on a DRAFT count — counted, expected, price and name per material (matched by inventory_count_id + material_id). Only updates existing lines: it cannot add (add_inventory_count_item) or remove lines. Use get_inventory_count first; stock only changes when the count is approved in the dashboard. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_inventory_count_materials"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| inventory_count_id | number | required | Count id (list_inventory_counts / get_inventory_count). |
| materials | array<object> | required | Lines to overwrite (non-empty; no duplicate material_id → "Duplicate material_id in materials array is not allowed"). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_inventory_count_materials",
"inventory_count_id": 11,
"materials": [
11
]
}'
{
"status": true,
"message": "Inventory count updated successfully",
"data": {
"inventory_count_id": 478,
"materials_updated": 1,
"materials": [
{
"material_id": 119196,
"counted_quantity": 47,
"expected_quantity": 50,
"price": 100,
"name": "Test Material"
}
]
},
"_note": "Code-derived."
}
PUT /api/inventory-counts/update-item-count. Why: Change only the counted quantity of one or more count LINES, addressed by line id (get_inventory_count → materials[].id) + material_id. Lighter than update_inventory_count_materials (no expected/price/name needed). Use on draft counts; stock changes only on dashboard approval. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_inventory_count_item"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| materials | array<object> | required | Non-empty array of { id, material_id, counted_quantity }. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_inventory_count_item",
"materials": [
11
]
}'
{
"status": true,
"message": "Item count updated successfully",
"data": [
{
"id": 9747,
"material_id": 119197,
"counted_quantity": 10.5
}
],
"_note": "Code-derived."
}
POST /api/inventory-counts/add-item. Why: Add material lines to an existing DRAFT count (e.g. an item found on the shelf that was not on the list). Material ids / expected stock / cost from list_materials (loc = the count's branch) or get_material + get_material_stock. No stock change until the count is approved in the dashboard. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"add_inventory_count_item"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| inventory_count_id | number | required | Count id (list_inventory_counts / get_inventory_count). |
| materials | array<object> | required | Non-empty array of new lines { material_id, counted_quantity, expected_quantity, price, name }. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "add_inventory_count_item",
"inventory_count_id": 11,
"materials": [
11
]
}'
{
"status": true,
"message": "Item count saved",
"data": [
{
"material_id": 119197,
"name": "Test",
"price": 0.2873,
"counted_quantity": 3,
"expected_quantity": 2.5
}
],
"_note": "Code-derived."
}
POST /api/wastage. Why: Record wasted stock (expired / damaged / spoiled …) for one branch as a DRAFT wastage batch — MCP forces approval_status draft and strips skip_stock_movement, so no stock moves. A user then submits it and an approver approves it in the dashboard, which deducts the stock. Material ids, units and cost from list_materials / get_material; branch from list_locations; brand from list_brands. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_wastage_draft"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| wastage_reason | string | required | Batch reason (one for all lines). |
| location_id | number | required | ONE branch id (list_locations) where the waste happened; stock-out on approval happens here. |
| batch_notes | string | optional | Free-text note for the batch. |
| brand_id | number | optional | Batch brand (list_brands). |
| items | array<object> | required | Non-empty list of lines (empty → 500 "No items provided"). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_wastage_draft",
"wastage_reason": "example",
"location_id": 11,
"items": [
11
]
}'
{
"success": true,
"message": "Wastage batch created successfully",
"data": {
"id": 34,
"batch_number": "WB-2026-00031",
"approval_status": "draft"
},
"_note": "Code-derived (no write calls made)."
}
PUT /api/wastage/:id. Why: Change a DRAFT wastage batch (reason, branch, notes, brand, lines). NOT a patch: header fields you omit become null and the lines are replaced by exactly the items you send — get_wastage first and resend everything with your change. The API rejects non-draft batches. No stock change. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_wastage_draft"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Wastage batch id (list_wastage / get_wastage) whose approval_status is draft. |
| wastage_reason | string | required | Batch reason; omitted → null. |
| location_id | number | required | Branch id; omitted → null → DB error (NOT NULL). |
| batch_notes | string | optional | Note; omitted → null (resend header.batch_notes to keep it). |
| brand_id | number | optional | Batch brand; omitted → null (resend header.brand_id when it is not null to keep it). |
| items | array<object> | required | ALL lines of the batch — FULL REPLACE: existing lines are deleted and these inserted; omitted/[] → the batch ends with 0 lines and total 0. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_wastage_draft",
"id": 11,
"wastage_reason": "example",
"location_id": 11,
"items": [
11
]
}'
{
"success": true,
"message": "Wastage batch updated successfully",
"data": {
"wastage_reason": "expired",
"items_count": 1,
"total_cost": 75,
"batch_notes": null,
"location_id": 907,
"brand_id": null
},
"_note": "Code-derived."
}
POST /api/stock-transfer-invoices. Why: Create an inter-branch stock transfer request (materials from one branch to another) in status pending — the same document the dashboard "Add New Transfer" wizard creates. No stock moves: the source approves, dispatches and the destination receives it in the dashboard. Branch ids from list_locations (must be two different branches the key may use); lines from list_materials (qty in storage unit, avg_price as cost); check source stock with list_inventory / get_material_stock. For buying from a supplier use create_draft_purchase_order instead; for a branch asking head office for goods the dashboard uses order requests (create_stock_order_request). Response data is null — find the new transfer with list_stock_transfers (status=pending, newest first, match uuid). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_stock_transfer"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | New random UUID v4 you generate (the dashboard does uuidv4()). |
| from_location_id | number | required | SOURCE branch id (list_locations) — stock will leave here on dispatch. |
| to_location_id | number | required | DESTINATION branch id (list_locations) — stock will arrive here on receive. |
| items | array<object> | required | Lines to transfer (at least one; the server crashes with 500 when items is missing). |
| subtotal | number | optional | Sum of line subtotals. |
| vat | number | optional | VAT AMOUNT (not percent): subtotal × branch VAT % when "Include VAT" is ticked (dashboard default ticked, VAT % from the source branch config, 5 if unset), els… |
| payable | number | optional | subtotal + vat. |
| note | string | optional | Header note, max 300 chars. |
| delivery_date | string | optional | Planned delivery date YYYY-MM-DD (dashboard optional date picker). |
| expected_delivery_date | string | optional | Expected delivery date YYYY-MM-DD. |
| priority | enum: low, normal, high, urgent | optional | Priority label. |
| info_name | string | optional | Requester name shown as "User" in the list. |
| info_email | string | optional | Requester email. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_stock_transfer",
"uuid": "example",
"from_location_id": 11,
"to_location_id": 11,
"items": [
11
]
}'
{
"success": true,
"message": "Stock transfer created successfully",
"data": null
}
PUT /api/stock-transfer-invoices/update/:id. Why: Edit the header of a PENDING (or draft) transfer: note, delivery date, expected delivery date, priority. Lines are edited with update_stock_transfer_items. Get the id and check status with list_stock_transfers / get_stock_transfer first; approved/dispatched/received transfers are rejected by the server. No stock moves. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_stock_transfer"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Numeric transfer id (list_stock_transfers data.data[].id). |
| note | string | optional | New header note (max 300). |
| delivery_date | string | optional | New delivery date YYYY-MM-DD. |
| expected_delivery_date | string | optional | New expected delivery date YYYY-MM-DD. |
| priority | enum: low, normal, high, urgent | optional | New priority label. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_stock_transfer",
"id": 11
}'
{
"success": true,
"message": "Stock transfer request updated successfully",
"data": {
"transfer": {
"id": 7776,
"items_number": 2,
"subtotal": 5841.84,
"vat": 292.092,
"payable": 6133.932,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "Deliver before lunch",
"uuid": "fe1ecd13-f615-4326-b6a1-482b5353a3c6",
"created_at": "2026-08-12T10:48:07.000Z",
"customer_id": 437,
"from_location_id": 907,
"info_name": "Nexus Cafe",
"info_address": null,
"info_email": "admin@devnexus.co",
"info_phone": null,
"status": "pending",
"user": "Nexus Cafe",
"table_id": null,
"payment_method": null,
"order_id": null,
"invoice_id": 44,
"to_location_id": 922,
"customer_location_id": 907,
"email": null,
"delivery_date": "2026-09-28T00:00:00.000Z",
"user_id": 4734,
"is_edit": null,
"username": "Nexus Cafe",
"transfer_no": "STR-2026-01628",
"requested_by": 4734,
"approved_by": null,
"dispatched_by": null,
"received_by": null,
"requested_at": "2026-08-12T10:48:07.000Z",
"approved_at": null,
"dispatched_at": null,
"received_at": null,
"notes": null,
"priority": "high",
"expected_delivery_date": null,
"updated_at": "2026-08-12T10:48:20.000Z",
"rejected_by": null,
"rejected_at": null,
"parent_transfer_id": null
}
}
}
PUT /api/stock-transfer-invoices/items/:id. Why: Edit the lines of a PENDING (or draft) transfer: change qty / price / note, add a material, or remove a line — the dashboard transfer page "Save" before approval. Read the lines with get_stock_transfer (items[].id, material_id, qty, price) and send the full list back; materials from list_materials. On approved/dispatched transfers this same route is the dashboard dispatch/receive action, so never call it unless status is pending or draft. No stock moves while pending. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_stock_transfer_items"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Numeric transfer id (list_stock_transfers / get_stock_transfer). |
| items | array<object> | required | The COMPLETE line list after your change (non-empty). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_stock_transfer_items",
"id": 11,
"items": [
11
]
}'
{
"success": true,
"message": "Transfer items updated successfully",
"data": {
"transfer": {
"id": 7770,
"items_number": 2,
"subtotal": 127.256,
"vat": 6.3628,
"payable": 133.6188,
"discountamount": 0,
"discountpct": 0,
"paid": 0,
"change": 0,
"note": "",
"uuid": "51ed4a86-e033-45a1-9a34-795bb89eeb2f",
"created_at": "2026-07-30T07:07:35.000Z",
"customer_id": 437,
"from_location_id": 907,
"info_name": "Nexus Cafe",
"info_address": "",
"info_email": "admin@devnexus.co",
"info_phone": "",
"status": "pending",
"user": "Nexus Cafe",
"table_id": null,
"payment_method": null,
"order_id": null,
"invoice_id": 40,
"to_location_id": 908,
"customer_location_id": 907,
"email": "admin@devnexus.co",
"delivery_date": null,
"user_id": 4734,
"is_edit": null,
"username": null,
"transfer_no": "STR-2026-01621",
"requested_by": 4734,
"approved_by": null,
"dispatched_by": null,
"received_by": null,
"requested_at": "2026-07-30T07:07:36.000Z",
"approved_at": null,
"dispatched_at": null,
"received_at": null,
"notes": "{\"is_auto_created\":true,\"source_transfer_id\":7769,\"source_transfer_no\":\"STR-2026-01620\",\"source_transfer_uuid\":\"f6248006-ef35-4098-a3a7-88130bf7a8f0\",\"originating_po_uuids\":[\"1ba0b8cd-8495-4e43-80c9-f74dd58214f9\"],\"created_at\":\"2026-07-30T07:07:35.982Z\"}",
"priority": null,
"expected_delivery_date": null,
"updated_at": "2026-07-30T07:07:35.000Z",
"rejected_by": null,
"rejected_at": null,
"parent_transfer_id": null,
"from_location": "Default location",
"to_location": "Johar Town",
"requested_by_username": "Nexus Cafe",
"approved_by_username": null,
"dispatched_by_username": null,
"received_by_username": null,
"items": [
{
"id": 67280,
"name": "Gotcha Recipe",
"price": 0.214,
"qty": 80,
"subtotal": 17.12,
"stock_transfer_uuid": "51ed4a86-e033-45a1-9a34-795bb89eeb2f",
"location_id": 907,
"customer_id": 437,
"material_id": 120802,
"extras": "{\"inherited_from_transfer_id\":7769,\"inherited_po_uuid\":\"1ba0b8cd-8495-4e43-80c9-f74dd58214f9\",\"inherited_po_number\":\"PO-006919\"}",
"is_edit": "0",
"approved_qty": 100,
"dispatched_qty": null,
"received_qty": null,
"variance_qty": null,
"variance_reason": null,
"unit": "Kilogram",
"unit_id": null,
"item_status": null,
"notes": "reduced",
"created_at": "2026-07-30T07:07:35.000Z",
"updated_at": "2026-07-30T07:07:35.000Z",
"stock_transfer_id": null,
"brand_id": null,
"disposition_type": null,
"disposition_status": null,
"resolved_by": null,
"resolved_at": null,
"disposition_notes": null
},
{
"id": 67300,
"name": "Test Material",
"price": 11.0136,
"qty": 10,
"subtotal": 110.136,
"stock_transfer_uuid": "51ed4a86-e033-45a1-9a34-795bb89eeb2f",
"location_id": 907,
"customer_id": 437,
"material_id": 119196,
"extras": null,
"is_edit": "0",
"approved_qty": null,
"dispatched_qty": null,
"received_qty": null,
"variance_qty": null,
"variance_reason": null,
"unit": "Kilogram",
"unit_id": null,
"item_status": null,
"notes": null,
"created_at": "2026-07-30T07:07:35.000Z",
"updated_at": "2026-07-30T07:07:35.000Z",
"stock_transfer_id": null,
"brand_id": null,
"disposition_type": null,
"disposition_status": null,
"resolved_by": null,
"resolved_at": null,
"disposition_notes": null
}
]
}
}
}
POST /api/stock-order-request. Why: Create a branch order request (status Pending): a list of materials with quantity, cost and supplier that the branch wants purchased — the dashboard "Create Order Request" form. Nothing is ordered and no stock moves; in the dashboard a manager Declines it or clicks Create Draft PO (one draft PO per supplier). Materials from list_materials, suppliers from list_suppliers / list_supplier_materials, branch from list_locations. To create a purchase order directly use create_draft_purchase_order; to move stock between branches use create_stock_transfer. Response echoes the body (no id); find it with list_stock_order_requests (st=Pending) by uuid. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_stock_order_request"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | New random UUID v4 you generate (dashboard uuidv4()). |
| from_location_id | number | required | Requesting branch id (list_locations). |
| to_location_id | number | required | Destination branch — the dashboard sends the SAME id as from_location_id (the branch receives what it requested). |
| items | array<object> | required | Requested lines (at least one). |
| subtotal | number | optional | Σ line subtotals (stored as sent; not recomputed). |
| vat | number | optional | VAT AMOUNT = subtotal × branch VAT % (dashboard "Include VAT" default on), or 0. |
| payable | number | optional | subtotal + vat (stored as sent). |
| note | string | optional | Header note (max 300). |
| delivery_date | string | optional | Requested delivery date-time "YYYY-MM-DD HH:mm:ss" (dashboard date picker, default now). |
| info_name | string | optional | Requester name (dashboard: logged-in username). |
| info_email | string | optional | Requester email. |
| table_id | number | optional | Legacy; dashboard sends 0. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_stock_order_request",
"uuid": "example",
"from_location_id": 11,
"to_location_id": 11,
"items": [
11
]
}'
{
"uuid": "b8a4c7f2-5d61-4f3e-9c0a-1e2d3f4a5b6c",
"from_location_id": 907,
"to_location_id": 907,
"subtotal": 6000,
"vat": 300,
"payable": 6300,
"note": "",
"info_name": "Nexus Cafe",
"delivery_date": "2026-09-30 09:00:00",
"table_id": 0,
"items": [
{
"material_id": 112157,
"name": "SRC-BT-86CBPR-SG3",
"qty": 2000,
"price": 3,
"subtotal": 6000,
"unit_name": "Pieces",
"supplier_id": 881
}
],
"status": "Pending",
"customer_id": 437,
"location_id": 907
}
PUT /api/stock-order-request/update/:uuid. Why: Edit a PENDING order request — lines (material, qty, price, supplier), totals, note, delivery date — like the dashboard Edit page / Draft PO page "Save changes". Read it first with get_stock_order_request; the line list is replaced entirely. MCP strips status / reason / reject_reason, so the request stays Pending; declining or creating the draft PO stays in the dashboard. No stock moves. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_stock_order_request"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Order request uuid (list_stock_order_requests). |
| items | array<object> | required | The COMPLETE new line list — all existing lines are deleted and these inserted (a line you omit is removed; a field you omit becomes null). |
| subtotal | number | optional | New Σ line subtotals. |
| vat | number | optional | New VAT AMOUNT (subtotal × branch VAT % or 0). |
| payable | number | optional | New subtotal + vat. |
| note | string | optional | Header note. |
| delivery_date | string | optional | "YYYY-MM-DD HH:mm:ss"; null/"" clears; omitted → unchanged. |
| from_location_id | number | optional | Requesting branch (list_locations). |
| to_location_id | number | optional | Destination branch (dashboard: same as from_location_id, or the PO destination on the Draft PO page). |
| info_name | string | optional | Requester name. |
| info_email | string | optional | Requester email. |
| table_id | number | optional | Legacy (dashboard sends the existing value or 0). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_stock_order_request",
"uuid": "example",
"items": [
11
]
}'
{
"message": "Order updated successfully",
"data": {
"uuid": "59c4fb6e-fb85-4ab1-ac0b-4338bd97b70a",
"subtotal": 4500,
"payable": 4725,
"vat": 225,
"status": "Pending",
"items": [
{
"material_id": 112157,
"name": "SRC-BT-86CBPR-SG3",
"qty": 1500,
"price": 3,
"subtotal": 4500,
"unit_name": "Pieces",
"supplier_id": 881
}
]
}
}
POST /api/work-orders. Why: Plan a production batch of STOCKABLE recipes at one branch, as a DRAFT (MCP forces status "draft"): what to produce (recipe, output qty) and what it will consume (one material line per ingredient, computed from get_recipe). No stock moves now: the user moves it to In Progress and completes it in the dashboard, which then consumes the material lines and adds the output to the recipes' prepared materials (list_inventory_movements). Each recipes[] line must carry materials[] (one per recipe ingredient, yield-adjusted qty, unit, unit_price, total_price) — the server never derives them, and completion consumes only these lines. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_work_order"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| production_date | string | required | Planned production day YYYY-MM-DD (NOT NULL). |
| expected_completion | string | optional | Expected completion day YYYY-MM-DD; omitted/null → none. |
| location_id | number | required | ONE branch id (list_locations) — where materials are consumed and the item produced (NOT NULL; completion refuses an order without it). |
| notes | string | optional | Free-text note. |
| net_total | number | optional | Planned cost = Σ recipes[].total_cost (stored as sent; 0/omitted → null). |
| recipes | array<object> | required | Recipes to produce, at least one (none → 500 "No recipes provided"). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_work_order",
"production_date": "example",
"location_id": 11,
"recipes": [
11
]
}'
{
"success": true,
"message": "Work order created successfully",
"data": {
"id": 1738,
"work_order_no": "PRD-2026-01660"
}
}
PUT /api/work-orders/:id. Why: Edit a DRAFT work order: dates, branch, notes and the full set of recipe + material lines (full replace; net_total is recomputed from the lines). MCP strips `status`. Non-draft orders are refused. No stock moves. Start from get_work_order. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_work_order"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Work order id (list_work_orders); must be a draft (get_work_order header.status). |
| production_date | string | required | Planned production day YYYY-MM-DD (NOT NULL). |
| expected_completion | string | optional | Expected completion day YYYY-MM-DD; omitted/null → none. |
| location_id | number | required | ONE branch id (list_locations) — where materials are consumed and the item produced (NOT NULL; completion refuses an order without it). |
| notes | string | optional | Free-text note. |
| recipes | array<object> | required | FULL list of lines — replaces all recipe and material lines. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_work_order",
"id": 11,
"production_date": "example",
"location_id": 11,
"recipes": [
11
]
}'
{
"success": true,
"message": "Work order updated successfully",
"data": {
"production_date": "2026-09-27",
"expected_completion": "2026-09-28",
"notes": "Morning batch",
"net_total": 22.3566,
"location_id": 907
}
}
PUT /api/users/:id. Why: Edit one staff user — what the dashboard user edit form saves: name, email, role (role_id from list_roles), branches (allow_locations + default location_id from list_locations), dashboard modules, app ios_modules, the positional POS switch string `permissions`, max discount, report e-mails, default brand (list_selling_brands) / channel and the named POS switches. Admin-only. It is a FULL-RECORD save: read the user with list_users first and resend every field. For the session user's own POS switches one at a time prefer update_user_permissions; role-only change: assign_user_role. Full replace: omitted role_id, allow_locations, permissions, max_discount_permit, report flags, image, organization_customer_ids → NULL; brand_id / dining_guest_count → 0; ios_modules → tenant default. Resend the list_users row. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_user"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | string | required | User id (list_users [].id). |
| username | string | required | Display name (dashboard "Name"). |
| string | required | Login email. | |
| role_id | string | optional | Role id (list_roles). |
| role | enum: admin, staff | optional | Legacy role text "admin" | "staff" (what non-master dashboards send). |
| location_id | number | required | Default branch id (list_locations) — one id, not csv. |
| allow_locations | string | required | CSV of every branch id the user may use, e.g. |
| modules | string | required | ';'-separated 0/1 dashboard/app module flags by position: 0 Dashboard, 1 POS, 2 KDS, 3 Sales, 4 Reporting, 5 Products, 6 Inventory, 7 Config, 8 Preview Invoice… |
| ios_modules | string | required | ';'-separated 0/1 app feature flags by position: 0 is_kds_enable, 1 is_daily_sale_report_enable, 2 is_order_hisotry_screen_enable, 3 is_sms_enable, 4 is_cloud_… |
| permissions | string | required | ';'-separated 0/1 POS switches by position (NOT role permissions): 0 print_receipt, 1 kds_auto_receipt, 2 invoice_auto_receipt, 3 cart_options, 4 print_and_sav… |
| max_discount_permit | number | optional | Max manual discount % at the POS (0-100). |
| can_preview_invoice | number | optional | 0 | 1. |
| is_daily_report | number | optional | 0 | 1. |
| is_weekly_report | number | optional | 0 | 1. |
| is_monthly_report | number | optional | 0 | 1. |
| is_ingredient_disabled | number | optional | 0 | 1. |
| image | string | optional | Profile image: the file name, or the list_users URL (the server keeps the part after "uploads/"). |
| organization_customer_ids | string | optional | CSV of linked tenant ids (dashboard "Linked Customers"); "" = none. |
| brand_id | number | optional | Default POS brand (list_selling_brands brand_id; 0 = none). |
| dining_guest_count | number | optional | 0 | 1. |
| channel_id | number | optional | Default sales channel (list_selling_channels channel_id; 0 = none). |
| print_no_of_times | number | optional | Receipt copies (≥ 1). |
| fraction_digits | number | optional | Quantity fraction digits (not money). |
| receipt_format | number | optional | Receipt template 0-3; other values → 0. |
| row_discount | number | optional | 0 | 1. |
| language | string | optional | App language code, e.g. |
| zebra_ip | string | optional | Zebra label-printer IP (null → ""). |
| zebra_port | number | optional | Zebra label-printer port (non-numbers → 0). |
| cloud_target_user_id | number | optional | User id (list_users) whose device receives this user's cloud prints; 0 = none. |
| cloud_target_device_id | string | optional | Device id that receives this user's cloud prints; null = none. |
| station_id | number | optional | KDS station id (0 = none). |
| currency | string | optional | BRANCH setting, not a user field: upserts the config `currency` of the user's location_id for everyone at that branch ("" clears an existing row). |
| orderhistory_print_receipt | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| show_receipt_screen | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_brand_groups | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_extras_enble | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| display_modifier_titles | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| modifier_sort_min_required_desc | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| print_only_save_kds_orders | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| print_note_on_receipt | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| order_type | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| open_dine_in | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| hide_cart_pay_button | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| tip_amount | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| print_void_orders | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| has_void_order_permission | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| hide_clear_checkout | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| kds_instruction | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| pay_default_usd | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_business_logo | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_order_type | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_is_modified | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_biller_info | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_total_before_vat | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_vat_info | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_net_total | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_exchange_rate | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_dine_guest_count | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| rpt_paid_at | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| esc_command | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| qr_code_on_print_receipt | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| delivery_detail_enable | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_attributed_receipt | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| exchange_format | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_channels_enable | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_cds_enable | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| external_order_print_receipt | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| text_receipt | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| each_category_kds_print | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| user_product_sales_counter | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_change_payment_method_enabled | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| skip_zero_quantity_items | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| skip_zero_amount_items | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| settle_wallet_topup | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| print_deleted_item_on_receipt | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| block_print_until_order_open | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| draft_order | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| open_price_pincode | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_transfer_enabled | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| sms_invoice | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| offline_order_sync | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_multi_location_allowed | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_qr_payment | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_kds_expeditor | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| unify_brands | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| dinein_serving_role | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| ingredient_stay_standalone | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| reservation_with_dine_in | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| pay_zero_amount | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| receipt_translation | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| product_translation | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| category_translation | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| print_invoice_translation | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| print_kds_translation | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| splitefire | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| allegy_item | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| one_table_one_order | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_socket_server | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_client | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| web_local_network_printer | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_shift_management | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| shift_transactions | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| eod_shift_report | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| shift_z_report | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| view_estimated_cash | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| external_order_ready | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| acknowledge_dine_table_edit | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| lock_screen | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| is_soldout | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
| force_crm_user | number | optional | 0 | 1 (1, "1" or true → 1, anything else → 0). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_user",
"id": 11,
"username": "example",
"email": "example",
"location_id": 11,
"allow_locations": "example",
"modules": "example",
"ios_modules": "example",
"permissions": "example"
}'
{
"data": {
"id": "4789",
"username": "Sara Cashier",
"email": "sara.cashier@example.com",
"role_id": 32,
"location_id": 907,
"allow_locations": "907,909,908",
"modules": "1;1;1;1;1;1;1;1;1;1;1",
"ios_modules": "1;1;1;1;1;1;1;1;1",
"permissions": "1;0;1;1;0;0;0;0;1;1;0;0;0;0;0;1;0;0;0;1;0;0;0;0;0;0",
"max_discount_permit": 30,
"can_preview_invoice": 1,
"is_daily_report": 0,
"is_weekly_report": 0,
"is_monthly_report": 0,
"is_ingredient_disabled": 0,
"image": "",
"organization_customer_ids": "",
"brand_id": 0,
"dining_guest_count": 0,
"role": "staff",
"is_admin": 0
},
"message": "User updated successfully"
}
POST /api/users/assign-role. Why: Change only a staff user's role (users.role_id) without resending the whole record — the lighter alternative to update_user. role_id from list_roles, user_id from list_users. The role's permissions decide which dashboard pages the user can open. Unlike update_user it changes role text and is_admin only for roles whose name contains "admin"; moving an admin to a non-admin role leaves is_admin 1 — use update_user with role_id for that. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"assign_user_role"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| user_id | string | required | User id (list_users [].id). |
| role_id | string | required | Role id (list_roles [].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "assign_user_role",
"user_id": 11,
"role_id": 11
}'
{
"success": true,
"message": "User role assigned successfully."
}
POST /api/v2/crm/users. Why: Create a customer from the POS — the web-pos "Add user" form. Only first_name is required; phone and email must be unique in the tenant. Returns the new id (user_id) to use as crm_user_id on the order. The customer is tenant-wide (visible at every branch and on the dashboard list_crm_users). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_pos_crm_user"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| first_name | string | required | REQUIRED. |
| last_name | string | optional | Optional. |
| mobile_number | string | optional | Optional. |
| string | optional | Optional. | |
| dob | string | optional | Optional. |
| gender | enum: male, female | optional | Optional. |
| delivery_area | string | optional | Optional. |
| city | string | optional | Optional. |
| country | string | optional | Optional. |
| preferred_currency | string | optional | Optional. |
| is_push_notification_enable | enum: 0, 1 | optional | Optional. |
| is_mobile_number_verified | enum: 0, 1 | optional | Optional. |
| latitude | number | optional | Optional. |
| longitude | number | optional | Optional. |
| trn_number | string | optional | Optional. |
| company_name | string | optional | Optional. |
| thumbnail | string | optional | Optional. |
| earn_points | number | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_pos_crm_user",
"first_name": "example"
}'
{
"status": true,
"message": "User has been created successfully.",
"user_id": 64900
}
POST /api/v2/crm/users/:id. Why: Edit a customer from the POS — the web-pos "Edit" form (fix a phone number, add a delivery area before a delivery order). Partial update: only the fields you send with a non-empty value change. Tenant-checked by id. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_pos_crm_user"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | string | required | Customer id (list_pos_crm_users data.users[].id). |
| first_name | string | optional | Optional. |
| last_name | string | optional | Optional. |
| mobile_number | string | optional | Optional. |
| string | optional | Optional. | |
| dob | string | optional | Optional. |
| gender | enum: male, female | optional | Optional. |
| delivery_area | string | optional | Optional. |
| city | string | optional | Optional. |
| country | string | optional | Optional. |
| preferred_currency | string | optional | Optional. |
| is_push_notification_enable | enum: 0, 1 | optional | Optional. |
| is_mobile_number_verified | enum: 0, 1 | optional | Optional. |
| latitude | number | optional | Optional. |
| longitude | number | optional | Optional. |
| trn_number | string | optional | Optional. |
| company_name | string | optional | Optional. |
| thumbnail | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_pos_crm_user",
"id": 11
}'
{
"status": true,
"message": "User has been updated successfully"
}
POST /api/crm-users. Why: Create a customer from the dashboard. Tenant-wide (every branch). Rejects a phone or email already used by another customer. Unlike create_pos_crm_user it stores no placeholders (missing dob/gender stay null). Returns user_id = crm_user_id. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_crm_user"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| first_name | string | required | REQUIRED. |
| last_name | string | optional | Optional. |
| company_name | string | optional | Optional. |
| string | optional | Optional. | |
| mobile_number | string | optional | Optional. |
| dob | string | optional | Optional. |
| delivery_area | string | optional | Optional. |
| city | string | optional | Optional. |
| country | string | optional | Optional. |
| preferred_currency | string | optional | Optional. |
| latitude | number | optional | Optional. |
| longitude | number | optional | Optional. |
| gender | enum: male, female | optional | Optional. |
| thumbnail | string | optional | Optional. |
| trn_number | string | optional | Optional. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_crm_user",
"first_name": "example"
}'
{
"success": true,
"message": "CRM User created successfully",
"user_id": 64900
}
PUT /api/crm-users/:id. Why: Replace a customer's profile from the dashboard. FULL REPLACE: every field below that you omit is set to NULL — read get_crm_user first and send every value you want to keep. Use it to CLEAR a field (update_pos_crm_user cannot). Does not touch loyalty points, wallet, flags or purchase stats. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_crm_user"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | string | required | Customer id (crm_user_id). |
| first_name | string | required | REQUIRED. |
| last_name | string | optional | Last name. |
| company_name | string | optional | Company name. |
| string | optional | Valid email. | |
| mobile_number | string | optional | Phone (digits, optional leading +). |
| dob | string | optional | Date of birth after 1960-01-01. |
| delivery_area | string | optional | Delivery area (letters, digits, spaces, commas, hyphens). |
| city | string | optional | City. |
| country | string | optional | Country. |
| preferred_currency | string | optional | Currency code. |
| latitude | number | optional | Latitude. |
| longitude | number | optional | Longitude. |
| gender | enum: male, female | optional | "male" or "female". |
| thumbnail | string | optional | Image path as returned by get_crm_user. |
| trn_number | string | optional | Tax registration number. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_crm_user",
"id": 11,
"first_name": "example"
}'
{
"success": true,
"message": "CRM User updated successfully"
}
POST /api/roles. Why: Create a role with a set of dashboard permissions — what the Roles & Permissions page's "New role" / Duplicate does. Assign it to users with update_user (role_id) or assign_user_role. Permission ids come from list_roles / get_role. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"create_role"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| name | string | required | Role name, unique in the tenant (409 when taken). |
| description | string | optional | Free text shown on the Roles page. |
| permissions | array<string> | optional | Permission ids (list_roles / get_role permissions[].id) — the COMPLETE set the role should grant. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "create_role",
"name": "Iced Latte"
}'
{
"success": true,
"message": "Role created successfully",
"data": {
"id": 190,
"name": "Shift Supervisor",
"description": "Brands and users read access",
"customer_id": 437,
"is_deleted": 0,
"created_at": "2026-09-26T09:00:00.000Z",
"updated_at": "2026-09-26T09:00:00.000Z",
"permissions": [
{
"id": 403,
"name": "Access",
"value": "dashboard.access",
"group": "Dashboard",
"sub_group": "Dashboard Access",
"action": null
},
{
"id": 405,
"name": "Brands",
"value": "products.brands",
"group": "Products",
"sub_group": "Brands",
"action": null
},
{
"id": 625,
"name": "List",
"value": "users.list",
"group": "Users",
"sub_group": "List",
"action": null
}
]
}
}
PUT /api/roles/:id. Why: Rename a role or change the dashboard permissions it grants — what Save on the Roles & Permissions page does. Affects every user with this role_id (list_users) immediately. Read it with get_role first. Send name AND description AND the complete permissions list like the dashboard: permissions replace the whole set ([] removes all), and a body with neither name nor description fails with 500. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"update_role"} BEFORE calling this tool. Safety: write. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | string | required | Role id (list_roles data[].id). |
| name | string | required | Role name (409 if another role has it). |
| description | string | optional | Free text. |
| permissions | array<string> | optional | Permission ids (list_roles / get_role permissions[].id) — the COMPLETE set the role should grant. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "update_role",
"id": 11,
"name": "Iced Latte"
}'
{
"success": true,
"message": "Role updated successfully",
"data": {
"id": 182,
"name": "Manager",
"description": "Store manager",
"customer_id": 437,
"is_deleted": 0,
"created_at": "2025-12-13T13:24:49.000Z",
"updated_at": "2025-12-13T13:24:49.000Z",
"permissions": [
{
"id": 139,
"name": "Attendance",
"value": "module_attendance",
"group": "module_access",
"sub_group": null,
"action": null
},
{
"id": 405,
"name": "Brands",
"value": "products.brands",
"group": "Products",
"sub_group": "Brands",
"action": null
},
{
"id": 406,
"name": "Add",
"value": "products.brands.add",
"group": "Products",
"sub_group": "Brands",
"action": "add"
}
]
}
}
DELETE /api/products/:id. Why: Soft-delete a product: sets is_deleted = 1 so it disappears from list_products, list_selling_products and the menus at every branch; the row, branch rows, modifier links and history stay (there is no undelete tool). Prefer archive_product when it may come back, or location_prices[].is_available=false (update_product) to hide it at one branch. Get the id from list_products. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_product"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Product id (list_products data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_product",
"id": 11
}'
{
"status": true,
"message": "Product deleted successfully"
}
DELETE /api/categories/:id. Why: Permanently delete an unused catalog category (dashboard trash icon / Edit page Delete). Refused while any active product has it in product.category_id (check get_category linked_products_count first; move those products with update_product, or set is_active 0 via update_category instead). Irreversible. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_category"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Category id (list_categories / get_category). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_category",
"id": 11
}'
{
"status": true,
"message": "category deleted successfully."
}
DELETE /api/modifiers/:id. Why: Permanently remove a modifier group (dashboard trash icon). Also deletes its product links (product_modifiers) at every branch. Does NOT delete its items (they stay as orphans — delete or re-group them first with delete_modifier_item / update_modifier_item), its modifiers_locations rows or product_modifier_item_settings. Past orders (sync_invoice lines) keep their own copy. Confirm with the user first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_modifier"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Modifier group id to delete (list_modifiers [].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_modifier",
"id": 11
}'
{
"status": true,
"message": "modifier deleted successfully"
}
DELETE /api/modifierItems/:id. Why: Permanently remove one option (dashboard row menu › Delete). Deletes the modifier_Items row and its modifier_item_assignments; its branch rows, kitchen instructions, recipes and per-product settings (product_modifier_item_settings) are left behind. Past orders (sync_invoice lines) keep their own copy. Confirm with the user first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_modifier_item"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Modifier item id to delete (list_modifier_items [].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_modifier_item",
"id": 11
}'
{
"status": true,
"message": "modifierItem deleted successfully"
}
DELETE /api/brands/:id. Why: Permanently delete a brand row. No safety checks: nothing blocks deleting a brand that is in use, and its groups, categories and products are untouched (groups just lose the brand). Prefer editing with update_brand; confirm the id via get_brand (customer_id must be yours) first. Irreversible. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_brand"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Brand id (list_brands). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_brand",
"id": 11
}'
{
"status": true,
"message": "Brand deleted successfully."
}
DELETE /api/groups/:id. Why: Permanently delete a group (hard delete; no undo). Its brand loses those categories on the POS menu. Consumes a group id from list_groups. Catalog management (dashboard) tool. For menu scoping on the POS use list_selling_brands, which already returns each brand's groups with their category ids. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_group"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Group id (list_groups data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_group",
"id": 11
}'
{
"status": true,
"message": "Group deleted successfully"
}
DELETE /api/products/:id. Why: Soft-delete an add-on (is_deleted = 1): it disappears from list_addons, list_selling_addons and the POS, but branch rows, links and the row stay (get_addon still returns it with is_deleted = 1); there is no undelete tool. To hide it reversibly use archive_product (or update_addon with is_archive = 1). Takes an id from list_addons. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_addon"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Add-on id (list_addons data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_addon",
"id": 11
}'
{
"status": true,
"message": "Product deleted successfully"
}
DELETE /api/label/:id. Why: Permanently delete a label and its branch links (dashboard Labels → Delete). Refused while an active product uses it: find them with list_products label_id=<id> and move them with update_product first. Archived products, menus and past sale lines (sync_invoice / list_orders_history items[].label_id) keep the dead id. Get the id from list_labels. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_label"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Label id (list_labels data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_label",
"id": 11
}'
{
"message": "Label deleted successfully"
}
DELETE /api/deals/:id. Why: Permanently remove a deal: hard-deletes the products row, its slots (deal_categories) and channel prices. Id from list_deals. Past orders keep their combo lines (sync_invoice history is untouched). To only hide a deal from the POS use archive_product on the deal id instead. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_deal"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Deal id (list_deals items[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_deal",
"id": 11
}'
{
"status": true,
"message": "Deal deleted successfully"
}
DELETE /api/channels/:id. Why: Hard-delete a channel and every product price for it (product_channel_price). No in-use check: orders keep their channel_id/channel_name, discounts keep the stale id in channel_ids, and channels_locations rows are left orphaned. Linkage: a channel id is referenced by list_selling_products data.products[].price_levels[].channel_id (per-channel price, product_channel_price), by orders (channel_id + channel_name, stored verbatim by sync_invoice — web-pos sends 0/""), and by discount codes channel_ids (list_discounts / get_user_extras data.discount_code[]). Branch availability lives in channels_locations (location_id in these tools); master channels have channels.location_id NULL. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_channel"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Channel id (list_channels). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_channel",
"id": 11
}'
{
"status": true,
"message": "Channels deleted successfully"
}
DELETE /api/menus/:id. Why: Permanently delete a menu and its category, branch and product links (hard delete, no restore). Products and categories themselves are not touched. Prefer update_menu with is_active: 0 to hide it. Menus are dashboard records only: nothing else in SysPOS reads them today. The POS (web-pos) never calls /menus, list_selling_products / list_selling_categories do not look at menus, and the aggregator syncs (Deliverect, Grubtech, Otter, LimeTray) build their menus from categories (sync_* flags, list_categories) and products, not from these menus. Adding or removing a product here does not change what a branch sells; for that use update_product (location_prices) or the category tools. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_menu"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Menu id (list_menus data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_menu",
"id": 11
}'
{
"success": true,
"message": "Menu deleted successfully"
}
DELETE /api/coupons/:id. Why: Hard-delete a coupon with its branch and product/category links. Past invoices keep their coupon_code. To stop a coupon but keep history, prefer update_coupon is_active: 0. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_coupon"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Coupon id (list_coupons data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_coupon",
"id": 11
}'
{
"status": true,
"message": "Coupon has been successfully deleted.",
"data": "1001"
}
DELETE /api/discounts/:id. Why: Permanently delete a discount (discount code); it disappears from the POS (get_user_extras) on its next refresh. There is no archive — to stop it temporarily use update_discount with is_active 0. Past orders keep their stored discount amounts. Id from list_discounts. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_discount"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Discount id (list_discounts data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_discount",
"id": 11
}'
{
"status": true,
"message": "Discount has been successfully deleted.",
"data": "2400"
}
DELETE /api/payment-methods/:id. Why: Permanently delete a payment method and all its branch links. NO in-use check (the guard is commented out) — past invoices keep the name string, but no branch can settle with it any more. To stop using it at one branch, update_payment_method with a smaller location_id list; to pause it, set is_enable = 0. Confirm with the user first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_payment_method"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Payment method id (list_payment_methods). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_payment_method",
"id": 11
}'
{
"status": true,
"message": "Payment method \"Wallet\" deleted successfully."
}
DELETE /api/order-type/:id. Why: Permanently delete a custom order type for the whole tenant (all branches lose it). System defaults (is_system_default = 1) cannot be deleted. There is NO in-use check — past invoices keep the name string, but the type disappears from the POS. Confirm with the user first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_order_type"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Order type id (list_order_types, must have is_system_default = 0). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_order_type",
"id": 11
}'
{
"status": true,
"message": "Order type deleted successfully"
}
DELETE /api/locations/:id. Why: Permanently delete a branch that was never used. The server refuses (status false) when ANY row still points at the branch in config, customers, inventory, invoices, invoice_items, orders, order_items, materials, product_materials, products, categories, tables or users — so a branch that sold anything, has staff, tables, or even saved business info / hours config cannot be deleted. On success it hard-deletes the locations row and removes the id from the calling user's allow_locations. Confirm with the user first; there is no undo and no soft delete. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_location"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Location id (list_locations[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_location",
"id": 11
}'
{
"status": false,
"message": "You can't delete this location. It's being used."
}
DELETE /api/materials/:id. Why: Deactivate a material: is_active = 0 and ALL its branch rows (stock + cost) are deleted, no inventory movement is posted and recipe links stay. Use it only for materials that are no longer bought or used — check get_material_used_in_recipes (and product BOMs) first, and zero stock through counts/wastage/transfers if you need an audit trail. Cannot be undone through the API (update_material cannot reactivate; a new create_material makes a new id). Never use DELETE /materials/all. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_material"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Material id (list_materials). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_material",
"id": 11
}'
{
"status": true,
"message": "Material deactivated and location mappings removed"
}
DELETE /api/inventory/categories/:id. Why: Soft-delete an EMPTY material category (no active materials assigned and no subcategories). Move materials first with update_material (category_id) and delete subcategories first. Taxonomy only; no stock impact. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_material_category"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Material category id (list_material_categories). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_material_category",
"id": 11
}'
{
"success": true,
"message": "Material category deleted successfully",
"data": {
"id": 6,
"deleted": true
}
}
DELETE /api/suppliers/:id. Why: Permanently remove a supplier from the directory. Only for suppliers that are no longer used: check list_supplier_invoices sid=<id> (and list_suppliers total_orders / items_in_catalog) first — the API's own "in use" check only looks at legacy tables and will delete a supplier that still has purchase orders or material links. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_supplier"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Supplier id from list_suppliers. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_supplier",
"id": 11
}'
{
"status": true,
"message": "supplier deleted successfully."
}
DELETE /api/recipe/:id. Why: Permanently delete a recipe: its lines, cost snapshot and product links, plus its prepared material and that material's rows at every branch (including any stock on hand). No undo. Linked products stop deducting ingredients on sale; other recipes that use it as a sub-recipe keep a dangling line. Check get_recipe (linked_products) and get_material_used_in_recipes on its prepared material first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_recipe"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Recipe id (list_recipes / get_recipe). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_recipe",
"id": 11
}'
{
"success": true,
"message": "Recipe deleted successfully"
}
DELETE /api/inventory-count-sheets/delete-sheet/:id. Why: Remove a count-sheet template (soft delete: is_deleted = 1). Existing counts made from it are untouched (counts do not store the sheet id) and no stock changes. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_inventory_count_sheet"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Count sheet id (list_inventory_count_sheets → data[].id). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_inventory_count_sheet",
"id": 11
}'
{
"status": true,
"message": "Inventory count sheet deleted successfully"
}
DELETE /api/supplier-invoices/:uuid. Why: Permanently delete a DRAFT purchase order and its lines (a receive-shortfall draft or a draft generated from a stock order request). The API refuses non-drafts (403), so live POs cannot be deleted. Check the draft with get_purchase_order and confirm with the user first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_draft_purchase_order"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| uuid | string | required | Draft uuid (list_supplier_invoices st=draft / create_draft_purchase_order → draft.uuid). |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_draft_purchase_order",
"uuid": "example"
}'
{
"success": true,
"message": "Draft deleted successfully"
}
DELETE /api/wastage/:id. Why: Permanently delete a DRAFT wastage batch and its lines (hard delete). The API does NOT check the status — it would also delete pending or approved batches without reversing their stock-out — so confirm approval_status is draft with get_wastage first. Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_wastage_draft"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Wastage batch id (list_wastage / get_wastage) whose approval_status is draft. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_wastage_draft",
"id": 11
}'
{
"success": true,
"message": "Wastage batch deleted successfully",
"data": {
"success": true
}
}
DELETE /api/work-orders/:id. Why: Permanently delete a DRAFT work order with its recipe and material lines (no stock was moved by a draft). The server does not check the status — never use it on in_progress / completed / cancelled orders (the dashboard cancels those instead, which has no MCP tool). Full guide (every response field, rules, caveats, examples): call describe_tool {"name":"delete_work_order"} BEFORE calling this tool. Safety: destructive. customer_id is taken from the MCP session — do not send it.
| Parameter | Type | Description | |
|---|---|---|---|
| id | number | required | Work order id (list_work_orders); must be a draft — check get_work_order header.status first. |
curl -X POST https://pos-mcp.syspos.ae/api/call \
-H "Authorization: Bearer syspos_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tool": "delete_work_order",
"id": 11
}'
{
"success": true,
"message": "Work order deleted successfully",
"data": {
"success": true
}
}