{
  "openapi": "3.1.0",
  "info": {
    "title": "Booking management API",
    "version": "2026-10-13",
    "description": "Server-to-server API for everything the admin does: catalogue, schedules, days off, business hours,\nsettings, coupons, extras, compound parts, notifications, events, packages, customers, appointments,\nbookings and payments. Create a key in the admin under Settings, API. Send it as `Authorization: Bearer nk_...`.\nScopes: `catalog:read|write` (catalogue, schedules, holidays, coupons, extras, parts, events),\n`settings:read|write` (settings, business hours, notifications), `customers:read|write`,\n`bookings:read|write` (appointments, bookings, packages, attendees, waiting list), `payments:read|write`\n(payments, complete/refund, Mollie key); write implies read.\nAppointment writes check the admin's warnings (overlap, duration, schedule, holiday): 422 with\n`error.code: warnings` and `error.fields` = the warnings; send `force: true` to save anyway.\n`notify: true` mails the customer exactly as the admin's \"inform the customer\" checkbox does. Every write is audited. JSON field names are camelCase. Timestamps in responses are RFC 3339 UTC with `Z`; requests accept any offset.\nValidation messages are the admin's Dutch texts; use `error.fields` keys to map them to input.\nPATCH merges the body over the current values; unknown fields are ignored.\nEvery POST accepts an `Idempotency-Key` header: a retry with the same key and body returns the stored\nresponse (header `Idempotent-Replayed: true`) instead of running again. Keys are per account, kept 24 hours.\nWebhooks (`/webhooks`) push signed booking, ticket and payment events to your HTTPS endpoint\n(Standard Webhooks headers); see https://guide.nisebookings.com/10-webhooks.html.\nVersioning: send `X-Api-Version: 2026-10-13` to pin the contract; omit it for the latest, which every\nresponse echoes — except the unknown-version `400` itself (rejected before a version is resolved).\nRate limit: 120 requests per minute per API key. Counted responses carry `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` and `X-RateLimit-Reset` (epoch seconds); a 429 adds `Retry-After` (seconds),\nwhile a `401`/`403` (no key identity yet), the unknown-version `400` and a public `OPTIONS` preflight\ncarry no rate-limit state. A breaking change ships as a new dated version and the previous one stays\nsupported for at least six months (`Deprecation` and `Sunset` headers announce the end). This document\nis served on the app origin as `GET /api/v1/openapi.json`; the public API spec as `GET /openapi.json`.\n"
  },
  "servers": [
    {
      "url": "https://app.nisebookings.com/api/v1"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "tags": [
    {
      "name": "Tenant"
    },
    {
      "name": "Categories"
    },
    {
      "name": "Services"
    },
    {
      "name": "Staff"
    },
    {
      "name": "Schedules"
    },
    {
      "name": "Settings"
    },
    {
      "name": "Coupons"
    },
    {
      "name": "Extras"
    },
    {
      "name": "Notifications"
    },
    {
      "name": "Events"
    },
    {
      "name": "Packages"
    },
    {
      "name": "Customers"
    },
    {
      "name": "Appointments"
    },
    {
      "name": "Payments"
    },
    {
      "name": "Outbox"
    },
    {
      "name": "Sandbox"
    },
    {
      "name": "Webhooks"
    }
  ],
  "paths": {
    "/me": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Tenant"
        ],
        "summary": "Account and scopes of the calling key (any valid key, no scope needed)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tenant": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "timeZone": {
                          "type": "string"
                        },
                        "currency": {
                          "type": "string"
                        }
                      }
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Scope"
                      }
                    }
                  }
                },
                "example": {
                  "tenant": {
                    "id": "a0000000-0000-0000-0000-000000000001",
                    "name": "Kapsalon Demo",
                    "timeZone": "Europe/Amsterdam",
                    "currency": "EUR"
                  },
                  "scopes": [
                    "catalog:write"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/categories": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Categories"
        ],
        "summary": "List categories (scope catalog:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoryList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Categories"
        ],
        "summary": "Create a category (scope catalog:write)",
        "requestBody": {
          "$ref": "#/components/requestBodies/Category"
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/categories/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Categories"
        ],
        "summary": "Get a category",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Categories"
        ],
        "summary": "Update a category",
        "requestBody": {
          "$ref": "#/components/requestBodies/Category"
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Category"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Categories"
        ],
        "summary": "Delete a category (its services keep existing without a category)",
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/services": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Services"
        ],
        "summary": "List services",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Services"
        ],
        "summary": "Create a service",
        "description": "Missing fields get the admin defaults (type simple, duration 60, deposit 100%, taxRate 21, ...). The type cannot change later. Parts of compound/collaborative services are not in this API yet.",
        "requestBody": {
          "$ref": "#/components/requestBodies/Service"
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/services/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Services"
        ],
        "summary": "Get a service",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Services"
        ],
        "summary": "Update a service",
        "requestBody": {
          "$ref": "#/components/requestBodies/Service"
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Services"
        ],
        "summary": "Delete a service",
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/staff": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Staff"
        ],
        "summary": "List staff (including archived, see visibility)",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffList"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Staff"
        ],
        "summary": "Create a staff member",
        "description": "The weekly schedule starts as a copy of the account's business hours.",
        "requestBody": {
          "$ref": "#/components/requestBodies/Staff"
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Staff"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/staff/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Staff"
        ],
        "summary": "Get a staff member",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Staff"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Staff"
        ],
        "summary": "Update a staff member",
        "requestBody": {
          "$ref": "#/components/requestBodies/Staff"
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Staff"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Staff"
        ],
        "summary": "Archive a staff member (visibility = archive; never deleted)",
        "responses": {
          "204": {
            "description": "Archived"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/staff/{id}/services": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Staff"
        ],
        "summary": "Services this staff member offers",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffServiceList"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/staff/{id}/services/{serviceId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "name": "serviceId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "put": {
        "tags": [
          "Staff"
        ],
        "summary": "Offer a service (create or update the link)",
        "description": "Missing fields keep the current link values, or for a new link the service's price and capacity, deposit null (= the service deposit).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StaffServiceInput"
              },
              "example": {
                "price": 20,
                "capacityMin": 1,
                "capacityMax": 2,
                "deposit": "10"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StaffService"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Staff"
        ],
        "summary": "Stop offering a service",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/staff/{id}/schedule": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Schedules"
        ],
        "summary": "Weekly working hours of a staff member (scope catalog:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Week"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "put": {
        "tags": [
          "Schedules"
        ],
        "summary": "Replace the whole week (scope catalog:write)",
        "description": "A missing weekday is a day off. end > start, breaks inside the day and not overlapping.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Week"
              },
              "example": {
                "days": [
                  {
                    "weekday": 1,
                    "start": "09:00",
                    "end": "17:00",
                    "breaks": [
                      {
                        "start": "12:00",
                        "end": "12:30"
                      }
                    ]
                  },
                  {
                    "weekday": 2,
                    "start": "09:00",
                    "end": "17:00",
                    "breaks": []
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Week"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/holidays": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Schedules"
        ],
        "summary": "List days off (scope catalog:read)",
        "parameters": [
          {
            "name": "staffId",
            "in": "query",
            "description": "A staff id, or `company` for company-wide days; absent = all",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HolidayList"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Schedules"
        ],
        "summary": "Add a day off (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HolidayInput"
              },
              "example": {
                "date": "2026-12-25",
                "kind": "yearly",
                "staffId": null
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Holiday"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/holidays/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "delete": {
        "tags": [
          "Schedules"
        ],
        "summary": "Remove a day off (scope catalog:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/business-hours": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "Business hours (scope settings:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hours"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "put": {
        "tags": [
          "Settings"
        ],
        "summary": "Replace business hours (scope settings:write)",
        "description": "One entry per weekday at most; a missing weekday = closed. No breaks.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Hours"
              },
              "example": {
                "days": [
                  {
                    "weekday": 1,
                    "start": "09:00",
                    "end": "18:00"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hours"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/settings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "All admin-editable account settings (scope settings:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Settings"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Settings"
        ],
        "summary": "Partial update (scope settings:write)",
        "description": "Only fields present are validated and written; returns all settings. `id`, `publicKey`, `currency`\nare read-only. The Mollie API key is set with `PUT /settings/mollie-key`; `payMollieEnabled: true` fails\nwith 422 until it is set.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SettingsInput"
              },
              "example": {
                "timeZone": "Europe/Amsterdam",
                "minPriorMinutes": 60,
                "maxDays": 90,
                "allowedOrigins": [
                  "https://www.example.com"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Settings"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/coupons": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Coupons"
        ],
        "summary": "List coupons (scope catalog:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Coupon"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Coupons"
        ],
        "summary": "Create (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CouponInput"
              },
              "example": {
                "code": "WELKOM10",
                "discount": 10,
                "usageLimit": 100,
                "dateEnd": "2026-12-31"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Coupon"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/coupons/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Coupons"
        ],
        "summary": "One (scope catalog:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Coupon"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Coupons"
        ],
        "summary": "Partial update (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CouponInput"
              },
              "example": {
                "code": "WELKOM10",
                "discount": 10,
                "usageLimit": 100,
                "dateEnd": "2026-12-31"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Coupon"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Coupons"
        ],
        "summary": "Delete (scope catalog:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/coupons/generate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Coupons"
        ],
        "summary": "Generate up to 100 unique codes from a mask (scope catalog:write)",
        "description": "Every `*` becomes one of 0-9A-Z; the other fields are the template for each coupon. 422 on `mask` when the mask is used up.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CouponGenerate"
              },
              "example": {
                "mask": "ZOMER-****",
                "count": 20,
                "discount": 15
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Coupon"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/services/{id}/extras": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Extras"
        ],
        "summary": "List extras of a service (scope catalog:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Extra"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Extras"
        ],
        "summary": "Add an extra (scope catalog:write; simple services only, else 409)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtraInput"
              },
              "example": {
                "title": "Wassen",
                "price": 5,
                "duration": 10,
                "minQty": 0,
                "maxQty": 1
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Extra"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/services/{id}/extras/{extraId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "name": "extraId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Extras"
        ],
        "summary": "One extra (scope catalog:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Extra"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Extras"
        ],
        "summary": "Partial update (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtraInput"
              },
              "example": {
                "price": 7.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Extra"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Extras"
        ],
        "summary": "Delete (scope catalog:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/services/{id}/fields": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Services"
        ],
        "summary": "Intake questions of a service, in order (scope catalog:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fields"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "put": {
        "tags": [
          "Services"
        ],
        "summary": "Replace all intake questions (scope catalog:write)",
        "description": "Replaces the whole set, like PUT /events/{id}/staff. Send an existing `id` back to keep it: answers already stored on bookings are keyed by field id. Answers arrive with a booking through the public API (item `fields`).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FieldsInput"
              },
              "example": {
                "fields": [
                  {
                    "label": "Allergieën",
                    "kind": "text",
                    "required": true,
                    "perPerson": true
                  },
                  {
                    "label": "Kies een kleur",
                    "kind": "select",
                    "options": [
                      "rood",
                      "blauw"
                    ],
                    "position": 1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fields"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/services/{id}/parts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Services"
        ],
        "summary": "Parts of a compound or collaborative service (scope catalog:read; 409 for other types)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Parts"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "put": {
        "tags": [
          "Services"
        ],
        "summary": "Replace all parts (scope catalog:write)",
        "description": "Parts are simple services of this account; `spare_time` only in a compound service. The service duration is recalculated (compound = sum, collaborative = longest), like the admin.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartsInput"
              },
              "example": {
                "parts": [
                  {
                    "kind": "service",
                    "serviceId": "82eff48b-ead1-4c2d-8038-058490d41470"
                  },
                  {
                    "kind": "spare_time",
                    "minutes": 15
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Parts"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/notifications": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Notifications"
        ],
        "summary": "List notification templates (scope settings:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Notification"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Notifications"
        ],
        "summary": "Create (scope settings:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationInput"
              },
              "example": {
                "type": "reminder",
                "name": "Herinnering",
                "subject": "Morgen: {service_name}",
                "body": "Tot morgen om {appointment_time}.",
                "toCustomer": true,
                "attachIcs": true,
                "reminderMode": "at_hour",
                "reminderAtHour": 18,
                "reminderDaysBefore": 1
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/notifications/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Notifications"
        ],
        "summary": "One (scope settings:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Notifications"
        ],
        "summary": "Partial update (scope settings:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationInput"
              },
              "example": {
                "type": "reminder",
                "name": "Herinnering",
                "subject": "Morgen: {service_name}",
                "body": "Tot morgen om {appointment_time}.",
                "toCustomer": true,
                "attachIcs": true,
                "reminderMode": "at_hour",
                "reminderAtHour": 18,
                "reminderDaysBefore": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Notifications"
        ],
        "summary": "Delete (scope settings:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/notifications/codes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Notifications"
        ],
        "summary": "Placeholder codes usable in subject/body for a type (scope settings:read)",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/NotificationType"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "example": "{client_name}"
                          },
                          "label": {
                            "type": "string"
                          },
                          "group": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/events": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "List events (scope catalog:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Events"
        ],
        "summary": "Create (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventInput"
              },
              "example": {
                "title": "Workshop",
                "startAt": "2026-11-20T19:00:00+01:00",
                "endAt": "2026-11-20T21:00:00+01:00",
                "status": "published",
                "ticketMask": "WS-****"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/events/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "One (scope catalog:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Events"
        ],
        "summary": "Partial update (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventInput"
              },
              "example": {
                "title": "Workshop",
                "startAt": "2026-11-20T19:00:00+01:00",
                "endAt": "2026-11-20T21:00:00+01:00",
                "status": "published",
                "ticketMask": "WS-****"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Events"
        ],
        "summary": "Delete (scope catalog:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/events/{id}/ticket-types": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Ticket types (scope catalog:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TicketType"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Events"
        ],
        "summary": "Add a ticket type (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TicketTypeInput"
              },
              "example": {
                "title": "Standaard",
                "price": 25,
                "taxRate": 21,
                "quantity": 30
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketType"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/events/{id}/ticket-types/{ticketTypeId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "name": "ticketTypeId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "One ticket type (scope catalog:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketType"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Events"
        ],
        "summary": "Partial update (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TicketTypeInput"
              },
              "example": {
                "quantity": 40
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TicketType"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Events"
        ],
        "summary": "Delete (scope catalog:write; 409 when tickets were sold)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/events/{id}/staff": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Event staff (scope catalog:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventStaff"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "put": {
        "tags": [
          "Events"
        ],
        "summary": "Replace event staff (scope catalog:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventStaff"
              },
              "example": {
                "staff": [
                  {
                    "staffId": "6a4ca508-ae33-4946-899c-388f1acb9d04",
                    "role": "organizer"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventStaff"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/events/{id}/attendees": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Attendees (scope bookings:read)",
        "parameters": [
          {
            "name": "ticketTypeId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Attendee"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/attendees/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Events"
        ],
        "summary": "Cancel a ticket; it goes back on sale (scope bookings:write)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Attendee"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/packages": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Packages"
        ],
        "summary": "List packages (scope bookings:read)",
        "parameters": [
          {
            "name": "customerId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Package"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Packages"
        ],
        "summary": "Sell a package to a customer (scope bookings:write)",
        "description": "Like the admin. `notify` defaults to true here and sends the package_new mail. `payment: local` creates an open local payment.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PackageInput"
              },
              "example": {
                "customer": {
                  "name": "Jan Jansen",
                  "email": "jan@example.com"
                },
                "serviceId": "9b3e2d4c-0d5e-4b8a-9c3e-2a1b0c9d8e7f",
                "payment": "local"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Package"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/packages/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Packages"
        ],
        "summary": "One package with used visits and the personal booking link (scope bookings:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PackageDetail"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/customers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers (`?q=` searches name, e-mail, phone) (scope customers:read)",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Customer"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Customers"
        ],
        "summary": "Create (scope customers:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerInput"
              },
              "example": {
                "fullName": "Jan Jansen",
                "email": "jan@example.com",
                "phone": "0612345678"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/customers/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "One (scope customers:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Customers"
        ],
        "summary": "Partial update (scope customers:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerInput"
              },
              "example": {
                "fullName": "Jan Jansen",
                "email": "jan@example.com",
                "phone": "0612345678"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Customers"
        ],
        "summary": "Delete (scope customers:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/appointments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "List appointments (scope bookings:read)",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "ISO date-time; appointments starting at or after",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "ISO date-time; starting before",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "staffId",
            "in": "query",
            "description": "",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "serviceId",
            "in": "query",
            "description": "",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only appointments with a booking in this status",
            "schema": {
              "$ref": "#/components/schemas/BookingStatus"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Appointment"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Appointments"
        ],
        "summary": "Create an appointment with one booking, like the admin (scope bookings:write)",
        "description": "Without `end` the service duration is used; without `status` the account default status. Warnings → 422 `warnings` unless `force: true`. `notify: true` mails the customer. With `recurrence` the call creates the whole series in one go: one appointment per occurrence (max 52) sharing one `seriesId`, every occurrence checked like a single creation.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppointmentInput"
              },
              "example": {
                "serviceId": "82eff48b-ead1-4c2d-8038-058490d41470",
                "staffId": "6a4ca508-ae33-4946-899c-388f1acb9d04",
                "start": "2026-10-14T09:00:00+02:00",
                "persons": 1,
                "status": "approved",
                "customerId": "0d2b6f0e-5c4a-4e83-9e0a-3b8c1f2d4e5a",
                "notify": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created; bookingId = the new booking. With `recurrence`: the first appointment plus seriesId and appointmentIds (all N, ascending). One audit entry per request, keyed to the first appointment.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Appointment"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "bookingId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "seriesId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "appointmentIds": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "format": "uuid"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Warnings"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/appointments/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "One appointment with bookings, customers, extras, series/group/order ids, meetingUrl (scope bookings:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Appointment"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Appointments"
        ],
        "summary": "Reschedule / change staff, service or note (scope bookings:write)",
        "description": "Send only what changes. `start` without `end` keeps the service duration. `scope: following|series` moves the later/all appointments of a recurring series by the same shift (response `seriesMoved`). Warnings → 422 `warnings` unless `force: true`.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppointmentUpdate"
              },
              "example": {
                "start": "2026-10-14T10:00:00+02:00",
                "force": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Appointment"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "seriesMoved": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Warnings"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Appointments"
        ],
        "summary": "Delete (scope bookings:write)",
        "description": "With `notify=true` approved bookings are first set to cancelled and pending ones to rejected, and the customer is mailed; then the appointment is deleted.",
        "parameters": [
          {
            "$ref": "#/components/parameters/scope"
          },
          {
            "name": "notify",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/appointments/{id}/bookings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Appointments"
        ],
        "summary": "Add a customer to an appointment (scope bookings:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingAdd"
              },
              "example": {
                "customer": {
                  "name": "Piet",
                  "email": "piet@example.com"
                },
                "persons": 2,
                "notify": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created; the appointment, bookingId = the new booking",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Appointment"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "bookingId": {
                          "type": "string",
                          "format": "uuid"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bookings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "List bookings (scope bookings:read)",
        "description": "One row per booking, with the appointment it is on. `from`/`to` (ISO date-times) and staffId/serviceId narrow on that appointment. Event tickets are not bookings; they are attendees under `/events/{id}/attendees`.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/BookingStatus"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "ISO date-time; appointments starting at or after",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "ISO date-time; starting before",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "staffId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "serviceId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK, ordered by appointment start",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookingItem"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bookings/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Appointments"
        ],
        "summary": "One booking with its appointment, the same shape as the list (scope bookings:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingItem"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Appointments"
        ],
        "summary": "Change status and/or persons (scope bookings:write)",
        "description": "The status also applies to the other parts of the same group (compound/collaborative). `scope: following|series` applies it to the series. A status change mails the customer when `notify: true`. Bookings that no longer fit (capacity, package) are counted in `failed`.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingUpdate"
              },
              "example": {
                "status": "cancelled",
                "notify": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingUpdateResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Appointments"
        ],
        "summary": "Remove a booking without mail (scope bookings:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/bookings/{id}/give-seat": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Appointments"
        ],
        "summary": "Waiting list → the default status when it fits (scope bookings:write)",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "notify": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "capacity_exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/payments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "List payments, newest first (scope payments:read)",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "completed",
                "rejected",
                "refunded"
              ]
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "local",
                "free",
                "mollie"
              ]
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/payments/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "One payment with breakdown (details) and what it paid for (scope payments:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentDetail"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/payments/{id}/complete": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Payments"
        ],
        "summary": "Mark a pending local payment as paid (scope payments:write)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentDetail"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/payments/{id}/refund": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Payments"
        ],
        "summary": "Refund a completed Mollie payment through Mollie (scope payments:write)",
        "description": "On a sandbox account the refund succeeds locally without contacting Mollie.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentDetail"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/settings/mollie-key": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "put": {
        "tags": [
          "Payments"
        ],
        "summary": "Set the Mollie API key (scope payments:write; write-only)",
        "description": "Stored encrypted. It is never returned by any endpoint; the response only has a hint (the last characters) to recognise it.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "key"
                ],
                "properties": {
                  "key": {
                    "type": "string",
                    "pattern": "^(test|live)_[A-Za-z0-9]{20,}$"
                  }
                }
              },
              "example": {
                "key": "test_abcdefghijklmnopqrstuvwxyz12"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hint": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "hint": "…yz12"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Payments"
        ],
        "summary": "Remove the Mollie key (scope payments:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/outbox": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Outbox"
        ],
        "summary": "Mails sent, queued or suppressed for this account, newest first (scope settings:read)",
        "description": "Use it to verify the mails a booking or status change produced. A sandbox account never delivers\nmail; its rows end with status `suppressed`. Rows can appear with a short delay, so poll\nfor a minute rather than asserting once. The calendar attachment is not included.\n",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "sending",
                "sent",
                "failed",
                "suppressed"
              ]
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Recipient e-mail address",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OutboxMail"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/sandbox": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "delete": {
        "tags": [
          "Sandbox"
        ],
        "summary": "Delete this sandbox account now (any scope; sandbox accounts only)",
        "description": "Deletes the calling key's sandbox account and all its data; the key stops working. Sandboxes are\ncreated without a key by `POST /api/sandbox` (outside `/api/v1`) and expire after 24 hours anyway.\nA real account gets `404`.\n",
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks/events": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Event types you can subscribe to, with descriptions (scope settings:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "$ref": "#/components/schemas/WebhookEventType"
                          },
                          "description": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook endpoints; never includes the secret (scope settings:read)",
        "parameters": [
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "summary": "Register an endpoint; the response carries the signing secret once (scope settings:write)",
        "description": "`url` must be `https://` and resolve to a public address (checked again before every send); redirects are not\nfollowed. Each event is POSTed as JSON with the Standard Webhooks headers `webhook-id` (= event id),\n`webhook-timestamp` (unix seconds) and `webhook-signature` (`v1,` + base64 HMAC-SHA256 over\n`id.timestamp.body`, keyed with the base64-decoded part of the secret after `whsec_`). 2xx within 10 s =\ndelivered; otherwise retried after 1 m, 5 m, 30 m, 2 h, 12 h, 24 h, then `failed`.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookInput"
              },
              "example": {
                "url": "https://shop.example.com/hooks/nise",
                "events": [
                  "booking.created",
                  "booking.cancelled"
                ],
                "description": "shop sync"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "One endpoint (scope settings:read)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Change url, events, description or active (scope settings:write)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookInput"
              },
              "example": {
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Remove the endpoint and its delivery log (scope settings:write)",
        "responses": {
          "204": {
            "description": "Removed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks/{id}/rotate-secret": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "summary": "Replace the signing secret; the new one is returned once and used for every send from now on (scope settings:write)",
        "responses": {
          "200": {
            "description": "The endpoint with the new `secret`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks/{id}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "summary": "Queue a `ping` event for this endpoint and send it right away (scope settings:write)",
        "responses": {
          "202": {
            "description": "Queued; check the deliveries log for the result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks/{id}/deliveries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delivery log of this endpoint, newest first (scope settings:read)",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "sending",
                "delivered",
                "failed"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/webhooks/{id}/deliveries/{deliveryId}/retry": {
      "parameters": [
        {
          "$ref": "#/components/parameters/id"
        },
        {
          "name": "deliveryId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "$ref": "#/components/parameters/ApiVersion"
        }
      ],
      "post": {
        "parameters": [
          {
            "$ref": "#/components/parameters/idempotencyKey"
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "summary": "Send this delivery again now (same event id and payload, fresh signature) (scope settings:write)",
        "responses": {
          "202": {
            "description": "Queued with a fresh retry schedule",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key, nk_ + 43 characters"
      }
    },
    "parameters": {
      "id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      },
      "offset": {
        "name": "offset",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      },
      "idempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Optional. 1–255 visible ASCII characters (use a UUID). Same key + same method, path and body within 24 hours →\nthe stored status and body are returned with header `Idempotent-Replayed: true`, nothing runs again. Same key +\ndifferent request → 422 `idempotency_mismatch`. First request still running → 409 `idempotency_in_progress`\nwith `Retry-After: 1`. Malformed → 400 `bad_request`. 5xx responses are not stored. Keys are per account.\n",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "pattern": "^[\\x21-\\x7E]+$"
        }
      },
      "scope": {
        "name": "scope",
        "in": "query",
        "description": "Recurring series: this (default), following, series",
        "schema": {
          "type": "string",
          "enum": [
            "this",
            "following",
            "series"
          ]
        }
      },
      "ApiVersion": {
        "name": "X-Api-Version",
        "in": "header",
        "required": false,
        "description": "Defaults to the latest version when omitted.",
        "schema": {
          "type": "string",
          "enum": [
            "2026-10-13"
          ]
        }
      }
    },
    "headers": {
      "Retry-After": {
        "description": "Seconds until the rate-limit window resets. Only on 429.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Limit": {
        "description": "Requests allowed per minute for this API key.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current minute.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Epoch seconds when the current window resets.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "requestBodies": {
      "Category": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CategoryInput"
            },
            "example": {
              "name": "Knippen"
            }
          }
        }
      },
      "Service": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ServiceInput"
            },
            "example": {
              "title": "Knippen",
              "categoryId": "3f0c3c1e-2b5e-4f43-9a52-0b9b8f1d2c11",
              "duration": 30,
              "price": 25,
              "deposit": "30%",
              "taxRate": 21
            }
          }
        }
      },
      "Staff": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/StaffInput"
            },
            "example": {
              "fullName": "Anna de Vries",
              "email": "anna@example.com"
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Body is not a JSON object",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing",
        "unknown or revoked key": null,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Key lacks the scope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found in this account",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Still in use, or not possible in this state (also capacity_exceeded, slot_unavailable)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Warnings": {
        "description": "Invalid fields (code validation), package problem (package_*), or admin warnings (code warnings): resend with force: true",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "warnings",
                "message": "Deze tijd valt buiten het rooster of in een pauze van de medewerker.",
                "fields": {
                  "schedule": "Deze tijd valt buiten het rooster of in een pauze van de medewerker."
                }
              }
            }
          }
        }
      },
      "Validation": {
        "description": "Invalid fields",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "validation",
                "message": "Vul een naam in.",
                "fields": {
                  "title": "Vul een naam in.",
                  "duration": "De duur moet een heel getal tussen 5 en 1440 zijn."
                }
              }
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded (`rate_limited`): 120 requests per minute per API key. Wait for `Retry-After` seconds.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Scope": {
        "type": "string",
        "enum": [
          "catalog:read",
          "catalog:write",
          "settings:read",
          "settings:write",
          "customers:read",
          "customers:write",
          "bookings:read",
          "bookings:write",
          "payments:read",
          "payments:write"
        ]
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "bad_request",
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "conflict",
                  "validation",
                  "warnings",
                  "capacity_exceeded",
                  "slot_unavailable",
                  "package_used_up",
                  "package_expired",
                  "package_invalid",
                  "idempotency_in_progress",
                  "idempotency_mismatch",
                  "rate_limited",
                  "server"
                ],
                "description": "capacity_exceeded, slot_unavailable and idempotency_in_progress are 409; warnings, package_* and idempotency_mismatch are 422; rate_limited is 429"
              },
              "message": {
                "type": "string"
              },
              "fields": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "camelCase field → message (validation); warning → message (warnings)"
              }
            }
          }
        }
      },
      "Money": {
        "oneOf": [
          {
            "type": "number"
          },
          {
            "type": "string",
            "description": "decimal comma allowed, e.g. \"12,50\""
          }
        ],
        "minimum": 0
      },
      "Deposit": {
        "type": "string",
        "pattern": "^\\d+(\\.\\d{1,2})?%?$",
        "description": "Percentage (\"30%\", ≤ 100%) or fixed amount per person (\"20\", \"12.50\")"
      },
      "CategoryInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255
          }
        },
        "required": [
          "name"
        ]
      },
      "Category": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          }
        }
      },
      "CategoryList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Category"
            }
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "ServiceInput": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "simple",
              "compound",
              "collaborative",
              "package"
            ],
            "default": "simple",
            "description": "Create only"
          },
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "categoryId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "info": {
            "type": "string",
            "maxLength": 5000
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "private"
            ]
          },
          "deposit": {
            "$ref": "#/components/schemas/Deposit"
          },
          "taxRate": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "onlineMeeting": {
            "type": "string",
            "enum": [
              "off",
              "jitsi"
            ],
            "description": "Not for packages"
          },
          "duration": {
            "type": "integer",
            "minimum": 5,
            "maximum": 1440,
            "description": "Simple only"
          },
          "durationDays": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365,
            "default": 0,
            "description": "Simple only. 0 = a normal slot service; 1-365 = a day-based service (a boat, a room) whose bookings span that many whole days from local midnight. Day services are booked through the public API; business hours and the weekly schedule do not apply, but holidays and existing appointments do."
          },
          "slotLength": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 5,
            "maximum": 1440,
            "description": "Simple only; null = account default"
          },
          "capacityMin": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999
          },
          "capacityMax": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999
          },
          "paddingBefore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1440
          },
          "paddingAfter": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1440
          },
          "staffPreference": {
            "type": "string",
            "enum": [
              "order",
              "least_expensive",
              "most_expensive"
            ]
          },
          "oneBookingPerSlot": {
            "type": "boolean"
          },
          "recurrenceFrequencies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "daily",
                "weekly",
                "biweekly",
                "monthly"
              ]
            }
          },
          "waitingListCapacity": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999,
            "description": "null = unlimited, 0 = none"
          },
          "sameStaffForSubservices": {
            "type": "boolean",
            "description": "Compound only"
          },
          "collaborativeEqualDuration": {
            "type": "boolean",
            "description": "Collaborative only"
          },
          "packageServiceId": {
            "type": "string",
            "format": "uuid",
            "description": "Package only: a simple service of this account"
          },
          "packageSize": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999
          },
          "packageLifeDays": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 3650,
            "description": "null = never expires"
          }
        }
      },
      "Service": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ServiceInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "position": {
                "type": "integer"
              },
              "price": {
                "type": "number"
              }
            }
          }
        ],
        "example": {
          "id": "82eff48b-ead1-4c2d-8038-058490d41470",
          "type": "simple",
          "title": "Knippen",
          "categoryId": null,
          "info": "",
          "duration": 30,
          "durationDays": 0,
          "slotLength": null,
          "price": 25,
          "capacityMin": 1,
          "capacityMax": 1,
          "paddingBefore": 0,
          "paddingAfter": 0,
          "staffPreference": "least_expensive",
          "visibility": "public",
          "deposit": "30%",
          "taxRate": 21,
          "onlineMeeting": "off",
          "oneBookingPerSlot": false,
          "recurrenceFrequencies": [
            "daily",
            "weekly",
            "biweekly",
            "monthly"
          ],
          "waitingListCapacity": null,
          "sameStaffForSubservices": false,
          "collaborativeEqualDuration": false,
          "packageServiceId": null,
          "packageSize": null,
          "packageLifeDays": null,
          "position": 0
        }
      },
      "ServiceList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Service"
            }
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "StaffInput": {
        "type": "object",
        "required": [
          "fullName"
        ],
        "properties": {
          "fullName": {
            "type": "string",
            "maxLength": 128
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "maxLength": 128
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 32
          },
          "info": {
            "type": "string",
            "maxLength": 5000
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "private",
              "archive"
            ],
            "default": "public"
          }
        }
      },
      "Staff": {
        "allOf": [
          {
            "$ref": "#/components/schemas/StaffInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "position": {
                "type": "integer"
              }
            }
          }
        ],
        "example": {
          "id": "6a4ca508-ae33-4946-899c-388f1acb9d04",
          "fullName": "Anna de Vries",
          "email": "anna@example.com",
          "phone": null,
          "info": "",
          "visibility": "public",
          "position": 0
        }
      },
      "StaffList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Staff"
            }
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "StaffServiceInput": {
        "type": "object",
        "properties": {
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "capacityMin": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999
          },
          "capacityMax": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999
          },
          "deposit": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Deposit"
              },
              {
                "type": "null"
              }
            ],
            "description": "null = the service deposit"
          }
        }
      },
      "StaffService": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "serviceId": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "$ref": "#/components/schemas/StaffServiceInput"
          }
        ],
        "example": {
          "serviceId": "82eff48b-ead1-4c2d-8038-058490d41470",
          "price": 20,
          "capacityMin": 1,
          "capacityMax": 2,
          "deposit": null
        }
      },
      "StaffServiceList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StaffService"
            }
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "Time": {
        "type": "string",
        "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
        "example": "09:00"
      },
      "Week": {
        "type": "object",
        "required": [
          "days"
        ],
        "properties": {
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "weekday",
                "start",
                "end"
              ],
              "properties": {
                "weekday": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 7,
                  "description": "1 = Monday"
                },
                "start": {
                  "$ref": "#/components/schemas/Time"
                },
                "end": {
                  "$ref": "#/components/schemas/Time"
                },
                "breaks": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "required": [
                      "start",
                      "end"
                    ],
                    "properties": {
                      "start": {
                        "$ref": "#/components/schemas/Time"
                      },
                      "end": {
                        "$ref": "#/components/schemas/Time"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Hours": {
        "type": "object",
        "required": [
          "days"
        ],
        "properties": {
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "weekday",
                "start",
                "end"
              ],
              "properties": {
                "weekday": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 7
                },
                "start": {
                  "$ref": "#/components/schemas/Time"
                },
                "end": {
                  "$ref": "#/components/schemas/Time"
                }
              }
            }
          }
        }
      },
      "HolidayInput": {
        "type": "object",
        "required": [
          "date",
          "kind"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "kind": {
            "type": "string",
            "enum": [
              "once",
              "yearly",
              "exception"
            ],
            "description": "yearly = same day every year; exception = a working day despite company days off"
          },
          "staffId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "null = company-wide"
          }
        }
      },
      "Holiday": {
        "allOf": [
          {
            "$ref": "#/components/schemas/HolidayInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ]
      },
      "HolidayList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Holiday"
            }
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "Status": {
        "type": "string",
        "enum": [
          "pending",
          "approved",
          "cancelled",
          "rejected",
          "done"
        ]
      },
      "SettingsInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "timeZone": {
            "type": "string",
            "description": "IANA time zone"
          },
          "slotLength": {
            "type": "integer",
            "minimum": 5,
            "maximum": 1440
          },
          "minPriorMinutes": {
            "type": "integer",
            "minimum": 0,
            "maximum": 525600
          },
          "maxDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3650
          },
          "locale": {
            "type": "string",
            "enum": [
              "nl",
              "en"
            ],
            "description": "Language of the booking widget and event page for this account"
          },
          "senderName": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "replyTo": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "notifyIntervalHours": {
            "type": "integer",
            "minimum": 1,
            "maximum": 23
          },
          "cancelMinMinutes": {
            "type": "integer",
            "minimum": 0,
            "maximum": 525600
          },
          "defaultStatus": {
            "type": "string",
            "enum": [
              "approved",
              "pending"
            ]
          },
          "autoStatusEnabled": {
            "type": "boolean"
          },
          "autoStatusFrom": {
            "$ref": "#/components/schemas/Status"
          },
          "autoStatusTo": {
            "$ref": "#/components/schemas/Status"
          },
          "calBusinessDaysOnly": {
            "type": "boolean"
          },
          "calBusinessHoursOnly": {
            "type": "boolean"
          },
          "calHideEmptyStaff": {
            "type": "boolean"
          },
          "cartEnabled": {
            "type": "boolean"
          },
          "chainEnabled": {
            "type": "boolean"
          },
          "recurringEnabled": {
            "type": "boolean"
          },
          "extrasMultiplyPersons": {
            "type": "boolean"
          },
          "recurringPayment": {
            "type": "string",
            "enum": [
              "first",
              "all"
            ]
          },
          "showOccupancy": {
            "type": "string",
            "enum": [
              "off",
              "busy",
              "free"
            ]
          },
          "waitlistEnabled": {
            "type": "boolean"
          },
          "waitlistDespiteCapacity": {
            "type": "boolean"
          },
          "combinedPriceMethod": {
            "type": "string",
            "enum": [
              "regular",
              "nested"
            ]
          },
          "collaborativeHideStaff": {
            "type": "boolean"
          },
          "jitsiServer": {
            "type": "string",
            "format": "uri",
            "description": "https only"
          },
          "pricesIncludeTax": {
            "type": "boolean"
          },
          "depositMode": {
            "type": "string",
            "enum": [
              "deposit_only",
              "choose_deposit",
              "choose_full"
            ]
          },
          "payLocalEnabled": {
            "type": "boolean"
          },
          "payMollieEnabled": {
            "type": "boolean"
          },
          "mollieIncreasePct": {
            "type": "number",
            "minimum": -100,
            "maximum": 100
          },
          "mollieAddition": {
            "type": "number",
            "minimum": -9999,
            "maximum": 9999
          },
          "paymentTimeoutMinutes": {
            "type": "integer",
            "minimum": 5,
            "maximum": 10080
          },
          "successStatus": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "approved",
              "pending",
              null
            ]
          },
          "allowedOrigins": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Widget/public API origins; empty = any"
          }
        }
      },
      "Settings": {
        "allOf": [
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "publicKey": {
                "type": "string",
                "description": "For /api/public/{publicKey}/..."
              },
              "currency": {
                "type": "string"
              }
            }
          },
          {
            "$ref": "#/components/schemas/SettingsInput"
          }
        ]
      },
      "Uuid": {
        "type": "string",
        "format": "uuid"
      },
      "Ids": {
        "type": [
          "array",
          "null"
        ],
        "items": {
          "type": "string",
          "format": "uuid"
        },
        "description": "null or [] = all"
      },
      "CouponInput": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "maxLength": 64,
            "description": "Stored upper case; no spaces; unique per account (409)"
          },
          "discount": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Percent; discount or deduction must be > 0"
          },
          "deduction": {
            "$ref": "#/components/schemas/Money"
          },
          "usageLimit": {
            "type": "integer",
            "minimum": 1,
            "default": 1
          },
          "oncePerCustomer": {
            "type": "boolean"
          },
          "dateStart": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "dateEnd": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "minAppointments": {
            "type": "integer",
            "minimum": 1,
            "default": 1
          },
          "maxAppointments": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "serviceIds": {
            "$ref": "#/components/schemas/Ids"
          },
          "staffIds": {
            "$ref": "#/components/schemas/Ids"
          }
        }
      },
      "Coupon": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CouponInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "used": {
                "type": "integer"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ],
        "example": {
          "id": "5c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
          "code": "WELKOM10",
          "discount": 10,
          "deduction": 0,
          "usageLimit": 100,
          "used": 0,
          "oncePerCustomer": false,
          "dateStart": null,
          "dateEnd": "2026-12-31",
          "minAppointments": 1,
          "maxAppointments": null,
          "serviceIds": null,
          "staffIds": null
        }
      },
      "CouponGenerate": {
        "allOf": [
          {
            "type": "object",
            "required": [
              "mask",
              "count"
            ],
            "properties": {
              "mask": {
                "type": "string",
                "maxLength": 64
              },
              "count": {
                "type": "integer",
                "minimum": 1,
                "maximum": 100
              }
            }
          },
          {
            "$ref": "#/components/schemas/CouponInput"
          }
        ]
      },
      "ExtraInput": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1440,
            "description": "Extra minutes"
          },
          "minQty": {
            "type": "integer",
            "minimum": 0,
            "maximum": 99,
            "default": 0
          },
          "maxQty": {
            "type": "integer",
            "minimum": 1,
            "maximum": 99,
            "default": 1
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "maximum": 999
          }
        }
      },
      "Extra": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ExtraInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "serviceId": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ]
      },
      "Part": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "kind",
              "serviceId"
            ],
            "properties": {
              "kind": {
                "const": "service"
              },
              "serviceId": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "kind",
              "minutes"
            ],
            "properties": {
              "kind": {
                "const": "spare_time"
              },
              "minutes": {
                "type": "integer",
                "minimum": 5,
                "maximum": 1440
              }
            }
          }
        ]
      },
      "PartsInput": {
        "type": "object",
        "required": [
          "parts"
        ],
        "properties": {
          "parts": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/Part"
            }
          }
        }
      },
      "Parts": {
        "type": "object",
        "properties": {
          "parts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Part"
            }
          },
          "duration": {
            "type": "integer"
          }
        }
      },
      "FieldKind": {
        "type": "string",
        "enum": [
          "text",
          "textarea",
          "select",
          "checkbox",
          "number",
          "date",
          "file"
        ]
      },
      "ServiceFieldInput": {
        "type": "object",
        "required": [
          "label",
          "kind"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Send back an existing id to keep it (and the answers stored on bookings); anything else is ignored"
          },
          "label": {
            "type": "string",
            "maxLength": 255
          },
          "kind": {
            "$ref": "#/components/schemas/FieldKind"
          },
          "required": {
            "type": "boolean",
            "default": false
          },
          "perPerson": {
            "type": "boolean",
            "default": false,
            "description": "One answer per person; the booking stores an array"
          },
          "options": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "maxLength": 100
            },
            "description": "select only, at least one"
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "maximum": 999,
            "description": "Display order; defaults to the array index"
          },
          "showIf": {
            "type": [
              "object",
              "null"
            ],
            "description": "Ask this question only while the referenced answer equals `is`. Must point at an earlier question of the same service that is itself unconditional, not per-person and not a file; a question whose condition fails is not asked and its answer is not stored.",
            "required": [
              "field",
              "is"
            ],
            "properties": {
              "field": {
                "type": "string",
                "format": "uuid"
              },
              "is": {
                "type": "string",
                "maxLength": 100
              }
            }
          }
        }
      },
      "ServiceField": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ServiceFieldInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ]
      },
      "FieldsInput": {
        "type": "object",
        "required": [
          "fields"
        ],
        "properties": {
          "fields": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/ServiceFieldInput"
            }
          }
        }
      },
      "Fields": {
        "type": "object",
        "properties": {
          "fields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ServiceField"
            }
          }
        }
      },
      "NotificationType": {
        "type": "string",
        "enum": [
          "new_booking",
          "status_changed",
          "reminder",
          "waitlisted",
          "waitlist_offer",
          "package_new",
          "event_tickets"
        ]
      },
      "NotificationInput": {
        "type": "object",
        "required": [
          "type",
          "name",
          "subject",
          "body"
        ],
        "properties": {
          "type": {
            "$ref": "#/components/schemas/NotificationType"
          },
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "active": {
            "type": "boolean",
            "default": true
          },
          "subject": {
            "type": "string",
            "maxLength": 255,
            "description": "May use codes from /notifications/codes"
          },
          "body": {
            "type": "string",
            "maxLength": 20000
          },
          "toCustomer": {
            "type": "boolean"
          },
          "toStaff": {
            "type": "boolean",
            "description": "Not for event_tickets"
          },
          "toAdmin": {
            "type": "boolean"
          },
          "toCustom": {
            "type": "boolean"
          },
          "customRecipients": {
            "oneOf": [
              {
                "type": "string",
                "description": "one address per line"
              },
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "email"
                }
              }
            ]
          },
          "attachIcs": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "any",
              "pending",
              "approved",
              "cancelled",
              "rejected",
              "done",
              "waitlisted"
            ],
            "description": "Booking status filter; always any for package_new and event_tickets"
          },
          "serviceIds": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "minItems": 1,
            "description": "null = all services"
          },
          "reminderMode": {
            "type": "string",
            "enum": [
              "relative",
              "at_hour"
            ],
            "description": "reminder only"
          },
          "reminderHours": {
            "type": "integer",
            "minimum": 1,
            "maximum": 720
          },
          "reminderAtHour": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23
          },
          "reminderDaysBefore": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30
          }
        },
        "description": "At least one recipient is required."
      },
      "Notification": {
        "allOf": [
          {
            "$ref": "#/components/schemas/NotificationInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ]
      },
      "EventInput": {
        "type": "object",
        "required": [
          "title",
          "startAt",
          "endAt"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "info": {
            "type": "string",
            "maxLength": 5000
          },
          "imageUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "startAt": {
            "type": "string",
            "format": "date-time"
          },
          "endAt": {
            "type": "string",
            "format": "date-time"
          },
          "salesEndAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "published",
              "hidden"
            ],
            "default": "hidden"
          },
          "ticketMask": {
            "type": "string",
            "maxLength": 32,
            "default": "****-****",
            "description": "At least 4 *"
          }
        }
      },
      "Event": {
        "allOf": [
          {
            "$ref": "#/components/schemas/EventInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "TicketTypeInput": {
        "type": "object",
        "required": [
          "title",
          "quantity"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 255
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "taxRate": {
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "quantity": {
            "type": "integer",
            "minimum": 0
          },
          "reserved": {
            "type": "integer",
            "minimum": 0
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "maximum": 999
          }
        }
      },
      "TicketType": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TicketTypeInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "eventId": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ]
      },
      "EventStaff": {
        "type": "object",
        "required": [
          "staff"
        ],
        "properties": {
          "staff": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "staffId",
                "role"
              ],
              "properties": {
                "staffId": {
                  "type": "string",
                  "format": "uuid"
                },
                "role": {
                  "type": "string",
                  "enum": [
                    "organizer",
                    "support"
                  ]
                }
              }
            }
          }
        }
      },
      "Attendee": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "eventId": {
            "type": "string",
            "format": "uuid"
          },
          "ticketTypeId": {
            "type": "string",
            "format": "uuid"
          },
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "paymentId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "orderId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "customer": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "fullName": {
                "type": "string"
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "CustomerRef": {
        "type": "object",
        "required": [
          "name"
        ],
        "description": "An existing customer with this e-mail address is reused; otherwise a new customer is created",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 128
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PackageInput": {
        "type": "object",
        "required": [
          "serviceId",
          "payment"
        ],
        "description": "customerId or customer",
        "properties": {
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "customer": {
            "$ref": "#/components/schemas/CustomerRef"
          },
          "serviceId": {
            "type": "string",
            "format": "uuid",
            "description": "A service of type package"
          },
          "staffId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "payment": {
            "type": "string",
            "enum": [
              "none",
              "local"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "notify": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "Package": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "serviceId": {
            "type": "string",
            "format": "uuid"
          },
          "staffId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "size": {
            "type": "integer"
          },
          "expiresOn": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "paymentId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "orderId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PackageDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Package"
          },
          {
            "type": "object",
            "properties": {
              "used": {
                "type": "integer",
                "description": "Visits not cancelled/rejected"
              },
              "publicUrl": {
                "type": "string",
                "format": "uri",
                "description": "The customer's personal booking link"
              },
              "visits": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "bookingId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string"
                    },
                    "appointmentId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "startAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "staffId": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "CustomerInput": {
        "type": "object",
        "required": [
          "fullName"
        ],
        "properties": {
          "fullName": {
            "type": "string",
            "maxLength": 128
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "maxLength": 128,
            "description": "Unique per account (409)"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 32
          },
          "notes": {
            "type": "string",
            "maxLength": 5000
          }
        }
      },
      "Customer": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CustomerInput"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "BookingStatus": {
        "type": "string",
        "enum": [
          "pending",
          "approved",
          "cancelled",
          "rejected",
          "done",
          "waitlisted"
        ]
      },
      "AppointmentInput": {
        "type": "object",
        "required": [
          "serviceId",
          "staffId",
          "start"
        ],
        "description": "customerId or customer",
        "properties": {
          "serviceId": {
            "type": "string",
            "format": "uuid"
          },
          "staffId": {
            "type": "string",
            "format": "uuid",
            "description": "Must offer the service (staff↔service link)"
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "For a day service (`durationDays` > 0) this must be the local midnight (00:00) of the first day"
          },
          "end": {
            "type": "string",
            "format": "date-time",
            "description": "Default start + service duration; for a day service, start + durationDays days"
          },
          "persons": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999,
            "default": 1,
            "description": "Default 1; max = capacityMax of the link, 1 with a package"
          },
          "status": {
            "$ref": "#/components/schemas/BookingStatus"
          },
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "customer": {
            "$ref": "#/components/schemas/CustomerRef"
          },
          "internalNote": {
            "type": "string",
            "maxLength": 2000
          },
          "packageId": {
            "type": "string",
            "format": "uuid",
            "description": "A visit of this package (package_* errors when not usable)"
          },
          "force": {
            "type": "boolean",
            "default": false,
            "description": "Save despite warnings"
          },
          "notify": {
            "type": "boolean",
            "default": false,
            "description": "Mail the customer"
          },
          "recurrence": {
            "$ref": "#/components/schemas/RecurrenceRule"
          }
        }
      },
      "RecurrenceRule": {
        "type": "object",
        "required": [
          "freq"
        ],
        "description": "Adds the appointment to a recurring series: one appointment per occurrence (max 52), all sharing one seriesId, so PATCH/DELETE `scope: following|series` works on the set. `count` or `until` is required; without it → 422.",
        "properties": {
          "freq": {
            "type": "string",
            "enum": [
              "daily",
              "weekly",
              "biweekly",
              "monthly"
            ]
          },
          "every": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "description": "Every n days (daily), weeks (weekly/biweekly) or months (monthly); default 1"
          },
          "on": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7
            },
            "description": "Weekdays, 1 = Monday (weekly/biweekly; default the weekday of `start`)"
          },
          "monthly": {
            "description": "Day of the month, or the nth weekday",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "day"
                ],
                "properties": {
                  "day": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 31
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "nth",
                  "weekday"
                ],
                "properties": {
                  "nth": {
                    "type": "integer",
                    "enum": [
                      1,
                      2,
                      3,
                      4,
                      -1
                    ]
                  },
                  "weekday": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 7
                  }
                }
              }
            ]
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 52,
            "description": "Number of occurrences",
            "including the first": null
          },
          "until": {
            "type": "string",
            "format": "date",
            "description": "Last occurrence date, inclusive (YYYY-MM-DD)"
          }
        }
      },
      "AppointmentUpdate": {
        "type": "object",
        "properties": {
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "end": {
            "type": "string",
            "format": "date-time"
          },
          "staffId": {
            "type": "string",
            "format": "uuid"
          },
          "serviceId": {
            "type": "string",
            "format": "uuid"
          },
          "internalNote": {
            "type": "string",
            "maxLength": 2000
          },
          "scope": {
            "type": "string",
            "enum": [
              "this",
              "following",
              "series"
            ],
            "default": "this"
          },
          "force": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "Booking": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "persons": {
            "type": "integer"
          },
          "status": {
            "$ref": "#/components/schemas/BookingStatus"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdFrom": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "paymentId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "orderId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "seriesId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "groupToken": {
            "type": [
              "string",
              "null"
            ]
          },
          "packageId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "extras": {
            "description": "Chosen extras with quantities"
          },
          "fields": {
            "type": "object",
            "additionalProperties": true,
            "description": "Answers to the service intake questions, keyed by field id (GET /services/{id}/fields); a perPerson field is an array with one entry per person"
          },
          "customer": {
            "type": "object",
            "properties": {
              "fullName": {
                "type": "string"
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "Appointment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "staffId": {
            "type": "string",
            "format": "uuid"
          },
          "serviceId": {
            "type": "string",
            "format": "uuid"
          },
          "startAt": {
            "type": "string",
            "format": "date-time"
          },
          "endAt": {
            "type": "string",
            "format": "date-time"
          },
          "internalNote": {
            "type": [
              "string",
              "null"
            ]
          },
          "extrasDuration": {
            "type": "integer"
          },
          "meetingUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "bookings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Booking"
            },
            "description": "In lists only id, status, persons, customerId"
          }
        },
        "example": {
          "id": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
          "staffId": "6a4ca508-ae33-4946-899c-388f1acb9d04",
          "serviceId": "82eff48b-ead1-4c2d-8038-058490d41470",
          "startAt": "2026-10-14T07:00:00Z",
          "endAt": "2026-10-14T07:30:00Z",
          "internalNote": "",
          "extrasDuration": 0,
          "meetingUrl": null,
          "bookings": [
            {
              "id": "3a4b5c6d-7e8f-4091-a2b3-c4d5e6f7a8b9",
              "customerId": "0d2b6f0e-5c4a-4e83-9e0a-3b8c1f2d4e5a",
              "persons": 1,
              "status": "approved",
              "seriesId": null,
              "groupToken": null,
              "customer": {
                "fullName": "Jan Jansen",
                "email": "jan@example.com",
                "phone": null
              }
            }
          ]
        }
      },
      "BookingItem": {
        "description": "A booking plus the appointment it is on (no nested bookings). Returned by GET /bookings and GET /bookings/{id}.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Booking"
          },
          {
            "type": "object",
            "properties": {
              "appointmentId": {
                "type": "string",
                "format": "uuid"
              },
              "appointment": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "staffId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "serviceId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "startAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "endAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "internalNote": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "extrasDuration": {
                    "type": "integer"
                  },
                  "meetingUrl": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "createdAt": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        ]
      },
      "BookingAdd": {
        "type": "object",
        "required": [
          "persons"
        ],
        "description": "customerId or customer",
        "properties": {
          "customerId": {
            "type": "string",
            "format": "uuid"
          },
          "customer": {
            "$ref": "#/components/schemas/CustomerRef"
          },
          "persons": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999
          },
          "status": {
            "$ref": "#/components/schemas/BookingStatus"
          },
          "notify": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "BookingUpdate": {
        "type": "object",
        "properties": {
          "status": {
            "$ref": "#/components/schemas/BookingStatus"
          },
          "persons": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999
          },
          "scope": {
            "type": "string",
            "enum": [
              "this",
              "following",
              "series"
            ],
            "default": "this"
          },
          "notify": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "BookingUpdateResult": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "changed": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Other bookings (group parts",
            "series) whose status followed": null
          },
          "failed": {
            "type": "integer",
            "description": "Bookings that could not follow (capacity/package)"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Payment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "type": {
            "type": "string",
            "enum": [
              "local",
              "free",
              "mollie"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed",
              "rejected",
              "refunded"
            ]
          },
          "total": {
            "type": "number"
          },
          "tax": {
            "type": "number"
          },
          "paid": {
            "type": "number"
          },
          "paidType": {
            "type": "string"
          },
          "payNow": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "latePaid": {
            "type": "boolean"
          },
          "customerId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "couponId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        }
      },
      "OutboxMail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sending",
              "sent",
              "failed",
              "suppressed"
            ]
          },
          "toEmail": {
            "type": "string"
          },
          "toName": {
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "type": "string"
          },
          "text": {
            "type": [
              "string",
              "null"
            ]
          },
          "html": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "sentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "booking.created",
          "booking.updated",
          "booking.cancelled",
          "ticket.created",
          "ticket.approved",
          "ticket.cancelled",
          "payment.completed",
          "payment.failed",
          "payment.refunded"
        ]
      },
      "WebhookInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "https:// only, public address"
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "description": {
            "type": "string",
            "maxLength": 200
          },
          "active": {
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "url",
          "events"
        ]
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "description": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "required": [
              "secret"
            ],
            "properties": {
              "secret": {
                "type": "string",
                "description": "whsec_ + base64 of 32 random bytes; shown once",
                "example": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
              }
            }
          }
        ]
      },
      "WebhookEvent": {
        "type": "object",
        "description": "The JSON body POSTed to your endpoint. Ids and status fields only; fetch details via the API.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Event id; equals the webhook-id header and stays the same on retries"
          },
          "type": {
            "type": "string",
            "description": "A WebhookEventType, or ping"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "tenantId": {
            "type": "string",
            "format": "uuid"
          },
          "data": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "example": {
          "id": "0f8e3c1a-6d2b-4b7e-9a51-3c2d1e0f9a87",
          "type": "booking.cancelled",
          "createdAt": "2026-10-12T09:15:00Z",
          "tenantId": "6f1c2b4e-0000-4000-8000-000000000000",
          "data": {
            "bookingId": "c41e0000-0000-4000-8000-000000000000",
            "appointmentId": "8a070000-0000-4000-8000-000000000000",
            "status": "cancelled",
            "previousStatus": "approved",
            "start": "2026-10-14T09:30:00Z"
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "endpointId": {
            "type": "string",
            "format": "uuid"
          },
          "eventId": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sending",
              "delivered",
              "failed"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "nextAttemptAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lastStatus": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status of the last attempt; null when the connection failed or timed out"
          },
          "lastError": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "deliveredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "payload": {
            "$ref": "#/components/schemas/WebhookEvent"
          }
        }
      },
      "PaymentDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Payment"
          },
          {
            "type": "object",
            "properties": {
              "gatewayPriceCorrection": {
                "type": "number"
              },
              "details": {
                "description": "Breakdown: items, extras, coupon, deposit"
              },
              "bookings": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "appointmentId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string"
                    },
                    "persons": {
                      "type": "integer"
                    }
                  }
                }
              },
              "attendees": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "eventId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "status": {
                      "type": "string"
                    }
                  }
                }
              },
              "packages": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "serviceId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "customerId": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        ]
      }
    }
  }
}
