BookMeTable Integration API · V1 · revision 8

Integration API documentation

Connect a web ePOS or till to BookMeTable. Each restaurant has a permanent numeric id (1, 2, 3…) shown on its Integration tab. Create that venue's API key so the till can list bookings, load guest details, push its floor plan, redeem voucher and offer QR codes, and receive signed webhooks. Guests only see area and table pickers after the till has sent those areas and tables.

Canonical URL: https://bookmetable.uk/API/Doc

Per-restaurant API keys

Create one API key per restaurant. That key can only read and update bookings for that venue. ePOS should store the key against the matching till / shop so incoming reservations are attributed to the correct restaurant.

  • Admin: Restaurants → [restaurant] → Integration
  • Restaurant portal: Integration
  • Platform-wide keys and partners: Admin → Integrations

Every booking includes origin and restaurant so the till can display the source venue even if one ePOS account connects several restaurants:

origin.platform          = "bookmetable"
origin.restaurant_id     = 1
origin.restaurant_name   = "Annapurna Nepalese"
origin.restaurant_slug   = "annapurna-nepalese"
origin.booking_url       = "https://bookmetable.uk/annapurna-nepalese"
source                   = "bookmetable"

Restaurant id

Every venue is assigned a permanent integer when it is created. The first restaurant is 1, the next is 2, and so on. If a restaurant is deleted, that number is never given to another venue.

The same number appears on the Integration tab and in every API payload:

GET https://bookmetable.uk/api/v1/restaurant

{
  "object": "restaurant",
  "data": {
    "id": 1,
    "name": "Annapurna Nepalese",
    "slug": "annapurna-nepalese",
    "booking_url": "https://bookmetable.uk/annapurna-nepalese",
    "table_selection_enabled": false,
    "table_lock_minutes": 15
  }
}

table_selection_enabled stays false until the till has pushed a floor plan with PUT /areas or PUT /tables. After that sync, guests can pick those areas and tables on the booking form.

  • restaurant.id — permanent number
  • restaurant_id and origin.restaurant_id on bookings — the same number

ePOS should store this number against the till / shop. Do not use the old rst_… UUID as the public restaurant id.

Authentication

Send the restaurant's secret key on every request. Keys start with bmt_live_ and are created on that restaurant's Integration tab. Store them on the ePOS server — not in a public website bundle.

Authorization: Bearer bmt_live_…
Accept: application/json
Content-Type: application/json

Machine-readable catalog: https://bookmetable.uk/api. Version manifest: https://bookmetable.uk/api/v1.

REST endpoints

Base URL: https://bookmetable.uk/api/v1

Load the connected restaurant

GET https://bookmetable.uk/api/v1/restaurant

data.id is the permanent restaurant number shown on the Integration tab.

MethodPathAuthPurpose
GET/restaurantYesVenue profile. id is the permanent restaurant number (1, 2, 3…). Also hours, capacity, and table_selection_enabled.
GET/bookingsYesList reservations. Query: date, from, to, status, floor_status, phone, q (name/email/phone/reference).
GET/bookings/{reference}YesSingle booking by reference (e.g. BK-SYF8GG) or booking id.
PATCH/bookings/{reference}YesUpdate floor_status or cancel (status: cancelled).
GET/areasYesDining areas and table numbers for this restaurant.
PUT/areasYesPush the till floor plan. BookMeTable creates the same areas and tables. Guest pickers appear only after this sync.
GET/tablesYesFlat table list for this restaurant, grouped under the same floor plan as GET /areas.
PUT/tablesYesSame as PUT /areas. Send the ePOS tables (nested areas, flat tables, or table_count) and BookMeTable will create them.
GET/tables/availabilityYesWhich tables are free for a date and time. Query: date, time, party_size.
POST/tables/statusYesReport a walk-in or blocked table so BookMeTable will not offer it.
POST/offers/redeemYesScan a loyalty voucher or offer QR and redeem it only for the restaurant this API key is connected to.
GET/NoVersion manifest, schema fields, and changelog for this API version.

List today's bookings

