HULPBRONNEN
API-documentatie
Koppel je eigen systemen aan Tafels, zoals je website, een kassasysteem of een CRM. De API zit in het Premium-abonnement.
Aan de slag
- Open in Tafels Instellingen → API en maak een sleutel aan.
- Stuur de sleutel bij elk verzoek mee, in de Authorization-header.
- Houd de sleutel geheim. Iedereen die hem heeft, kan je reserveringen inzien en wijzigen. Trek sleutels in die je niet meer gebruikt.
Authorization: Bearer tfl_…
GET https://tafels.app/api/v1/restaurantBasis-URL: https://tafels.app/api/v1
Afspraken
- Verzoeken en antwoorden zijn JSON. Stuur Content-Type: application/json mee.
- Datums zijn JJJJ-MM-DD en tijden UU:MM op het kwartier, in de lokale tijd van het restaurant (Europe/Amsterdam).
- Elke sleutel kan 120 verzoeken per minuut doen.
- Elke reservering heeft een versie die bij elke wijziging één omhoog gaat. Stuur die mee als je de reservering wijzigt, zodat je nooit de wijziging van iemand anders overschrijft.
Foutmeldingen
Fouten komen terug als JSON met een leesbare melding, in het Engels, of in het Nederlands als je Accept-Language: nl meestuurt.
{ "error": "No suitable table is available at this time. Choose another time or join the waitlist." }400 | Ongeldige invoer, bijvoorbeeld een datum in het verleden of een ontbrekend veld. |
401 | De API-sleutel ontbreekt of is niet geldig. |
402 | API-toegang staat uit of zit niet in het abonnement. |
404 | De reservering bestaat niet, of hoort bij een ander restaurant. |
409 | Er is geen passende tafel vrij, een ticket is vol, of de reservering is gewijzigd sinds je hem las. |
415 | De body is geen JSON. Stuur Content-Type: application/json mee. |
429 | Meer dan 120 verzoeken in een minuut met deze sleutel. |
GET/api/v1/restaurant
Het restaurant waar de sleutel bij hoort: naam, contactgegevens, reserveringspagina en sluitingen.
Antwoord
{
"id": "…",
"slug": "the-juniper-room",
"name": "The Juniper Room",
"address": "Prinsengracht 1, Amsterdam",
"phone": "+31 20 123 4567",
"email": "hello@juniper.example",
"timezone": "Europe/Amsterdam",
"bookingEnabled": true,
"bookingPage": "https://tafels.app/book/the-juniper-room",
"buffer": 15,
"closures": [{ "from": "2026-12-31", "to": "2027-01-01", "closed": true, "note": "New Year" }]
}GET/api/v1/tickets
Alles wat gasten kunnen boeken, zoals lunch of een kerstmenu, met dagen, aankomsttijden, groepsgroottes en limieten. Prijzen zijn in centen.
Antwoord
{
"tickets": [{
"id": "tk_2b1e…",
"name": "Christmas dinner",
"description": "Five courses",
"active": true,
"days": [0, 1, 2, 3, 4, 5, 6],
"startDate": "2026-12-24",
"endDate": "2026-12-26",
"firstTime": "18:00",
"lastTime": "19:30",
"duration": 180,
"minGuests": 2,
"maxGuests": 6,
"maxPerSlot": 12,
"maxPerDay": 60,
"rooms": ["Garden room"],
"pricePerPerson": 8950,
"depositPerPerson": null,
"sort": 1,
"showImage": true,
"imageUrl": "https://tafels.app/api/images/img_4c1d…"
}]
}GET/api/v1/tables
Alle tafels met hun plaatsen en ruimte.
Antwoord
{
"tables": [
{ "id": "t-07", "name": "07", "seats": 4, "room": "Dining room", "shape": "square", "active": true }
]
}GET/api/v1/availability?date=2026-12-24&guests=4
Boekbare tijden voor een datum en groepsgrootte, per ticket, zoals gasten ze op de reserveringspagina zien.
Queryparameters
dateYYYY-MM-DD | De dag om te controleren. |
guestsnumber | Groepsgrootte, 1 tot en met 20. |
Antwoord
{
"tickets": [{
"id": "tk_2b1e…",
"name": "Christmas dinner",
"description": "Five courses",
"duration": 180,
"minGuests": 2,
"maxGuests": 6,
"pricePerPerson": 8950,
"depositPerPerson": null,
"imageUrl": "https://tafels.app/api/images/img_4c1d…",
"slots": [
{ "time": "18:00", "available": true },
{ "time": "18:15", "available": false }
]
}]
}GET/api/v1/reservations?from=2026-12-01&to=2026-12-31
Reserveringen in een periode van maximaal 93 dagen, op datum en tijd.
Queryparameters
fromYYYY-MM-DD | Eerste dag. |
toYYYY-MM-DD | Laatste dag. Optioneel, standaard gelijk aan from. |
statusstring | Optioneel. Alleen deze status, bijvoorbeeld Confirmed of Cancelled. |
emailstring | Optioneel. Alleen reserveringen voor dit e-mailadres. |
Antwoord
{ "reservations": [ { …reservation } ] }GET/api/v1/reservations/{id}
Eén reservering.
Antwoord
{
"id": "3f0c1a9e-6c1d-4a51-9a3e-2b8f4f0c7d21",
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+31 6 12345678",
"date": "2026-12-24",
"time": "19:00",
"guests": 4,
"tableId": "t-07",
"ticketId": "tk_2b1e…",
"ticketName": "Christmas dinner",
"status": "Confirmed",
"notes": "Window table if possible",
"source": "API",
"channel": "",
"voucherCode": "",
"duration": 180,
"lang": "en",
"createdAt": "2026-11-02T10:14:03.512Z",
"version": 1
}POST/api/v1/reservations
Maakt een reservering met dezelfde regels als reserveringen door personeel: er moet een passende tafel vrij zijn, maar ticketlimieten mogen overschreden worden. De gast krijgt een bevestiging als e-mails aanstaan.
Body
namestring | Verplicht. De naam van de gast. |
emailstring | Optioneel. Nodig voor bevestigingen en herinneringen per e-mail. |
phonestring | Optioneel. |
dateYYYY-MM-DD | Verplicht. |
timeHH:MM | Verplicht. Een kwartier. |
guestsnumber | Verplicht. 1 tot en met 20. |
ticketIdstring | Optioneel. Zonder wordt het ticket gebruikt dat op dat moment loopt. |
tableIdstring | Optioneel. Zonder wordt de best passende vrije tafel gekozen. |
statusstring | Optioneel. Confirmed (standaard) of Waitlist. |
notesstring | Optioneel. Maximaal 1000 tekens. |
langnl | en | Optioneel. De taal van de e-mails aan de gast. |
voucherCodestring | Optioneel. Voor dealtickets: gebruikt deze code uit de lijst van het ticket. |
Voorbeeld
curl -X POST https://tafels.app/api/v1/reservations \
-H "Authorization: Bearer tfl_…" \
-H "Content-Type: application/json" \
-d '{"name":"Ada Lovelace","email":"ada@example.com","date":"2026-12-24","time":"19:00","guests":4}'Antwoord
{
"id": "3f0c1a9e-6c1d-4a51-9a3e-2b8f4f0c7d21",
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+31 6 12345678",
"date": "2026-12-24",
"time": "19:00",
"guests": 4,
"tableId": "t-07",
"ticketId": "tk_2b1e…",
"ticketName": "Christmas dinner",
"status": "Confirmed",
"notes": "Window table if possible",
"source": "API",
"channel": "",
"voucherCode": "",
"duration": 180,
"lang": "en",
"createdAt": "2026-11-02T10:14:03.512Z",
"version": 1
}PATCH/api/v1/reservations/{id}
Wijzigt een reservering. Stuur de versie die je het laatst las en de velden die veranderen; andere velden blijven zoals ze zijn.
Is de reservering gewijzigd sinds je hem las, dan klopt de versie niet meer en krijg je een 409. Lees hem opnieuw en probeer het nog eens.
Body
versionnumber | Verplicht. De versie van je laatste leesactie. |
statusstring | Optioneel. Confirmed, Arrived, Seated, Completed, Cancelled, No-show of Waitlist. |
… | Elk ander veld van de reservering, zoals date, time, guests of notes. |
Voorbeeld
curl -X PATCH https://tafels.app/api/v1/reservations/3f0c1a9e-… \
-H "Authorization: Bearer tfl_…" \
-H "Content-Type: application/json" \
-d '{"version":1,"time":"19:30"}'Antwoord
{
"id": "3f0c1a9e-6c1d-4a51-9a3e-2b8f4f0c7d21",
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+31 6 12345678",
"date": "2026-12-24",
"time": "19:30",
"guests": 4,
"tableId": "t-07",
"ticketId": "tk_2b1e…",
"ticketName": "Christmas dinner",
"status": "Confirmed",
"notes": "Window table if possible",
"source": "API",
"channel": "",
"voucherCode": "",
"duration": 180,
"lang": "en",
"createdAt": "2026-11-02T10:14:03.512Z",
"version": 2
}DELETE/api/v1/reservations/{id}
Annuleert een reservering en maakt de tafel vrij. De reservering blijft bewaard met status Cancelled, en de gast krijgt een annuleringsmail als e-mails aanstaan.
Antwoord
{
"id": "3f0c1a9e-6c1d-4a51-9a3e-2b8f4f0c7d21",
"name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+31 6 12345678",
"date": "2026-12-24",
"time": "19:00",
"guests": 4,
"tableId": "t-07",
"ticketId": "tk_2b1e…",
"ticketName": "Christmas dinner",
"status": "Cancelled",
"notes": "Window table if possible",
"source": "API",
"channel": "",
"voucherCode": "",
"duration": 180,
"lang": "en",
"createdAt": "2026-11-02T10:14:03.512Z",
"version": 2
}Het reserveringsobject
idstring | Uniek id. |
name, email, phonestring | Gegevens van de gast. |
date, timestring | Aankomst, in lokale tijd. |
guestsnumber | Groepsgrootte. |
durationnumber | Minuten dat de tafel vastgehouden wordt. |
tableIdstring | null | De toegewezen tafel. Null op de wachtlijst. |
ticketId, ticketNamestring | Wat er geboekt is, zoals Lunch. |
statusstring | Confirmed, Arrived, Seated, Completed, Cancelled, No-show of Waitlist. |
sourcestring | Online, Staff, Phone, Walk-in, Waitlist of API. |
channelstring | Bij online reserveringen: de ?via= van de reserveringslink, zoals instagram. Leeg bij direct reserveren. |
voucherCodestring | De gebruikte deal- of cadeauvouchercode, als die er is. |
notesstring | Opmerkingen van de gast of het personeel. |
langnl | en | De taal van de e-mails aan de gast. |
createdAtISO 8601 | Wanneer de reservering is gemaakt. |
versionnumber | Gaat bij elke wijziging één omhoog. |
Webhooks
Met webhooks hoort je systeem direct over wijzigingen, in plaats van elke paar minuten te vragen. Voeg een HTTPS-URL toe onder Instellingen → API → Webhooks en kies de gebeurtenissen. Tafels stuurt per gebeurtenis een POST met een JSON-body.
reservation.created | Er is een reservering gemaakt, online, door personeel of via de API. |
reservation.updated | Een reservering is gewijzigd: tijd, tafel, gasten, status (bijvoorbeeld Seated) of gegevens. |
reservation.cancelled | Een reservering is geannuleerd, door de gast of het restaurant. |
Payload
{
"id": "evt_9b2f…",
"type": "reservation.created",
"createdAt": "2026-11-02T10:14:03.601Z",
"restaurant": "the-juniper-room",
"data": {
"reservation": { …reservation }
}
}Headers
Tafels-Event | Het type gebeurtenis, zoals reservation.created. |
Tafels-Delivery | Het id van de aflevering. Dat blijft hetzelfde bij een nieuwe poging. |
Tafels-Signature | t=tijdstempel,v1=handtekening. De handtekening is een HMAC-SHA256 van "tijdstempel.body" met je ondertekeningsgeheim. |
De handtekening controleren
Gebruik het ondertekeningsgeheim dat je zag bij het toevoegen van de webhook. Weiger gebeurtenissen met een verkeerde handtekening of een oud tijdstempel.
import { createHmac, timingSafeEqual } from 'node:crypto';
// In your webhook handler, with the raw request body as a string:
function isFromTafels(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(signatureHeader.split(',').map((p) => p.split('=')));
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const recent = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return recent && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''));
}Nieuwe pogingen
Antwoord binnen 5 seconden met een 2xx-status. Anders probeert Tafels het opnieuw na 1, 5 en 30 minuten, en na 2, 6, 12 en 24 uur. Dezelfde gebeurtenis kan meer dan eens aankomen en de volgorde kan verschillen: gebruik het event-id om dubbele over te slaan en de reserveringsversie om de nieuwste te houden.