# Booking Management This guide covers the full booking lifecycle: creating, retrieving, updating, and cancelling reservations. ## Create a booking ``` POST /venues/{compositeId}/booking ``` cURL ```bash COMPOSITE_ID="29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b" curl -X POST "https://api.bookabletech.com/venues/${COMPOSITE_ID}/booking" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -H "x-Partner-Reference: YOUR_INTERNAL_REF" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+441234567890", "partySize": 4, "date": "2025-08-15", "time": "19:00", "type": "book", "notes": "Anniversary dinner — please add candles" }' ``` JavaScript ```javascript const BASE_URL = 'https://api.bookabletech.com'; const compositeId = '29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b'; const response = await fetch(`${BASE_URL}/venues/${compositeId}/booking`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', 'x-Partner-Reference': 'YOUR_INTERNAL_REF', }, body: JSON.stringify({ firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com', phone: '+441234567890', partySize: 4, date: '2025-08-15', time: '19:00', type: slot.type, // from availability check notes: 'Anniversary dinner — please add candles', }), }); const booking = await response.json(); // booking.id — store for future operations // booking.operatorBookingId — share with the guest // booking.status — "Confirmed" or "Pending" ``` Python ```python import requests BASE_URL = 'https://api.bookabletech.com' composite_id = '29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b' response = requests.post( f'{BASE_URL}/venues/{composite_id}/booking', json={ 'firstName': 'Jane', 'lastName': 'Smith', 'email': 'jane.smith@example.com', 'phone': '+441234567890', 'partySize': 4, 'date': '2025-08-15', 'time': '19:00', 'type': slot['type'], # from availability check 'notes': 'Anniversary dinner — please add candles', }, headers={ 'Authorization': f'Bearer {access_token}', 'x-Partner-Reference': 'YOUR_INTERNAL_REF', }, ) response.raise_for_status() booking = response.json() # booking['id'] — store for future operations # booking['operatorBookingId'] — share with the guest # booking['status'] — "Confirmed" or "Pending" ``` Java ```java String body = """ { "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+441234567890", "partySize": 4, "date": "2025-08-15", "time": "19:00", "type": "book", "notes": "Anniversary dinner — please add candles" } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/venues/" + compositeId + "/booking")) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/json") .header("x-Partner-Reference", "YOUR_INTERNAL_REF") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); ``` C# ```csharp var payload = new { firstName = "Jane", lastName = "Smith", email = "jane.smith@example.com", phone = "+441234567890", partySize = 4, date = "2025-08-15", time = "19:00", type = "book", notes = "Anniversary dinner — please add candles" }; var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/venues/{compositeId}/booking"); request.Headers.Add("x-Partner-Reference", "YOUR_INTERNAL_REF"); request.Content = JsonContent.Create(payload); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); var booking = await response.Content.ReadFromJsonAsync(); ``` **Example response:** ```json { "id": "29|CO|550e8400e29b41d4a716446655440000", "operatorBookingId": "BKA-7X2P", "status": "Confirmed", "firstName": "Jane", "lastName": "Smith", "partySize": 4, "date": "2025-08-15", "time": "19:00", "venueGroupName": "The Grand Table Group" } ``` ## Preorders Some venues offer **packages** (standalone add-ons — a bottle of wine, a tasting platter, a cocktail kit) and **menus** (structured multi-course offerings where the guest selects specific dishes). Both are submitted inside the `preorders` field of the booking request. If `products[].preOrderRequired` is `true` for the chosen product, a preorder must be included — the booking will be rejected without one. ### Step 1: Get the preorder catalogue Packages and menus available at the venue are returned in the `GET /venues` response under `data[].preorders`. **Example venue response (truncated):** ```json { "data": [ { "name": "The Grand Table", "preorders": { "packages": [ { "id": "5888a5afc71620e04002d5bb", "name": "Prosecco on arrival", "price": 8.50, "type": "drink" }, { "id": "62692e90d2cf1f163744efc4", "name": "Sharing charcuterie board", "price": 18.00, "type": "food", "sub_type": "starter" } ], "menus": [ { "id": "68a5f2eee07ae30fcd653b03", "name": "Set Menu", "description": "2 or 3 courses from our seasonal menu", "items": [ { "packageId": "62c6c9f532c7cc7e4265dfdc", "name": "Pan roasted Padron peppers (V)", "price": 9.00 }, { "packageId": "619d19eb40e1512ebb03b378", "name": "Beef fillet with truffle jus", "price": 28.00 } ] } ] } } ] } ``` ### Step 2: Check which preorders are valid for your slot The availability response filters this catalogue per time slot. Each entry in `times[]` includes a `preOrderItems` object: | `allowedPackages` / `allowedMenus` | Meaning | | --- | --- | | `"all"` | Every item in the venue catalogue is available | | `"set"` | Only the IDs listed in `packageIds` / `menuIds` are available | | `"none"` | No packages / menus for this slot | **Example availability response (truncated):** ```json { "times": [ { "time": "19:00", "type": "book", "preOrderItems": { "allowedPackages": "set", "packageIds": ["5888a5afc71620e04002d5bb"], "allowedMenus": "all", "menuIds": null } } ] } ``` In this example, only the `5888a5afc71620e04002d5bb` package is allowed at 19:00, but all menus are available. ### Step 3: Add preorders to the booking Include a `preorders` object in the booking request body. **Packages** — supply the package `id` and `quantity`: cURL ```bash curl -X POST "https://api.bookabletech.com/venues/${COMPOSITE_ID}/booking" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+441234567890", "partySize": 4, "date": "2025-08-15", "time": "19:00", "type": "book", "preorders": { "packages": [ { "id": "5888a5afc71620e04002d5bb", "quantity": 4 } ] } }' ``` JavaScript ```javascript const response = await fetch(`${BASE_URL}/venues/${compositeId}/booking`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com', phone: '+441234567890', partySize: 4, date: '2025-08-15', time: '19:00', type: slot.type, preorders: { packages: [ { id: '5888a5afc71620e04002d5bb', quantity: 4 }, ], }, }), }); ``` Python ```python response = requests.post( f'{BASE_URL}/venues/{composite_id}/booking', json={ 'firstName': 'Jane', 'lastName': 'Smith', 'email': 'jane.smith@example.com', 'phone': '+441234567890', 'partySize': 4, 'date': '2025-08-15', 'time': '19:00', 'type': slot['type'], 'preorders': { 'packages': [ {'id': '5888a5afc71620e04002d5bb', 'quantity': 4}, ], }, }, headers={'Authorization': f'Bearer {access_token}'}, ) response.raise_for_status() booking = response.json() ``` Java ```java String body = """ { "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+441234567890", "partySize": 4, "date": "2025-08-15", "time": "19:00", "type": "book", "preorders": { "packages": [ { "id": "5888a5afc71620e04002d5bb", "quantity": 4 } ] } } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/venues/" + compositeId + "/booking")) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); ``` C# ```csharp var payload = new { firstName = "Jane", lastName = "Smith", email = "jane.smith@example.com", phone = "+441234567890", partySize = 4, date = "2025-08-15", time = "19:00", type = "book", preorders = new { packages = new[] { new { id = "5888a5afc71620e04002d5bb", quantity = 4 } } } }; var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/venues/{compositeId}/booking"); request.Content = JsonContent.Create(payload); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); ``` **Menus** — supply the menu `id`, `quantity`, and optionally `items[]` to pre-select specific dishes. Each item uses the `packageId` from the menu's `items[]` catalogue: cURL ```bash curl -X POST "https://api.bookabletech.com/venues/${COMPOSITE_ID}/booking" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+441234567890", "partySize": 4, "date": "2025-08-15", "time": "19:00", "type": "book", "preorders": { "menus": [ { "id": "68a5f2eee07ae30fcd653b03", "quantity": 4, "items": [ { "id": "62c6c9f532c7cc7e4265dfdc", "quantity": 2 }, { "id": "619d19eb40e1512ebb03b378", "quantity": 2 } ] } ] } }' ``` JavaScript ```javascript const response = await fetch(`${BASE_URL}/venues/${compositeId}/booking`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com', phone: '+441234567890', partySize: 4, date: '2025-08-15', time: '19:00', type: slot.type, preorders: { menus: [ { id: '68a5f2eee07ae30fcd653b03', quantity: 4, items: [ { id: '62c6c9f532c7cc7e4265dfdc', quantity: 2 }, { id: '619d19eb40e1512ebb03b378', quantity: 2 }, ], }, ], }, }), }); ``` Python ```python response = requests.post( f'{BASE_URL}/venues/{composite_id}/booking', json={ 'firstName': 'Jane', 'lastName': 'Smith', 'email': 'jane.smith@example.com', 'phone': '+441234567890', 'partySize': 4, 'date': '2025-08-15', 'time': '19:00', 'type': slot['type'], 'preorders': { 'menus': [ { 'id': '68a5f2eee07ae30fcd653b03', 'quantity': 4, 'items': [ {'id': '62c6c9f532c7cc7e4265dfdc', 'quantity': 2}, {'id': '619d19eb40e1512ebb03b378', 'quantity': 2}, ], }, ], }, }, headers={'Authorization': f'Bearer {access_token}'}, ) response.raise_for_status() booking = response.json() ``` Java ```java String body = """ { "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+441234567890", "partySize": 4, "date": "2025-08-15", "time": "19:00", "type": "book", "preorders": { "menus": [ { "id": "68a5f2eee07ae30fcd653b03", "quantity": 4, "items": [ { "id": "62c6c9f532c7cc7e4265dfdc", "quantity": 2 }, { "id": "619d19eb40e1512ebb03b378", "quantity": 2 } ] } ] } } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/venues/" + compositeId + "/booking")) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); ``` C# ```csharp var payload = new { firstName = "Jane", lastName = "Smith", email = "jane.smith@example.com", phone = "+441234567890", partySize = 4, date = "2025-08-15", time = "19:00", type = "book", preorders = new { menus = new[] { new { id = "68a5f2eee07ae30fcd653b03", quantity = 4, items = new[] { new { id = "62c6c9f532c7cc7e4265dfdc", quantity = 2 }, new { id = "619d19eb40e1512ebb03b378", quantity = 2 } } } } } }; var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/venues/{compositeId}/booking"); request.Content = JsonContent.Create(payload); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); ``` Omitting `items` is valid — the venue will send the guest a link after booking to complete their menu selections. Packages and menus can be combined in the same request using both `preorders.packages` and `preorders.menus` together. ## Retrieve a booking ``` GET /venues/bookings/{bookingId} ``` Use this to poll the current status of a `Pending` booking, or to sync booking details into your system. cURL ```bash BOOKING_ID="29|CO|550e8400e29b41d4a716446655440000" curl "https://api.bookabletech.com/venues/bookings/${BOOKING_ID}" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` JavaScript ```javascript const bookingId = '29|CO|550e8400e29b41d4a716446655440000'; const response = await fetch(`${BASE_URL}/venues/bookings/${bookingId}`, { headers: { Authorization: `Bearer ${accessToken}` }, }); const booking = await response.json(); console.log('Status:', booking.status); // "Confirmed", "Pending", "Cancelled" ``` Python ```python booking_id = '29|CO|550e8400e29b41d4a716446655440000' response = requests.get( f'{BASE_URL}/venues/bookings/{booking_id}', headers={'Authorization': f'Bearer {access_token}'}, ) response.raise_for_status() booking = response.json() print('Status:', booking['status']) # "Confirmed", "Pending", "Cancelled" ``` Java ```java String bookingId = "29|CO|550e8400e29b41d4a716446655440000"; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/venues/bookings/" + bookingId)) .header("Authorization", "Bearer " + accessToken) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); ``` C# ```csharp var bookingId = "29|CO|550e8400e29b41d4a716446655440000"; var request = new HttpRequestMessage(HttpMethod.Get, $"{BASE_URL}/venues/bookings/{bookingId}"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); var booking = await response.Content.ReadFromJsonAsync(); Console.WriteLine($"Status: {booking.GetProperty("status")}"); ``` For ongoing status tracking, prefer [webhooks](/getting-started/webhook) over polling. ## Update a booking ``` PATCH /venues/bookings/{bookingId} ``` Updates use [JSON Patch (RFC 6902)](https://jsonpatch.com/) with `Content-Type: application/json-patch+json`. Only confirmed bookings can be updated. **Patchable fields:** `date`, `time`, `partySize`, `duration`, `firstName`, `lastName`, `email`, `phone`, `notes`, `preorders/packages`, `preorders/menus` cURL ```bash curl -X PATCH "https://api.bookabletech.com/venues/bookings/${BOOKING_ID}" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json-patch+json" \ -d '[ { "op": "replace", "path": "/partySize", "value": 6 }, { "op": "replace", "path": "/notes", "value": "Now 6 guests — please arrange accordingly" } ]' ``` JavaScript ```javascript const response = await fetch(`${BASE_URL}/venues/bookings/${bookingId}`, { method: 'PATCH', headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json-patch+json', }, body: JSON.stringify([ { op: 'replace', path: '/partySize', value: 6 }, { op: 'replace', path: '/notes', value: 'Now 6 guests — please arrange accordingly' }, ]), }); const updated = await response.json(); ``` Python ```python response = requests.patch( f'{BASE_URL}/venues/bookings/{booking_id}', json=[ {'op': 'replace', 'path': '/partySize', 'value': 6}, {'op': 'replace', 'path': '/notes', 'value': 'Now 6 guests — please arrange accordingly'}, ], headers={ 'Authorization': f'Bearer {access_token}', 'Content-Type': 'application/json-patch+json', }, ) response.raise_for_status() updated = response.json() ``` Java ```java String patch = """ [ { "op": "replace", "path": "/partySize", "value": 6 }, { "op": "replace", "path": "/notes", "value": "Now 6 guests — please arrange accordingly" } ] """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/venues/bookings/" + bookingId)) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/json-patch+json") .method("PATCH", HttpRequest.BodyPublishers.ofString(patch)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); ``` C# ```csharp var patch = new[] { new { op = "replace", path = "/partySize", value = (object)6 }, new { op = "replace", path = "/notes", value = (object)"Now 6 guests — please arrange accordingly" } }; var request = new HttpRequestMessage(HttpMethod.Patch, $"{BASE_URL}/venues/bookings/{bookingId}"); request.Content = new StringContent( JsonSerializer.Serialize(patch), Encoding.UTF8, "application/json-patch+json"); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); ``` If you're changing `date`, `time`, or `partySize`, check availability first — the update will be rejected if the new slot is unavailable. ## Cancel a booking ``` DELETE /venues/bookings/{bookingId} ``` Cancellation is permanent. The booking status changes to `Cancelled` and a `booking.cancelled` webhook fires if configured. cURL ```bash curl -X DELETE "https://api.bookabletech.com/venues/bookings/${BOOKING_ID}" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" # 204 No Content on success ``` JavaScript ```javascript const response = await fetch(`${BASE_URL}/venues/bookings/${bookingId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${accessToken}` }, }); if (response.status === 204) { console.log('Booking cancelled'); } ``` Python ```python response = requests.delete( f'{BASE_URL}/venues/bookings/{booking_id}', headers={'Authorization': f'Bearer {access_token}'}, ) response.raise_for_status() # 204 No Content on success ``` Java ```java HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/venues/bookings/" + bookingId)) .header("Authorization", "Bearer " + accessToken) .DELETE() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); // 204 No Content on success ``` C# ```csharp var request = new HttpRequestMessage(HttpMethod.Delete, $"{BASE_URL}/venues/bookings/{bookingId}"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); // 204 No Content on success ``` ## Error handling | HTTP Status | Meaning | | --- | --- | | `401` | Token expired — refresh and retry | | `403` | You don't have permission to modify this booking | | `404` | Booking not found | | `409` | Booking cannot be modified in its current state | | `422` | Business rule violation (e.g. cancellation window has passed) | See the [Error Catalog](/resources/error-catalog) for detailed error codes. ## Related - [Quickstart](/getting-started/quickstart) — first booking in 5 steps - [Webhooks](/getting-started/webhook) — real-time booking status events - [Bookings API Reference](/apis/production/bookingapi)