PHP2Shell: Is Your Joomla Site Safe? What to Do Today Introducing Next Boards: Kanban project and task boards for Joomla Next Exams 6.2.0: Exam Studio, six visual question types, exam embeds and six new languages Building the Future of Joomla Extensions
NextSoftware

Endpoint details

Endpoints

Purpose REST Query-string
Published locations GET /locations task=publicApi.locations
A location's live menu GET /locations/{id}/menu task=publicApi.menu&location_id={id}
Place an order POST /orders task=publicApi.createOrder

Locations

REST          GET /api/index.php/v1/nextrestaurant/locations
Query-string  GET /index.php?option=com_nextrestaurant&format=json&task=publicApi.locations
curl -s "https://your-site.example/api/index.php/v1/nextrestaurant/locations" \
  -H "Authorization: Bearer $NR_TOKEN"

Returns every published location. Unpublished ones are never listed.

On REST, service_modes is an array, opening_hours an object and tax_rate a number:

{
  "data": [
    {
      "id": 1,
      "code": "MAIN",
      "title": "Downtown Bistro",
      "currency": "USD",
      "timezone": "America/Chicago",
      "service_modes": ["dine_in", "takeaway", "delivery", "qr"],
      "opening_hours": {
        "mon": { "open": "11:00", "close": "22:00" },
        "sat": { "open": "10:00", "close": "23:30" }
      },
      "tax_rate": 8.25
    }
  ]
}

The query-string lane returns the same data exactly as stored, which means two of those fields arrive as JSON-encoded strings and need a second decode:

{
  "success": true,
  "locations": [
    {
      "id": 1,
      "code": "MAIN",
      "title": "Downtown Bistro",
      "currency": "USD",
      "timezone": "America/Chicago",
      "service_modes": "[\"dine_in\",\"takeaway\",\"delivery\",\"qr\"]",
      "opening_hours": "{\"mon\":{\"open\":\"11:00\",\"close\":\"22:00\"}}",
      "tax_rate": "8.25",
      "state": 1
    }
  ]
}

opening_hours is keyed by mon–sun on both lanes; a day that is absent or empty means closed. The query-string lane also carries a state field, which is always 1 because unpublished locations are never returned.

The id from here is the location id every other endpoint needs.


REST          GET /api/index.php/v1/nextrestaurant/locations/{id}/menu
Query-string  GET /index.php?option=com_nextrestaurant&format=json&task=publicApi.menu&location_id={id}
curl -s "https://your-site.example/api/index.php/v1/nextrestaurant/locations/2/menu" \
  -H "Authorization: Bearer $NR_TOKEN"
Parameter Required Meaning
id (REST path) / location_id (query-string) Yes A published location's id

Both lanes return the same item structure. On REST it is {"data": {"location_id": …, "categories": [ … ]}}; on the query-string lane the same object sits inside Joomla's envelope.

The menu reflects live availability for that location: unpublished items, items switched off for that branch, and 86'd items are all absent, and the price is the branch's price where one overrides the base. An integration therefore cannot sell something you cannot make.

Items are grouped into categories, in the order you arranged them in the admin.

{
  "success": true,
  "location_id": 2,
  "categories": [
    {
      "id": 2,
      "title": "Starters",
      "items": [
        {
          "id": 1,
          "title": "Garlic Bread",
          "description": "Stone-baked sourdough with roasted garlic butter, melted mozzarella and parsley.",
          "price": 6.5,
          "has_variations": 0,
          "image": "images/nextrestaurant/items/1/gallery/cb901db407fa4277.jpg",
          "prep_time_minutes": 19,
          "calories": null,
          "spice_level": 0,
          "is_alcohol": 0,
          "variations": [],
          "modifier_groups": [],
          "allergens": [
            { "id": 1, "title": "Celery", "presence": "contains" }
          ]
        }
      ]
    },
    {
      "id": 5,
      "title": "Pizza",
      "items": [
        {
          "id": 16,
          "title": "Margherita Pizza",
          "description": "San Marzano tomato, fior di latte and fresh basil on a 48-hour dough.",
          "price": 13,
          "has_variations": 1,
          "image": "images/nextrestaurant/items/16/gallery/ca98f3a212f1aac0.jpg",
          "prep_time_minutes": 23,
          "calories": null,
          "spice_level": 0,
          "is_alcohol": 0,
          "variations": [
            { "id": 1, "title": "Regular", "price": 13, "is_default": 1 },
            { "id": 2, "title": "Large",   "price": 17, "is_default": 0 }
          ],
          "modifier_groups": [
            {
              "id": 2,
              "title": "Extra Toppings",
              "selection_type": "multiple",
              "is_required": 0,
              "min_select": 0,
              "max_select": 4,
              "free_quantity": 0,
              "modifiers": [
                { "id": 5, "title": "Extra Cheese", "price": 1.5,  "is_default": 0, "max_quantity": 1 },
                { "id": 6, "title": "Mushrooms",    "price": 1,    "is_default": 0, "max_quantity": 1 },
                { "id": 7, "title": "Olives",       "price": 1,    "is_default": 0, "max_quantity": 1 },
                { "id": 8, "title": "Jalapenos",    "price": 0.75, "is_default": 0, "max_quantity": 1 }
              ]
            }
          ],
          "allergens": [
            { "id": 5,  "title": "Fish", "presence": "contains" },
            { "id": 14, "title": "Sulphur dioxide / sulphites", "presence": "contains" }
          ]
        }
      ]
    }
  ]
}

