{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-resources/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["tabs","tab"]},"type":"markdown"},"seo":{"title":"Availability Feed","description":"Bookable is a TMS API gateway API — one integration to access real-time availability and manage bookings across venues on any table management system.","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"availability-feed","__idx":0},"children":["Availability Feed"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each bookable product exposes a pre-generated availability feed: a downloadable file containing up to 90 days of time slots, refreshed every 4 hours. Use it to build discovery interfaces, search widgets, and availability calendars without making live API calls per venue or date."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"when-to-use-the-feed-vs-get-availability","__idx":1},"children":["When to use the feed vs. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /availability"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /venues/{compositeId}/availability"]}," is a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["real-time"]}," endpoint. It calls the underlying TMS to return current availability for a specific product, date, and party size. It is designed to be called ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["once, immediately before creating a booking"]}," — not for bulk data loading."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The feed serves the opposite use case. If you need to display availability across many products, dates, or party sizes — a search results page, a calendar view, a \"next available\" filter — use the feed. It is pre-generated and served as a static file; there are no API rate limits, no per-request latency, and no TMS calls involved."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Use case"},"children":["Use case"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Right tool"},"children":["Right tool"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Search results with available dates"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Feed"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Calendar / date-picker UI"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Feed"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["\"Next available slot\" across a set of venues"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Feed"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Displaying available party sizes for a date"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Feed"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Real-time confirmation immediately before booking"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /venues/{compositeId}/availability"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"finding-the-feed-url","__idx":2},"children":["Finding the feed URL"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The feed URL is available on each product in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /venues"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /venues/{venueId}"]}," responses, in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["products[].feedUrl"]}," field."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"data\": [{\n    \"name\": \"Dishoom Covent Garden\",\n    \"products\": [{\n      \"compositeId\": \"29|CO|275cc44dd2e2496fba44857c9257443a|5c4af02d6354a83e3a0ea3b4\",\n      \"productName\": \"Dinner\",\n      \"feedUrl\": \"https://feeds.bookabletech.com/29/275cc44dd2e2496fba44857c9257443a/5c4af02d6354a83e3a0ea3b4.json.gz\"\n    }]\n  }]\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["feedUrl"]}," is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["null"]}," when no feed has been generated yet for a product. Always null-check before attempting a download."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"downloading-the-feed","__idx":3},"children":["Downloading the feed"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The file is served as a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["gzip-compressed JSON document"]},". No authentication is required — download it directly from the URL."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"import { gunzipSync } from 'zlib';\n\nasync function fetchFeed(feedUrl) {\n  const response = await fetch(feedUrl);\n  const buffer = Buffer.from(await response.arrayBuffer());\n  const json = gunzipSync(buffer).toString('utf-8');\n  return JSON.parse(json);\n}\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import gzip, json, urllib.request\n\ndef fetch_feed(feed_url: str) -> dict:\n    with urllib.request.urlopen(feed_url) as response:\n        return json.loads(gzip.decompress(response.read()))\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"HttpResponse<InputStream> response = HttpClient.newHttpClient()\n    .send(HttpRequest.newBuilder(URI.create(feedUrl)).build(),\n          HttpResponse.BodyHandlers.ofInputStream());\n\ntry (var reader = new InputStreamReader(new GZIPInputStream(response.body()))) {\n    Feed feed = new ObjectMapper().readValue(reader, Feed.class);\n}\n","lang":"java"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"using var response = await httpClient.GetStreamAsync(feedUrl);\nusing var gzip = new GZipStream(response, CompressionMode.Decompress);\nvar feed = await JsonSerializer.DeserializeAsync<AvailabilityFeed>(gzip);\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"feed-format","__idx":4},"children":["Feed format"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The decompressed file is a JSON object with metadata at the top level and a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["slots"]}," array."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"top-level-fields","__idx":5},"children":["Top-level fields"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"product_id\": \"1|CO|512b201dd5d190d2978ca231|5d2d312913b85008ae110b75\",\n  \"product_name\": \"Venue Hire\",\n  \"venue_name\": \"The Grand Hall\",\n  \"slots\": [...]\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["product_id"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The product's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["compositeId"]}," — same value as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["products[].compositeId"]}," in the venue response"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["product_name"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Human-readable product name"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["venue_name"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Human-readable venue name"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["slots"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Array of available time slots (see below)"]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"slot-fields","__idx":6},"children":["Slot fields"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each entry in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["slots"]}," represents one available time for a specific party size:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"date\": \"2026-06-15\",\n  \"time\": \"18:00:00\",\n  \"party_size\": 4,\n  \"duration_minutes\": 90,\n  \"spots_total\": 116,\n  \"spots_open\": 32,\n  \"type\": \"book\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Type"},"children":["Type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["date"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Date in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["YYYY-MM-DD"]},", in the venue's local timezone"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["time"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Time in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HH:MM:SS"]},", in the venue's local timezone"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["party_size"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["integer"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The party size this slot applies to. The feed contains one entry per party size supported by the product"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["duration_minutes"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["integer"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Expected booking duration"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["spots_total"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["integer"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Total capacity for this slot"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["spots_open"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["integer"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Remaining capacity at the time the feed was generated"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["string"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"book\""]}," — instant confirmation; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"request\""]}," — pending operator approval. Same semantics as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /availability"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Slots with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["spots_open: 0"]}," are excluded from the feed entirely."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"freshness","__idx":7},"children":["Freshness"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The feed covers a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["rolling 90-day window"]}," from the current date and is regenerated ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["every 4 hours"]},". It reflects the state of bookings and operator rules as of the last generation cycle — it is not a live snapshot."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is intentional: the feed is optimised for discovery and display. The exact state at the moment of booking is confirmed by the real-time ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /availability"]}," call."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"example-venues--products--slots","__idx":8},"children":["Example: venues → products → slots"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The following example authenticates, pages through all venues, and for each product with a feed prints every available slot."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"javascript","header":{"controls":{"copy":{}}},"source":"import { gunzipSync } from 'zlib';\n\nconst AUTH_URL = 'https://auth.bookabletech.com/oauth/token';\nconst API_URL  = 'https://api.bookabletech.com';\n\nasync function getToken(clientId, clientSecret) {\n  const res = await fetch(AUTH_URL, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      grant_type:    'client_credentials',\n      client_id:     clientId,\n      client_secret: clientSecret,\n      audience:      'api.bookabletech.com',\n    }),\n  });\n  const { access_token } = await res.json();\n  return access_token;\n}\n\nasync function fetchFeed(feedUrl) {\n  const res = await fetch(feedUrl);\n  const buf = Buffer.from(await res.arrayBuffer());\n  return JSON.parse(gunzipSync(buf).toString('utf-8'));\n}\n\nasync function* getVenues(token) {\n  let page = 1;\n  while (true) {\n    const res = await fetch(`${API_URL}/venues?pageNumber=${page}&pageSize=100`, {\n      headers: { Authorization: `Bearer ${token}` },\n    });\n    const { data, meta } = await res.json();\n    yield* data;\n    if (page >= meta.totalPages) break;\n    page++;\n  }\n}\n\nconst token = await getToken(process.env.CLIENT_ID, process.env.CLIENT_SECRET);\n\nfor await (const venue of getVenues(token)) {\n  console.log(`\\nVenue: ${venue.name}`);\n\n  for (const product of venue.products ?? []) {\n    if (!product.feedUrl) continue;\n    console.log(`  Product: ${product.productName}`);\n\n    const feed = await fetchFeed(product.feedUrl);\n\n    for (const slot of feed.slots) {\n      console.log(\n        `    ${slot.date} ${slot.time}` +\n        `  party:${slot.party_size}` +\n        `  open:${slot.spots_open}/${slot.spots_total}` +\n        `  [${slot.type}]`\n      );\n    }\n  }\n}\n","lang":"javascript"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"import gzip, json, os, urllib.request\n\nAUTH_URL = 'https://auth.bookabletech.com/oauth/token'\nAPI_URL  = 'https://api.bookabletech.com'\n\n\ndef get_token(client_id, client_secret):\n    payload = json.dumps({\n        'grant_type':    'client_credentials',\n        'client_id':     client_id,\n        'client_secret': client_secret,\n        'audience':      'api.bookabletech.com',\n    }).encode()\n    req = urllib.request.Request(\n        AUTH_URL, data=payload, headers={'Content-Type': 'application/json'}\n    )\n    with urllib.request.urlopen(req) as r:\n        return json.loads(r.read())['access_token']\n\n\ndef get_venues(token):\n    page = 1\n    while True:\n        req = urllib.request.Request(\n            f'{API_URL}/venues?pageNumber={page}&pageSize=100',\n            headers={'Authorization': f'Bearer {token}'},\n        )\n        with urllib.request.urlopen(req) as r:\n            body = json.loads(r.read())\n        yield from body['data']\n        if page >= body['meta']['totalPages']:\n            break\n        page += 1\n\n\ndef fetch_feed(feed_url):\n    with urllib.request.urlopen(feed_url) as r:\n        return json.loads(gzip.decompress(r.read()))\n\n\ntoken = get_token(os.environ['CLIENT_ID'], os.environ['CLIENT_SECRET'])\n\nfor venue in get_venues(token):\n    print(f\"\\nVenue: {venue['name']}\")\n\n    for product in venue.get('products', []):\n        if not product.get('feedUrl'):\n            continue\n        print(f\"  Product: {product['productName']}\")\n\n        feed = fetch_feed(product['feedUrl'])\n\n        for slot in feed['slots']:\n            print(\n                f\"    {slot['date']} {slot['time']}\"\n                f\"  party:{slot['party_size']}\"\n                f\"  open:{slot['spots_open']}/{slot['spots_total']}\"\n                f\"  [{slot['type']}]\"\n            )\n","lang":"python"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"Java","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","header":{"controls":{"copy":{}}},"source":"import com.fasterxml.jackson.databind.*;\nimport java.io.*;\nimport java.net.*;\nimport java.net.http.*;\nimport java.util.Map;\nimport java.util.zip.GZIPInputStream;\n\npublic class FeedExample {\n\n    static final String AUTH_URL = \"https://auth.bookabletech.com/oauth/token\";\n    static final String API_URL  = \"https://api.bookabletech.com\";\n    static final HttpClient HTTP = HttpClient.newHttpClient();\n    static final ObjectMapper JSON = new ObjectMapper();\n\n    public static void main(String[] args) throws Exception {\n        String token = getToken(System.getenv(\"CLIENT_ID\"), System.getenv(\"CLIENT_SECRET\"));\n        iterateVenues(token);\n    }\n\n    static String getToken(String clientId, String clientSecret) throws Exception {\n        String body = JSON.writeValueAsString(Map.of(\n            \"grant_type\",    \"client_credentials\",\n            \"client_id\",     clientId,\n            \"client_secret\", clientSecret,\n            \"audience\",      \"api.bookabletech.com\"\n        ));\n        var req = HttpRequest.newBuilder(URI.create(AUTH_URL))\n            .POST(HttpRequest.BodyPublishers.ofString(body))\n            .header(\"Content-Type\", \"application/json\")\n            .build();\n        return JSON.readTree(HTTP.send(req, HttpResponse.BodyHandlers.ofString()).body())\n                   .get(\"access_token\").asText();\n    }\n\n    static void iterateVenues(String token) throws Exception {\n        int page = 1, totalPages;\n        do {\n            var req = HttpRequest.newBuilder(\n                    URI.create(API_URL + \"/venues?pageNumber=\" + page + \"&pageSize=100\"))\n                .header(\"Authorization\", \"Bearer \" + token)\n                .build();\n            JsonNode body = JSON.readTree(HTTP.send(req, HttpResponse.BodyHandlers.ofString()).body());\n            totalPages = body.get(\"meta\").get(\"totalPages\").asInt();\n\n            for (JsonNode venue : body.get(\"data\")) {\n                System.out.println(\"\\nVenue: \" + venue.get(\"name\").asText());\n\n                for (JsonNode product : venue.withArray(\"products\")) {\n                    JsonNode feedUrlNode = product.get(\"feedUrl\");\n                    if (feedUrlNode == null || feedUrlNode.isNull()) continue;\n                    System.out.println(\"  Product: \" + product.get(\"productName\").asText());\n                    iterateFeed(feedUrlNode.asText());\n                }\n            }\n        } while (++page <= totalPages);\n    }\n\n    static void iterateFeed(String feedUrl) throws Exception {\n        var req = HttpRequest.newBuilder(URI.create(feedUrl)).build();\n        var res = HTTP.send(req, HttpResponse.BodyHandlers.ofInputStream());\n        try (var reader = new InputStreamReader(new GZIPInputStream(res.body()))) {\n            for (JsonNode slot : JSON.readTree(reader).withArray(\"slots\")) {\n                System.out.printf(\"    %s %s  party:%d  open:%d/%d  [%s]%n\",\n                    slot.get(\"date\").asText(),\n                    slot.get(\"time\").asText(),\n                    slot.get(\"party_size\").asInt(),\n                    slot.get(\"spots_open\").asInt(),\n                    slot.get(\"spots_total\").asInt(),\n                    slot.get(\"type\").asText());\n            }\n        }\n    }\n}\n","lang":"java"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"C#","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"csharp","header":{"controls":{"copy":{}}},"source":"using System.IO.Compression;\nusing System.Net.Http.Json;\nusing System.Text.Json;\n\nconst string authUrl = \"https://auth.bookabletech.com/oauth/token\";\nconst string apiUrl  = \"https://api.bookabletech.com\";\n\nusing var http = new HttpClient();\n\n// Get token\nvar tokenRes = await http.PostAsJsonAsync(authUrl, new {\n    grant_type    = \"client_credentials\",\n    client_id     = Environment.GetEnvironmentVariable(\"CLIENT_ID\"),\n    client_secret = Environment.GetEnvironmentVariable(\"CLIENT_SECRET\"),\n    audience      = \"api.bookabletech.com\",\n});\nusing var tokenDoc = await JsonDocument.ParseAsync(await tokenRes.Content.ReadAsStreamAsync());\nhttp.DefaultRequestHeaders.Authorization =\n    new(\"Bearer\", tokenDoc.RootElement.GetProperty(\"access_token\").GetString());\n\n// Page through venues\nint page = 1, totalPages;\ndo {\n    using var venuesDoc = await http.GetFromJsonAsync<JsonDocument>(\n        $\"{apiUrl}/venues?pageNumber={page}&pageSize=100\");\n    totalPages = venuesDoc!.RootElement.GetProperty(\"meta\").GetProperty(\"totalPages\").GetInt32();\n\n    foreach (var venue in venuesDoc.RootElement.GetProperty(\"data\").EnumerateArray()) {\n        Console.WriteLine($\"\\nVenue: {venue.GetProperty(\"name\").GetString()}\");\n\n        foreach (var product in venue.GetProperty(\"products\").EnumerateArray()) {\n            if (!product.TryGetProperty(\"feedUrl\", out var feedUrlProp)\n                || feedUrlProp.ValueKind == JsonValueKind.Null)\n                continue;\n\n            Console.WriteLine($\"  Product: {product.GetProperty(\"productName\").GetString()}\");\n\n            // Feed download requires no authentication\n            using var feedStream = await http.GetStreamAsync(feedUrlProp.GetString());\n            using var gzip       = new GZipStream(feedStream, CompressionMode.Decompress);\n            using var feedDoc    = await JsonDocument.ParseAsync(gzip);\n\n            foreach (var slot in feedDoc.RootElement.GetProperty(\"slots\").EnumerateArray()) {\n                Console.WriteLine(\n                    $\"    {slot.GetProperty(\"date\").GetString()} {slot.GetProperty(\"time\").GetString()}\" +\n                    $\"  party:{slot.GetProperty(\"party_size\").GetInt32()}\" +\n                    $\"  open:{slot.GetProperty(\"spots_open\").GetInt32()}/{slot.GetProperty(\"spots_total\").GetInt32()}\" +\n                    $\"  [{slot.GetProperty(\"type\").GetString()}]\");\n            }\n        }\n    }\n} while (++page <= totalPages);\n","lang":"csharp"},"children":[]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"booking-from-feed-data","__idx":9},"children":["Booking from feed data"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When a user selects a slot from your feed-powered UI and proceeds to book, always call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /venues/{compositeId}/availability"]}," in real-time to confirm the slot is still open before submitting the booking request. The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["type"]}," value passed to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /venues/{compositeId}/booking"]}," must come from that live response."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"Feed (discovery)  →  GET /venues/{compositeId}/availability (real-time check)  →  POST /venues/{compositeId}/booking\n"},"children":[]}]},"headings":[{"value":"Availability Feed","id":"availability-feed","depth":1},{"value":"When to use the feed vs. GET /availability","id":"when-to-use-the-feed-vs-get-availability","depth":2},{"value":"Finding the feed URL","id":"finding-the-feed-url","depth":2},{"value":"Downloading the feed","id":"downloading-the-feed","depth":2},{"value":"Feed format","id":"feed-format","depth":2},{"value":"Top-level fields","id":"top-level-fields","depth":3},{"value":"Slot fields","id":"slot-fields","depth":3},{"value":"Freshness","id":"freshness","depth":2},{"value":"Example: venues → products → slots","id":"example-venues--products--slots","depth":2},{"value":"Booking from feed data","id":"booking-from-feed-data","depth":2}],"frontmatter":{"seo":{"title":"Availability Feed"}},"lastModified":"2026-06-10T15:02:28.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/resources/availability-feed","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}