GET https://bookmetable.uk/api/v1/bookings?date=2026-10-18&status=confirmed

Find a guest by phone or name

GET https://bookmetable.uk/api/v1/bookings?phone=07123456789
GET https://bookmetable.uk/api/v1/bookings?q=Alex

Load one booking

GET https://bookmetable.uk/api/v1/bookings/BK-SYF8GG

Mark seated from the till

PATCH https://bookmetable.uk/api/v1/bookings/BK-SYF8GG
Content-Type: application/json

{ "floor_status": "seated" }

Cancel with { "status": "cancelled" }.

Pull the floor plan

GET https://bookmetable.uk/api/v1/areas
GET https://bookmetable.uk/api/v1/tables

Check if Outside Table 6 is free at 19:30

GET https://bookmetable.uk/api/v1/tables/availability?date=2026-10-18&time=19:30&party_size=2

Tell BookMeTable a walk-in sat at a table

POST https://bookmetable.uk/api/v1/tables/status
Content-Type: application/json

{ "epos_table_id": "T6", "date": "2026-10-18", "time": "19:30", "status": "occupied" }

Scan a voucher or offer QR and redeem it

Send the scanner text for a loyalty voucher or promotional offer. The code is redeemed only for the restaurant on this API key. A QR for another shop is rejected. Loyalty vouchers can be used once. Promotional offers stay valid until their end date.

POST https://bookmetable.uk/api/v1/offers/redeem
Content-Type: application/json

{ "code": "BMT:your-restaurant-slug:LOYAB12CD34" }

Apply the returned discount on the till. Field details and error codes are in QR vouchers and offers.

QR vouchers and offers

When the till scans a customer's BookMeTable QR, POST the raw scanner text to /offers/redeem with this restaurant's API key. The code is redeemed only if it belongs to that same shop.

Accepted scanner text:

  • BMT:restaurant-slug:CODE — voucher or offer QR
  • BMT:CODE or the code on its own
  • A URL that contains c or code

If the QR includes a restaurant slug, that slug must match the shop on the API key. The voucher or offer must also be issued by that shop. A code for another restaurant is rejected and is not marked redeemed.

Redeem

POST https://bookmetable.uk/api/v1/offers/redeem
Authorization: Bearer bmt_live_…
Content-Type: application/json

{ "code": "BMT:annapurna-nepalese:LOYAB12CD34" }

qr is accepted as an alias for code.

Success

{
  "object": "offer_redemption",
  "data": {
    "code": "LOYAB12CD34",
    "kind": "loyalty",
    "title": "10% off your next visit",
    "discount_type": "percent",
    "discount_value": 10,
    "discount_label": "10% off",
    "currency": "GBP",
    "single_use": true,
    "restaurant_id": 1,
    "restaurant_slug": "annapurna-nepalese",
    "redeemed": true
  }
}
  • kind is loyalty or promotion.
  • discount_type is percent, fixed_amount, or free_item.
  • discount_value is the percent, or the amount in pence when the type is fixed_amount (£5.00 is 500).
  • Loyalty vouchers are single-use. A successful call marks them redeemed. Promotional offers stay valid for every guest until their end date, so single_use is false.

Redeem errors

  • 400 invalid_code — the scan did not contain a code
  • 403 wrong_restaurant — the QR or code belongs to another shop
  • 404 not_found — no voucher or offer for this shop
  • 409 already_redeemed — loyalty voucher already used
  • 410 expired — loyalty voucher expired
  • 422 offer_not_valid — promotional offer is outside its dates or inactive

Booking object

Every list item, detail response, and webhook uses the same JSON shape so a web ePOS can show guest name, contact details, covers, time, notes, visit state, and which BookMeTable restaurant the booking came from. restaurant_id is that venue's permanent number (not a UUID).

