Get the app Map (opens in a new tab)List your business

Developers

Catalog

menus and products

Publish the full menu in CM v1, patch it with deltas, and track versions and hashes. And why shops get a separate product model with variants and stock.

Coming soonThe Partner API is not open yet. These docs are here so you can plan your integration; hosts and keys are issued at launch.Register as a developer →

On this page

Endpoints and scopes

Request Scope
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… Coming soon catalog:write

Two models, not one

A menu (restaurants and cafés) is a small document read as a whole: sections, items with sizes, add-on groups with a minimum and a maximum, combo meals, time schedules. It is published and patched as one versioned unit.

Products (shops) are a large catalog of thousands of items, browsed by category and filter. Every sellable unit is a variant with its own SKU, barcode, price and stock. It is never sent as one document, but in batches.

Merging them would make menus lose their add-on rules, or shops lose per-variant stock. So each has its own model, and each place has one source: a menu or products.

The CM v1 menu

json
{
  "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": [] }
  }
}

All amounts are in halalas: 2990 means 29.90 in riyals. Every text is a language map.

Top-level fields

Field Description
schema aurmap.menu/1; optional
currency currency code; default SAR
default_language, languages the base language and every language used; default ar
menu_url an https link to order from your system, shown when ordering on AurMap is off
identity the menu’s look; optional, the place’s identity on AurMap is used without it
tax prices_include_vat, vat_percent, service_charge_percent, as on the merchant’s menu
aurmap_ordering what your menu can take: enabled, pickup, delivery, cash
sections sections
items items; required
groups add-on groups, each defined once
combos combo meals
schedules times when a section or item can be ordered
state state: pause, sold out, temporary prices; kept apart from the structure

Do not send version, hash or per-item hashes. We compute them and return them to you.

Identity

Field Values
name, tagline language maps
logo, cover, slider[] https image links
template classic, editorial, darkluxe or delivery
theme light, dark or custom
colors with custom: background, text, card, accent, on_accent as #RRGGBB
font font name
button_shape rounded, pill or square

Sections

Field Description
id your section id
parent_id the parent of a sub-section, or null
name, image, sort name, image and order
schedule_id a schedule that limits when the section can be ordered, or null

An item names its section in section_id. A section does not list its items.

Items

Field Description
id your stable item id, up to 64 chars of [A-Za-z0-9_.-]; returned to you in orders as is
product_id your system’s internal product number if it differs from id; optional
type menu_item, product or service
section_id, sort section and order
name, description, image name, description, image
price_minor the price; with sizes, the lowest size price, for display only
original_price_minor the price before a discount, shown struck through
calories, prep_min, prep_max calories, and preparation time in minutes
label a badge such as “Best seller”: { "name": {…} }
warnings[] notices such as allergens: id, name, icon
sizes[] sizes; see the next table
group_ids[] the add-on groups that apply
order_types pickup, delivery, dine_in
recommended_ids[] items we suggest alongside
schedule_id the item’s own schedule, or null
order_url an order link for this item; optional

Sizes

Field Description
id your size id
name name
price_minor the item’s whole price in this size, not a difference
original_price_minor, calories optional

Add-on groups

Field Description
id, name id and name
required whether a choice is required
min, max the fewest and most options; max of 0 means no limit
options[] id, name, price_minor (added to the line), image

Combos

item_id is the combo’s own item, with its own price in items. components[] are its parts: item_id and quantity.

Schedules

Field Description
id schedule id
days[] weekdays as numbers: 0 Sunday, 1 Monday, up to 6 Saturday
from, to HH:MM in the place’s time zone

When to is earlier than from, the span runs past midnight and belongs to the day it starts on. Example: days: [4] from 16:00 to 02:00 means Thursday 4 pm to Friday 2 am.

State

Field Description
paused true stops ordering from the whole menu
sold_out items[] item ids, sizes[] of item_id and size_id, options[] of group_id and option_id
prices temporary prices over the structure’s prices: items[], sizes[] and options[], each with price_minor

Updating state alone is on availability.

