Public API Reference

Use the sidebar to jump by endpoint or search inside the rendered documentation.

Base URL https://staging.resevu.com/api 27 endpoints 39 sections
No matching sections found.

API Documentation

Base URL: https://resevu.com/api

This document describes the public endpoints available for website widgets and custom integrations.

Quick links:

  • HTML overview: GET https://resevu.com/api/
  • JSON info: GET https://resevu.com/api/info
  • OpenAPI YAML: GET https://resevu.com/resources/docs/public-api.yaml
  • Booking widget analytics hooks: widget analytics events

Access, tenancy, and security

Tenant resolution

  • Most endpoints require a tenant context via:
    • query: tenant=<slug>, or
    • header: X-Tenant: <slug>

Origin checks

  • Browser integrations should be used from a domain that is allowed for the tenant.
  • Send normal browser Origin or Referer headers. The API can reject requests from unapproved domains.

Public API keys

  • Some /public/* endpoints require a public API key depending on the tenant configuration.
  • Send API keys only in the X-Api-Key: <key> header. Query-string API keys are rejected to prevent leakage through logs and browser history.

Chatbot widget access

  • The chatbot script is served from GET https://resevu.com/chatbot/widget.js.
  • Preferred tenant resolution is the public chatbot widget key:
    • query/body: widget_key=<widget_key>, or
    • header: X-Chatbot-Key: <widget_key>
  • tenant=<slug> or X-Tenant: <slug> also works for controlled integrations, but external websites should use the widget key.

Signed URLs

  • Calendar download links must use the signed URL generated by Resevu.

Rate limits

  • Public endpoints are rate limited. If you receive 429 Too Many Requests, retry later and reduce request frequency.

Endpoint catalog

Method Path Group Notes
GET / Info API overview page
GET /info Info Machine-readable endpoint list
GET /chatbot/config Chatbot Widget config, tenant info, styling, messages
POST /chatbot/conversations Chatbot Start a public chatbot conversation
POST /chatbot/conversations/{conversation}/messages Chatbot Send message and receive assistant reply
POST /chatbot/conversations/{conversation}/handoff Chatbot Mark conversation as needing tenant follow-up
GET /public/bookings/bootstrap Widget booking Tenant required
GET /public/bookings/employees Widget booking Tenant required
GET /public/bookings/massage-types Widget booking Tenant required
GET /public/bookings/available-durations Widget booking Tenant required
GET /public/bookings/available-time-slots Widget booking Tenant required
GET /public/bookings/price-estimate Widget booking Tenant required
GET /public/bookings/widget-config Widget booking Tenant required
POST /public/bookings/store Widget booking Tenant required
GET /public/bookings/available-days Widget booking Tenant required
GET /public/bookings/coupon/validate Widget booking Tenant required
POST /public/vouchers/initiate Voucher Tenant required
GET /payment/start/{booking} Payment Tenant required, redirects to payment checkout
GET /payment/start-voucher/{order} Payment Tenant required, redirects to payment checkout
POST /payment/webhook Payment Payment provider callback
GET /public/tenants/{tenant}/bookings/{booking}/calendar.ics Calendar Signed URL required
GET /public/config Public API v1 Tenant + API key when required
GET /public/opening-hours Public API v1 Tenant + API key when required
GET /public/employees Public API v1 Tenant + API key when required
GET /public/employees/available Public API v1 Tenant + API key when required
GET /public/employees/{employee}/availability Public API v1 Tenant + API key when required
GET /public/pricing/estimate Public API v1 Tenant + API key when required

Availability behavior

Availability endpoints return bookable dates and start times for the selected tenant, service, duration, employee, and duo settings.

Availability can change between reading slots and creating a booking. Always treat POST /public/bookings/store as the final confirmation step. A slot returned earlier can still be rejected if it was booked or blocked before the create request.

Chatbot widget setup

The chatbot is an optional tenant feature. To embed it on a website:

  1. Get the chatbot widget key from the tenant's Resevu chatbot settings.
  2. Confirm the website domain is allowed for the tenant.
  3. When the chatbot is embedded on a page without the booking widget, set Boekingspagina voor chatbotlinks in Admin > Chatbot > Instellingen to the page that contains the booking widget or custom booking flow.
  4. Place the generated embed code on the tenant website:
<script src="https://resevu.com/chatbot/widget.js" data-resevu-chatbot-key="<widget_key>" defer></script>

Optional attributes:

<script
  src="https://resevu.com/chatbot/widget.js"
  data-resevu-chatbot-key="<widget_key>"
  data-locale="nl"
  data-api-base="https://resevu.com/api"
  defer
></script>

Chatbot booking handoff

When prepare_booking_checkout is enabled, the chatbot may prepare a booking link. It first gathers treatment, duration, date, and start time. If details are missing, it asks one compact follow-up question for those missing details only.

When all details are ready, the chatbot asks the customer to type bevestigen. The booking link is appended only after that final confirmation. The link is based on the configured Boekingspagina voor chatbotlinks when present; otherwise the submitted page_url is used. Generated links include resevu_booking=1, massage_type_id, duration_minutes, date, and start_time.

Endpoint details

GET /

  • Returns HTML API overview page.

GET /info

  • Returns JSON with metadata, links, and endpoint list.

GET /chatbot/config

  • Query/header:
    • widget_key or X-Chatbot-Key recommended
    • tenant or X-Tenant for controlled integrations
  • Response:
    • success
    • tenant { slug, name }
    • chatbot { bot_name, welcome_messages, fallback_messages, allowed_actions, primary_color, accent_color, avatar_path, privacy_notice }
    • credits { balance }

POST /chatbot/conversations

  • Query/header:
    • widget_key or X-Chatbot-Key
  • JSON body:
    • locale optional, one of configured app locales
    • channel optional, normally widget
  • Response:
    • success
    • conversation { id, locale, is_test }

POST /chatbot/conversations/{conversation}/messages

  • Query/header:
    • widget_key or X-Chatbot-Key
  • Path:
    • conversation public conversation UUID
  • JSON body:
    • message required, max 4000 chars
    • page_url optional
  • Response:
    • success
    • conversation { id, status, locale, is_test }
    • message { role, content }
    • fallback, fallback_reason
    • usage { provider, model, credits_delta }

POST /chatbot/conversations/{conversation}/handoff

  • Query/header:
    • widget_key or X-Chatbot-Key
  • Path:
    • conversation public conversation UUID
  • Response:
    • success
    • conversation { id, status }, with status needs_review

GET /public/bookings/bootstrap

  • Query:
    • tenant (or X-Tenant header)
  • Recommended for first widget load to reduce startup round-trips.
  • Response includes:
    • config (tenant, currency, timezone, booking, deposit)
    • employees[] (active employees + linked active massage types)
    • massage_types[] with:
      • durations[] (effective for today)
      • initial_duration
      • initial_price

GET /public/bookings/employees

  • Query:
    • tenant (or X-Tenant header)
    • days_ahead (optional integer, capped by tenant max_days_in_advance)
    • massage_type_id (optional, active massage type in current tenant)
  • Recommended when the frontend wants a larger employee dataset up front to reduce follow-up requests.
  • Response includes:
    • success
    • data[] with:
      • id, name, photo_url, bio
      • massage_types[] (active massage types the employee can perform)
      • available_dates[] (Y-m-d values for the requested window)
    • meta { days_ahead, start_date, massage_type_id }

GET /public/bookings/massage-types

  • Query:
    • tenant (or X-Tenant header)
  • Response:
    • success
    • massage_types[], including allow_duo and allow_as_mixed_duo
  • allow_as_mixed_duo=true means the service may use its single price as one participant in a mixed duo. It may not be selected twice unless it also has a regular duo price.

GET /public/bookings/available-durations

  • Query:
    • tenant (or X-Tenant)
    • massage_type_id (required)
    • is_duo (optional boolean)
    • second_massage_type_id (optional)
    • date (optional Y-m-d)
  • Response includes:
    • durations[]
    • deposit_type, deposit_value, deposit_is_full, can_pay_full
  • For a mixed duo, durations are the intersection supported by both services. A service with allow_as_mixed_duo=true contributes its single durations; selecting that same single-only service for both participants returns no durations.

GET /public/bookings/available-time-slots

  • Query:
    • tenant (or X-Tenant)
    • massage_type_id (required)
    • duration_id (required)
    • date (required Y-m-d)
    • is_duo (optional boolean)
    • second_massage_type_id (optional)
    • employee_id (optional, active employee in current tenant)
    • second_employee_id (optional, active employee in current tenant; only for duo bookings and requires employee_id)
  • Response includes:
    • time_slots[]
    • employee_assignments map
    • is_duo
  • Notes:
    • A returned start time means at least one suitable employee, or employee pair for duo, has enough continuous free 15-minute blocks for the selected duration.
    • Existing bookings block their assigned employees, including configured buffer time between massages.

GET /public/bookings/price-estimate

  • Query:
    • tenant (or X-Tenant)
    • duration_id (required)
    • massage_type_id (required)
    • coupon_code (optional)
    • date (optional)
    • is_duo (optional boolean)
    • second_massage_type_id (optional)
  • Response includes:
    • price { base, discount, total }
    • deposit { type, value, is_full, amount, can_pay_full }
  • When either participant uses allow_as_mixed_duo, the base price is the sum of both single prices minus the tenant's configured duo discount.

GET /public/bookings/widget-config

  • Query:
    • tenant (or X-Tenant)
  • Response includes:
    • currency_code, currency_symbol

POST /public/bookings/store

  • Query:
    • tenant (or X-Tenant)
  • JSON body:
    • name (required)
    • firstname (required)
    • massage_type_id (required)
    • duration (required)
    • date (required)
    • start_time (required H:i)
    • email, phone, coupon_code, is_duo, second_massage_type_id, employee_id, second_employee_id, redirect_domain, tracking_url, payment_type, language (optional)
  • Notes:
    • For duo bookings, pass both employee_id and second_employee_id to lock the requested pair.
    • second_employee_id must differ from employee_id and is only accepted when is_duo=true.
    • If only employee_id is supplied for a duo booking, the second employee is assigned automatically for backward compatibility.
  • Response:
    • success
    • redirect_url when accepted

GET /public/bookings/available-days

  • Query:
    • tenant (or X-Tenant)
    • is_duo (optional boolean)
    • massage_type_id (optional)
    • duration_id (optional)
    • second_massage_type_id (optional)
    • employee_id (optional, active employee in current tenant)
    • second_employee_id (optional, active employee in current tenant; only for duo bookings and requires employee_id)
    • days_ahead (optional)
  • Response:
    • success
    • dates[] (available Y-m-d values)
  • Notes:
    • Without service, duration, duo, or employee filters, this endpoint returns open shop days.
    • With massage_type_id, duration_id, is_duo, second_massage_type_id, employee_id, or second_employee_id, this endpoint returns only dates where the requested combination can actually be scheduled.
    • Pass duration_id when the frontend needs exact availability for the selected service duration.

GET /public/bookings/coupon/validate

  • Query:
    • tenant (or X-Tenant)
    • code (required)
    • massage_type_id (optional)
  • Response:
    • Coupon fields (id, code, type, value) or 404 when invalid.

POST /public/vouchers/initiate

  • Query:
    • tenant (or X-Tenant)
  • JSON body:
    • buyer_name (required)
    • buyer_email (required)
    • amount (required, numeric, min 0)
    • redirect_domain, tracking_url, meta, language (optional)
  • Response:
    • success
    • redirect_url
    • order_id

GET /payment/start/{booking}

  • Path:
    • booking (booking id)
  • Query:
    • tenant (or X-Tenant)
    • type (optional: deposit or full)
    • redirect_domain (optional)
  • Response:
    • Redirects (303) to the payment checkout URL.

GET /payment/start-voucher/{order}

  • Path:
    • order (gift voucher order id)
  • Query:
    • tenant (or X-Tenant)
  • Response:
    • Redirects (303) to the payment checkout URL.

POST /payment/webhook

  • Body:
    • id (payment provider transaction id)
  • Response:
    • status update JSON.

GET /public/tenants/{tenant}/bookings/{booking}/calendar.ics

  • Path:
    • tenant (tenant slug)
    • booking (booking id)
  • Query:
    • Signed URL parameters generated by backend.
  • Response:
    • ICS file download.

GET /public/config

  • Query:
    • tenant (or X-Tenant)
  • Headers:
    • Origin recommended
    • X-Api-Key optional/required per tenant policy
  • Response:
    • data.tenant, data.currency, data.timezone, data.booking, data.deposit, data.features

GET /public/opening-hours

  • Query:
    • tenant (or X-Tenant)
  • Headers:
    • X-Api-Key optional/required per tenant policy
  • Response:
    • data.timezone
    • data.opening_hours[]

GET /public/employees

  • Query:
    • tenant (or X-Tenant)
  • Headers:
    • X-Api-Key optional/required per tenant policy
  • Response:
    • employee list with massage_types.

GET /public/employees/available

  • Query:
    • tenant (or X-Tenant)
    • date (required)
    • duration_minutes (required)
    • is_duo, massage_type_id, second_massage_type_id, time (optional)
  • Headers:
    • X-Api-Key optional/required per tenant policy
  • Response:
    • time_slots[]
    • employee_assignments
    • available_employee_ids[]
    • is_duo

GET /public/employees/{employee}/availability

  • Path:
    • employee (employee id in current tenant)
  • Query:
    • tenant (or X-Tenant)
    • date (required)
    • duration_minutes (required)
    • massage_type_id (optional)
  • Headers:
    • X-Api-Key optional/required per tenant policy
  • Response:
    • employee_id
    • time_slots[]
    • employee_assignments

GET /public/pricing/estimate

  • Query:
    • tenant (or X-Tenant)
    • duration_id (required)
    • coupon_code, massage_type_id, date, is_duo, second_massage_type_id (optional)
    • If is_duo is omitted and second_massage_type_id is provided, the API infers duo mode automatically.
  • Headers:
    • X-Api-Key optional/required per tenant policy
  • Response:
    • currency { code, symbol }
    • price { base, discount, total, details }
    • deposit { type, value, is_full, amount, can_pay_full }