Skip to content
Last updated

Availability Feed

Each bookable product exposes a pre-generated availability feed: a lightweight index file listing up to 90 days of availability, with one downloadable daily shard file per day that has open slots. The feed is refreshed once daily. Use it to build discovery interfaces, search widgets, and availability calendars without making live API calls per venue or date.

Because availability is sharded by date, you download only the days you need — a calendar widget showing next week fetches the index plus seven small files, not a 90-day payload.


When to use the feed vs. GET /availability

GET /venues/{compositeId}/availability is a 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 once, immediately before creating a booking — not for bulk data loading.

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 static files; there are no API rate limits, no per-request latency, and no TMS calls involved.

Use caseRight tool
Search results with available datesFeed
Calendar / date-picker UIFeed
"Next available slot" across a set of venuesFeed
Displaying available party sizes for a dateFeed
Real-time confirmation immediately before bookingGET /venues/{compositeId}/availability

Finding the feed URL

The feed's index URL is available on each product in the GET /venues and GET /venues/{venueId} responses, in the products[].feedUrl field.

{
  "data": [{
    "name": "Dishoom Covent Garden",
    "products": [{
      "compositeId": "29|CO|275cc44dd2e2496fba44857c9257443a|5c4af02d6354a83e3a0ea3b4",
      "productName": "Dinner",
      "feedUrl": "https://feeds.bookabletech.com/29/275cc44dd2e2496fba44857c9257443a/5c4af02d6354a83e3a0ea3b4/index.json"
    }]
  }]
}

feedUrl is null when no feed has been generated yet for a product. Always null-check before attempting a download.

Do not construct index or shard URLs yourself — read feedUrl from the venue response, and read shard URLs from the index. The URL structure is internal and may change.


Feed structure: index + daily shards

The feed for a product consists of two file types:

  1. Index file (index.json) — plain, uncompressed JSON. Lists every day in the 90-day window that has at least one open slot, with a direct download URL for each day's shard file.
  2. Daily shard files ({date}.json.gz) — gzip-compressed JSON. One file per day, containing every open slot for that date.

Days with no availability have no shard file and are omitted from the index. An index with an empty shards array means the product has no availability in the entire window.

Index format (index.json)

{
  "product_id": "29|CO|275cc44dd2e2496fba44857c9257443a|5c4af02d6354a83e3a0ea3b4",
  "product_name": "Dinner",
  "venue_name": "Dishoom Covent Garden",
  "shards": [
    { "date": "2026-07-09", "url": "https://feeds.bookabletech.com/29/275cc44dd2e2496fba44857c9257443a/5c4af02d6354a83e3a0ea3b4/2026-07-09.json.gz" },
    { "date": "2026-07-10", "url": "https://feeds.bookabletech.com/29/275cc44dd2e2496fba44857c9257443a/5c4af02d6354a83e3a0ea3b4/2026-07-10.json.gz" }
  ]
}
FieldDescription
product_idThe product's compositeId — same value as products[].compositeId in the venue response
product_nameHuman-readable product name
venue_nameHuman-readable venue name
shardsOne entry per day with availability. Sorted by date ascending
shards[].dateDate in YYYY-MM-DD, in the venue's local timezone
shards[].urlDirect download URL for that day's shard file

Daily shard format ({date}.json.gz)

The decompressed shard is a JSON object with the product ID, the date, and a slots array.

{
  "product_id": "29|CO|275cc44dd2e2496fba44857c9257443a|5c4af02d6354a83e3a0ea3b4",
  "date": "2026-07-09",
  "slots": [
    {
      "date": "2026-07-09",
      "time": "18:00:00",
      "party_size": 4,
      "duration_minutes": 90,
      "spots_total": 116,
      "spots_open": 32,
      "type": "book"
    }
  ]
}
FieldTypeDescription
datestringDate in YYYY-MM-DD, in the venue's local timezone
timestringTime in HH:MM:SS, in the venue's local timezone
party_sizeintegerThe party size this slot applies to. The feed contains one entry per party size supported by the product
duration_minutesintegerExpected booking duration
spots_totalintegerTotal capacity for this slot
spots_openintegerRemaining capacity at the time the feed was generated
typestring"book" — instant confirmation; "request" — pending operator approval. Same semantics as GET /availability

