☰ Menu · Restaurants and cafes
Guides

Restaurants and cafes

Three things a venue's POS can do with Loyaltyfy: keep the QR menu in sync, take orders from tables, and run loyalty at the till.

The POS is usually the source of truth for prices and what is out of stock. Give every dish your SKU in external_id, then address dishes by that SKU; you never need to store our ids.

  1. 1
    Read what is there

    GET /v1/menu returns categories, dishes and modifiers in all languages. Add ?lang=en to get plain strings in one language.

  2. 2
    Create what is missing

    POST /v1/menu/items with category (a category id or your category code; an unknown code creates the category), external_id, name in one or more languages and price. Sending the same external_id again returns the existing dish.

  3. 3
    Push changes

    On every change, or every few minutes, send POST /v1/menu/items/bulk with prices and availability. Up to 500 dishes per call; unknown SKUs come back in not_found.

curl
curl https://loyaltyfy.io/api/v1/menu/items/bulk \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "item": "1001", "price": 4800 },
      { "item": "2040", "available": false },
      { "item": "3017", "price": 9900, "old_price": 12000 }
    ]
  }'
Response · 200
{
  "object": "menu_bulk_result",
  "updated": ["1001", "2040"],
  "unchanged": [],
  "not_found": ["3017"],
  "failed": []
}

Stop list: available: false keeps the dish visible with a "sold out" label and removes the order button; visible: false hides it. Modifiers have their own switch, so you can turn off oat milk without turning off coffee:

curl
curl -X PATCH https://loyaltyfy.io/api/v1/menu/option_values/MOD-OAT \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "available": false }'
Photos, descriptions and translations are usually edited by the venue in the Loyaltyfy menu editor. Sync only what the POS really owns (prices, availability, maybe names) so the two do not overwrite each other.

Orders from the table

With ordering turned on (Menu → Orders in the dashboard), guests who scan the table QR can send their cart to the venue. Each table gets its own link with the table number:

curl
https://loyaltyfy.io/m/your-venue?table=12

We price every order on our side from the current menu, so the POS can trust the amounts. Orders for a dish or a modifier on the stop list are refused before they reach you.

  1. 1
    Get new orders

    Subscribe to the order.created webhook, or poll GET /v1/orders?status=new every 10 to 15 seconds.

  2. 2
    Accept

    Create the order in the POS, then call POST /v1/orders/{id}/accept with your own order id as external_id. Use each line's external_id to match your SKUs.

  3. 3
    Move it along

    PATCH /v1/orders/{id} with preparing, ready, served, completed. Or POST /v1/orders/{id}/reject with a reason if the kitchen cannot take it.

curl
curl "https://loyaltyfy.io/api/v1/orders?status=new" \
  -H "Authorization: Bearer sk_test_..."
One order from the list
{
  "object": "order",
  "id": "0c69a81c-0fbc-4333-b26c-789566b9bea4",
  "number": 10,
  "status": "new",
  "table": "7",
  "note": "No onions, please",
  "currency": "MDL",
  "subtotal": 14000,
  "item_count": 2,
  "items": [
    {
      "item_id": "a48343bd-9f6e-424a-b003-fe790617f4d6",
      "external_id": "2040",
      "name": "Zeamă",
      "quantity": 2,
      "unit_price": 7000,
      "total": 14000,
      "options": []
    }
  ],
  "external_id": null,
  "reject_reason": null,
  "guest_language": "en",
  "source": "qr_menu",
  "livemode": true,
  "created_at": "2026-10-06T18:14:48.233+00:00",
  "accepted_at": null,
  "completed_at": null,
  "updated_at": "2026-10-06T18:14:48.233+00:00"
}
curl
curl -X POST https://loyaltyfy.io/api/v1/orders/0c69a81c-0fbc-4333-b26c-789566b9bea4/accept \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "POS-ORDER-55821" }'

Statuses go forward only: new → accepted → preparing → ready → served → completed; new can also become rejected, and any open order can become cancelled. Steps can be skipped (accepted straight to completed is fine). A move backwards gets 409 invalid_status_transition. Venues without a POS integration manage the same statuses on the Orders screen, so it is fine if your POS only accepts and completes.

Loyalty at the cafe till

Same as any POS integration: look the guest up when the card is scanned, send the check when it is paid. For cafes with stamp cards ("every 6th coffee free"), the check is what earns the stamp. Send line_items with your SKUs so the venue sees which products guests buy.

Questions about an integration: api@loyaltyfy.io. We answer within one business day.