tafels.

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

  1. Open in Tafels Instellingen → API en maak een sleutel aan.
  2. Stuur de sleutel bij elk verzoek mee, in de Authorization-header.
  3. 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/restaurant

Basis-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." }
400Ongeldige invoer, bijvoorbeeld een datum in het verleden of een ontbrekend veld.
401De API-sleutel ontbreekt of is niet geldig.
402API-toegang staat uit of zit niet in het abonnement.
404De reservering bestaat niet, of hoort bij een ander restaurant.
409Er is geen passende tafel vrij, een ticket is vol, of de reservering is gewijzigd sinds je hem las.
415De body is geen JSON. Stuur Content-Type: application/json mee.
429Meer 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-DDDe dag om te controleren.
guestsnumberGroepsgrootte, 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-DDEerste dag.
toYYYY-MM-DDLaatste dag. Optioneel, standaard gelijk aan from.
statusstringOptioneel. Alleen deze status, bijvoorbeeld Confirmed of Cancelled.
emailstringOptioneel. 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

namestringVerplicht. De naam van de gast.
emailstringOptioneel. Nodig voor bevestigingen en herinneringen per e-mail.
phonestringOptioneel.
dateYYYY-MM-DDVerplicht.
timeHH:MMVerplicht. Een kwartier.
guestsnumberVerplicht. 1 tot en met 20.
ticketIdstringOptioneel. Zonder wordt het ticket gebruikt dat op dat moment loopt.
tableIdstringOptioneel. Zonder wordt de best passende vrije tafel gekozen.
statusstringOptioneel. Confirmed (standaard) of Waitlist.
notesstringOptioneel. Maximaal 1000 tekens.
langnl | enOptioneel. De taal van de e-mails aan de gast.
voucherCodestringOptioneel. 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

versionnumberVerplicht. De versie van je laatste leesactie.
statusstringOptioneel. 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

idstringUniek id.
name, email, phonestringGegevens van de gast.
date, timestringAankomst, in lokale tijd.
guestsnumberGroepsgrootte.
durationnumberMinuten dat de tafel vastgehouden wordt.
tableIdstring | nullDe toegewezen tafel. Null op de wachtlijst.
ticketId, ticketNamestringWat er geboekt is, zoals Lunch.
statusstringConfirmed, Arrived, Seated, Completed, Cancelled, No-show of Waitlist.
sourcestringOnline, Staff, Phone, Walk-in, Waitlist of API.
channelstringBij online reserveringen: de ?via= van de reserveringslink, zoals instagram. Leeg bij direct reserveren.
voucherCodestringDe gebruikte deal- of cadeauvouchercode, als die er is.
notesstringOpmerkingen van de gast of het personeel.
langnl | enDe taal van de e-mails aan de gast.
createdAtISO 8601Wanneer de reservering is gemaakt.
versionnumberGaat 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.createdEr is een reservering gemaakt, online, door personeel of via de API.
reservation.updatedEen reservering is gewijzigd: tijd, tafel, gasten, status (bijvoorbeeld Seated) of gegevens.
reservation.cancelledEen 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-EventHet type gebeurtenis, zoals reservation.created.
Tafels-DeliveryHet id van de aflevering. Dat blijft hetzelfde bij een nieuwe poging.
Tafels-Signaturet=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.