---
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?"}}`.

```bash
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|en"` on either element; the account's language is the default and the widget ships Dutch and English); the scripts log `nisebookings@<version>` on load and `?v=<version>` is a supported cache-bust |
| 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>`

```bash
# 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 `start` values.** Use exactly what `GET/POST /slots` returned, with its
  `staffId`. Instants are UTC ISO; `from`/`until` are `YYYY-MM-DD` in the account's time zone; display in
  `tenant.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 validation` with `fields` (show per-field errors), `502 payment_unavailable` (order rejected;
  offer pay-on-site or retry).
- **Retry safely:** send an `Idempotency-Key: <uuid>` header on `POST /bookings` and `POST /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).
  `returnUrl` must be http(s) and its origin allowed or equal to the request Origin.
- **Mollie return:** redirect to `payment.checkoutUrl`; on return poll
  `GET /payments/<token>` (the server appends `nb_pay=<token>` to your `returnUrl`) every 2 s until
  the status is not `pending`. 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:

```bash
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

1. Catalogue and one slot list fetched from the project's real runtime.
2. One real booking created end to end (a `gateway: "local"` order needs no payment keys).
3. Confirm nothing else broke, and tell the user how to see the booking in the admin.
4. 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.
