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.
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 case | Right tool |
|---|---|
| Search results with available dates | Feed |
| Calendar / date-picker UI | Feed |
| "Next available slot" across a set of venues | Feed |
| Displaying available party sizes for a date | Feed |
| Real-time confirmation immediately before booking | GET /venues/{compositeId}/availability |
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.
The feed for a product consists of two file types:
- 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. - 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.
{
"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" }
]
}| Field | Description |
|---|---|
product_id | The product's compositeId — same value as products[].compositeId in the venue response |
product_name | Human-readable product name |
venue_name | Human-readable venue name |
shards | One entry per day with availability. Sorted by date ascending |
shards[].date | Date in YYYY-MM-DD, in the venue's local timezone |
shards[].url | Direct download URL for that day's shard file |
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"
}
]
}| Field | Type | Description |
|---|---|---|
date | string | Date in YYYY-MM-DD, in the venue's local timezone |
time | string | Time in HH:MM:SS, in the venue's local timezone |
party_size | integer | The party size this slot applies to. The feed contains one entry per party size supported by the product |
duration_minutes | integer | Expected booking duration |
spots_total | integer | Total capacity for this slot |
spots_open | integer | Remaining capacity at the time the feed was generated |
type | string | "book" — instant confirmation; "request" — pending operator approval. Same semantics as GET /availability |
Slots with spots_open: 0 are excluded from the feed entirely.
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'));
}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.
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}]`
);
}
}
}
}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}/bookingPrior 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:
- Re-read
feedUrlfromGET /venues— it now points toindex.json. Do not gunzip it; the index is plain JSON. - Fetch the shard URLs listed in
shards[]for the dates you need. Each shard is gzip-compressed, as the old single file was. - The slot object shape is unchanged. The top level of each shard adds a
datefield, andproduct_name/venue_namenow appear only in the index, not in shard files.