A storefront calls the public routes from the browser, and sends the two order calls through a small server of its own that holds the secret key. The examples below are shortened: every field is in the API reference.
Load the store
GET /storetells you theorderTypesyou can offer, the payment methods, delivery zones and service cost. An emptyorderTypesmeans the restaurant does not accept orders right now.{ "id": "k3x9p", "name": "Example Fries", "timezone": "Europe/Brussels", "orderTypes": ["TAKEOUT", "DELIVERY"], "payment": { "methods": ["online", "offline"], "default": "online" }, "delivery": { "enabled": true, "minimumAmount": 50, "zones": [{ "id": "z81qm", "postcode": "1000", "name": "1000", "cost": 10, "freeFrom": 100 }] }, "serviceCost": { "amount": 0.5, "omitFrom": 0 } }Show the menu
GET /menu?orderType=TAKEOUTreturns the categories and products for an order type. Get the product whenhasOptionsis true, to read its option groups.{ "categories": [{ "id": "c4n2w", "name": "Fries", "products": [{ "id": "p7d2x", "name": "Medium fries", "price": 4, "available": true, "soldOut": false, "hasOptions": true, "allergens": ["gluten"], "allergenSources": { "gluten": ["wheat"] } }] }] }Pick a slot
GET /schedule?orderType=TAKEOUT&products=p7d2xwith the cart’sproducts, so the slots respect preparation times. Reload it whenever the set of products in the cart changes.{ "timezone": "Europe/Brussels", "days": [{ "date": "2026-10-23", "weekday": "FRI", "isOpen": true, "slots": [{ "time": "12:15", "at": 1792750500, "available": true }] }] }Quote the cart
POST /cart/quotewith the lines, delivery zone, coupon and the chosenslotAt. Show the lines,chargesandtotal, and only enable checkout whencanCheckoutis true.Requestjson { "orderType": "TAKEOUT", "slotAt": 1792750500, "lines": [{ "productId": "p7d2x", "quantity": 1, "options": [{ "groupId": "g5t1r", "optionId": "o9w3e" }] }] }Responsejson { "lines": [{ "index": 0, "productId": "p7d2x", "name": "Medium fries", "quantity": 1, "unitPrice": 4, "optionsLabel": "Toppings: Feta", "total": 4, "errors": [] }], "subtotal": 4, "charges": [{ "name": "Packaging", "amount": 0.5 }], "deliveryCost": 0, "serviceCost": 0.5, "couponDiscount": 0, "coupon": null, "total": 5, "minimum": null, "errors": [], "canCheckout": true }Place the order, from your server
POST /orderswith the secret key and areturnUrlon one of your registered origins. Unipage validates everything again, so handleSLOT_UNAVAILABLE,PRODUCT_UNAVAILABLE,COUPON_INVALIDandMINIMUM_NOT_REACHEDby sending the shopper back to the step it concerns.Requestjson { "orderType": "TAKEOUT", "slotAt": 1792750500, "lines": [{ "productId": "p7d2x", "quantity": 1, "options": [{ "groupId": "g5t1r", "optionId": "o9w3e" }] }], "customer": { "firstName": "Ann", "lastName": "Example", "email": "ann@example.com", "phone": "+32470000000" }, "paymentType": "online", "returnUrl": "https://shop.example.com/order/{orderId}" }Response (201)json { "order": { "id": "q4m8z", "status": "PENDING_PAYMENT", "paymentType": "online", "isPaid": false, "total": 5, "...": "..." }, "next": { "type": "payment", "url": "https://demo.unipage.eu/pay/mollie/x7k2p" } }Pay
If
next.typeispayment(paymentType: "online"), send the shopper’s browser tonext.url. Forofflineandexternal(next.type: "confirmation") the order is placed and nothing is left to pay through Unipage. See Payments.Confirm
The shopper comes back on your
returnUrlwith?status=successor?status=failed. Don’t rely on that value: readGET /orders/{id}from your server and useisPaidandstatus, or wait for the webhook.{ "id": "q4m8z", "status": "PLACED", "label": "Order received", "isPaid": true, "total": 5, "...": "..." }