API documentation

Take table bookings and collection orders from your own website or the systems you work with. They land on the shop's tills and in Reservations, priced and checked by the same rules as the till.

Base URLhttps://api.unitedpos.co.uk

Overview

There are two ways in. Both follow the same rules and give the same answers.

Public routes
Under /public, with no key. A shop is named by its slug, the last part of its ordering page address. Every caller is rate limited. This is what the shop's own ordering and booking page uses.
Shop API
Under /api/v1, with the shop's API key. The key names the shop, so there is no slug. It can also read the shop's bookings. Use it from your own server, for the shop you work for.

Nothing in the API takes money. A collection order is paid at the counter, and a booking is a reservation. Prices always come from the shop's own menu, never from the request.

Switch it on

The shop's owner or a manager sets it up in the back office at office.unitedpos.co.uk.

  1. Settings, Online ordering. Tick "Take table bookings", "Take orders to collect (paid at the counter)", or both, and set the opening hours. A shop with no opening hours offers no times. Set the preparation time, and for bookings the seats the web may book at once, the largest booking online, how long a table is kept, the notice and how many days ahead.
  2. The slug. The same page shows the shop's order and book links. The slug is the part after /shop/, such as example-kitchen.
  3. Settings, API. The owner or a manager can show and copy the shop's API key, and rotate it. A key starts up_live_ and is 40 characters long. Rotating ends the old key at once.

With bookings switched off, every booking route answers 404; with ordering switched off, the menu and order routes do.

Requests and answers

  • Send and receive JSON, with Content-Type: application/json on a POST. A body is at most 64 KB.
  • Dates are YYYY-MM-DD and times HH:MM, on the shop's own clock (UK time unless the shop set another). reserved_at is the same moment in UTC.
  • Money is a string in pounds with VAT included, such as "8.50".
  • Ids are UUIDs.
  • Bookings are every 15 minutes; collection orders are for today only.

Authentication

Public routes need no key. Every /api/v1 route needs the shop's key in one of two headers. Keep the key on your server: never put it in a web page's code or share it in a message.

Sending the key
curl https://api.unitedpos.co.uk/api/v1/menu \
  -H "Authorization: Bearer up_live_YOUR_KEY"

# or
curl https://api.unitedpos.co.uk/api/v1/menu \
  -H "X-Api-Key: up_live_YOUR_KEY"

Rate limits

Over a limit, the answer is 429 with the reason. Wait and try again.

WhatLimit
Any /api/v1 request60 a minute for each API key.
Requests with a wrong or missing key30 a minute from one address, then 429.
POST /public/shops/{slug}/bookings5 in 10 minutes from one caller, and 30 web bookings an hour for the shop.
POST /public/shops/{slug}/orders5 in 10 minutes from one caller.
Orders, either route5 an hour and 3 open at once for one phone number at a shop.
GET /public/orders/{token}120 a minute from one caller.

Errors

A refusal is JSON with an error in plain words you can show to the customer. A body that is not valid JSON, or is missing a required field, is refused before it reaches the shop with 400, 415 or 422 and a plain-text reason.

Response 409 Conflict
{
  "error": "19:00 is no longer free for 2. Choose another time."
}
  • 200Done. For an order sent again with the same key, the first order.
  • 201Created: a new booking or order.
  • 400Something in the request is missing or not valid. error says what.
  • 401/api/v1: no key, or not the shop's current key (for example after it was rotated).
  • 403/api/v1: the shop's licence is not active.
  • 404No such shop, booking or order, or bookings or ordering are switched off.
  • 409That time, dish or choice is no longer available, or the customer already has a booking that day or orders on the way. Ask again.
  • 429Over a rate limit. Wait and try again.
  • 503The shop is too busy to take more web orders, or the system cannot reach its data for a moment.

Browsers and CORS

The API does not answer browser requests from other websites: only the origins UnitedPos runs are allowed. Call it from your own server and pass the answers to your page. For a page with nothing to build, use the shop's own ordering and booking links.

Endpoints

The shop

  • GET/public/shops/{slug}Public, no key

The shop's name, what it takes online, its opening hours and today's collection times. Use it to build the first screen of a booking or ordering form.

Opening hours are keyed by ISO weekday, "1" Monday to "7" Sunday. A closing time before the opening time runs past midnight. A day left out is closed.

Request
curl https://api.unitedpos.co.uk/public/shops/example-kitchen
Response 200 OK
{
  "name": "Example Kitchen",
  "ordering": true,
  "booking": true,
  "open_now": true,
  "hours": {
    "1": { "open": "12:00", "close": "22:00" },
    "5": { "open": "12:00", "close": "02:00" }
  },
  "prep_minutes": 20,
  "asap": true,
  "pickup_times": ["18:45", "19:00", "19:15"],
  "booking_max_party": 8,
  "booking_days_ahead": 30,
  "today": "2026-10-27"
}

