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.
- 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.
- The slug. The same page shows the shop's order and book links. The slug is the part after
/shop/, such asexample-kitchen. - 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/jsonon a POST. A body is at most 64 KB. - Dates are
YYYY-MM-DDand timesHH:MM, on the shop's own clock (UK time unless the shop set another).reserved_atis 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.
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.
| What | Limit |
|---|---|
| Any /api/v1 request | 60 a minute for each API key. |
| Requests with a wrong or missing key | 30 a minute from one address, then 429. |
| POST /public/shops/{slug}/bookings | 5 in 10 minutes from one caller, and 30 web bookings an hour for the shop. |
| POST /public/shops/{slug}/orders | 5 in 10 minutes from one caller. |
| Orders, either route | 5 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.
{
"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.
curl https://api.unitedpos.co.uk/public/shops/example-kitchen{
"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
| Field | Type | Required | What it is |
|---|---|---|---|
date | date | Yes | The day, YYYY-MM-DD. |
party | integer | Yes | Guests, from 1 to the shop's largest online booking (booking_max_party). |
curl "https://api.unitedpos.co.uk/api/v1/booking-times?date=2026-10-30&party=2" \
-H "Authorization: Bearer up_live_YOUR_KEY"{
"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
| Field | Type | Required | What it is |
|---|---|---|---|
date | date | Yes | YYYY-MM-DD. |
time | string | Yes | HH:MM, one of the free times for that date and party. |
party | integer | Yes | Guests, 1 to booking_max_party. |
name | string | Yes | Up to 60 characters. |
phone | string | Yes | 7 to 15 digits; spaces, +, - and brackets allowed. |
email | string | No | A valid address, up to 254 characters. |
note | string | No | Up to 200 characters, for the shop. |
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"}'{
"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
| Field | Type | Required | What it is |
|---|---|---|---|
from | date | No | YYYY-MM-DD. Today when left out. |
to | date | No | YYYY-MM-DD. from plus 30 days when left out. |
curl "https://api.unitedpos.co.uk/api/v1/bookings?from=2026-10-27&to=2026-11-03" \
-H "Authorization: Bearer up_live_YOUR_KEY"{
"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.
curl https://api.unitedpos.co.uk/api/v1/bookings/7f1c2a4e-3b5d-4c8e-9a61-0d2f4b6e8a10 \
-H "Authorization: Bearer up_live_YOUR_KEY"{
"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
| Field | Type | Required | What it is |
|---|---|---|---|
items | array | Yes | 1 to 30 lines, 50 items at most in all. |
items[].product_id | uuid | Yes | A product id from the menu. |
items[].variant_id | uuid | No | A size id from the product's sizes. |
items[].quantity | integer | Yes | 1 to 20. |
items[].modifiers | array | No | Choices: { "option_id": uuid, "quantity": integer (optional) }, within each group's rules. |
items[].note | string | No | Up to 100 characters. |
name | string | Yes | Up to 60 characters. |
phone | string | Yes | 7 to 15 digits; spaces, +, - and brackets allowed. |
email | string | No | A valid address. |
note | string | No | Up to 200 characters. |
pickup | string | Yes | "asap", or HH:MM from the shop's pickup_times. |
key | uuid | Yes | Your own id for this order, so a resend is not a second order. |
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"
}'{
"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.
curl https://api.unitedpos.co.uk/public/orders/4f0c9a2e7b1d4c6a8e3f5b7d9a1c2e4f6b8d0a2c4e6f8a1b3d5f7a9c1e3b5d7f{
"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.