Item fields

Field Meaning
price The branch price for the item as ordered with no variation
has_variations 1 when the item is sold in sizes or versions
image Path relative to the site root, or null. Prefix with your site URL to display it
prep_time_minutes, calories, spice_level, is_alcohol Optional presentation data; any may be null or 0
variations Sizes/versions. Empty when the item has none
modifier_groups Option groups attached to this item
allergens Declared allergens; presence is contains or may_contain

Variations

When variations is non-empty, pick one and send its id. The variation's price replaces the item's price — it is not added to it. is_default marks the one to pre-select.

Submitting an item that has variations without a variation_id is accepted, and is charged at the base price with no size recorded. That is almost never what a customer meant, so treat a missing selection as a validation error in your own UI.

Modifier groups

Field Meaning
selection_type single (choose one) or multiple (choose several)
is_required 1 when the customer must choose
min_select / max_select How many modifiers may be chosen from this group
free_quantity How many are included before the per-modifier price applies
modifiers[].max_quantity How many times one modifier may be repeated

min_select and max_select already include any per-item override, so use them as given.

Modifier prices are added to the line.


Create order

REST          POST /api/index.php/v1/nextrestaurant/orders      -> 201 Created
Query-string  POST /index.php?...&task=publicApi.createOrder    -> 200 OK
Content-Type: application/json
curl -s -X POST "https://your-site.example/api/index.php/v1/nextrestaurant/orders" \
  -H "Authorization: Bearer $NR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "location_id": 2,
    "channel": "web",
    "service_mode": "takeaway",
    "notes": "Ring the bell at the side door",
    "customer": { "name": "Dana Whitfield", "phone": "+1 555 0142", "email": "[email protected]" },
    "items": [
      { "item_id": 16, "variation_id": 2, "qty": 1, "notes": "Well done",
        "modifiers": [ { "modifier_id": 5, "qty": 1 } ] },
      { "item_id": 1, "qty": 2 }
    ]
  }'

Request body

Field Required Meaning
location_id Yes Where the order is placed
items Yes At least one line — see below
channel No web (default), phone, takeaway or delivery. Anything else falls back to web. Recorded on the order and visible in reports
service_mode No Free text stored with the order, e.g. takeaway, dine_in
notes No Order-level note, shown to the kitchen
customer No { name, phone, email } — all optional, stored with the order

Each entry in items:

Field Required Meaning
item_id Yes Menu item id from the menu endpoint
qty Yes Quantity
variation_id When the item has variations Chosen variation
modifiers No [{ "modifier_id": 5, "qty": 1 }, …]
notes No Line note, e.g. "no ice"

A body wrapped as {"data": { … }} is also accepted, for clients that always send that envelope.

Response

The created record, with its priced lines:

{
  "success": true,
  "id": 580,
  "order_number": "ORD-000580",
  "grand_total": 34.1,
  "status": "placed",
  "order": {
    "id": 580,
    "order_number": "ORD-000580",
    "location_id": 2,
    "channel": "web",
    "status": "placed",
    "fulfillment_status": "in_kitchen",
    "subtotal": "31.50",
    "discount_total": "0.00",
    "tax_total": "2.60",
    "service_charge": "0.00",
    "delivery_fee": "0.00",
    "grand_total": "34.10",
    "currency": null,
    "notes": "Ring the bell at the side door",
    "placed_at": "2026-09-07 20:42:51",
    "items": [
      {
        "id": 1290,
        "item_id": 16,
        "variation_id": 2,
        "name_snapshot": "Margherita Pizza (Large)",
        "qty": "1.000",
        "unit_price": "17.00",
        "modifiers_total": "1.50",
        "line_total": "18.50",
        "notes": "Well done"
      },
      {
        "id": 1291,
        "item_id": 1,
        "variation_id": null,
        "name_snapshot": "Garlic Bread",
        "qty": "2.000",
        "unit_price": "6.50",
        "modifiers_total": "0.00",
        "line_total": "13.00",
        "notes": null
      }
    ]
  }
}

name_snapshot is what the kitchen and the receipt will show — it records the item as it was priced, including the variation. placed_at is in the site's configured timezone.

Note: order.currency is often null. Take the currency from the location instead.

What the totals mean

Totals are recomputed on the server from your menu records. A submitted price is ignored, so a tampered request buys nothing at the wrong price — and so your integration never has to replicate your tax or pricing rules. In the example above: 17.00 + 1.50 for the pizza plus 6.50 × 2 for the bread gives a 31.50 subtotal, and tax brings the grand_total to 34.10.

The order's status is always placed. It cannot be set by the caller.

What an API order becomes

An ordinary order. It routes to kitchen stations by your routing rules, depletes stock at whatever status you configured, settles into the payments ledger, and appears in reports with its channel recorded — exactly like an order from the storefront.

Orders arrive unpaid. The API records an order; it does not take payment. Collect at the counter, on delivery, or through your own checkout.

All Extensions