Refusals

  • 404"No shop at this address": no shop has that slug, or it takes neither orders nor bookings online.

Free booking times

  • GET/public/shops/{slug}/booking-timesPublic, no key
  • GET/api/v1/booking-timesAPI key

The times on one day that still have room for a party. Times are every 15 minutes from the opening (or from now plus the shop's notice) to an hour before the close, on the shop's own clock.

The web books only part of the room: a time is offered while the bookings that overlap it, from every source, leave room for the party within the seats the shop gives the web. A day outside the booking window, or a closed day, returns an empty list.

Query

FieldTypeRequiredWhat it is
datedateYesThe day, YYYY-MM-DD.
partyintegerYesGuests, from 1 to the shop's largest online booking (booking_max_party).
Request
curl "https://api.unitedpos.co.uk/api/v1/booking-times?date=2026-10-30&party=2" \
  -H "Authorization: Bearer up_live_YOUR_KEY"
Response 200 OK
{
  "date": "2026-10-30",
  "times": ["18:00", "18:15", "18:30", "20:45", "21:00"]
}

Refusals

  • 400date or party is missing or not valid, or the party is outside 1 to booking_max_party: "Book for 1 to 8 online; call the shop for more".
  • 404"This shop does not take bookings here": table bookings are switched off.

Book a table

  • POST/public/shops/{slug}/bookingsPublic, no key
  • POST/api/v1/bookingsAPI key

A confirmed booking, if the time still has room. It goes straight into the shop's Reservations, marked as from the web (public route) or from the API (key route), and holds its seats for the shop's booking length.

Ask for the free times first and send one of them. Two bookings at the same moment cannot both take the last seats: the second is refused with 409.

JSON body

FieldTypeRequiredWhat it is
datedateYesYYYY-MM-DD.
timestringYesHH:MM, one of the free times for that date and party.
partyintegerYesGuests, 1 to booking_max_party.
namestringYesUp to 60 characters.
phonestringYes7 to 15 digits; spaces, +, - and brackets allowed.
emailstringNoA valid address, up to 254 characters.
notestringNoUp to 200 characters, for the shop.
Request
curl -X POST https://api.unitedpos.co.uk/api/v1/bookings \
  -H "Authorization: Bearer up_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"date": "2026-10-30", "time": "19:00", "party": 2, "name": "Ann Smith", "phone": "07700 900123"}'
Response 201 Created
{
  "id": "7f1c2a4e-3b5d-4c8e-9a61-0d2f4b6e8a10",
  "shop": "Example Kitchen",
  "date": "2026-10-30",
  "time": "19:00",
  "party": 2
}

Refusals

  • 400A field is missing or not valid, for example "Give a phone number the shop can call", "Give a name, up to 60 characters", "The email address is not valid", "A note is up to 200 characters" or "Choose a time".
  • 404"This shop does not take bookings here".
  • 409"19:00 is no longer free for 2. Choose another time.", or "This phone number already has a booking that day. Please call the shop to change it." (one booking per phone number per trading day).
  • 429Public route only: more than 5 bookings from one caller in 10 minutes, more than 30 web bookings at the shop in an hour, or more covers from one caller in 12 hours than the web may take (the larger of the largest party and half the web seats).

List bookings

  • GET/api/v1/bookingsAPI key

The shop's bookings from every source (till, web, API) between two dates on the shop's clock, both included, in time order. Key only.

Left out, the range is today and the 30 days after. A range is at most 92 days, and a list returns at most 500 bookings.

Query

FieldTypeRequiredWhat it is
fromdateNoYYYY-MM-DD. Today when left out.
todateNoYYYY-MM-DD. from plus 30 days when left out.
Request
curl "https://api.unitedpos.co.uk/api/v1/bookings?from=2026-10-27&to=2026-11-03" \
  -H "Authorization: Bearer up_live_YOUR_KEY"
Response 200 OK
{
  "from": "2026-10-27",
  "to": "2026-11-26",
  "bookings": [
    {
      "date": "2026-10-30",
      "time": "19:00",
      "id": "7f1c2a4e-3b5d-4c8e-9a61-0d2f4b6e8a10",
      "name": "Ann Smith",
      "phone": "07700 900123",
      "email": null,
      "party": 2,
      "reserved_at": "2026-10-30T19:00:00Z",
      "duration_minutes": 90,
      "status": "confirmed",
      "source": "api",
      "notes": null
    }
  ]
}

Refusals

  • 400"Give from and to as dates, from before to, at most 92 days apart".
  • 404"This shop does not take bookings here".

One booking

  • GET/api/v1/bookings/{id}API key

One of the shop's own bookings by its id, in the same shape as an entry in the list. status is confirmed, seated, completed, cancelled or no_show. Key only.

Request
curl https://api.unitedpos.co.uk/api/v1/bookings/7f1c2a4e-3b5d-4c8e-9a61-0d2f4b6e8a10 \
  -H "Authorization: Bearer up_live_YOUR_KEY"
Response 200 OK
{
  "date": "2026-10-30",
  "time": "19:00",
  "id": "7f1c2a4e-3b5d-4c8e-9a61-0d2f4b6e8a10",
  "name": "Ann Smith",
  "phone": "07700 900123",
  "email": null,
  "party": 2,
  "reserved_at": "2026-10-30T19:00:00Z",
  "duration_minutes": 90,
  "status": "seated",
  "source": "api",
  "notes": null
}

Refusals

  • 404"No such booking" (including another shop's), or bookings are switched off.

Place a collection order

  • POST/public/shops/{slug}/ordersPublic, no key
  • POST/api/v1/ordersAPI key

An order to collect today, paid at the counter. Nothing here takes money. The order rings on the shop's tills as a web order and goes to the kitchen only once someone at a till accepts it; if nobody answers within 10 minutes it lapses.

Send dishes and choices, never amounts: the server prices every line from the shop's own menu at the take-away price. Send the same key again (for example after a timeout) and you get the first order back with 200, never a second order.

JSON body

FieldTypeRequiredWhat it is
itemsarrayYes1 to 30 lines, 50 items at most in all.
items[].product_iduuidYesA product id from the menu.
items[].variant_iduuidNoA size id from the product's sizes.
items[].quantityintegerYes1 to 20.
items[].modifiersarrayNoChoices: { "option_id": uuid, "quantity": integer (optional) }, within each group's rules.
items[].notestringNoUp to 100 characters.
namestringYesUp to 60 characters.
phonestringYes7 to 15 digits; spaces, +, - and brackets allowed.
emailstringNoA valid address.
notestringNoUp to 200 characters.
pickupstringYes"asap", or HH:MM from the shop's pickup_times.
keyuuidYesYour own id for this order, so a resend is not a second order.
Request
curl -X POST https://api.unitedpos.co.uk/api/v1/orders \
  -H "Authorization: Bearer up_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "product_id": "c3a1e9d7-5b2f-4e8a-9c61-7d0f2b4e6a18",
        "quantity": 2,
        "modifiers": [{ "option_id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b" }]
      }
    ],
    "name": "Ann Smith",
    "phone": "07700 900123",
    "pickup": "asap",
    "key": "2b6f0d84-91c3-4e57-a8d2-6c0e4f1b9a37"
  }'
