Getting started
Issue a sandbox key and make your first reservation.
The flow is four steps, all in the Catch Table sandbox: issue a key, search restaurants, read availability and create the reservation. You need nothing beyond a terminal and a runtime with fetch. Every call below is answered from sandbox fixtures, so no request ever reaches a real venue.
APIFuse issues two kinds of keys. Live keys start with frism_live_ and sandbox keys start with frism_test_. Both are managed keys: APIFuse holds the per service credentials behind them, so you never create a Catch Table account yourself.
frism_test_ and is shown once, so store it in your secret manager.catch-table:read and catch-table:write later, see scopes.Start by finding a bookable venue. Search restaurants filters by area, cuisine and party_size, and pages with a cursor.
curl "https://api.frism.dev/v1/catch-table/restaurants?area=seongsu&cuisine=japanese&party_size=2" \
-H "Authorization: Bearer frism_test_k3xample"A trimmed response looks like this. The id of each item is the restaurant identifier the later calls use. Venues with bookable_online set to false only take waitlist entries.
{
"items": [
{
"id": "res_01J8Q9W3TE",
"name": "Sushi Aoyagi",
"area": "seongsu",
"cuisine": "japanese",
"price_band": "high",
"bookable_online": true
}
],
"next_cursor": "cur_9f2kq"
}Pass next_cursor back as the cursor parameter to fetch the next page. It is null on the last page.
A reservation takes two calls. returns the open slots for a date and party size, and books one of them. The POST carries an Idempotency-Key header, so after a timeout or a crash you can retry the same creation safely for 24 hours.
const base = "https://api.frism.dev/v1";
const headers = { Authorization: "Bearer frism_test_k3xample" };
// 1. Read the open slots for the date and party size.
const availabilityRes = await fetch(
base +
"/catch-table/restaurants/res_01J8Q9W3TE/availability" +
"?date=2026-09-02&party_size=2",
{ headers },
);
const availability = await availabilityRes.json();
const slot = availability.slots[0];
// slot: { slot_id: "slt_1900_2", starts_at: "2026-09-02T19:00:00+09:00" }
// 2. Book the slot. The Idempotency-Key makes retrying this POST safe.
const created = await fetch(base + "/catch-table/reservations", {
method: "POST",
headers: {
...headers,
"Content-Type": "application/json",
"Idempotency-Key": "idem_b1f7c2d0",
},
body: JSON.stringify({
restaurant_id: "res_01J8Q9W3TE",
slot_id: slot.slot_id,
party_size: 2,
contact_name: "Han Jiwoo",
note: "Window seat if possible.",
}),
});
console.log(created.status);
console.log( created.json());On success the gateway answers 201 with the reservation body.
{
"id": "rsv_01J8QDGT2K",
"status": "confirmed",
"restaurant_id": "res_01J8Q9W3TE",
"starts_at": "2026-09-02T19:00:00+09:00",
"party_size": 2
}The status is confirmed because this venue confirms instantly. Venues that approve manually start at pending, and the moment they confirm, a reservation.confirmed event arrives as a . Prefer that subscription over polling .
If someone takes the slot between the two calls, the gateway answers 409 with code conflict in the standard . Re-read availability and try another slot.
Your key never touched Catch Table. The gateway resolved the catch-table path segment, checked the key's scopes, performed the booking with the venue credential APIFuse holds, and normalised the answer into the schemas above. Service failures come back the same way: a 502 with provider_unavailable lands in the same envelope with retryable: true, so your client knows a backoff can succeed.