TableFai

Developers

The TableFai partner API

For a restaurant's own website, its agency, a POS or an AI agent: read availability, hold a table, book for a guest and follow every booking, with the same engine and rules as tablefai.com. No commission and no fee per booking.

TableFai partner API, version 1.0.0 · The OpenAPI document (JSON)

Start in five minutes

  1. The restaurant's owner or admin makes a key under Venue setup → Website & links → API keys, with the permissions you need. It is shown once.

  2. Check the key: the venue, its services and policies.

    export TABLEFAI_KEY=tf_live_…
    curl https://tablefai.com/api/partner/v1/venue \
      -H "Authorization: Bearer $TABLEFAI_KEY"
  3. Find a free time for the party.

    curl "https://tablefai.com/api/partner/v1/availability?date=2026-12-04&party=2" \
      -H "Authorization: Bearer $TABLEFAI_KEY"
  4. Hold it: the table is kept for ten minutes.

    curl -X POST https://tablefai.com/api/partner/v1/holds \
      -H "Authorization: Bearer $TABLEFAI_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{"date": "2026-12-04", "time": "19:00", "party": 2}'
  5. Book the hold for the guest: they get TableFai's confirmation with their manage link.

    curl -X POST https://tablefai.com/api/partner/v1/bookings \
      -H "Authorization: Bearer $TABLEFAI_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{"hold": "<the hold's id>", "guest": {"name": "Ada Lind", "phone": "+46701234567", "email": "ada@example.se"}, "consent": {"terms": true}}'
  6. Then follow every booking as it happens: Webhooks

Authentication

Every call except the OpenAPI document sends the venue's key as a bearer token. A key belongs to one venue, is shown once and can be revoked at any time (then 401).

Authorization: Bearer tf_live_…

Each key has the permissions it was given; a call outside them is 403 scope:

  • availability:read — Read availability and seating
  • bookings:read — Read the venue's bookings
  • bookings:write — Hold tables, book and cancel

A key of the venue: tf_live_ and 43 characters.

Limits

120 requests a minute per key; above it 429 rate_limited.

  • X-RateLimit-Limit, X-RateLimit-Remaining — on every answer: the limit and what is left of it this minute
  • Retry-After — on a 429: seconds until the key may call again
  • Idempotency-Key — a UUID on POST holds and POST bookings: the same key answers the same hold or booking again

Answers and errors

Answers are JSON in data; an error is a stable code with the HTTP status, and validation names each field's rule (codes, never sentences).

{"error": {"code": "validation", "fields": {"date": ["date_format"]}}}
  • errorobjectrequired
    • codestringrequired
    • fieldsobject

      validation: the rule codes per field

The API's own notes (English)

TableFai's partner API lets a restaurant's own systems — its developer's or agency's website, a POS, an AI agent — read the venue's availability and seating, hold tables, book for a guest, read and sync the venue's bookings and cancel them, with the same engine and rules as tablefai.com. There is no commission and no per-booking fee.

Authentication. Every call except this document sends Authorization: Bearer tf_live_…: a key the venue's owner or admin makes under Venue → Website & links → API keys. A key belongs to one venue (the venue every call works on), is shown once when it is made and can be revoked at any time (then 401). TableFai keeps only its SHA-256.