Rules

  • Every referenced id must exist: the section, groups, schedules, recommended items, combo parts and everything in the state.
  • required: true means min is one or more. max is either 0 or not below min.
  • Days 0 to 6, times 00:00 to 23:59, and from differs from to.
  • Every id of yours, for items, sizes, groups, options, sections and schedules, is up to 64 chars of [A-Za-z0-9_.-]. The colon : is refused because we reserve it.
  • Every link must be https, on a public internet address.
  • Limits: 2 MB, 3,000 items, 300 groups, 50 options per group.

Any violation answers 422 MENU_INVALID with the location of each problem:

json
{
  "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." }
    ]
  }
}

Publish the full menu

PUT /places/{place_id}/menu with catalog:write.

bash
IDEMPOTENCY_KEY="menu-olaya-2026-10-14-01" \
aurmap PUT /v1/places/plc_9q3w7e/menu "$(cat menu.json)"

202

json
{ "place_id": "plc_9q3w7e", "version": 42, "prev_version": 41, "kind": "full", "status": "processing", "hash": "9f2c4be1d07a65e3c1f8a2b4d6e8f0a1b3c5d7e9f1a3b5c7d9e1f3a5b7c9d1e3", "warnings": [] }
  • We validate the format at once, then prepare the images and publish within seconds, and you receive menu.changed with the same version and hash.
  • An image we cannot fetch never fails the publish. We skip it and list it in warnings.
  • If you send a menu identical to the current one, we answer 200 with "status": "unchanged" and create no version.
  • To avoid overwriting a change you have not seen, send If-Match: "41". If the current version is not 41 we answer 409 VERSION_CONFLICT.
js
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);
php
$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 the structure

PATCH /places/{place_id}/menu with catalog:write.

bash
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}]}]}'
json
{ "place_id": "plc_9q3w7e", "version": 44, "prev_version": 43, "kind": "structure", "status": "processing", "hash": "b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2", "warnings": [] }
Field Description
base_version the version your change is based on; required
upsert[] whole items; an existing item is replaced whole
remove[] item ids to delete
groups[] whole groups, added or replaced
remove_groups[] group ids to delete
sections[], combos[], schedules[] each one sent replaces the whole list
identity, tax, aurmap_ordering each one sent replaces the whole value
state state, under the availability rules

If the menu changed after base_version, we answer 409 VERSION_CONFLICT with current_version. Read the menu or publish it in full.

Remove the menu

DELETE /places/{place_id}/menu takes your menu off the place, and you receive menu.removed. After that, GET /places/{place_id}/menu answers 404 MENU_NOT_FOUND.

Versions and hashes

Request Returns
GET /places/{place_id}/menu the current version, its hash, publish time, and the full menu with state and per-item hashes
GET /places/{place_id}/menu?version=42 an earlier version while it is kept; otherwise 404 MENU_VERSION_GONE
GET /places/{place_id}/menu/hashes the menu hash and one hash per item
POST /menus/reconcile compares what you hold with what we hold, for many places at once
  • version grows by one with every accepted change: a full publish, a structure patch or a state update.
  • hash is a 64-character string. Compare it for equality only.

Once a night, send the last version and hash you know for each place, up to 500 places per request:

bash
aurmap POST /v1/menus/reconcile '{"places":[{"place_id":"plc_9q3w7e","version":44,"hash":"b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2"}]}'
json
{ "stale": [] }

For every place listed in stale, read its menu or publish it in full again.

Images

  • We fetch each image once and keep it in several sizes.
  • We fetch only from public https addresses. Private and local addresses, such as 10.x, 192.168.x, 127.0.0.1 and 169.254.x, are refused, and redirects are not followed. A refused image is skipped with warning IMAGE_URL_NOT_ALLOWED.
  • To change an image, change its address.
  • Up to 8 MB, in JPEG, PNG or WebP.

Products for shops (CP v1) Coming soon

The product model for shops and e-commerce platforms. Each product has option axes (size, colour) and variants, each with its own SKU, price and stock.

json
{
  "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"
}
Request Description
PUT /places/{place_id}/products/{product_id} add or replace a product
POST /places/{place_id}/products/batch up to 500 products per call, with a result per product
DELETE /places/{place_id}/products/{product_id} delete a product
GET /places/{place_id}/products products, paged
PUT /places/{place_id}/product-categories the category tree
GET /places/{place_id}/products/hashes a hash per product, for reconciling