name: nisebookings description: Integrate nisebookings, a hosted booking service (appointments, group sessions, recurring series, packages, waiting lists, event tickets), into a website or app. Use when the user asks to add booking, appointments, reservations, a booking widget, an availability calendar, ticket sales or scheduling to their product. Covers embedding the widget, driving the public REST API from a custom UI, payment flows, and verification.
Integrate nisebookings
Booking is a backend concern. This service owns availability, capacity, pricing, payments and e-mail; the project you are working in owns the UI (or embeds the widget and owns nothing).
Everything is reached at https://app.nisebookings.com with the account's public key. There is no auth on the public side.
0. Check fit first
Refuse or redirect when the user needs: per-night rental pricing (whole-day bookings themselves are
supported — durationDays 1–365, one rate per day, the span is fixed once booked), two-way staff
calendar sync, SMS, conditional intake questions (plain per-service questions, including file
attachments, are supported), a customer-facing UI in a language other than Dutch or English (the widget
ships both; mails are Dutch), a customer account with a booking history (cancelling and rescheduling
work through magic mail links, not a login), or personal (Calendly-style)
scheduling. Those are not supported.
If in doubt, read https://guide.nisebookings.com/09-when-to-use.html.
Two APIs, two jobs. The public API (below, no auth) reads the catalogue and creates bookings.
The management API (/api/v1, scoped key) configures the account: settings, opening hours,
categories, services, staff, per-staff prices, the working week and holidays, plus coupons, extras,
notification templates, events, customers, appointments and payments.
For a management-API key, ask the human to create one under Instellingen → API with the scopes
you need (catalog:write + settings:write covers a full setup); it is shown once. Call it from a
server, never from a browser (no CORS). Conventions: JSON camelCase, PATCH merges, PUT replaces
(so PUT /staff/{id}/schedule replaces the whole week), lists paginate with limit/offset and
return {data,total}, errors are {"error":{"code","message","fields?"}}.
M=https://app.nisebookings.com/api/v1
H=(-H "Authorization: Bearer nk_…" -H 'Content-Type: application/json')
curl -s "${H[@]}" $M/me # account + the scopes this key has
curl -s "${H[@]}" -X POST $M/services -d '{"title":"Knippen","duration":30,"price":25}'
curl -s "${H[@]}" -X PUT $M/staff/$STAFF/schedule -d '{"days":[{"weekday":1,"start":"09:00","end":"17:00"}]}'
Full walkthrough: https://guide.nisebookings.com/api/
To operate the account through tool calls instead of curl, connect the hosted MCP server with the same
key (claude mcp add --transport http nise-booking https://app.nisebookings.com/api/mcp --header "Authorization: Bearer $NISE_API_KEY"); tools mirror /api/v1, including find_slots and a generic
api_request. Details: https://guide.nisebookings.com/api/mcp.html
1. Choose the integration path
| Situation | Path |
|---|---|
| Standard web app or landing page | Widget: <script src="https://app.nisebookings.com/widget.js" defer></script> + <nise-booking data-key="…"> (optional `data-lang="nl |
| WordPress | The nisebookings plugin (listed as nise booking); shortcodes [nise_booking], [nise_events] |
| Custom UI / mobile / server-side / cart / series / tickets | Public REST API (section 3) |
| Configure the account instead of the human doing it in the admin | Management API (section 0 above) |
The widget needs no build step and no dependencies, renders in a shadow DOM, and handles the whole
flow including the Mollie redirect and return polling. Theme it with --nb-* CSS custom properties
on the element. It has no JS API — no methods, no events. If you need programmatic control,
use the API.
2. Get the key
Ask the human for the account's public key (admin → Instellingen → Insluiten). The key is not a
secret; it belongs in the front-end config. A management key (nk_…) and the Mollie key are secrets:
never put them in browser code or commit them.
3. REST API — the flow that always works
Base: https://app.nisebookings.com/api/public/<public key>
# 1. catalogue: account config (timeZone, currency, gateways, depositMode) + services
curl -s "$API/services"
# 2. free slots — send back the exact `start` you get here
curl -s "$API/slots?service=$SVC&staff=any&from=$(date +%F)&days=14&persons=1"
# 3. price (authoritative; recomputed again on booking)
curl -s -X POST "$API/quote" -H 'Content-Type: application/json' \
-d '{"items":[{"service":"'$SVC'","staff":"'$STAFF'","start":"'$START'","persons":1}],"gateway":"local"}'
# 4. book — 409 slot_unavailable means the slot went to someone else; re-fetch slots and retry.
# Idempotency-Key: a retry after a timeout with the same key + body returns the same order.
curl -s -X POST "$API/bookings" -H 'Content-Type: application/json' -H "Idempotency-Key: $(uuidgen)" \
-d '{"items":[{"service":"'$SVC'","staff":"'$STAFF'","start":"'$START'","persons":1}],
"name":"Ada Jansen","email":"ada@example.com","gateway":"local"}'
Full request/response shapes: https://guide.nisebookings.com/openapi.yaml; recipes (Mollie checkout, recurring series, waiting list, tickets, cart/chain): https://guide.nisebookings.com/04-public-api.html. A runnable end-to-end script: the headless example at https://guide.nisebookings.com/examples/.
Feature map for the item object: extras (id → quantity, clamped server-side), series: "<key>"
(+ a series map with the rule) for recurrence, waitlist: true for a full group slot,
package: "<token>" to schedule a bought package visit, kind: "package" to buy a package.
Multi-item orders = cart/chain; hold: [] on slot searches marks the customer's own picks as taken.
4. Non-negotiables
- Never compute availability, price, deposit, discount, capacity or duration client-side. The server recomputes everything on every quote and booking; local numbers are ignored.
- Never construct
startvalues. Use exactly whatGET/POST /slotsreturned, with itsstaffId. Instants are UTC ISO;from/untilareYYYY-MM-DDin the account's time zone; display intenant.timeZone. - One checkout = one order = one payment, including carts, chains, series and compound services.
- Handle the three real failures:
409 slot_unavailable(re-fetch and let the user pick),422 validationwithfields(show per-field errors),502 payment_unavailable(order rejected; offer pay-on-site or retry). - Retry safely: send an
Idempotency-Key: <uuid>header onPOST /bookingsandPOST /events/<id>/tickets(one key per checkout attempt, generated before the first try). On a timeout or 5xx, resend the same body with the same key: you get the original response (Idempotent-Replayed: true), never a second order.409 idempotency_in_progress→ wait 1 s and resend;422 idempotency_mismatch→ you changed the body, use a new key. Without the header a retry books twice. - Cross-origin: the page's origin must be in the account's allowed-sites list (empty = any).
returnUrlmust be http(s) and its origin allowed or equal to the request Origin. - Mollie return: redirect to
payment.checkoutUrl; on return pollGET /payments/<token>(the server appendsnb_pay=<token>to yourreturnUrl) every 2 s until the status is notpending. The widget does this for you; with a custom UI you must do it.
5. Verify, then report
Verify against a sandbox, not the user's real account. One unauthenticated call creates a 24-hour test account with sample staff and services:
curl -s -X POST https://app.nisebookings.com/api/sandbox
# → {"publicKey","apiKey" (nk_, all scopes, shown once),"expiresAt","endpoints":{…}}
It never delivers mail — GET /api/v1/outbox?to=<email> (or MCP list_outbox) shows what would
have been sent, with status suppressed — and its Mollie checkoutUrl is a test page with
Betalen / Mislukt buttons that redirects to your returnUrl?nb_pay=<token>. Limits: 5 per
hour per IP, 24 h lifetime; DELETE /api/v1/sandbox with its key removes it. Details: https://guide.nisebookings.com/08-sandbox.html
- Catalogue and one slot list fetched from the project's real runtime.
- One real booking created end to end (a
gateway: "local"order needs no payment keys). - Confirm nothing else broke, and tell the user how to see the booking in the admin.
- If the booking was only a test, cancel it (admin → Afspraken) or ask the user to.
Do not report an integration as working on the basis of code alone — the verification in steps 1–2 is what makes the claim true.