{
  "object": "booking",
  "data": {
    "id": "bkg_8f2c1a",
    "reference": "BK-SYF8GG",
    "restaurant_id": 1,
    "restaurant": {
      "id": 1,
      "name": "Annapurna Nepalese",
      "slug": "annapurna-nepalese",
      "booking_url": "https://bookmetable.uk/annapurna-nepalese"
    },
    "origin": {
      "platform": "bookmetable",
      "restaurant_id": 1,
      "restaurant_name": "Annapurna Nepalese",
      "restaurant_slug": "annapurna-nepalese",
      "booking_url": "https://bookmetable.uk/annapurna-nepalese"
    },
    "guest": {
      "name": "Alex Guest",
      "email": "alex@example.com",
      "phone": "07123456789"
    },
    "party_size": 4,
    "covers": 4,
    "date": "2026-09-20",
    "time": "19:30",
    "starts_at": "2026-09-20T19:30:00",
    "timezone": "Europe/London",
    "status": "confirmed",
    "floor_status": "pending",
    "visit_completed": false,
    "special_requests": "Window table, nut allergy",
    "area": { "id": "area_outside", "name": "Outside" },
    "table": { "id": "tbl_6", "number": "6" },
    "lock_at": "2026-09-20T19:15:00",
    "lock_minutes": 15,
    "source": "bookmetable",
    "created_at": "2026-09-19T12:00:00.000Z",
    "updated_at": "2026-09-19T12:00:00.000Z"
  }
}

Fields: id, reference, restaurant_id, restaurant.id, restaurant.name, restaurant.slug, restaurant.booking_url, origin.platform, origin.restaurant_id, origin.restaurant_name, origin.restaurant_slug, origin.booking_url, guest.name, guest.email, guest.phone, party_size, covers, date, time, starts_at, timezone, status, floor_status, visit_completed, special_requests, area.id, area.name, table.id, table.number, lock_at, lock_minutes, source, created_at, updated_at.

Restaurant profile fields from GET /restaurant: id, name, slug, booking_url, source, timezone, opening_time, closing_time, max_party_size, slot_minutes, active, address, city, postcode, phone, table_selection_enabled, table_lock_minutes.

Areas, tables, and two-way ePOS sync

Guests only see area and table pickers after the till has sent its floor plan. Push that layout with PUT /areas or PUT /tables. BookMeTable then creates the same areas and the same number of tables, and turns guest selection on.

  1. Floor plan from ePOS. Send areas and tables (or a flat table list / table_count). Include epos_area_id / epos_table_id when the till has them so later updates match.
  2. Guest books a table. The booking webhook includes area, table, starts_at, and lock_at (seating time minus lock_minutes, default 15).
  3. ePOS locks the table. At lock_at, the till should reserve that table so walk-ins cannot sit there. You can also poll GET /bookings?date=… and lock any booking whose lock_at has passed.
  4. Walk-ins and blocked tables. When the till seats a walk-in or marks a table out of service, POST /tables/status with occupied or blocked. BookMeTable will not offer that table. Send free when it is cleared.
  5. Availability check. Before confirming a booking or a walk-in, call GET /tables/availability. A table is unavailable if BookMeTable already has a reservation or ePOS has posted a hold.

Push the till floor plan (areas + tables)

PUT https://bookmetable.uk/api/v1/areas
Content-Type: application/json

{
  "areas": [
    {
      "name": "Outside",
      "epos_area_id": "out",
      "tables": [
        { "number": "6", "epos_table_id": "T6", "seats": 4 }
      ]
    },
    {
      "name": "Downstairs",
      "epos_area_id": "down",
      "table_count": 8
    }
  ]
}

Or send the ePOS table list as-is

PUT https://bookmetable.uk/api/v1/tables
Content-Type: application/json

{
  "tables": [
    { "area": "Main hall", "number": "1", "epos_table_id": "T1", "seats": 2 },
    { "area": "Main hall", "number": "2", "epos_table_id": "T2", "seats": 4 },
    { "area": "Garden", "number": "10", "epos_table_id": "G10", "seats": 6 }
  ]
}

Or create one dining room with N tables

PUT https://bookmetable.uk/api/v1/tables
Content-Type: application/json

{ "table_count": 12 }

BookMeTable creates matching areas and tables, stores epos_area_id / epos_table_id, and turns guest table selection on. A flat list or table_count replaces the previous till floor plan unless you send "replace": false.