Scopes. availability:read (availability, seating), bookings:read (the venue's bookings), bookings:write (holds, bookings, cancellations). Every key reads its venue. A call outside the key's scopes is 403 scope with the scope it needs in required.

Limits. 120 requests a minute per key; above it 429 rate_limited with Retry-After (seconds). Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining.

Plan. The partner API and webhooks are part of TableFai's Pro plan. While the venue's company is on Start, every call answers 402 plan_required (feature: api_keys, plan: pro); the key is kept and works again with Pro. While the company's subscription is paused or canceled, GET venue and GET bookings… still answer and every other call is 402 subscription_inactive (status).

Answers. JSON in {"data": …} (a page adds meta); errors are {"error": {"code": …}} with the HTTP status, validation errors {"error": {"code": "validation", "fields": {"<field>": ["<rule>"]}}} (codes, never sentences). Every answer has an X-Request-Id. Dates (date, from, to) and times (time, HH:MM) are the venue's, in its timezone; a booking's moments (starts_at, ends_at) carry their offset; other timestamps are UTC (…Z). Texts the restaurant writes come as {text, locale} in the language asked for with ?locale= (en, sv), else the venue's.

Booking. GET availability → GET seating (when the service lets guests choose) → POST holds (the tables are kept ten minutes) → POST bookings with the hold and the guest. POST holds and POST bookings take an Idempotency-Key (a UUID): the same key answers the same hold or booking again; another request under it is 422 idempotency_mismatch. The venue must be published (else 409 not_published). The guest gets TableFai's confirmation mail with their manage link, as when they book on tablefai.com.

Webhooks. The venue's owner or admin adds an https address under Website & links → Webhooks and gets its signing secret (whsec_…) once. TableFai POSTs booking.created, booking.updated and booking.cancelled (and ping) — see webhooks — with TableFai-Event, TableFai-Delivery and TableFai-Signature: t=<unix seconds>, v1=<hex HMAC-SHA256 of "<t>.<body>" with the whole secret as the key>. Check the signature over the raw body and refuse a t more than five minutes away. Answer 2xx within 10 seconds; anything else is tried again after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h, then given up; a webhook failing for three days is turned off. Redirects are not followed. Deliveries may come more than once and out of order: use the event id, and the booking's version or updated_at.

Venue

GET /venue

The key's venue

Its name, page, timezone, languages, party limits, running services with their seating modes, policies, and the key itself. Any scope.

Parameters

Parameters
NameInTypeAbout
localequerystringthe language of the restaurant's textsValues: en, sv

Answers

  • 200The venue.
    Headers: X-RateLimit-Limit, X-RateLimit-Remaining
    The answer's fields ({data: Venue})
    • dataVenuerequired
      • idstringrequired
      • namestringrequired
      • slugstringrequired
      • publishedbooleanrequired
      • urlstring (uri)required
      • countrystringrequired

        ISO 3166-1 alpha-2

      • timezonestringrequired
      • currencystringrequired

        ISO 4217

      • default_localestringrequired
      • localesarray of stringrequired
      • addressAddressrequired
        • streetstring | nullrequired
        • postal_codestring | nullrequired
        • citystring | nullrequired
        • countrystringrequired
      • phonestring | nullrequired
      • booking_phonestring | nullrequired
      • emailstring | nullrequired
      • websitestring | nullrequired
      • party_limitsPartyLimitsrequired

        The party sizes bookable online.

        • minintegerrequired
        • maxintegerrequired
      • servicesarray of Servicerequired
        • idstringrequired
        • nameText | nullrequired
          • textstringrequired
          • localestringrequired
            Values: en, sv
        • weekdaysarray of integer (1–7)required

          ISO weekdays, Monday 1

        • startsstringrequired
        • endsstringrequired
        • last_seatingstringrequired
        • modestringrequired

          request: bookings wait for the restaurant (status requested)

          Values: auto, request
        • seatingstringrequired

          the seating choice a hold may make

          Values: none, area, type, table
      • policiesobjectrequired
        • cancellation_hoursintegerrequired
        • late_grace_minutesintegerrequired
        • no_show_fee_minorinteger | nullrequired
        • deposit_minorinteger | nullrequired
        • textText | nullrequired
          • textstringrequired
          • localestringrequired
            Values: en, sv
      • keyobjectrequired

        The key this call was made with.

        • idstringrequired
        • namestringrequired
        • prefixstringrequired

          the key's first 12 characters

        • scopesarray of stringrequired
          Values: availability:read, bookings:read, bookings:write
  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

Availability

GET /availability

A date's free times (availability:read)

Parameters

Parameters
NameInTypeAbout
daterequiredquerystring (date)Example: 2026-12-02
partyrequiredqueryinteger (1–50)
localequerystringthe language of the restaurant's textsValues: en, sv

Answers

  • 200The date's services and slots.
    Headers: X-RateLimit-Limit, X-RateLimit-Remaining
    The answer's fields ({data: Availability})
    • dataAvailabilityrequired

      The date's open services and their slots by the venue's online rules; nothing but whether a slot is free.

      • datestring (date)required
      • partyintegerrequired
      • party_limitsPartyLimitsrequired

        The party sizes bookable online.

        • minintegerrequired
        • maxintegerrequired
      • large_partyobject | nullrequired
        • textText | nullrequired
          • textstringrequired
          • localestringrequired
            Values: en, sv
        • phonestring | nullrequired
      • servicesarray of objectrequired
        • idstringrequired
        • nameText | nullrequired
          • textstringrequired
          • localestringrequired
            Values: en, sv
        • slotsarray of Slotrequired
          • timestringrequired
          • availablebooleanrequired
          • offerOffer | nullrequired

            The best of the restaurant's own offers this time has (given at the till; payments are unaffected), or null.

        • preorderstringrequired

          Whether the service takes pre-orders (slice 8b). A `required` one cannot be booked through this API: its holds answer 409 `preorder_required`.

          Values: off, optional, required
        • verificationstringrequired

          Whether the service asks a party of this size to verify with BankID on TableFai's own pages (Verifierad gäst, slice 8b); such a party cannot be booked through this API: its holds answer 409 `verification_required`.

          Values: none, bankid
        • waitlistbooleanrequired

          Whether a guest with a party of this size may join the venue's online waitlist for this service on TableFai's own pages (slice 9a): its waitlist is on, the party is within its online sizes, and a service with a guarantee can take payments. This API takes no waitlist requests.

      • nextarray of NextSlotrequired

        the next three free slots of the 30 days after, when the date has none

        • datestring (date)required
        • timestringrequired
  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 409not_published: the venue's page is not published, so it takes no online bookings.
  • 422validation with fields, or another refusal of the request (idempotency_mismatch, seating_unavailable, table_too_large).
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

GET /seating

The seating choice at a time (availability:read)

Parameters

Parameters
NameInTypeAbout
daterequiredquerystring (date)Example: 2026-12-02
timerequiredquerystringHH:MM on the 15-minute gridExample: 19:00
partyrequiredqueryinteger (1–50)
localequerystringthe language of the restaurant's textsValues: en, sv

Answers

  • 200By the service's seating mode.
    Headers: X-RateLimit-Limit, X-RateLimit-Remaining
    The answer's fields ({data: Seating})
    • dataSeatingrequired

      What a guest may choose at a date, time and party, by the service's seating mode.

  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 409not_published: the venue's page is not published, so it takes no online bookings.
  • 422validation with fields, or another refusal of the request (idempotency_mismatch, seating_unavailable, table_too_large).
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

Holds

POST /holds

Keep tables ten minutes (bookings:write)

Parameters

Parameters
NameInTypeAbout
Idempotency-Keyrequiredheaderstring (uuid)a UUID: the same key answers the same hold or booking again
localequerystringthe language of the restaurant's textsValues: en, sv

Body HoldRequest

  • datestring (date)required
  • timestringrequired

    on the 15-minute grid

  • partyinteger (1–50)required
  • seatingSeatingChoice

    none lets the restaurant choose; another mode must be the service's own.

    • modestringrequired
      Values: none, area, type, table
    • zonestring

      area: the zone

    • tagstring

      type: the kind of table

      Values: window, booth, sofa, quiet, view, outdoor, bar, high_top, private, accessible, high_chair, near_entrance
    • tablestring

      table: the table

Answers

  • 201The hold.
    Headers: X-RateLimit-Limit, X-RateLimit-Remaining
    The answer's fields ({data: object})
    • dataobjectrequired
      • holdHoldrequired
        • idstringrequired
        • expires_atstring (date-time)required

          ten minutes after it was made

        • tableTableRef | nullrequired
          • idstringrequired
          • codestringrequired
        • zoneZone | nullrequired
          • idstringrequired
          • nameText | nullrequired
          • colorstringrequired
  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 409slot_taken (with alternatives: [{date, time}]), not_published, invalid_transition (with from, to), stale, payment_required: the service takes a card guarantee, a deposit or a prepaid menu, which only TableFai's own booking pages take (the guest books there), preorder_required (the service needs a pre-order) or verification_required (the service asks the party to verify with BankID, slice 8b).
  • 422validation with fields, or another refusal of the request (idempotency_mismatch, seating_unavailable, table_too_large).
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

DELETE /holds/{id}

Give a hold's tables back (bookings:write)

Parameters

Parameters
NameInTypeAbout
idrequiredpathstringExample: 01JC9Y7F3K2M8Q4R6T0V1W2X3Y

Answers

  • 204Released (also when there was nothing to release).
  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

Bookings

GET /bookings

The venue's bookings (bookings:read)

50 a page. Without updated_since by start; with it, the bookings changed at or after it, in the order they changed (sync by the last updated_at you saw).

Parameters

Parameters
NameInTypeAbout
fromquerystring (date)the first date (the venue's)Example: 2026-12-02
toquerystring (date)the last dateExample: 2026-12-02
statusquerystringone status or several, comma-separatedExample: confirmed,seated
updated_sincequerystring (date-time)ISO 8601; without a zone UTC
pagequeryinteger (≥ 1)
localequerystringthe language of the restaurant's textsValues: en, sv

Answers

  • 200A page of bookings.
    Headers: X-RateLimit-Limit, X-RateLimit-Remaining
    The answer's fields ({data: array of Booking})
    • dataarray of Bookingrequired
      • referencestringrequired
      • statusstringrequired
        Values: requested, confirmed, waitlisted, arrived, seated, finished, no_show, cancelled_guest, cancelled_venue
      • datestring (date)required

        the service's date (a booking after midnight belongs to the evening before)

      • timestringrequired
      • endstringrequired
      • minutesintegerrequired
      • partyintegerrequired
      • starts_atstring (date-time)required

        with the venue's offset

      • ends_atstring (date-time)required
      • serviceobject | nullrequired
        • idstringrequired
        • nameText | nullrequired
          • textstringrequired
          • localestringrequired
            Values: en, sv
      • tablesarray of TableRefrequired
        • idstringrequired
        • codestringrequired
      • seatingobjectrequired

        the guest's seating choice {mode, zone?, tag?, table?}

      • sourcestringrequired
        Values: widget, site_link, qr, instagram, facebook, google, email, marketplace, api, phone, walk_in, staff, app, import
      • source_originstring | nullrequired
      • utmobject | nullrequired
        • sourcestring | nullrequired
        • mediumstring | nullrequired
        • campaignstring | nullrequired
      • guestobject | nullrequired
        • namestringrequired
        • phonestring | nullrequired
        • emailstring | nullrequired
      • localestringrequired
      • occasionstring | nullrequired
        Values: birthday, anniversary, date_night, business, celebration, other
      • accessibilityarray of stringrequired
        Values: wheelchair, step_free, walking_aid, hearing, vision, service_dog, high_chair, pram
      • requestsstring | nullrequired
      • allergiesstring | nullrequired
      • confirmed_atstring (date-time) | nullrequired
      • late_minutesinteger | nullrequired
      • cancelled_latebooleanrequired

        cancelled by the guest inside the cancellation window

      • status_atstring (date-time) | nullrequired
      • created_atstring (date-time)required
      • updated_atstring (date-time)required
      • versionintegerrequired

        grows with every change

    • metaMetarequired
      • pageintegerrequired
      • pagesintegerrequired
      • totalintegerrequired
  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 422validation with fields, or another refusal of the request (idempotency_mismatch, seating_unavailable, table_too_large).
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

POST /bookings

Book a hold for a guest (bookings:write)

The hold becomes the booking in one transaction (confirmed, or requested in a service in request mode); source api. The guest is mailed the confirmation with their manage link.

Parameters

Parameters
NameInTypeAbout
Idempotency-Keyrequiredheaderstring (uuid)a UUID: the same key answers the same hold or booking again
localequerystringthe language of the restaurant's textsValues: en, sv

Body BookingRequest

  • holdstringrequired
  • guestobjectrequired
    • namestring (≤ 120)required
    • phonestring (≤ 40)required

      E.164, or a national number of the venue's country

    • emailstring (email, ≤ 190)required
    • localestring | null
      Values: en, sv
  • occasionstring | null
    Values: birthday, anniversary, date_night, business, celebration, other
  • requestsstring (≤ 2000) | null
  • allergiesstring (≤ 2000) | null
  • accessibilityarray of string | null
    Values: wheelchair, step_free, walking_aid, hearing, vision, service_dog, high_chair, pram
  • consentobjectrequired
    • termstruerequired

      the guest accepted the booking terms and the venue's policy

    • marketingboolean

Answers

  • 201The booking.
    Headers: X-RateLimit-Limit, X-RateLimit-Remaining
    The answer's fields ({data: object})
    • dataobjectrequired
      • bookingBookingrequired

        A booking as the venue sees it, without its staff's own notes and tags.

        • referencestringrequired
        • statusstringrequired
          Values: requested, confirmed, waitlisted, arrived, seated, finished, no_show, cancelled_guest, cancelled_venue
        • datestring (date)required

          the service's date (a booking after midnight belongs to the evening before)

        • timestringrequired
        • endstringrequired
        • minutesintegerrequired
        • partyintegerrequired
        • starts_atstring (date-time)required

          with the venue's offset

        • ends_atstring (date-time)required
        • serviceobject | nullrequired
          • idstringrequired
          • nameText | nullrequired
        • tablesarray of TableRefrequired
          • idstringrequired
          • codestringrequired
        • seatingobjectrequired

          the guest's seating choice {mode, zone?, tag?, table?}

        • sourcestringrequired
          Values: widget, site_link, qr, instagram, facebook, google, email, marketplace, api, phone, walk_in, staff, app, import
        • source_originstring | nullrequired
        • utmobject | nullrequired
          • sourcestring | nullrequired
          • mediumstring | nullrequired
          • campaignstring | nullrequired
        • guestobject | nullrequired
          • namestringrequired
          • phonestring | nullrequired
          • emailstring | nullrequired
        • localestringrequired
        • occasionstring | nullrequired
          Values: birthday, anniversary, date_night, business, celebration, other
        • accessibilityarray of stringrequired
          Values: wheelchair, step_free, walking_aid, hearing, vision, service_dog, high_chair, pram
        • requestsstring | nullrequired
        • allergiesstring | nullrequired
        • confirmed_atstring (date-time) | nullrequired
        • late_minutesinteger | nullrequired
        • cancelled_latebooleanrequired

          cancelled by the guest inside the cancellation window

        • status_atstring (date-time) | nullrequired
        • created_atstring (date-time)required
        • updated_atstring (date-time)required
        • versionintegerrequired

          grows with every change

  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 409not_published: the venue's page is not published, so it takes no online bookings; payment_required: the hold's service takes a card guarantee, a deposit or a prepaid menu, which only TableFai's own booking pages take; preorder_required: the service needs a pre-order; or verification_required: the service asks a party of this size to verify with BankID (slice 8b), which only TableFai's own booking pages do.
  • 410hold_expired: the hold ran out, was released or is not the venue's.
  • 422validation with fields, or another refusal of the request (idempotency_mismatch, seating_unavailable, table_too_large).
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

GET /bookings/{reference}

One booking (bookings:read)

Parameters

Parameters
NameInTypeAbout
referencerequiredpathstring
localequerystringthe language of the restaurant's textsValues: en, sv

Answers

  • 200The booking.
    Headers: X-RateLimit-Limit, X-RateLimit-Remaining
    The answer's fields ({data: object})
    • dataobjectrequired
      • bookingBookingrequired

        A booking as the venue sees it, without its staff's own notes and tags.

        • referencestringrequired
        • statusstringrequired
          Values: requested, confirmed, waitlisted, arrived, seated, finished, no_show, cancelled_guest, cancelled_venue
        • datestring (date)required

          the service's date (a booking after midnight belongs to the evening before)

        • timestringrequired
        • endstringrequired
        • minutesintegerrequired
        • partyintegerrequired
        • starts_atstring (date-time)required

          with the venue's offset

        • ends_atstring (date-time)required
        • serviceobject | nullrequired
          • idstringrequired
          • nameText | nullrequired
        • tablesarray of TableRefrequired
          • idstringrequired
          • codestringrequired
        • seatingobjectrequired

          the guest's seating choice {mode, zone?, tag?, table?}

        • sourcestringrequired
          Values: widget, site_link, qr, instagram, facebook, google, email, marketplace, api, phone, walk_in, staff, app, import
        • source_originstring | nullrequired
        • utmobject | nullrequired
          • sourcestring | nullrequired
          • mediumstring | nullrequired
          • campaignstring | nullrequired
        • guestobject | nullrequired
          • namestringrequired
          • phonestring | nullrequired
          • emailstring | nullrequired
        • localestringrequired
        • occasionstring | nullrequired
          Values: birthday, anniversary, date_night, business, celebration, other
        • accessibilityarray of stringrequired
          Values: wheelchair, step_free, walking_aid, hearing, vision, service_dog, high_chair, pram
        • requestsstring | nullrequired
        • allergiesstring | nullrequired
        • confirmed_atstring (date-time) | nullrequired
        • late_minutesinteger | nullrequired
        • cancelled_latebooleanrequired

          cancelled by the guest inside the cancellation window

        • status_atstring (date-time) | nullrequired
        • created_atstring (date-time)required
        • updated_atstring (date-time)required
        • versionintegerrequired

          grows with every change

  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 404not_found: not the key's venue's.
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

POST /bookings/{reference}/cancel

Cancel for the venue (bookings:write)

cancelled_venue, from requested or confirmed; a cancelled booking answers as it is. The guest is told by mail. With version, only at that version (409 stale).

Parameters

Parameters
NameInTypeAbout
referencerequiredpathstring
localequerystringthe language of the restaurant's textsValues: en, sv

Body object

  • versioninteger (≥ 1)

Answers

  • 200The booking, cancelled.
    Headers: X-RateLimit-Limit, X-RateLimit-Remaining
    The answer's fields ({data: object})
    • dataobjectrequired
      • bookingBookingrequired

        A booking as the venue sees it, without its staff's own notes and tags.

        • referencestringrequired
        • statusstringrequired
          Values: requested, confirmed, waitlisted, arrived, seated, finished, no_show, cancelled_guest, cancelled_venue
        • datestring (date)required

          the service's date (a booking after midnight belongs to the evening before)

        • timestringrequired
        • endstringrequired
        • minutesintegerrequired
        • partyintegerrequired
        • starts_atstring (date-time)required

          with the venue's offset

        • ends_atstring (date-time)required
        • serviceobject | nullrequired
          • idstringrequired
          • nameText | nullrequired
        • tablesarray of TableRefrequired
          • idstringrequired
          • codestringrequired
        • seatingobjectrequired

          the guest's seating choice {mode, zone?, tag?, table?}

        • sourcestringrequired
          Values: widget, site_link, qr, instagram, facebook, google, email, marketplace, api, phone, walk_in, staff, app, import
        • source_originstring | nullrequired
        • utmobject | nullrequired
          • sourcestring | nullrequired
          • mediumstring | nullrequired
          • campaignstring | nullrequired
        • guestobject | nullrequired
          • namestringrequired
          • phonestring | nullrequired
          • emailstring | nullrequired
        • localestringrequired
        • occasionstring | nullrequired
          Values: birthday, anniversary, date_night, business, celebration, other
        • accessibilityarray of stringrequired
          Values: wheelchair, step_free, walking_aid, hearing, vision, service_dog, high_chair, pram
        • requestsstring | nullrequired
        • allergiesstring | nullrequired
        • confirmed_atstring (date-time) | nullrequired
        • late_minutesinteger | nullrequired
        • cancelled_latebooleanrequired

          cancelled by the guest inside the cancellation window

        • status_atstring (date-time) | nullrequired
        • created_atstring (date-time)required
        • updated_atstring (date-time)required
        • versionintegerrequired

          grows with every change

  • 401No key, an unknown or a revoked key (unauthenticated).
    Headers: WWW-Authenticate
  • 402plan_required (feature, plan: the venue's company is on the Start plan) or subscription_inactive (status: its subscription is paused or canceled).
  • 403scope (the key lacks the scope in required) or company_suspended.
  • 404not_found: not the key's venue's.
  • 409slot_taken (with alternatives: [{date, time}]), not_published, invalid_transition (with from, to), stale, payment_required: the service takes a card guarantee, a deposit or a prepaid menu, which only TableFai's own booking pages take (the guest books there), preorder_required (the service needs a pre-order) or verification_required (the service asks the party to verify with BankID, slice 8b).
  • 429rate_limited: more than 120 requests in a minute (retry_after in seconds).
    Headers: Retry-After

Document

GET /openapi.json

This document

Open: no key needed.

Answers

  • 200OpenAPI 3.1.

Webhooks

Add an https address under Website & links → Webhooks and choose its events. TableFai POSTs JSON to it within a minute of each change, in the booking's own order of events.

  • TableFai-Signature: t=<unix>, v1=<hex> — an HMAC-SHA256 of "<t>.<raw body>" with the webhook's secret (whsec_…)
  • TableFai-Event — the event: booking.created, booking.updated, booking.cancelled or ping
  • TableFai-Delivery — this delivery's id: an event may arrive more than once, use the event's id
  • Answer 2xx within 10 seconds. Anything else is tried again after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h; a webhook failing for three days is turned off. Redirects are not followed.

Check the signature

Check it over the raw body before you parse it, with the secret exactly as it was shown, and refuse a t more than five minutes away.

PHP

function tablefai_signed(string $body, string $header, string $secret, int $tolerance = 300): bool
{
    $t = null;
    $signatures = [];
    foreach (explode(',', $header) as $part) {
        [$name, $value] = array_pad(explode('=', trim($part), 2), 2, '');
        if ($name === 't') {
            $t = (int) $value;
        } elseif ($name === 'v1') {
            $signatures[] = $value;
        }
    }
    if ($t === null || abs(time() - $t) > $tolerance) {
        return false;
    }
    $expected = hash_hmac('sha256', $t.'.'.$body, $secret);
    foreach ($signatures as $signature) {
        if (hash_equals($expected, $signature)) {
            return true;
        }
    }

    return false;
}

$ok = tablefai_signed(file_get_contents('php://input'), $_SERVER['HTTP_TABLEFAI_SIGNATURE'] ?? '', getenv('TABLEFAI_WEBHOOK_SECRET'));

Node.js

import { createHmac, timingSafeEqual } from 'node:crypto';

export function tablefaiSigned(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = header.split(',').map((part) => part.trim().split('='));
  const t = Number(parts.find(([name]) => name === 't')?.[1]);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = Buffer.from(createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'));
  return parts.filter(([name]) => name === 'v1').some(([, signature]) => {
    const given = Buffer.from(signature ?? '');
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}
// Express: app.post('/tablefai', express.raw({ type: 'application/json' }), (req, res) =>
//   tablefaiSigned(req.body.toString('utf8'), req.get('TableFai-Signature') ?? '', process.env.TABLEFAI_WEBHOOK_SECRET)
//     ? res.sendStatus(204) : res.sendStatus(400));

The events

POST booking.created

A booking was made (by staff, a guest or a partner)

The body's fields (BookingEvent)
  • idstringrequired

    the event's id: the same for every webhook it goes to

  • typestringrequired
    Values: booking.created, booking.updated, booking.cancelled
  • created_atstring (date-time)required
  • venueEventVenuerequired
    • idstringrequired
    • slugstringrequired
    • namestringrequired
  • dataobjectrequired
    • bookingBookingrequired

      A booking as the venue sees it, without its staff's own notes and tags.

      • referencestringrequired
      • statusstringrequired
        Values: requested, confirmed, waitlisted, arrived, seated, finished, no_show, cancelled_guest, cancelled_venue
      • datestring (date)required

        the service's date (a booking after midnight belongs to the evening before)

      • timestringrequired
      • endstringrequired
      • minutesintegerrequired
      • partyintegerrequired
      • starts_atstring (date-time)required

        with the venue's offset

      • ends_atstring (date-time)required
      • serviceobject | nullrequired
        • idstringrequired
        • nameText | nullrequired
      • tablesarray of TableRefrequired
        • idstringrequired
        • codestringrequired
      • seatingobjectrequired

        the guest's seating choice {mode, zone?, tag?, table?}

      • sourcestringrequired
        Values: widget, site_link, qr, instagram, facebook, google, email, marketplace, api, phone, walk_in, staff, app, import
      • source_originstring | nullrequired
      • utmobject | nullrequired
        • sourcestring | nullrequired
        • mediumstring | nullrequired
        • campaignstring | nullrequired
      • guestobject | nullrequired
        • namestringrequired
        • phonestring | nullrequired
        • emailstring | nullrequired
      • localestringrequired
      • occasionstring | nullrequired
        Values: birthday, anniversary, date_night, business, celebration, other
      • accessibilityarray of stringrequired
        Values: wheelchair, step_free, walking_aid, hearing, vision, service_dog, high_chair, pram
      • requestsstring | nullrequired
      • allergiesstring | nullrequired
      • confirmed_atstring (date-time) | nullrequired
      • late_minutesinteger | nullrequired
      • cancelled_latebooleanrequired

        cancelled by the guest inside the cancellation window

      • status_atstring (date-time) | nullrequired
      • created_atstring (date-time)required
      • updated_atstring (date-time)required
      • versionintegerrequired

        grows with every change

Answers

  • 2XXDelivered. Anything else, or no answer within 10 seconds, is tried again.

POST booking.updated

A booking changed (time, party, tables, status, the guest's running late …)

The body's fields (BookingEvent)
  • idstringrequired

    the event's id: the same for every webhook it goes to

  • typestringrequired
    Values: booking.created, booking.updated, booking.cancelled
  • created_atstring (date-time)required
  • venueEventVenuerequired
    • idstringrequired
    • slugstringrequired
    • namestringrequired
  • dataobjectrequired
    • bookingBookingrequired

      A booking as the venue sees it, without its staff's own notes and tags.

      • referencestringrequired
      • statusstringrequired
        Values: requested, confirmed, waitlisted, arrived, seated, finished, no_show, cancelled_guest, cancelled_venue
      • datestring (date)required

        the service's date (a booking after midnight belongs to the evening before)

      • timestringrequired
      • endstringrequired
      • minutesintegerrequired
      • partyintegerrequired
      • starts_atstring (date-time)required

        with the venue's offset

      • ends_atstring (date-time)required
      • serviceobject | nullrequired
        • idstringrequired
        • nameText | nullrequired
      • tablesarray of TableRefrequired
        • idstringrequired
        • codestringrequired
      • seatingobjectrequired

        the guest's seating choice {mode, zone?, tag?, table?}

      • sourcestringrequired
        Values: widget, site_link, qr, instagram, facebook, google, email, marketplace, api, phone, walk_in, staff, app, import
      • source_originstring | nullrequired
      • utmobject | nullrequired
        • sourcestring | nullrequired
        • mediumstring | nullrequired
        • campaignstring | nullrequired
      • guestobject | nullrequired
        • namestringrequired
        • phonestring | nullrequired
        • emailstring | nullrequired
      • localestringrequired
      • occasionstring | nullrequired
        Values: birthday, anniversary, date_night, business, celebration, other
      • accessibilityarray of stringrequired
        Values: wheelchair, step_free, walking_aid, hearing, vision, service_dog, high_chair, pram
      • requestsstring | nullrequired
      • allergiesstring | nullrequired
      • confirmed_atstring (date-time) | nullrequired
      • late_minutesinteger | nullrequired
      • cancelled_latebooleanrequired

        cancelled by the guest inside the cancellation window

      • status_atstring (date-time) | nullrequired
      • created_atstring (date-time)required
      • updated_atstring (date-time)required
      • versionintegerrequired

        grows with every change

Answers

  • 2XXDelivered. Anything else, or no answer within 10 seconds, is tried again.

POST booking.cancelled

A booking was cancelled (by the guest or the venue)

The body's fields (BookingEvent)
  • idstringrequired

    the event's id: the same for every webhook it goes to

  • typestringrequired
    Values: booking.created, booking.updated, booking.cancelled
  • created_atstring (date-time)required
  • venueEventVenuerequired
    • idstringrequired
    • slugstringrequired
    • namestringrequired
  • dataobjectrequired
    • bookingBookingrequired

      A booking as the venue sees it, without its staff's own notes and tags.

      • referencestringrequired
      • statusstringrequired
        Values: requested, confirmed, waitlisted, arrived, seated, finished, no_show, cancelled_guest, cancelled_venue
      • datestring (date)required

        the service's date (a booking after midnight belongs to the evening before)

      • timestringrequired
      • endstringrequired
      • minutesintegerrequired
      • partyintegerrequired
      • starts_atstring (date-time)required

        with the venue's offset

      • ends_atstring (date-time)required
      • serviceobject | nullrequired
        • idstringrequired
        • nameText | nullrequired
      • tablesarray of TableRefrequired
        • idstringrequired
        • codestringrequired
      • seatingobjectrequired

        the guest's seating choice {mode, zone?, tag?, table?}

      • sourcestringrequired
        Values: widget, site_link, qr, instagram, facebook, google, email, marketplace, api, phone, walk_in, staff, app, import
      • source_originstring | nullrequired
      • utmobject | nullrequired
        • sourcestring | nullrequired
        • mediumstring | nullrequired
        • campaignstring | nullrequired
      • guestobject | nullrequired
        • namestringrequired
        • phonestring | nullrequired
        • emailstring | nullrequired
      • localestringrequired
      • occasionstring | nullrequired
        Values: birthday, anniversary, date_night, business, celebration, other
      • accessibilityarray of stringrequired
        Values: wheelchair, step_free, walking_aid, hearing, vision, service_dog, high_chair, pram
      • requestsstring | nullrequired
      • allergiesstring | nullrequired
      • confirmed_atstring (date-time) | nullrequired
      • late_minutesinteger | nullrequired
      • cancelled_latebooleanrequired

        cancelled by the guest inside the cancellation window

      • status_atstring (date-time) | nullrequired
      • created_atstring (date-time)required
      • updated_atstring (date-time)required
      • versionintegerrequired

        grows with every change

Answers

  • 2XXDelivered. Anything else, or no answer within 10 seconds, is tried again.

POST ping

A test from the venue's settings

The body's fields (PingEvent)
  • idstringrequired
  • type"ping"required
  • created_atstring (date-time)required
  • venueEventVenuerequired
    • idstringrequired
    • slugstringrequired
    • namestringrequired
  • dataobjectrequired
    • webhookstringrequired

Answers

  • 2XXDelivered. Anything else, or no answer within 10 seconds, is tried again.