Response 201 Created (200 OK for a resend with the same key)
{
  "token": "4f0c9a2e7b1d4c6a8e3f5b7d9a1c2e4f6b8d0a2c4e6f8a1b3d5f7a9c1e3b5d7f",
  "order_number": "261027-0042",
  "total": "19.00",
  "collect_at": "19:15"
}

Refusals

  • 400A field is missing or not valid: lines, quantities, a dish, size or choice that is not on the menu, choices outside a group's rules, "Choose a collection time", or "Orders over £250 are taken by phone. Please call the shop." (subtotal).
  • 404"This shop does not take orders here".
  • 409"Chicken wrap is not available at 18:30", a size or choice switched off, "The shop is closed now. Choose a later time.", "18:30 is no longer available. Choose another time.", or "You already have orders on the way. Please collect them, or call the shop." (3 open web orders per phone number).
  • 429More than 5 orders for one phone number at the shop in an hour (both routes), or, on the public route, more than 5 orders from one caller in 10 minutes.
  • 503"The shop is very busy right now. Please call to order.": 20 web orders are already waiting for the till.

Follow an order

  • GET/public/orders/{token}Public, no key

Where a collection order is, by the token its placing returned. The token is the only key, so give it only to the customer.

status is waiting (for the shop to confirm), confirmed, ready, collected, declined, cancelled (by the shop, after it was confirmed) or lapsed (nobody answered in time). reason is the shop's reason when it declined.

Request
curl https://api.unitedpos.co.uk/public/orders/4f0c9a2e7b1d4c6a8e3f5b7d9a1c2e4f6b8d0a2c4e6f8a1b3d5f7a9c1e3b5d7f
Response 200 OK
{
  "shop": "Example Kitchen",
  "order_number": "261027-0042",
  "status": "confirmed",
  "reason": null,
  "collect_at": "19:15",
  "total": "19.00",
  "items": [
    { "name": "Chicken wrap", "quantity": 2, "total": "17.00" },
    { "name": "Chips", "quantity": 1, "total": "2.00" }
  ]
}

Refusals

  • 404"No order at this address".
  • 429More than 120 requests from one caller in a minute.

AppointmentsComing soon

Appointments for services are booked by staff on the till today. There is no public route for them yet: an appointment booking API is coming soon.

Questions about the API

Email info@unitedpos.co.uk with the shop you are working for, or get in touch.