# Quickstart Make your first booking in 6 steps. This guide takes you from zero to a confirmed booking using the Bookable API. ## Before you start You'll need: - A `client_id` and `client_secret` — log in to the [Bookable Portal](https://portal.bookabletech.com) to find your credentials | | Sandbox | Production | | --- | --- | --- | | **Base URL** | `https://api-sandbox.bookabletech.com` | `https://api.bookabletech.com` | | **Auth endpoint** | `https://auth-sandbox.bookabletech.com/oauth/token` | `https://auth.bookabletech.com/oauth/token` | Start with sandbox credentials during development. ## Step 1: Get an access token The Bookable API uses OAuth 2.0 Client Credentials. Exchange your `client_id` and `client_secret` for a short-lived bearer token. cURL ```bash curl -X POST https://auth.bookabletech.com/oauth/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "client_credentials", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "audience": "api.bookabletech.com" }' ``` JavaScript ```javascript const response = await fetch('https://auth.bookabletech.com/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'client_credentials', client_id: process.env.BOOKABLE_CLIENT_ID, client_secret: process.env.BOOKABLE_CLIENT_SECRET, audience: 'api.bookabletech.com', }), }); const { access_token, expires_in } = await response.json(); // Store access_token and expires_in — reuse until expiry ``` Python ```python import requests, os resp = requests.post( 'https://auth.bookabletech.com/oauth/token', json={ 'grant_type': 'client_credentials', 'client_id': os.environ['BOOKABLE_CLIENT_ID'], 'client_secret': os.environ['BOOKABLE_CLIENT_SECRET'], 'audience': 'api.bookabletech.com', } ) resp.raise_for_status() token_data = resp.json() access_token = token_data['access_token'] # Store access_token and token_data['expires_in'] — reuse until expiry ``` Java ```java import java.net.http.*; import java.net.URI; HttpClient client = HttpClient.newHttpClient(); String body = """ { "grant_type": "client_credentials", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "audience": "api.bookabletech.com" } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://auth.bookabletech.com/oauth/token")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); // Parse response.body() JSON to extract access_token and expires_in ``` C# ```csharp using System.Net.Http.Json; using System.Text.Json; var client = new HttpClient(); var payload = new { grant_type = "client_credentials", client_id = Environment.GetEnvironmentVariable("BOOKABLE_CLIENT_ID"), client_secret = Environment.GetEnvironmentVariable("BOOKABLE_CLIENT_SECRET"), audience = "api.bookabletech.com" }; var response = await client.PostAsJsonAsync("https://auth.bookabletech.com/oauth/token", payload); response.EnsureSuccessStatusCode(); using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); var accessToken = doc.RootElement.GetProperty("access_token").GetString(); var expiresIn = doc.RootElement.GetProperty("expires_in").GetInt32(); ``` **Example response:** ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "scope": "venue:read venue-booking:create", "expires_in": 3600, "token_type": "Bearer" } ``` Tokens are valid for 1 hour. See [Authentication](/getting-started/auth) for a full token manager with caching. ## Step 2: Onboard an operator and connect their TMS Venues are scoped to operators you have onboarded. An **operator** is the hospitality business whose venues you want to make bookable. You onboard them once, supply their TMS credentials, and their venues immediately become available via `GET /venues`. ### 2a — Onboard the operator cURL ```bash curl -X POST https://api.bookabletech.com/operators \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "businessName": "Acme Restaurant Group", "partnerSource": "YourTradingName" }' ``` JavaScript ```javascript const BASE_URL = 'https://api.bookabletech.com'; const res = await fetch(`${BASE_URL}/operators`, { method: 'POST', headers: { Authorization: `Bearer ${access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ businessName: 'Acme Restaurant Group', partnerSource: 'YourTradingName', }), }); const { id: operatorId } = await res.json(); ``` Python ```python BASE_URL = 'https://api.bookabletech.com' res = requests.post( f'{BASE_URL}/operators', json={'businessName': 'Acme Restaurant Group', 'partnerSource': 'YourTradingName'}, headers={'Authorization': f'Bearer {access_token}'} ) res.raise_for_status() operator_id = res.json()['id'] ``` Java ```java String BASE_URL = "https://api.bookabletech.com"; String opBody = """ {"businessName": "Acme Restaurant Group", "partnerSource": "YourTradingName"} """; HttpRequest opRequest = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/operators")) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(opBody)) .build(); HttpResponse opResponse = client.send(opRequest, HttpResponse.BodyHandlers.ofString()); // Parse opResponse.body() — extract id as operatorId ``` C# ```csharp string BASE_URL = "https://api.bookabletech.com"; var opPayload = new { businessName = "Acme Restaurant Group", partnerSource = "YourTradingName" }; var opResponse = await client.PostAsJsonAsync($"{BASE_URL}/operators", opPayload); opResponse.EnsureSuccessStatusCode(); using var opDoc = JsonDocument.Parse(await opResponse.Content.ReadAsStringAsync()); var operatorId = opDoc.RootElement.GetProperty("id").GetInt32(); ``` **Example response:** ```json { "id": 3, "businessName": "Acme Restaurant Group", "createdAt": "2025-03-01T10:00:00Z", "updatedAt": "2025-03-01T10:00:00Z" } ``` Store the `id` — you need it in the next sub-step. ### 2b — Add TMS credentials The operator must tell you which TMS they use and provide their credentials. Bookable supports Collins (`CO`), SevenRooms (`SR`), and Zonal (`ZO`). The example below uses Collins. cURL ```bash OPERATOR_ID=3 curl -X POST "https://api.bookabletech.com/operators/${OPERATOR_ID}/tms-credentials" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "tmsSlug": "CO", "bearer": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...", "externalOperatorId": "514ada610df690b6770000fd", "active": true }' ``` JavaScript ```javascript await fetch(`${BASE_URL}/operators/${operatorId}/tms-credentials`, { method: 'POST', headers: { Authorization: `Bearer ${access_token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ tmsSlug: 'CO', bearer: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...', externalOperatorId: '514ada610df690b6770000fd', active: true, }), }); ``` Python ```python requests.post( f'{BASE_URL}/operators/{operator_id}/tms-credentials', json={ 'tmsSlug': 'CO', 'bearer': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...', 'externalOperatorId': '514ada610df690b6770000fd', 'active': True, }, headers={'Authorization': f'Bearer {access_token}'} ).raise_for_status() ``` Java ```java String credBody = """ { "tmsSlug": "CO", "bearer": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...", "externalOperatorId": "514ada610df690b6770000fd", "active": true } """; HttpRequest credRequest = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/operators/" + operatorId + "/tms-credentials")) .header("Authorization", "Bearer " + accessToken) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(credBody)) .build(); client.send(credRequest, HttpResponse.BodyHandlers.ofString()); ``` C# ```csharp var credPayload = new { tmsSlug = "CO", bearer = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...", externalOperatorId = "514ada610df690b6770000fd", active = true }; var credResponse = await client.PostAsJsonAsync( $"{BASE_URL}/operators/{operatorId}/tms-credentials", credPayload); credResponse.EnsureSuccessStatusCode(); ``` Once `active: true`, the operator's venues are available in `GET /venues`. For SevenRooms and Zonal credential fields, and full management endpoints, see the [Operator Setup guide](/getting-started/operator-setup). ## Step 3: Search for a venue Use `GET /venues` to find venues. Filter by city, date, or other criteria to narrow results. cURL ```bash curl "https://api.bookabletech.com/venues?city=London&date=2025-08-15" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` JavaScript ```javascript const response = await fetch( `${BASE_URL}/venues?city=London&date=2025-08-15`, { headers: { Authorization: `Bearer ${access_token}` }, } ); const { data: venues } = await response.json(); ``` Python ```python response = requests.get( f'{BASE_URL}/venues', params={'city': 'London', 'date': '2025-08-15'}, headers={'Authorization': f'Bearer {access_token}'} ) response.raise_for_status() venues = response.json()['data'] ``` Java ```java HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(BASE_URL + "/venues?city=London&date=2025-08-15")) .header("Authorization", "Bearer " + accessToken) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); // Parse response.body() — extract data[] array ``` C# ```csharp client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); var response = await client.GetAsync($"{BASE_URL}/venues?city=London&date=2025-08-15"); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(); ``` **Example response (truncated):** ```json { "data": [ { "id": "29|X9|275cc44dd2e2496fba44857c9257443a", "name": "The Grand Table", "location": { "city": "London" }, "products": [ { "compositeId": "29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b", "productName": "Table Reservation" } ] } ] } ``` The `compositeId` inside `products[]` is what you need for the next steps. Each product has its own `compositeId`. ## Step 4: Check availability Before creating a booking, verify that the venue has slots available for your desired date, time, and party size. ``` GET /venues/{compositeId}/availability?date=YYYY-MM-DD&startTime=HH:MM&partySize=N ``` cURL ```bash COMPOSITE_ID="29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b" curl "https://api.bookabletech.com/venues/${COMPOSITE_ID}/availability?date=2025-08-15&startTime=19:00&partySize=4" \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" ``` JavaScript ```javascript const compositeId = '29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b'; const response = await fetch( `${BASE_URL}/venues/${compositeId}/availability?date=2025-08-15&startTime=19:00&partySize=4`, { headers: { Authorization: `Bearer ${access_token}` }, } ); const availability = await response.json(); // Pick the slot you want from the times array const slot = availability.times?.find(t => t.time === '19:00'); // slot.type — "book" or "request" ``` Python ```python composite_id = '29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b' response = requests.get( f'{BASE_URL}/venues/{composite_id}/availability', params={'date': '2025-08-15', 'startTime': '19:00', 'partySize': 4}, headers={'Authorization': f'Bearer {access_token}'} ) response.raise_for_status() availability = response.json() # Pick the slot you want from the times array slot = next(t for t in availability['times'] if t['time'] == '19:00') # slot['type'] — "book" or "request" ``` Java ```java String compositeId = "29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b"; String url = BASE_URL + "/venues/" + compositeId + "/availability?date=2025-08-15&startTime=19:00&partySize=4"; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Authorization", "Bearer " + accessToken) .GET() .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); // Parse response.body() — iterate times[] to find your slot and extract its type ``` C# ```csharp string compositeId = "29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b"; var response = await client.GetAsync( $"{BASE_URL}/venues/{compositeId}/availability?date=2025-08-15&startTime=19:00&partySize=4" ); response.EnsureSuccessStatusCode(); // Parse response — iterate times[] to find your slot and extract its type ``` **Example response:** ```json { "name": "Table Reservation", "times": [ { "time": "19:00", "duration": 90, "type": "book", "productId": "29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b" }, { "time": "19:30", "duration": 90, "type": "request", "productId": "29|CO|275cc44dd2e2496fba44857c9257443a|d99128c546b34b619c4477b712869f2b" } ] } ``` If `times` is null or empty, there are no slots available — try a different date. Use the `type` from your chosen slot (`book` or `request`) in the next step. ## Step 5: Create a booking Submit the booking with the guest details and the `type` from Step 4. ``` POST /venues/{compositeId}/booking ``` cURL ```bash 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_ORDER_ID" \ -d '{ "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+441234567890", "partySize": 4, "date": "2025-08-15", "time": "19:00", "type": "book", "notes": "Window table if possible" }' ``` JavaScript ```javascript const response = await fetch(`${BASE_URL}/venues/${compositeId}/booking`, { method: 'POST', headers: { Authorization: `Bearer ${access_token}`, 'Content-Type': 'application/json', 'x-Partner-Reference': 'YOUR_INTERNAL_ORDER_ID', }, 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 Step 4 notes: 'Window table if possible', }), }); const booking = await response.json(); console.log('Booking ID:', booking.id); ``` 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'], # from Step 4 'notes': 'Window table if possible', }, headers={ 'Authorization': f'Bearer {access_token}', 'x-Partner-Reference': 'YOUR_INTERNAL_ORDER_ID', } ) response.raise_for_status() booking = response.json() print('Booking ID:', booking['id']) ``` Java ```java String bookingBody = """ { "firstName": "Jane", "lastName": "Smith", "email": "jane.smith@example.com", "phone": "+441234567890", "partySize": 4, "date": "2025-08-15", "time": "19:00", "type": "book", "notes": "Window table if possible" } """; 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_ORDER_ID") .POST(HttpRequest.BodyPublishers.ofString(bookingBody)) .build(); HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); ``` C# ```csharp var bookingPayload = new { firstName = "Jane", lastName = "Smith", email = "jane.smith@example.com", phone = "+441234567890", partySize = 4, date = "2025-08-15", time = "19:00", type = "book", notes = "Window table if possible" }; var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/venues/{compositeId}/booking"); request.Headers.Add("x-Partner-Reference", "YOUR_INTERNAL_ORDER_ID"); request.Content = JsonContent.Create(bookingPayload); var response = await client.SendAsync(request); response.EnsureSuccessStatusCode(); ``` **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" } ``` ## Step 6: Handle the response ### For `book` type (instant confirmation) The booking `status` will be `Confirmed`. Store the `id` for future retrieval or cancellation, and the `operatorBookingId` for guest-facing communications. ### For `request` type (manual approval) The booking `status` will be `Pending`. The venue operator will review and confirm or decline. Listen for status changes via [webhooks](/getting-started/webhook) — the `booking.confirmed` and `booking.declined` events will fire when the operator responds. ### Handling errors | HTTP Status | Meaning | | --- | --- | | `400` | Invalid request body — check required fields | | `401` | Token expired or invalid — refresh your token | | `404` | Venue not found or `compositeId` incorrect | | `409` | No longer available at the requested time | | `422` | Business rule violation (e.g. party size out of range) | See the [Error Catalog](/resources/error-catalog) for the full list of error codes and resolution steps. ## Next steps Operator Setup Full operator management and all TMS credential types Core Concepts Understand venues, composite IDs, and availability types Booking Management Retrieve, update, and cancel bookings Webhooks Receive real-time status updates