Read the tables BookMeTable created

GET https://bookmetable.uk/api/v1/tables

The response includes areas and a flat tables array with area_name, number, seats, and the ePOS ids. Use those epos_table_id values later with POST /tables/status.

Floor status

Use floor status for the on-site workflow. Booking status (confirmed / cancelled) is separate.

pending → arrived → seated → completed (or no_show).

Allowed values: pending, arrived, seated, no_show, completed.

Webhooks

Register a webhook URL on the restaurant connection. BookMeTable POSTs a signed JSON body when subscribed events occur: booking.created, booking.updated, booking.cancelled, booking.completed.

Verify HMAC-SHA256 of {timestamp}.{raw_body} using your webhook secret.

  • X-BookMeTable-Signature
  • X-BookMeTable-Timestamp
  • X-BookMeTable-Event
  • X-BookMeTable-Delivery

The webhook data object matches the REST booking object above.

Errors

  • 401 — missing or invalid API key
  • 404 — booking, voucher, or offer not found for this restaurant
  • 403 — voucher or offer belongs to another restaurant
  • 409 — loyalty voucher already redeemed
  • 410 — loyalty voucher expired
  • 422 — promotional offer is not currently valid
  • 400 — invalid JSON, floor status, or missing areas / tables / table_count on a floor-plan push
  • 503 — Integration API disabled platform-wide

Successful JSON responses include X-BookMeTable-Api-Version: v1. Browser tills may call the API cross-origin (CORS + OPTIONS).

Changelog

  • v1 r8 (2026-09-23)
    • POST /offers/redeem lets the connected ePOS scan a BookMeTable QR and redeem that shop’s loyalty voucher or promotional offer.
    • Redemption uses the restaurant on the API key. A code for another shop is rejected.
    • Loyalty vouchers are single-use. Promotional offers stay valid for every guest until their dates end.
  • v1 r7 (2026-09-21)
    • Guest area/table selection is shown only after ePOS has pushed a floor plan (epos_area_id / epos_table_id).
    • PUT /areas and PUT /tables accept nested areas, a flat tables list, or table_count and create the same areas and tables in BookMeTable.
    • GET /tables returns the flat till table list. table_selection_enabled is true only when that ePOS floor plan exists.
  • v1 r6 (2026-09-21)
    • Documented permanent numeric restaurant id on GET /restaurant and all booking payloads.
    • Public docs and FAQ updated: restaurant.id / restaurant_id / origin.restaurant_id are integers and are never reused.
  • v1 r5 (2026-09-21)
    • Restaurant id is now a permanent number (1, 2, 3…) assigned in creation order and never reused after delete.
    • API restaurant.id, restaurant_id, and origin.restaurant_id use that number instead of rst_… UUIDs.
  • v1 r4 (2026-09-20)
    • Areas and tables: GET/PUT /areas, GET /tables/availability, POST /tables/status.
    • Booking payload includes area, table, lock_at, and lock_minutes so ePOS can lock the table 15 minutes before seating.
    • Restaurant profile reports table_selection_enabled and table_lock_minutes.
  • v1 r3 (2026-09-20)
    • Per-restaurant Integration tab: each venue has its own API key for ePOS.
    • Booking payload origin + restaurant.booking_url so the till knows which restaurant the booking came from.
  • v1 r2 (2026-09-20)
    • Web EPOS booking payload: id, restaurant name/slug, covers, starts_at, visit_completed.
    • Look up a booking by reference or internal id; search list by phone or q.
    • Filter list by floor_status. CORS + OPTIONS for browser-based tills.
    • Restaurant portal amend/cancel now emit booking.updated / booking.cancelled webhooks.
  • v1 r1 (2026-06-30)
    • Initial Integration API release.
    • REST: restaurant, bookings list/detail, PATCH floor_status.
    • Webhooks: booking.created, booking.updated, booking.cancelled, booking.completed.
    • Auto contract sync on database migration.

Looking for diner help instead? See the FAQ. This page is also available at /API/Doc.