نقاط النهاية والصلاحيات
| الطلب | الصلاحية |
|---|---|
PUT /places/{place_id}/menu |
catalog:write |
PATCH /places/{place_id}/menu |
catalog:write |
DELETE /places/{place_id}/menu |
catalog:write |
GET /places/{place_id}/menu |
catalog:write |
GET /places/{place_id}/menu/hashes |
catalog:write |
POST /menus/reconcile |
catalog:write |
/places/{place_id}/products… قريبا |
catalog:write |
نموذجان، لا نموذج واحد
القائمة (مطاعم ومقاه): وثيقة صغيرة تقرأ كاملة. فيها أقسام، وأصناف بأحجام، ومجموعات إضافات لها حد أدنى وحد أعلى، ووجبات مركبة، وجداول أوقات. تنشر وتعدل كوحدة واحدة لها رقم نسخة.
المنتجات (متاجر): كتالوج كبير بآلاف الأصناف، يتصفح بالتصنيفات والفلاتر. كل وحدة تباع متغير له SKU وباركود وسعر ومخزون خاص. لا يرسل كوثيقة واحدة، بل على دفعات.
لو دمجناهما لخسرت القوائم قواعد الإضافات، أو خسرت المتاجر مخزون كل متغير. لذلك لكل منهما نموذج. ولكل مكان مصدر واحد: قائمة أو منتجات.
صيغة القائمة CM v1
{
"schema": "aurmap.menu/1",
"currency": "SAR",
"default_language": "ar",
"languages": ["ar", "en"],
"menu_url": "https://order.partner.example/al-rayhan-olaya",
"identity": {
"name": { "ar": "مطعم الريحان", "en": "Al Rayhan" },
"tagline": { "ar": "برجر وعصائر طازجة", "en": "Burgers and fresh juice" },
"logo": "https://cdn.partner.example/logo.png",
"cover": "https://cdn.partner.example/cover.jpg",
"slider": ["https://cdn.partner.example/slide-1.jpg"],
"template": "classic",
"theme": "custom",
"colors": { "background": "#F2F4F7", "text": "#041731", "card": "#FFFFFF", "accent": "#C8102E", "on_accent": "#FFFFFF" },
"font": "Tajawal",
"button_shape": "rounded"
},
"tax": { "prices_include_vat": true, "vat_percent": 15, "service_charge_percent": 0 },
"aurmap_ordering": { "enabled": true, "pickup": true, "delivery": false, "cash": true },
"sections": [
{ "id": "burgers", "parent_id": null, "name": { "ar": "البرجر", "en": "Burgers" }, "image": null, "sort": 0, "schedule_id": null },
{ "id": "juices", "parent_id": null, "name": { "ar": "العصائر", "en": "Juices" }, "image": null, "sort": 1, "schedule_id": null },
{ "id": "desserts", "parent_id": null, "name": { "ar": "الحلى", "en": "Desserts" }, "image": null, "sort": 2, "schedule_id": "evening" },
{ "id": "meals", "parent_id": null, "name": { "ar": "الوجبات", "en": "Meals" }, "image": null, "sort": 3, "schedule_id": null }
],
"items": [
{
"id": "chicken-burger",
"type": "menu_item",
"section_id": "burgers",
"sort": 0,
"name": { "ar": "برجر دجاج", "en": "Chicken burger" },
"description": { "ar": "صدر دجاج مقرمش، خس، صوص البيت", "en": "Crispy chicken breast, lettuce, house sauce" },
"image": "https://cdn.partner.example/img/chicken-burger.jpg",
"price_minor": 2300,
"original_price_minor": null,
"calories": 540,
"prep_min": 10,
"prep_max": 15,
"label": { "name": { "ar": "الأكثر طلبا", "en": "Best seller" } },
"warnings": [ { "id": "gluten", "name": { "ar": "يحتوي على الغلوتين", "en": "Contains gluten" }, "icon": null } ],
"sizes": [
{ "id": "m", "name": { "ar": "وسط", "en": "Medium" }, "price_minor": 2300, "original_price_minor": null, "calories": 540 },
{ "id": "l", "name": { "ar": "كبير", "en": "Large" }, "price_minor": 2990, "original_price_minor": null, "calories": 720 }
],
"group_ids": ["sauce", "extras"],
"order_types": { "pickup": true, "delivery": true, "dine_in": true },
"recommended_ids": ["orange-juice"],
"schedule_id": null,
"order_url": null
},
{
"id": "orange-juice",
"type": "menu_item",
"section_id": "juices",
"sort": 1,
"name": { "ar": "عصير برتقال", "en": "Orange juice" },
"price_minor": 920,
"sizes": [
{ "id": "s", "name": { "ar": "صغير", "en": "Small" }, "price_minor": 920 },
{ "id": "l", "name": { "ar": "كبير", "en": "Large" }, "price_minor": 1380 }
],
"group_ids": [],
"order_types": { "pickup": true, "delivery": true, "dine_in": true }
},
{
"id": "kunafa",
"type": "menu_item",
"section_id": "desserts",
"sort": 2,
"name": { "ar": "كنافة", "en": "Kunafa" },
"price_minor": 1840,
"sizes": [],
"group_ids": [],
"order_types": { "pickup": true, "delivery": true, "dine_in": true }
},
{
"id": "chicken-burger-meal",
"type": "menu_item",
"section_id": "meals",
"sort": 3,
"name": { "ar": "وجبة برجر دجاج", "en": "Chicken burger meal" },
"price_minor": 3000,
"original_price_minor": 3220,
"sizes": [],
"group_ids": ["sauce"],
"order_types": { "pickup": true, "delivery": true, "dine_in": true }
}
],
"groups": [
{
"id": "sauce", "name": { "ar": "الصوص", "en": "Sauce" }, "required": true, "min": 1, "max": 1,
"options": [
{ "id": "hot", "name": { "ar": "صوص حار", "en": "Hot sauce" }, "price_minor": 230, "image": null },
{ "id": "none", "name": { "ar": "بدون صوص", "en": "No sauce" }, "price_minor": 0, "image": null }
]
},
{
"id": "extras", "name": { "ar": "إضافات", "en": "Extras" }, "required": false, "min": 0, "max": 2,
"options": [
{ "id": "cheese", "name": { "ar": "جبن إضافي", "en": "Extra cheese" }, "price_minor": 345, "image": null },
{ "id": "no-onion", "name": { "ar": "بدون بصل", "en": "No onion" }, "price_minor": 0, "image": null },
{ "id": "pickles", "name": { "ar": "مخلل إضافي", "en": "Extra pickles" }, "price_minor": 0, "image": null }
]
}
],
"combos": [
{ "item_id": "chicken-burger-meal", "components": [ { "item_id": "chicken-burger", "quantity": 1 }, { "item_id": "orange-juice", "quantity": 1 } ] }
],
"schedules": [
{ "id": "evening", "days": [0, 1, 2, 3, 4, 5, 6], "from": "16:00", "to": "02:00" }
],
"state": {
"paused": false,
"sold_out": { "items": ["kunafa"], "sizes": [], "options": [] },
"prices": { "items": [], "sizes": [], "options": [] }
}
}كل المبالغ بالهللة: 2990 تعني 29.90 ريال. وكل نص خريطة لغات.
الحقول العليا
| الحقل | الوصف |
|---|---|
schema |
aurmap.menu/1. اختياري |
currency |
رمز العملة. الافتراضي SAR |
default_language، languages |
اللغة الأساسية، وكل لغات النصوص. الافتراضي ar |
menu_url |
رابط https للطلب من نظامك، يظهر حين يكون الطلب من AurMap متوقفا |
identity |
هوية القائمة. اختياري. بدونه نستعمل هوية المكان على AurMap |
tax |
prices_include_vat وvat_percent وservice_charge_percent، كما هي في قائمة التاجر |
aurmap_ordering |
ما تستقبله قائمتك: enabled وpickup وdelivery وcash |
sections |
الأقسام |
items |
الأصناف. مطلوب |
groups |
مجموعات الإضافات، مرة واحدة لكل مجموعة |
combos |
الوجبات المركبة |
schedules |
أوقات يطلب فيها قسم أو صنف |
state |
الحالة: إيقاف، نفاد، أسعار مؤقتة. منفصلة عن البنية |
لا ترسل version ولا hash ولا بصمة الأصناف. نحن نحسبها ونعيدها لك.
الهوية
| الحقل | القيم |
|---|---|
name، tagline |
خريطة لغات |
logo، cover، slider[] |
روابط صور https |
template |
classic أو editorial أو darkluxe أو delivery |
theme |
light أو dark أو custom |
colors |
مع custom: background وtext وcard وaccent وon_accent بصيغة #RRGGBB |
font |
اسم الخط |
button_shape |
rounded أو pill أو square |
القسم
| الحقل | الوصف |
|---|---|
id |
معرفك للقسم |
parent_id |
القسم الأب لقسم فرعي، أو null |
name، image، sort |
الاسم والصورة والترتيب |
schedule_id |
جدول يحدد متى يطلب من القسم، أو null |
الصنف يذكر قسمه في section_id. القسم لا يذكر أصنافه.
الصنف
| الحقل | الوصف |
|---|---|
id |
معرفك الثابت للصنف، حتى 64 حرفا من [A-Za-z0-9_.-]. يعود لك في الطلبات كما هو |
product_id |
رقم المنتج الداخلي في نظامك إن اختلف عن id. اختياري |
type |
menu_item أو product أو service |
section_id، sort |
القسم والترتيب |
name، description، image |
الاسم والوصف والصورة |
price_minor |
السعر. مع الأحجام هو أقل سعر حجم، للعرض فقط |
original_price_minor |
السعر قبل الخصم، يظهر مشطوبا |
calories، prep_min، prep_max |
السعرات، ومدة التجهيز بالدقائق |
label |
شارة مثل «الأكثر طلبا»: { "name": {…} } |
warnings[] |
تنبيهات مثل مسببات الحساسية: id وname وicon |
sizes[] |
الأحجام. انظر الجدول التالي |
group_ids[] |
مجموعات الإضافات التي تنطبق على الصنف |
order_types |
pickup وdelivery وdine_in |
recommended_ids[] |
أصناف نقترحها مع هذا الصنف |
schedule_id |
جدول خاص بالصنف، أو null |
order_url |
رابط طلب خاص بالصنف. اختياري |
الحجم
| الحقل | الوصف |
|---|---|
id |
معرفك للحجم |
name |
الاسم |
price_minor |
سعر الصنف كاملا بهذا الحجم، لا فرق السعر |
original_price_minor، calories |
اختياريان |
مجموعة الإضافات
| الحقل | الوصف |
|---|---|
id، name |
المعرف والاسم |
required |
هل الاختيار إلزامي |
min، max |
أقل وأكثر عدد خيارات. max يساوي 0 يعني بلا حد |
options[] |
id وname وprice_minor (يضاف إلى السطر) وimage |
الوجبة المركبة
item_id هو صنف الوجبة نفسه، وله سعره في items. وcomponents[] مكوناتها: item_id وquantity.
الجدول
| الحقل | الوصف |
|---|---|
id |
معرف الجدول |
days[] |
أيام الأسبوع أرقاما: 0 الأحد، 1 الاثنين، حتى 6 السبت |
from، to |
HH:MM بتوقيت المكان |
إذا كان to أصغر من from فالفترة تمتد بعد منتصف الليل، وتحسب لليوم الذي بدأت فيه. مثال: days: [4] من 16:00 إلى 02:00 يعني من الخميس الساعة 4 مساء حتى الجمعة الساعة 2 فجرا.
الحالة
| الحقل | الوصف |
|---|---|
paused |
true يوقف الطلب من القائمة كلها |
sold_out |
items[] معرفات أصناف، وsizes[] من item_id وsize_id، وoptions[] من group_id وoption_id |
prices |
أسعار مؤقتة فوق أسعار البنية: items[] وsizes[] وoptions[]، ولكل منها price_minor |
تحديث الحالة وحدها في التوفر.
القواعد
- كل معرف مذكور يجب أن يكون موجودا: القسم، والمجموعات، والجداول، والأصناف المقترحة، ومكونات الوجبات، وما في الحالة.
required: trueيعني أنminواحد فأكثر. وmaxإما0أو لا يقل عنmin.- الأيام من 0 إلى 6، والوقت من
00:00إلى23:59، وfromلا يساويto. - كل معرفاتك، للأصناف والأحجام والمجموعات والخيارات والأقسام والجداول، حتى 64 حرفا من
[A-Za-z0-9_.-]. النقطتان:مرفوضتان لأنهما محجوزتان عندنا. - كل الروابط
httpsفقط، ولعناوين عامة على الإنترنت. - الحد الأقصى: 2 ميغابايت، و3000 صنف، و300 مجموعة، و50 خيارا في المجموعة.
أي مخالفة ترد 422 MENU_INVALID مع مكان الخطأ:
{
"error": {
"code": "MENU_INVALID",
"message": "The menu has 2 problems.",
"request_id": "req_01JAA2",
"details": [
{ "path": "items[0].section_id", "code": "UNKNOWN_SECTION", "message": "Section breakfast does not exist." },
{ "path": "groups[1]", "code": "MIN_ABOVE_MAX", "message": "min is above max." }
]
}
}نشر القائمة كاملة
PUT /places/{place_id}/menu بصلاحية catalog:write.
IDEMPOTENCY_KEY="menu-olaya-2026-10-14-01" \
aurmap PUT /v1/places/plc_9q3w7e/menu "$(cat menu.json)"202
{ "place_id": "plc_9q3w7e", "version": 42, "prev_version": 41, "kind": "full", "status": "processing", "hash": "9f2c4be1d07a65e3c1f8a2b4d6e8f0a1b3c5d7e9f1a3b5c7d9e1f3a5b7c9d1e3", "warnings": [] }- نتحقق من الصيغة فورا. ثم نجهز الصور وننشر القائمة في ثوان، ويصلك
menu.changedبالنسخة والبصمة نفسيهما. - الصورة التي يتعذر جلبها لا تفشل النشر. نتجاوزها ونذكرها في
warnings. - إذا أرسلت قائمة مطابقة للحالية، نرد
200مع"status": "unchanged"ولا ننشئ نسخة جديدة. - لتجنب الكتابة فوق تغيير لم تره، أرسل
If-Match: "41". إذا لم تكن النسخة الحالية 41 نرد409 VERSION_CONFLICT.
import { readFile } from 'node:fs/promises';
const menu = JSON.parse(await readFile('menu.json', 'utf8'));
const res = await aurmap('PUT', '/v1/places/plc_9q3w7e/menu', menu, 'menu-olaya-2026-10-14-01');
console.log(res.version, res.hash);$menu = json_decode(file_get_contents('menu.json'), true);
$res = aurmap('PUT', '/v1/places/plc_9q3w7e/menu', $menu, 'menu-olaya-2026-10-14-01');
echo $res['version'], ' ', $res['hash'];تعديل البنية بالفروقات
PATCH /places/{place_id}/menu بصلاحية catalog:write.
aurmap PATCH /v1/places/plc_9q3w7e/menu '{"base_version":43,"upsert":[{"id":"mint-lemonade","type":"menu_item","section_id":"juices","sort":4,"name":{"ar":"ليموناضة بالنعناع","en":"Mint lemonade"},"price_minor":1150,"sizes":[],"group_ids":[],"order_types":{"pickup":true,"delivery":true,"dine_in":true}}],"remove":["kunafa"],"groups":[{"id":"extras","name":{"ar":"إضافات","en":"Extras"},"required":false,"min":0,"max":3,"options":[{"id":"cheese","name":{"ar":"جبن إضافي","en":"Extra cheese"},"price_minor":345,"image":null},{"id":"no-onion","name":{"ar":"بدون بصل","en":"No onion"},"price_minor":0,"image":null},{"id":"pickles","name":{"ar":"مخلل إضافي","en":"Extra pickles"},"price_minor":0,"image":null},{"id":"jalapeno","name":{"ar":"هالبينو","en":"Jalapeno"},"price_minor":230,"image":null}]}]}'{ "place_id": "plc_9q3w7e", "version": 44, "prev_version": 43, "kind": "structure", "status": "processing", "hash": "b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2", "warnings": [] }| الحقل | الوصف |
|---|---|
base_version |
النسخة التي بنيت عليها التعديل. مطلوب |
upsert[] |
أصناف كاملة. الصنف الموجود يستبدل كله |
remove[] |
معرفات أصناف تحذف |
groups[] |
مجموعات كاملة، تضاف أو تستبدل |
remove_groups[] |
معرفات مجموعات تحذف |
sections[]، combos[]، schedules[] |
إذا أرسلت أحدها استبدل كله |
identity، tax، aurmap_ordering |
إذا أرسلت أحدها استبدل كله |
state |
الحالة، بقواعد التوفر |
إذا تغيرت القائمة بعد base_version نرد 409 VERSION_CONFLICT مع current_version. عندها اقرأ القائمة أو انشرها كاملة.
إزالة القائمة
DELETE /places/{place_id}/menu يزيل قائمتك من المكان، ويصلك menu.removed. بعدها يرد GET /places/{place_id}/menu بـ 404 MENU_NOT_FOUND.
النسخ والبصمات
| الطلب | يرجع |
|---|---|
GET /places/{place_id}/menu |
النسخة الحالية، وبصمتها، ووقت نشرها، والقائمة كاملة مع الحالة وبصمة كل صنف |
GET /places/{place_id}/menu?version=42 |
نسخة سابقة ما دامت محفوظة. وإلا 404 MENU_VERSION_GONE |
GET /places/{place_id}/menu/hashes |
بصمة القائمة وبصمة كل صنف |
POST /menus/reconcile |
يقارن ما عندك بما عندنا لعدة أماكن مرة واحدة |
versionيزيد واحدا مع كل تغيير مقبول: نشر كامل، أو تعديل بنية، أو تحديث حالة.hashنص من 64 حرفا. قارنه بالمساواة فقط.
مرة كل ليلة أرسل آخر نسخة وبصمة تعرفهما لكل مكان، حتى 500 مكان في الطلب:
aurmap POST /v1/menus/reconcile '{"places":[{"place_id":"plc_9q3w7e","version":44,"hash":"b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2"}]}'{ "stale": [] }كل مكان يظهر في stale اقرأ قائمته أو انشرها كاملة من جديد.
الصور
- نجلب كل صورة مرة واحدة ونحفظها عندنا بعدة مقاسات.
- نجلب من عناوين
httpsعامة فقط. نرفض العناوين الخاصة والمحلية، مثل10.xو192.168.xو127.0.0.1و169.254.x، ولا نتبع التحويلات. الصورة المرفوضة نتجاوزها مع تحذيرIMAGE_URL_NOT_ALLOWED. - لتغيير صورة، غير عنوانها.
- الحجم حتى 8 ميغابايت، بصيغة JPEG أو PNG أو WebP.
المنتجات للمتاجر (CP v1) قريبا
نموذج المنتجات للمتاجر ومنصات التجارة الإلكترونية. كل منتج له محاور خيارات (المقاس، اللون)، ومتغيرات لكل منها SKU وسعر ومخزون.
{
"schema": "cp/1",
"id": "tshirt-basic",
"name": { "ar": "تيشيرت قطن", "en": "Cotton T-shirt" },
"category_ids": ["men", "tshirts"],
"images": [ { "url": "https://cdn.partner.example/p/tshirt.jpg" } ],
"option_axes": [
{ "id": "size", "name": { "ar": "المقاس", "en": "Size" }, "values": [ { "id": "m", "name": { "ar": "M", "en": "M" } }, { "id": "l", "name": { "ar": "L", "en": "L" } } ] }
],
"variants": [
{ "id": "tshirt-basic-m", "sku": "TS-M-BLK", "barcode": "6281234567890", "options": { "size": "m" }, "price_minor": 7900, "stock": { "tracked": true, "quantity": 12 } },
{ "id": "tshirt-basic-l", "sku": "TS-L-BLK", "options": { "size": "l" }, "price_minor": 7900, "stock": { "tracked": true, "quantity": 0 } }
],
"status": "active"
}| الطلب | الوصف |
|---|---|
PUT /places/{place_id}/products/{product_id} |
إضافة أو استبدال منتج |
POST /places/{place_id}/products/batch |
حتى 500 منتج في طلب، ونتيجة لكل منتج |
DELETE /places/{place_id}/products/{product_id} |
حذف منتج |
GET /places/{place_id}/products |
المنتجات صفحة بصفحة |
PUT /places/{place_id}/product-categories |
شجرة التصنيفات |
GET /places/{place_id}/products/hashes |
بصمة كل منتج للمطابقة |