Slots with spots_open: 0 are excluded from the feed entirely.


Downloading the feed

No authentication is required for either file type — download directly from the URLs. The index is plain JSON; shard files are gzip-compressed JSON.

import { gunzipSync } from 'zlib';

async function fetchIndex(feedUrl) {
  const response = await fetch(feedUrl);
  return response.json();
}

async function fetchShard(shardUrl) {
  const response = await fetch(shardUrl);
  const buffer = Buffer.from(await response.arrayBuffer());
  return JSON.parse(gunzipSync(buffer).toString('utf-8'));
}

Freshness

The feed covers a rolling 90-day window from the current date and is regenerated once daily. Each regeneration rewrites the index and all shard files; shard URLs for a given date are stable across regenerations, but their contents change. The feed reflects the state of bookings and operator rules as of the last generation cycle — it is not a live snapshot.

Always fetch the index first, on every refresh cycle. Do not cache shard URLs across days: as the window rolls forward, past dates disappear from the index and new dates appear, and a day that sells out is dropped from the index entirely.

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 GET /availability call.


Example: venues → products → index → slots

The following example authenticates, pages through all venues, and for each product with a feed downloads the index and prints every available slot from each daily shard.

import { gunzipSync } from 'zlib';

const AUTH_URL = 'https://auth.bookabletech.com/oauth/token';
const API_URL  = 'https://api.bookabletech.com';

async function getToken(clientId, clientSecret) {
  const res = await fetch(AUTH_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type:    'client_credentials',
      client_id:     clientId,
      client_secret: clientSecret,
      audience:      'api.bookabletech.com',
    }),
  });
  const { access_token } = await res.json();
  return access_token;
}

async function fetchIndex(feedUrl) {
  const res = await fetch(feedUrl);
  return res.json();
}

async function fetchShard(shardUrl) {
  const res = await fetch(shardUrl);
  const buf = Buffer.from(await res.arrayBuffer());
  return JSON.parse(gunzipSync(buf).toString('utf-8'));
}

async function* getVenues(token) {
  let page = 1;
  while (true) {
    const res = await fetch(`${API_URL}/venues?pageNumber=${page}&pageSize=100`, {
      headers: { Authorization: `Bearer ${token}` },
    });
    const { data, meta } = await res.json();
    yield* data;
    if (page >= meta.totalPages) break;
    page++;
  }
}

const token = await getToken(process.env.CLIENT_ID, process.env.CLIENT_SECRET);

for await (const venue of getVenues(token)) {
  console.log(`\nVenue: ${venue.name}`);

  for (const product of venue.products ?? []) {
    if (!product.feedUrl) continue;
    console.log(`  Product: ${product.productName}`);

    const index = await fetchIndex(product.feedUrl);

    for (const { date, url } of index.shards) {
      const shard = await fetchShard(url);

      for (const slot of shard.slots) {
        console.log(
          `    ${slot.date} ${slot.time}` +
          `  party:${slot.party_size}` +
          `  open:${slot.spots_open}/${slot.spots_total}` +
          `  [${slot.type}]`
        );
      }
    }
  }
}

Booking from feed data

When a user selects a slot from your feed-powered UI and proceeds to book, always call GET /venues/{compositeId}/availability in real-time to confirm the slot is still open before submitting the booking request. The type value passed to POST /venues/{compositeId}/booking must come from that live response.

Feed (discovery)  →  GET /venues/{compositeId}/availability (real-time check)  →  POST /venues/{compositeId}/booking

Migrating from the single-file format

Prior to BookingApi v7.3.0, feedUrl pointed to a single gzip-compressed file ({productId}.json.gz) containing all 90 days of slots. That format is no longer generated. To migrate:

  1. Re-read feedUrl from GET /venues — it now points to index.json. Do not gunzip it; the index is plain JSON.
  2. Fetch the shard URLs listed in shards[] for the dates you need. Each shard is gzip-compressed, as the old single file was.
  3. The slot object shape is unchanged. The top level of each shard adds a date field, and product_name / venue_name now appear only in the index, not in shard files.